swarmsort


Nameswarmsort JSON
Version 1.1.0 PyPI version JSON
download
home_pagehttps://github.com/cfosseprez/swarmsort
SummaryStandalone SwarmSort real-time multi-object tracker with integrated lightweight embeddings
upload_time2025-09-09 04:25:55
maintainerNone
docs_urlNone
authorCharles Fosseprez
requires_python<3.12,>=3.9
licenseGPL-3.0-or-later
keywords tracking computer-vision multi-object-tracking embeddings real-time
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            [![Documentation Status](https://readthedocs.org/projects/swarmsort/badge/?version=latest)](https://swarmsort.readthedocs.io/en/latest/)
[![PyPI Version](https://img.shields.io/pypi/v/swarmsort.svg)](https://pypi.org/project/swarmsort/)
[![Python Version](https://img.shields.io/pypi/pyversions/swarmsort.svg)](https://pypi.org/project/swarmsort/)
[![CI Tests](https://github.com/cfosseprez/swarmsort/actions/workflows/test.yml/badge.svg)](https://github.com/cfosseprez/swarmsort/actions/workflows/test.yml)
[![GPL-3.0 License](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/cfosseprez/swarmsort/blob/main/LICENSE)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.17051857.svg)](https://doi.org/10.5281/zenodo.17051857)

![logo](https://raw.githubusercontent.com/cfosseprez/swarmsort/main/docs/_static/readme_assets/logo-swarmsort-horizontal.jpg)



# SwarmSort

**Reliable multi-object tracking-by-detection: fast, accurate, and easy — perfect for top-view microscopy with hundreds of objects** 🎯


<div align="center">
  <table>
    <tr>
      <td align="center">
        <img src="https://github.com/user-attachments/assets/f67f8cb0-4d57-407c-9723-6dc7e5037a2c" style="width:80%;" alt="Detection Demo">
        <br><b>Real-time tracking of 150 paramecia at 80 FPS</b>
      </td>
      <td align="center">
        <img src="https://github.com/user-attachments/assets/b1a95557-a1db-4328-9442-d85c41d82e7c" style="width:90%;" alt="Tracking Demo">
        <br><b>Real time performances for up to 500 individuals</b>
      </td>
    </tr>
  </table>
</div>

## Core Capabilities

SwarmSort solves the data association problem in multi-object tracking by:
- **Maintaining temporal consistency** of object identities across frames using motion prediction, appearance and uncertainty
- **Handling occlusions and collisions** through re-identification with visual embeddings 
- **optional lightweight gpu based embedding integrated** for more accuracy and speed
- **Preventing ID switches** in dense scenarios using uncertainty-aware cost computation and embedding freezing
- **Fast!** The library achieves real-time performance (80-100 FPS for 100 objects) through Numba JIT compilation, vectorized operations, and optional GPU acceleration.


## Key Features

### Advanced Tracking Algorithms
- **Uncertainty-based cost system** - Adaptive association costs based on track age, local density, and detection reliability
- **Smart collision handling** - Density-based embedding freezing prevents ID switches in crowded scenarios
- **Re-identification capability** - Recovers lost tracks using visual embeddings and motion prediction
- **Hybrid assignment strategy** - Combines greedy matching for obvious associations with Hungarian algorithm for complex cases
- **Dual Kalman filter options** - Simple constant velocity or OC-SORT style acceleration model
- **Occlusion handling** - Maintains tracks through temporary occlusions using motion prediction

### Real-time performance
- **Numba JIT compilation** - Critical mathematical functions compiled to machine code
- **Vectorized operations** - Batch processing using NumPy for efficient computation
- **GPU acceleration** - Optional CUDA support via CuPy for embedding extraction
- **Memory efficient** - Bounded memory usage with automatic cleanup of stale tracks

### Flexible Integration
- **Detector agnostic** - Works with any object detection source (YOLO, Detectron2, custom detectors)
- **Configurable parameters** - Fine-tune behavior for specific species (microscopy, crowds ..)
- **Multiple embedding methods** - Support for various visual feature extractors
- **Comprehensive API** - Access to track states, lifecycle management, and detailed statistics

### Production Ready
- **Extensive test coverage** - 200+ unit tests covering edge cases and error conditions
- **Cross-platform support** - Tested on Linux, Windows, macOS
- **Detailed documentation** - Complete API reference with practical examples

## Citation

If you use SwarmSort in your research, please cite:

```bibtex
@software{swarmsort,
    title={SwarmSort: High-Performance Multi-Object Tracking},
    author={Charles Fosseprez},
    year={2025},
    url={https://github.com/cfosseprez/swarmsort},
    doi={10.5281/zenodo.17051857}
}
```
## 📖 Documentation

**[Full Documentation](https://swarmsort.readthedocs.io/en/latest/)**

## 📦 Installation


```bash
# Option 1: Install from PyPI 
pip install swarmsort

# Option 2: Install from PyPI with gpu embedding support
pip install swarmsort[gpu]

# Option 3: Install from GitHub
pip install git+https://github.com/cfosseprez/swarmsort.git
```



##  Quick Start

### Your First Tracker in 30 Seconds

```python
import numpy as np
from swarmsort import SwarmSortTracker, Detection

# Step 1: Create a tracker (it's that simple!)
tracker = SwarmSortTracker()

# Step 2: Tell the tracker what you detected this frame
# In real use, these would come from your object detector (YOLO, etc.)
detections = [
    Detection(position=[100, 200], confidence=0.9),  # A person at position (100, 200)
    Detection(position=[300, 400], confidence=0.8),  # Another person at (300, 400)
]

# Step 3: Get tracking results - SwarmSort handles all the complexity!
tracked_objects = tracker.update(detections)

# Step 4: Use the results - each object has a unique ID that persists across frames
for obj in tracked_objects:
    print(f"Person {obj.id} is at position {obj.position} with {obj.confidence:.0%} confidence")
    # Output: Person 1 is at position [100. 200.] with 90% confidence
```

###  Real-World Example: Tracking Paramecia in Video

```python
import cv2
from swarmsort import SwarmSortTracker, Detection

tracker = SwarmSortTracker()

# Process a video file
video = cv2.VideoCapture('microscopy.mp4')

while True:
    ret, frame = video.read()
    if not ret:
        break
    
    # Get detections from your favorite detector
    # For this example, let's say we detected 2 people:
    detections = [
        Detection(
            position=[320, 240],  # Center of bounding box
            confidence=0.95,
            bbox=[300, 220, 340, 260]  # x1, y1, x2, y2
        ),
        Detection(
            position=[150, 180],
            confidence=0.87,
            bbox=[130, 160, 170, 200]
        )
    ]
    
    # SwarmSort assigns consistent IDs across frames
    tracked = tracker.update(detections)
    
    # Draw results on frame
    for person in tracked:
        if person.bbox is not None:
            x1, y1, x2, y2 = person.bbox.astype(int)
            # Each Paramecium keeps the same ID and color throughout the video!
            color = (0, 255, 0)  # Green for tracked objects
            cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
            cv2.putText(frame, f"ID: {person.id}", (x1, y1-10),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
    
    cv2.imshow('Tracking Results', frame)
    if cv2.waitKey(1) == ord('q'):
        break
```

### 🔌 Direct Integration with YOLO and Other Detectors

SwarmSort seamlessly integrates with popular object detectors through optimized conversion utilities:

#### **YOLO v8/v11 Integration**
```python
from ultralytics import YOLO
from swarmsort import yolo_to_detections, SwarmSortTracker, SwarmSortConfig

# Initialize YOLO detector
model = YOLO('yolov8n.pt')  # or yolov11n.pt, yolov8x.pt, etc.

# Initialize SwarmSort tracker
tracker = SwarmSortTracker(SwarmSortConfig(
    max_distance=150,
    uncertainty_weight=0.3
))

# Process video stream
cap = cv2.VideoCapture('video.mp4')
while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    # Detect objects with YOLO
    results = model.predict(frame, conf=0.5)
    
    # Convert YOLO output to SwarmSort format (optimized, zero-copy when possible)
    detections = yolo_to_detections(
        results[0], 
        confidence_threshold=0.5,
        class_filter=[0, 1, 2]  # Only track persons, bicycles, cars
    )
    
    # Track objects
    tracked_objects = tracker.update(detections)
    
    # Draw results
    for obj in tracked_objects:
        if obj.bbox is not None:
            x1, y1, x2, y2 = obj.bbox.astype(int)
            cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
            cv2.putText(frame, f"ID:{obj.id}", (x1, y1-10),
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
```

#### **Custom Detector Integration**
```python
from swarmsort import numpy_to_detections, prepare_detections

# If your detector outputs numpy arrays
boxes = np.array([[100, 100, 200, 200],  # [x1, y1, x2, y2] format
                  [300, 150, 400, 250]])
confidences = np.array([0.9, 0.85])

# Convert to SwarmSort format
detections = numpy_to_detections(
    boxes=boxes,
    confidences=confidences,
    format='xyxy'  # Supports: 'xyxy', 'xywh', 'cxcywh'
)

# Or with embeddings from your own feature extractor
embeddings = your_feature_extractor(image_patches)  # Shape: (N, embedding_dim)
detections = numpy_to_detections(boxes, confidences, embeddings=embeddings)
```

#### **Universal Auto-Conversion**
```python
from swarmsort import prepare_detections

# Automatically detects format and converts + verifies
detections = prepare_detections(
    any_detection_data,  # YOLO results, numpy arrays, or Detection objects
    source_format='auto',  # Auto-detects format
    confidence_threshold=0.5,
    auto_fix=True  # Fixes common issues (clips bounds, normalizes, etc.)
)

# The prepare_detections function:
# ✓ Auto-detects input format (YOLO, numpy, etc.)
# ✓ Converts to SwarmSort Detection format
# ✓ Validates all inputs
# ✓ Auto-fixes common issues (out-of-bounds, NaN values, etc.)
# ✓ Optimized with vectorized operations
```

#### **Batch Processing for Maximum Speed**
```python
# Process entire video in batches (useful for offline analysis)
from swarmsort import yolo_to_detections_batch

# Get all YOLO predictions at once
results = model.predict('video.mp4', stream=True)
all_detections = yolo_to_detections_batch(list(results))

# Track through all frames
all_tracks = []
for frame_detections in all_detections:
    tracked = tracker.update(frame_detections)
    all_tracks.append(tracked)
```


###  Using Visual Features (Embeddings) for Better Tracking

Embeddings help the tracker recognize objects by their appearance, not just position. This is super useful when:
- Objects move quickly or unpredictably
- Multiple similar objects are close together
- Objects temporarily disappear and reappear


SwarmSort can use your GPU for the integrated default fast lightweight embedding:

```python
from swarmsort import SwarmSortTracker, SwarmSortConfig, Detection
import numpy as np

# Enable appearance-based tracking
config = SwarmSortConfig(
    do_embeddings=True,  # Use visual features for matching
    embedding_weight=1.0,  # How much to trust appearance vs motion
)
tracker = SwarmSortTracker(config)
```

Or you can use a personalized embedding, and pass it directly the Detection.

```python
# In practice, embeddings come from a feature extractor (ResNet, etc.)
# Here's a simple example:
def get_embedding_from_image(image_patch):
    """Your feature extractor - could be a neural network"""
    # This would be your CNN/feature extractor
    # Returns a N-dimensional feature vector
    return np.random.randn(128).astype(np.float32)

# Create detection with visual features
person_image = frame[160:200, 130:170]  # Crop person from frame
embedding = get_embedding_from_image(person_image)

detection = Detection(
    position=[150, 180],  # Center position
    confidence=0.9,
    embedding=embedding,  # Visual features help maintain ID
    bbox=[130, 160, 170, 200]  # Bounding box
)

# The tracker now uses BOTH motion AND appearance for matching!
tracked_objects = tracker.update([detection])
```

## ⚙️ Configuration Made Easy

### Understanding the max_distance Parameter

The `max_distance` parameter is the foundation of SwarmSort's configuration. **Important**: Set it **1.5-2x higher** than the expected maximum pixel movement between frames because:

- The actual matching uses a **combination** of spatial distance, embedding similarity, and uncertainty penalties
- With embeddings enabled, the effective matching distance is reduced by visual similarity  
- Uncertainty penalties further modify the association costs
- Example: If objects move up to 100 pixels between frames, set `max_distance=150-200`

Many other parameters **automatically scale** with `max_distance`:
```python
# When you set max_distance=150, these defaults are automatically set:
local_density_radius = 150      # Same as max_distance
greedy_threshold = 30           # max_distance / 5
reid_max_distance = 150         # Same as max_distance
```

### All Parameters

| Parameter                  | Default            | Description                                                                           |
|----------------------------|--------------------|---------------------------------------------------------------------------------------|
| **Core Tracking**          |                    |                                                                                       |
| `max_distance`             | 150.0              | Maximum distance for detection-track association                                      |
| `detection_conf_threshold` | 0.0                | Minimum confidence for detections                                                     |
| `max_track_age`            | 30                 | Maximum frames to keep track alive without detections                                 |
| **Motion prediction**      |                    |                                                                                       |
| `kalman_type`              | 'simple'           | Kalman filter type: 'simple' or 'oc' (OC-SORT style)                                  |
| **Uncertainty System**     |                    |                                                                                       |
| `uncertainty_weight`       | 0.33               | Weight for uncertainty penalties (0 = disabled)                                       |
| `local_density_radius`     | max_distance       | Radius for computing local track density (defaults to max_distance)                   |
| **Embeddings**             |                    |                                                                                       |
| `do_embeddings`            | True               | Whether to use embedding features                                                     |
| `embedding_function`       | 'cupytexture'      | Integrated embedding function: "cupytexture", "cupytexture_color", "mega_cupytexture" |
| `embedding_weight`         | 1.0                | Weight for embedding similarity in cost function                                      |
| `max_embeddings_per_track` | 15                 | Maximum embeddings stored per track                                                   |
| `embedding_matching_method` | 'weighted_average' | Method for multi-embedding matching                                                   |
| **Collision Handling**     |                    |                                                                                       |
| `collision_freeze_embeddings` | True               | Freeze embedding updates in dense areas                                               |
| `embedding_freeze_density` | 1                  | Freeze when ≥N tracks within radius                                                   |
| **Assignment Strategy**    |                    |                                                                                       |
| `assignment_strategy`      | 'hybrid'           | Assignment method: 'hungarian', 'greedy', or 'hybrid'                                 |
| `greedy_threshold`         | max_distance/5     | Distance threshold for greedy assignment                                              |
| **Track Initialization**   |                    |                                                                                       |
| `min_consecutive_detections` | 6                  | Minimum consecutive detections to create track                                        |
| `max_detection_gap`        | 2                  | Maximum gap between detections                                                        |
| `pending_detection_distance` | max_distance       | Distance threshold for pending detection matching                                     |
| **Re-identification**      |                    |                                                                                       |
| `reid_enabled`             | True               | Enable re-identification of lost tracks                                               |
| `reid_max_distance`        | max_distance*1.5   | Maximum distance for ReID                                                             |
| `reid_embedding_threshold` | 0.3                | Embedding threshold for ReID                                                          |
| **Experimental**           |                    |                                                                                       |
| `use_probabilistic_costs`  | False              | Use gaussian fusion for cost computation                                              |


### 🎯 Preset Configurations for Common Scenarios

Best Settings for Performance:


  For good balance (speed + accuracy): up to 300 individuals
```python
  config = SwarmSortConfig(
      kalman_type="simple",           # Fast but accurate enough                                                                                                                                         
      assignment_strategy="hybrid",    # Good balance                                                                                                                                                    
      uncertainty_weight=0.33,         # Some uncertainty handling                                                                                                                                       
      do_embeddings=True,              # Use embeddings if available                                                                                                                                     
      reid_enabled=False,              # Skip for speed                                                                                                                                                  
  )
```
  For maximum speed across all scales: 300+ individuals
```python
  config = SwarmSortConfig(
      kalman_type="simple",           # Fastest Kalman filter                                                                                                                                            
      assignment_strategy="greedy",    # Fastest assignment                                                                                                                                              
      uncertainty_weight=0.0,          # Disable uncertainty                                                                                                                                             
      do_embeddings=False,             # No embeddings                                                                                                                                                   
      reid_enabled=False,              # No re-identification                                                                                                                                            
  )
```

### 🔧 Understanding Key Parameters

```python
# The most important parameters to tune:

config = SwarmSortConfig(
    # 1. How far can an object move between frames?
    max_distance=150.0,  # Increase for fast objects, decrease for slow
    
    # 2. How many frames to confirm a new track?
    min_consecutive_detections=6,  # Lower = faster response, more false positives
                                   # Higher = slower response, fewer false positives
    
    # 3. How long to keep lost tracks?
    max_track_age=30,  # At 30 FPS, this is 1 second of "memory"
    
    # 4. Use appearance features?
    do_embeddings=True,  # True if objects look different from each other
                        # False if all objects look the same (e.g., identical boxes)
    
    # 5. How to handle crowded scenes?
    collision_freeze_embeddings=True,  # Prevents ID switches when objects touch
    uncertainty_weight=0.33,  # Higher = more conservative in uncertain situations
)
```

## Advanced Usage

### Different Configuration Methods

```python
from swarmsort import SwarmSortTracker, SwarmSortConfig

# Default tracker
tracker = SwarmSortTracker()

# With configuration object
config = SwarmSortConfig(max_distance=100.0, do_embeddings=True)
tracker = SwarmSortTracker(config)

# With dictionary config
tracker = SwarmSortTracker({'max_distance': 100.0, 'do_embeddings': True})
```

### Basic Standalone Usage

```python
from swarmsort import SwarmSortTracker, SwarmSortConfig

# SwarmSort is a standalone tracker - no special integration needed
tracker = SwarmSortTracker()

# Configure for specific use cases
config = SwarmSortConfig(
    do_embeddings=True,
    reid_enabled=True,
    max_distance=100.0,
    assignment_strategy='hybrid',  # Use hybrid assignment strategy
    uncertainty_weight=0.33         # Enable uncertainty-based costs
)
tracker_configured = SwarmSortTracker(config)
```

## 📦 Working with Data

### Input: Detection Objects

Detections are what you feed into the tracker - they represent objects found in the current frame:

```python
from swarmsort import Detection
import numpy as np

# Minimal detection - just position and confidence
simple_detection = Detection(
    position=[320, 240],  # Center point (x, y)
    confidence=0.9        # How sure are we this is real? (0-1)
)

# Full detection with all the bells and whistles
full_detection = Detection(
    position=np.array([320, 240]),        # Object center
    confidence=0.95,                      # Detection confidence
    bbox=np.array([300, 220, 340, 260]),  # Bounding box [x1, y1, x2, y2]
    embedding=feature_vector,             # Visual features (for example from your CNN)
    class_id=0,                          # 0=person, 1=car, etc.
    id="yolo_detection_42"               # Your detector's ID (optional)
)

# Real-world example: Converting YOLO output to SwarmSort
def yolo_to_swarmsort(yolo_results):
    detections = []
    for box in yolo_results.boxes:
        x1, y1, x2, y2 = box.xyxy[0].numpy()
        center_x = (x1 + x2) / 2
        center_y = (y1 + y2) / 2
        
        detections.append(Detection(
            position=[center_x, center_y],
            confidence=box.conf[0].item(),
            bbox=[x1, y1, x2, y2],
            class_id=int(box.cls[0])
        ))
    return detections
```

###  Output: TrackedObject Results

The tracker returns TrackedObject instances with rich information about each tracked object:

```python
# Get tracking results
tracked_objects = tracker.update(detections)

for obj in tracked_objects:
    # Identity
    print(f"🆔 Track ID: {obj.id}")  # Unique ID that persists across frames
    
    # Location & Motion
    print(f"📍 Position: {obj.position}")  # Current [x, y] position
    print(f"➡️ Velocity: {obj.velocity}")  # Speed and direction [vx, vy]
    
    # Confidence & Quality
    print(f"✅ Confidence: {obj.confidence:.1%}")  # How confident are we?
    print(f"📊 Track quality: {obj.hits}/{obj.age}")  # Hits/Age ratio
    
    # Track Status
    if obj.time_since_update == 0:
        print("🟢 Currently visible")
    else:
        print(f"🟡 Lost for {obj.time_since_update} frames")
    
    # Bounding Box (if available)
    if obj.bbox is not None:
        x1, y1, x2, y2 = obj.bbox
        width = x2 - x1
        height = y2 - y1
        print(f"📐 Size: {width:.0f}x{height:.0f} pixels")
```

### 🔄 Track Lifecycle Management

SwarmSort provides fine control over track states - perfect for different visualization needs:

```python
# Get only tracks that are currently visible
alive_tracks = tracker.update(detections)
print(f"👁️ Visible now: {len(alive_tracks)} objects")

# Get tracks that were recently lost (useful for smooth visualization)
recently_lost = tracker.get_recently_lost_tracks(max_frames_lost=5)
print(f"👻 Recently lost: {len(recently_lost)} objects")

# Get everything (visible + recently lost)
all_active = tracker.get_all_active_tracks(max_frames_lost=5)
print(f"📊 Total active: {len(all_active)} objects")

# Example: Different visualization for different states
for obj in alive_tracks:
    draw_solid_box(frame, obj, color='green')  # Solid box for visible
    
for obj in recently_lost:
    draw_dashed_box(frame, obj, color='yellow')  # Dashed box for lost
```


## ⚡ Performance & Optimization

### Why SwarmSort is Fast

SwarmSort is optimized for real-world performance:

- **Numba JIT Compilation**: Math operations run at C speed
- **Vectorized NumPy**: Batch operations instead of loops
- **Smart Caching**: Reuses computed embeddings and distances
- **Memory Pooling**: Reduces allocation overhead
- **Early Exit Logic**: Skips unnecessary computations


## Visualization Example

SwarmSort includes built-in visualization utilities for beautiful tracking displays:
<details>
  <summary>Click to expand code example</summary>

```python
import cv2
import numpy as np
from swarmsort import SwarmSortTracker, Detection, SwarmSortConfig
from swarmsort import TrackingVisualizer, VisualizationConfig

# Initialize tracker with your preferred settings
config = SwarmSortConfig(
    do_embeddings=True,
    embedding_function='cupytexture',  # or 'mega_cupytexture' for more features
    assignment_strategy='hybrid',
    uncertainty_weight=0.33
)
tracker = SwarmSortTracker(config)

# Initialize visualizer with custom settings
vis_config = VisualizationConfig(
    show_trails=True,
    trail_length=30,
    show_ids=True,
    show_confidence=True,
    show_velocity_vectors=True,
    id_font_scale=0.5,
    id_thickness=2,
    box_thickness=2
)
visualizer = TrackingVisualizer(vis_config)

# Example usage with video
cap = cv2.VideoCapture('video.mp4')  # Or use 0 for webcam

while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    # Detect objects (replace with your detector)
    # Here's a mock detection for demonstration
    detections = [
        Detection(
            position=np.array([100, 200]),
            confidence=0.9,
            bbox=np.array([80, 180, 120, 220])
        ),
        Detection(
            position=np.array([300, 400]),
            confidence=0.85,
            bbox=np.array([280, 380, 320, 420])
        )
    ]
    
    # Update tracker
    tracked_objects = tracker.update(detections)
    
    # Draw tracking results with built-in visualizer
    frame = visualizer.draw_tracks(frame, tracked_objects)
    
    # Optionally show recently lost tracks
    recently_lost = tracker.get_recently_lost_tracks(max_frames_lost=5)
    frame = visualizer.draw_lost_tracks(frame, recently_lost)
    
    # Display frame
    cv2.imshow('SwarmSort Tracking', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
```

</details>

### Quick Visualization (One-liner)

For quick prototyping, use the convenience function:

```python
from swarmsort import quick_visualize

# One line to visualize tracking results!
frame_with_tracks = quick_visualize(frame, tracked_objects, show_trails=True)
```

### Custom Drawing (If you need more control)

If you prefer to implement custom visualization:

<details>
  <summary>Click to expand code example</summary>

```python
import cv2
import numpy as np
from swarmsort import SwarmSortTracker, Detection, SwarmSortConfig

# Initialize tracker
tracker = SwarmSortTracker(SwarmSortConfig(do_embeddings=True))

# Function to draw tracking results
def draw_tracks(frame, tracked_objects, show_trails=True):
    """Draw bounding boxes and tracking information on frame."""
    # Store trail history (in production, store this outside the function)
    if not hasattr(draw_tracks, 'trails'):
        draw_tracks.trails = {}
    
    for obj in tracked_objects:
        # Get track color (consistent color per ID)
        color = np.random.RandomState(obj.id).randint(0, 255, 3).tolist()
        
        # Draw bounding box if available
        if obj.bbox is not None:
            x1, y1, x2, y2 = obj.bbox.astype(int)
            cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
            
            # Draw track ID and confidence
            label = f"ID:{obj.id} ({obj.confidence:.2f})"
            cv2.putText(frame, label, (x1, y1-10), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
        
        # Draw center point
        cx, cy = obj.position.astype(int)
        cv2.circle(frame, (cx, cy), 5, color, -1)
        
        # Update and draw trail
        if show_trails:
            if obj.id not in draw_tracks.trails:
                draw_tracks.trails[obj.id] = []
            draw_tracks.trails[obj.id].append((cx, cy))
            
            # Keep only last 30 points
            draw_tracks.trails[obj.id] = draw_tracks.trails[obj.id][-30:]
            
            # Draw trail
            points = draw_tracks.trails[obj.id]
            for i in range(1, len(points)):
                cv2.line(frame, points[i-1], points[i], color, 2)
    
    # Clean up old trails
    active_ids = {obj.id for obj in tracked_objects}
    draw_tracks.trails = {k: v for k, v in draw_tracks.trails.items() 
                         if k in active_ids}
    
    return frame
```

</details>

### Simple Visualization with Matplotlib

For a simpler visualization or for Jupyter notebooks:

<details>
  <summary>Click to expand code example</summary>

```python
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.animation import FuncAnimation
import numpy as np
from swarmsort import SwarmSortTracker, Detection

# Create figure
fig, ax = plt.subplots(figsize=(10, 8))
ax.set_xlim(0, 640)
ax.set_ylim(480, 0)  # Invert y-axis for image coordinates
ax.set_aspect('equal')
ax.set_title('SwarmSort Multi-Object Tracking')

tracker = SwarmSortTracker()
track_history = {}

def update_plot(frame_num):
    ax.clear()
    ax.set_xlim(0, 640)
    ax.set_ylim(480, 0)
    ax.set_title(f'Frame {frame_num}')
    
    # Generate mock detections (replace with real detections)
    detections = [
        Detection(
            position=np.array([320 + 100*np.sin(frame_num/10), 240]),
            confidence=0.9,
            bbox=np.array([300 + 100*np.sin(frame_num/10), 220, 
                          340 + 100*np.sin(frame_num/10), 260])
        ),
        Detection(
            position=np.array([200, 240 + 100*np.cos(frame_num/10)]),
            confidence=0.85,
            bbox=np.array([180, 220 + 100*np.cos(frame_num/10),
                          220, 260 + 100*np.cos(frame_num/10)])
        )
    ]
    
    # Update tracker
    tracked_objects = tracker.update(detections)
    
    # Plot tracked objects
    for obj in tracked_objects:
        # Get consistent color for track ID
        np.random.seed(obj.id)
        color = np.random.rand(3)
        
        # Draw bounding box
        if obj.bbox is not None:
            x1, y1, x2, y2 = obj.bbox
            rect = patches.Rectangle((x1, y1), x2-x1, y2-y1,
                                    linewidth=2, edgecolor=color, 
                                    facecolor='none')
            ax.add_patch(rect)
        
        # Draw center point
        ax.scatter(obj.position[0], obj.position[1], 
                  c=[color], s=100, marker='o')
        
        # Add ID label
        ax.text(obj.position[0], obj.position[1]-20, f'ID:{obj.id}',
               color=color, fontsize=12, ha='center', weight='bold')
        
        # Update history
        if obj.id not in track_history:
            track_history[obj.id] = []
        track_history[obj.id].append(obj.position.copy())
        
        # Draw trail
        if len(track_history[obj.id]) > 1:
            trail = np.array(track_history[obj.id])
            ax.plot(trail[:, 0], trail[:, 1], color=color, 
                   linewidth=2, alpha=0.5)
    
    # Clean old tracks
    active_ids = {obj.id for obj in tracked_objects}
    for track_id in list(track_history.keys()):
        if track_id not in active_ids:
            if len(track_history[track_id]) > 50:  # Remove very old tracks
                del track_history[track_id]

# Create animation
anim = FuncAnimation(fig, update_plot, frames=200, 
                    interval=50, repeat=True)
plt.show()

# To save as video:
# anim.save('tracking_visualization.mp4', writer='ffmpeg', fps=20)
```

</details>

## 🚀 Advanced Features

### 🧠 How SwarmSort Thinks: The Intelligence Behind the Tracking

#### **Uncertainty-Aware Tracking**
SwarmSort knows when it's confident and when it's not:

```python
# The tracker automatically adjusts behavior based on uncertainty:
# - New tracks: "I'm not sure yet, let me observe more"
# - Established tracks: "I know this object well"
# - Crowded areas: "Need to be extra careful here"

config = SwarmSortConfig(
    uncertainty_weight=0.33,  # How much to consider uncertainty
    # 0.0 = Ignore uncertainty (aggressive)
    # 0.5 = Balanced approach
    # 1.0 = Very conservative
)

# Example: High uncertainty for drone tracking (unpredictable motion)
drone_config = SwarmSortConfig(
    uncertainty_weight=0.6,  # Be more careful with uncertain tracks
    kalman_type='oc',       # Better motion model for erratic movement
)
```

#### **Smart Collision Prevention**
Prevents ID switches when objects get close:

```python
# Scenario: Tracking dancers who frequently cross paths
dance_config = SwarmSortConfig(
    collision_freeze_embeddings=True,  # Lock visual features when close
    embedding_freeze_density=1,        # Freeze when anyone is within...
    local_density_radius=100.0,        # ...100 pixels
)

# What happens:
# 1. Two Paramecium approach each other
# 2. SwarmSort detects they're getting close
# 3. Visual features are "frozen" - relies on motion only
# 4. Prevents mixing up their identities
# 5. Once separated, visual matching resumes
```

#### **Hybrid Assignment Strategy**
Combines the best of both worlds:

```python
config = SwarmSortConfig(
    assignment_strategy='hybrid',  # Smart mode (default)
    greedy_threshold=30.0,         # Fast matching for obvious cases
)

# How it works:
# 1. Obvious matches (very close): Uses fast greedy assignment
# 2. Ambiguous cases: Falls back to optimal Hungarian algorithm
# 3. Best of both: Fast AND accurate


config.assignment_strategy = 'hybrid'  # optimal matching
```

### 🔍 Re-Identification: Bringing Lost Objects Back

Perfect for scenarios where objects temporarily disappear:

```python
# Example: Security camera at a store entrance
config = SwarmSortConfig(
    reid_enabled=True,              # Enable re-identification
    reid_max_distance=200.0,        # Search this far for lost tracks
    reid_embedding_threshold=0.25,  # How similar must appearances be?
)

# What happens:
# 1. Person walks behind a pillar (track lost)
# 2. Person reappears on the other side
# 3. SwarmSort compares appearance with recently lost tracks
# 4. Same person? Same ID! Tracking continues seamlessly

# Real-world usage:
tracker = SwarmSortTracker(config)
results = tracker.update(detections)

# The person who disappeared at frame 100 and reappeared at frame 120
# will have the SAME track ID - perfect for counting and analytics!
```

## 🔧 Troubleshooting & FAQ

### Common Issues and Solutions

**Q: My tracks keep switching IDs when objects cross paths**
```python
# Solution: Enable collision handling
config = SwarmSortConfig(
    collision_freeze_embeddings=True,  # Prevent ID switches
    embedding_freeze_density=1,        # Freeze when objects are close
    do_embeddings=True,                # Use visual features
    embedding_weight=1.5,              # Trust appearance more
)
```

**Q: New tracks take too long to appear**
```python
# Solution: Reduce initialization requirements
config = SwarmSortConfig(
    min_consecutive_detections=2,  # Was 6, now faster
    init_conf_threshold=0.3,       # Accept lower confidence
)
```

**Q: Too many false tracks from noise**
```python
# Solution: Be more strict about track creation
config = SwarmSortConfig(
    min_consecutive_detections=8,      # Require more detections
    init_conf_threshold=0.7,           # Higher confidence needed
    detection_conf_threshold=0.5,      # Filter out weak detections
)
```

**Q: Tracks disappear too quickly**
```python
# Solution: Keep tracks alive longer
config = SwarmSortConfig(
    max_track_age=30,  # Keep for 2 seconds at 30 FPS (was 30)
    reid_enabled=True,  # Try to re-identify lost tracks
)
```

**Q: Performance is too slow**
Consider processing every other frame

## Examples

See the `examples/` directory for comprehensive usage examples.

## Testing

```bash
# Run tests
poetry run pytest

# Run tests with coverage
poetry run pytest --cov=swarmsort --cov-report=html

# Run specific test
poetry run pytest tests/test_basic.py::test_basic_tracking
```

## Development

### Development Setup

Want to contribute or modify SwarmSort? Here's how to set up a development environment:

```bash
# Clone the repository
git clone https://github.com/cfosseprez/swarmsort.git
cd swarmsort

# Install with Poetry (recommended for development)
poetry install --with dev

# Or use pip in editable mode
pip install -e ".[dev]"
```

## Benchmarking

```bash
# Run benchmarks
poetry run pytest tests/ --benchmark-only
```

## License

GPL 3.0 or later - see LICENSE file for details.

## Contributing

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

### Development Workflow


```bash
# Install development dependencies
poetry install --with dev

# Run linting
poetry run black swarmsort/
poetry run flake8 swarmsort/

# Run type checking
poetry run mypy swarmsort/
```


            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/cfosseprez/swarmsort",
    "name": "swarmsort",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<3.12,>=3.9",
    "maintainer_email": null,
    "keywords": "tracking, computer-vision, multi-object-tracking, embeddings, real-time",
    "author": "Charles Fosseprez",
    "author_email": "charles.fosseprez.pro@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/e4/89/ac044bd5a6580c9602110c73d71dc69e6cb8c720fed1d0e11c0df16af48f/swarmsort-1.1.0.tar.gz",
    "platform": null,
    "description": "[![Documentation Status](https://readthedocs.org/projects/swarmsort/badge/?version=latest)](https://swarmsort.readthedocs.io/en/latest/)\n[![PyPI Version](https://img.shields.io/pypi/v/swarmsort.svg)](https://pypi.org/project/swarmsort/)\n[![Python Version](https://img.shields.io/pypi/pyversions/swarmsort.svg)](https://pypi.org/project/swarmsort/)\n[![CI Tests](https://github.com/cfosseprez/swarmsort/actions/workflows/test.yml/badge.svg)](https://github.com/cfosseprez/swarmsort/actions/workflows/test.yml)\n[![GPL-3.0 License](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/cfosseprez/swarmsort/blob/main/LICENSE)\n[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.17051857.svg)](https://doi.org/10.5281/zenodo.17051857)\n\n![logo](https://raw.githubusercontent.com/cfosseprez/swarmsort/main/docs/_static/readme_assets/logo-swarmsort-horizontal.jpg)\n\n\n\n# SwarmSort\n\n**Reliable multi-object tracking-by-detection: fast, accurate, and easy \u2014 perfect for top-view microscopy with hundreds of objects** \ud83c\udfaf\n\n\n<div align=\"center\">\n  <table>\n    <tr>\n      <td align=\"center\">\n        <img src=\"https://github.com/user-attachments/assets/f67f8cb0-4d57-407c-9723-6dc7e5037a2c\" style=\"width:80%;\" alt=\"Detection Demo\">\n        <br><b>Real-time tracking of 150 paramecia at 80 FPS</b>\n      </td>\n      <td align=\"center\">\n        <img src=\"https://github.com/user-attachments/assets/b1a95557-a1db-4328-9442-d85c41d82e7c\" style=\"width:90%;\" alt=\"Tracking Demo\">\n        <br><b>Real time performances for up to 500 individuals</b>\n      </td>\n    </tr>\n  </table>\n</div>\n\n## Core Capabilities\n\nSwarmSort solves the data association problem in multi-object tracking by:\n- **Maintaining temporal consistency** of object identities across frames using motion prediction, appearance and uncertainty\n- **Handling occlusions and collisions** through re-identification with visual embeddings \n- **optional lightweight gpu based embedding integrated** for more accuracy and speed\n- **Preventing ID switches** in dense scenarios using uncertainty-aware cost computation and embedding freezing\n- **Fast!** The library achieves real-time performance (80-100 FPS for 100 objects) through Numba JIT compilation, vectorized operations, and optional GPU acceleration.\n\n\n## Key Features\n\n### Advanced Tracking Algorithms\n- **Uncertainty-based cost system** - Adaptive association costs based on track age, local density, and detection reliability\n- **Smart collision handling** - Density-based embedding freezing prevents ID switches in crowded scenarios\n- **Re-identification capability** - Recovers lost tracks using visual embeddings and motion prediction\n- **Hybrid assignment strategy** - Combines greedy matching for obvious associations with Hungarian algorithm for complex cases\n- **Dual Kalman filter options** - Simple constant velocity or OC-SORT style acceleration model\n- **Occlusion handling** - Maintains tracks through temporary occlusions using motion prediction\n\n### Real-time performance\n- **Numba JIT compilation** - Critical mathematical functions compiled to machine code\n- **Vectorized operations** - Batch processing using NumPy for efficient computation\n- **GPU acceleration** - Optional CUDA support via CuPy for embedding extraction\n- **Memory efficient** - Bounded memory usage with automatic cleanup of stale tracks\n\n### Flexible Integration\n- **Detector agnostic** - Works with any object detection source (YOLO, Detectron2, custom detectors)\n- **Configurable parameters** - Fine-tune behavior for specific species (microscopy, crowds ..)\n- **Multiple embedding methods** - Support for various visual feature extractors\n- **Comprehensive API** - Access to track states, lifecycle management, and detailed statistics\n\n### Production Ready\n- **Extensive test coverage** - 200+ unit tests covering edge cases and error conditions\n- **Cross-platform support** - Tested on Linux, Windows, macOS\n- **Detailed documentation** - Complete API reference with practical examples\n\n## Citation\n\nIf you use SwarmSort in your research, please cite:\n\n```bibtex\n@software{swarmsort,\n    title={SwarmSort: High-Performance Multi-Object Tracking},\n    author={Charles Fosseprez},\n    year={2025},\n    url={https://github.com/cfosseprez/swarmsort},\n    doi={10.5281/zenodo.17051857}\n}\n```\n## \ud83d\udcd6 Documentation\n\n**[Full Documentation](https://swarmsort.readthedocs.io/en/latest/)**\n\n## \ud83d\udce6 Installation\n\n\n```bash\n# Option 1: Install from PyPI \npip install swarmsort\n\n# Option 2: Install from PyPI with gpu embedding support\npip install swarmsort[gpu]\n\n# Option 3: Install from GitHub\npip install git+https://github.com/cfosseprez/swarmsort.git\n```\n\n\n\n##  Quick Start\n\n### Your First Tracker in 30 Seconds\n\n```python\nimport numpy as np\nfrom swarmsort import SwarmSortTracker, Detection\n\n# Step 1: Create a tracker (it's that simple!)\ntracker = SwarmSortTracker()\n\n# Step 2: Tell the tracker what you detected this frame\n# In real use, these would come from your object detector (YOLO, etc.)\ndetections = [\n    Detection(position=[100, 200], confidence=0.9),  # A person at position (100, 200)\n    Detection(position=[300, 400], confidence=0.8),  # Another person at (300, 400)\n]\n\n# Step 3: Get tracking results - SwarmSort handles all the complexity!\ntracked_objects = tracker.update(detections)\n\n# Step 4: Use the results - each object has a unique ID that persists across frames\nfor obj in tracked_objects:\n    print(f\"Person {obj.id} is at position {obj.position} with {obj.confidence:.0%} confidence\")\n    # Output: Person 1 is at position [100. 200.] with 90% confidence\n```\n\n###  Real-World Example: Tracking Paramecia in Video\n\n```python\nimport cv2\nfrom swarmsort import SwarmSortTracker, Detection\n\ntracker = SwarmSortTracker()\n\n# Process a video file\nvideo = cv2.VideoCapture('microscopy.mp4')\n\nwhile True:\n    ret, frame = video.read()\n    if not ret:\n        break\n    \n    # Get detections from your favorite detector\n    # For this example, let's say we detected 2 people:\n    detections = [\n        Detection(\n            position=[320, 240],  # Center of bounding box\n            confidence=0.95,\n            bbox=[300, 220, 340, 260]  # x1, y1, x2, y2\n        ),\n        Detection(\n            position=[150, 180],\n            confidence=0.87,\n            bbox=[130, 160, 170, 200]\n        )\n    ]\n    \n    # SwarmSort assigns consistent IDs across frames\n    tracked = tracker.update(detections)\n    \n    # Draw results on frame\n    for person in tracked:\n        if person.bbox is not None:\n            x1, y1, x2, y2 = person.bbox.astype(int)\n            # Each Paramecium keeps the same ID and color throughout the video!\n            color = (0, 255, 0)  # Green for tracked objects\n            cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)\n            cv2.putText(frame, f\"ID: {person.id}\", (x1, y1-10),\n                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)\n    \n    cv2.imshow('Tracking Results', frame)\n    if cv2.waitKey(1) == ord('q'):\n        break\n```\n\n### \ud83d\udd0c Direct Integration with YOLO and Other Detectors\n\nSwarmSort seamlessly integrates with popular object detectors through optimized conversion utilities:\n\n#### **YOLO v8/v11 Integration**\n```python\nfrom ultralytics import YOLO\nfrom swarmsort import yolo_to_detections, SwarmSortTracker, SwarmSortConfig\n\n# Initialize YOLO detector\nmodel = YOLO('yolov8n.pt')  # or yolov11n.pt, yolov8x.pt, etc.\n\n# Initialize SwarmSort tracker\ntracker = SwarmSortTracker(SwarmSortConfig(\n    max_distance=150,\n    uncertainty_weight=0.3\n))\n\n# Process video stream\ncap = cv2.VideoCapture('video.mp4')\nwhile True:\n    ret, frame = cap.read()\n    if not ret:\n        break\n    \n    # Detect objects with YOLO\n    results = model.predict(frame, conf=0.5)\n    \n    # Convert YOLO output to SwarmSort format (optimized, zero-copy when possible)\n    detections = yolo_to_detections(\n        results[0], \n        confidence_threshold=0.5,\n        class_filter=[0, 1, 2]  # Only track persons, bicycles, cars\n    )\n    \n    # Track objects\n    tracked_objects = tracker.update(detections)\n    \n    # Draw results\n    for obj in tracked_objects:\n        if obj.bbox is not None:\n            x1, y1, x2, y2 = obj.bbox.astype(int)\n            cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)\n            cv2.putText(frame, f\"ID:{obj.id}\", (x1, y1-10),\n                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)\n```\n\n#### **Custom Detector Integration**\n```python\nfrom swarmsort import numpy_to_detections, prepare_detections\n\n# If your detector outputs numpy arrays\nboxes = np.array([[100, 100, 200, 200],  # [x1, y1, x2, y2] format\n                  [300, 150, 400, 250]])\nconfidences = np.array([0.9, 0.85])\n\n# Convert to SwarmSort format\ndetections = numpy_to_detections(\n    boxes=boxes,\n    confidences=confidences,\n    format='xyxy'  # Supports: 'xyxy', 'xywh', 'cxcywh'\n)\n\n# Or with embeddings from your own feature extractor\nembeddings = your_feature_extractor(image_patches)  # Shape: (N, embedding_dim)\ndetections = numpy_to_detections(boxes, confidences, embeddings=embeddings)\n```\n\n#### **Universal Auto-Conversion**\n```python\nfrom swarmsort import prepare_detections\n\n# Automatically detects format and converts + verifies\ndetections = prepare_detections(\n    any_detection_data,  # YOLO results, numpy arrays, or Detection objects\n    source_format='auto',  # Auto-detects format\n    confidence_threshold=0.5,\n    auto_fix=True  # Fixes common issues (clips bounds, normalizes, etc.)\n)\n\n# The prepare_detections function:\n# \u2713 Auto-detects input format (YOLO, numpy, etc.)\n# \u2713 Converts to SwarmSort Detection format\n# \u2713 Validates all inputs\n# \u2713 Auto-fixes common issues (out-of-bounds, NaN values, etc.)\n# \u2713 Optimized with vectorized operations\n```\n\n#### **Batch Processing for Maximum Speed**\n```python\n# Process entire video in batches (useful for offline analysis)\nfrom swarmsort import yolo_to_detections_batch\n\n# Get all YOLO predictions at once\nresults = model.predict('video.mp4', stream=True)\nall_detections = yolo_to_detections_batch(list(results))\n\n# Track through all frames\nall_tracks = []\nfor frame_detections in all_detections:\n    tracked = tracker.update(frame_detections)\n    all_tracks.append(tracked)\n```\n\n\n###  Using Visual Features (Embeddings) for Better Tracking\n\nEmbeddings help the tracker recognize objects by their appearance, not just position. This is super useful when:\n- Objects move quickly or unpredictably\n- Multiple similar objects are close together\n- Objects temporarily disappear and reappear\n\n\nSwarmSort can use your GPU for the integrated default fast lightweight embedding:\n\n```python\nfrom swarmsort import SwarmSortTracker, SwarmSortConfig, Detection\nimport numpy as np\n\n# Enable appearance-based tracking\nconfig = SwarmSortConfig(\n    do_embeddings=True,  # Use visual features for matching\n    embedding_weight=1.0,  # How much to trust appearance vs motion\n)\ntracker = SwarmSortTracker(config)\n```\n\nOr you can use a personalized embedding, and pass it directly the Detection.\n\n```python\n# In practice, embeddings come from a feature extractor (ResNet, etc.)\n# Here's a simple example:\ndef get_embedding_from_image(image_patch):\n    \"\"\"Your feature extractor - could be a neural network\"\"\"\n    # This would be your CNN/feature extractor\n    # Returns a N-dimensional feature vector\n    return np.random.randn(128).astype(np.float32)\n\n# Create detection with visual features\nperson_image = frame[160:200, 130:170]  # Crop person from frame\nembedding = get_embedding_from_image(person_image)\n\ndetection = Detection(\n    position=[150, 180],  # Center position\n    confidence=0.9,\n    embedding=embedding,  # Visual features help maintain ID\n    bbox=[130, 160, 170, 200]  # Bounding box\n)\n\n# The tracker now uses BOTH motion AND appearance for matching!\ntracked_objects = tracker.update([detection])\n```\n\n## \u2699\ufe0f Configuration Made Easy\n\n### Understanding the max_distance Parameter\n\nThe `max_distance` parameter is the foundation of SwarmSort's configuration. **Important**: Set it **1.5-2x higher** than the expected maximum pixel movement between frames because:\n\n- The actual matching uses a **combination** of spatial distance, embedding similarity, and uncertainty penalties\n- With embeddings enabled, the effective matching distance is reduced by visual similarity  \n- Uncertainty penalties further modify the association costs\n- Example: If objects move up to 100 pixels between frames, set `max_distance=150-200`\n\nMany other parameters **automatically scale** with `max_distance`:\n```python\n# When you set max_distance=150, these defaults are automatically set:\nlocal_density_radius = 150      # Same as max_distance\ngreedy_threshold = 30           # max_distance / 5\nreid_max_distance = 150         # Same as max_distance\n```\n\n### All Parameters\n\n| Parameter                  | Default            | Description                                                                           |\n|----------------------------|--------------------|---------------------------------------------------------------------------------------|\n| **Core Tracking**          |                    |                                                                                       |\n| `max_distance`             | 150.0              | Maximum distance for detection-track association                                      |\n| `detection_conf_threshold` | 0.0                | Minimum confidence for detections                                                     |\n| `max_track_age`            | 30                 | Maximum frames to keep track alive without detections                                 |\n| **Motion prediction**      |                    |                                                                                       |\n| `kalman_type`              | 'simple'           | Kalman filter type: 'simple' or 'oc' (OC-SORT style)                                  |\n| **Uncertainty System**     |                    |                                                                                       |\n| `uncertainty_weight`       | 0.33               | Weight for uncertainty penalties (0 = disabled)                                       |\n| `local_density_radius`     | max_distance       | Radius for computing local track density (defaults to max_distance)                   |\n| **Embeddings**             |                    |                                                                                       |\n| `do_embeddings`            | True               | Whether to use embedding features                                                     |\n| `embedding_function`       | 'cupytexture'      | Integrated embedding function: \"cupytexture\", \"cupytexture_color\", \"mega_cupytexture\" |\n| `embedding_weight`         | 1.0                | Weight for embedding similarity in cost function                                      |\n| `max_embeddings_per_track` | 15                 | Maximum embeddings stored per track                                                   |\n| `embedding_matching_method` | 'weighted_average' | Method for multi-embedding matching                                                   |\n| **Collision Handling**     |                    |                                                                                       |\n| `collision_freeze_embeddings` | True               | Freeze embedding updates in dense areas                                               |\n| `embedding_freeze_density` | 1                  | Freeze when \u2265N tracks within radius                                                   |\n| **Assignment Strategy**    |                    |                                                                                       |\n| `assignment_strategy`      | 'hybrid'           | Assignment method: 'hungarian', 'greedy', or 'hybrid'                                 |\n| `greedy_threshold`         | max_distance/5     | Distance threshold for greedy assignment                                              |\n| **Track Initialization**   |                    |                                                                                       |\n| `min_consecutive_detections` | 6                  | Minimum consecutive detections to create track                                        |\n| `max_detection_gap`        | 2                  | Maximum gap between detections                                                        |\n| `pending_detection_distance` | max_distance       | Distance threshold for pending detection matching                                     |\n| **Re-identification**      |                    |                                                                                       |\n| `reid_enabled`             | True               | Enable re-identification of lost tracks                                               |\n| `reid_max_distance`        | max_distance*1.5   | Maximum distance for ReID                                                             |\n| `reid_embedding_threshold` | 0.3                | Embedding threshold for ReID                                                          |\n| **Experimental**           |                    |                                                                                       |\n| `use_probabilistic_costs`  | False              | Use gaussian fusion for cost computation                                              |\n\n\n### \ud83c\udfaf Preset Configurations for Common Scenarios\n\nBest Settings for Performance:\n\n\n  For good balance (speed + accuracy): up to 300 individuals\n```python\n  config = SwarmSortConfig(\n      kalman_type=\"simple\",           # Fast but accurate enough                                                                                                                                         \n      assignment_strategy=\"hybrid\",    # Good balance                                                                                                                                                    \n      uncertainty_weight=0.33,         # Some uncertainty handling                                                                                                                                       \n      do_embeddings=True,              # Use embeddings if available                                                                                                                                     \n      reid_enabled=False,              # Skip for speed                                                                                                                                                  \n  )\n```\n  For maximum speed across all scales: 300+ individuals\n```python\n  config = SwarmSortConfig(\n      kalman_type=\"simple\",           # Fastest Kalman filter                                                                                                                                            \n      assignment_strategy=\"greedy\",    # Fastest assignment                                                                                                                                              \n      uncertainty_weight=0.0,          # Disable uncertainty                                                                                                                                             \n      do_embeddings=False,             # No embeddings                                                                                                                                                   \n      reid_enabled=False,              # No re-identification                                                                                                                                            \n  )\n```\n\n### \ud83d\udd27 Understanding Key Parameters\n\n```python\n# The most important parameters to tune:\n\nconfig = SwarmSortConfig(\n    # 1. How far can an object move between frames?\n    max_distance=150.0,  # Increase for fast objects, decrease for slow\n    \n    # 2. How many frames to confirm a new track?\n    min_consecutive_detections=6,  # Lower = faster response, more false positives\n                                   # Higher = slower response, fewer false positives\n    \n    # 3. How long to keep lost tracks?\n    max_track_age=30,  # At 30 FPS, this is 1 second of \"memory\"\n    \n    # 4. Use appearance features?\n    do_embeddings=True,  # True if objects look different from each other\n                        # False if all objects look the same (e.g., identical boxes)\n    \n    # 5. How to handle crowded scenes?\n    collision_freeze_embeddings=True,  # Prevents ID switches when objects touch\n    uncertainty_weight=0.33,  # Higher = more conservative in uncertain situations\n)\n```\n\n## Advanced Usage\n\n### Different Configuration Methods\n\n```python\nfrom swarmsort import SwarmSortTracker, SwarmSortConfig\n\n# Default tracker\ntracker = SwarmSortTracker()\n\n# With configuration object\nconfig = SwarmSortConfig(max_distance=100.0, do_embeddings=True)\ntracker = SwarmSortTracker(config)\n\n# With dictionary config\ntracker = SwarmSortTracker({'max_distance': 100.0, 'do_embeddings': True})\n```\n\n### Basic Standalone Usage\n\n```python\nfrom swarmsort import SwarmSortTracker, SwarmSortConfig\n\n# SwarmSort is a standalone tracker - no special integration needed\ntracker = SwarmSortTracker()\n\n# Configure for specific use cases\nconfig = SwarmSortConfig(\n    do_embeddings=True,\n    reid_enabled=True,\n    max_distance=100.0,\n    assignment_strategy='hybrid',  # Use hybrid assignment strategy\n    uncertainty_weight=0.33         # Enable uncertainty-based costs\n)\ntracker_configured = SwarmSortTracker(config)\n```\n\n## \ud83d\udce6 Working with Data\n\n### Input: Detection Objects\n\nDetections are what you feed into the tracker - they represent objects found in the current frame:\n\n```python\nfrom swarmsort import Detection\nimport numpy as np\n\n# Minimal detection - just position and confidence\nsimple_detection = Detection(\n    position=[320, 240],  # Center point (x, y)\n    confidence=0.9        # How sure are we this is real? (0-1)\n)\n\n# Full detection with all the bells and whistles\nfull_detection = Detection(\n    position=np.array([320, 240]),        # Object center\n    confidence=0.95,                      # Detection confidence\n    bbox=np.array([300, 220, 340, 260]),  # Bounding box [x1, y1, x2, y2]\n    embedding=feature_vector,             # Visual features (for example from your CNN)\n    class_id=0,                          # 0=person, 1=car, etc.\n    id=\"yolo_detection_42\"               # Your detector's ID (optional)\n)\n\n# Real-world example: Converting YOLO output to SwarmSort\ndef yolo_to_swarmsort(yolo_results):\n    detections = []\n    for box in yolo_results.boxes:\n        x1, y1, x2, y2 = box.xyxy[0].numpy()\n        center_x = (x1 + x2) / 2\n        center_y = (y1 + y2) / 2\n        \n        detections.append(Detection(\n            position=[center_x, center_y],\n            confidence=box.conf[0].item(),\n            bbox=[x1, y1, x2, y2],\n            class_id=int(box.cls[0])\n        ))\n    return detections\n```\n\n###  Output: TrackedObject Results\n\nThe tracker returns TrackedObject instances with rich information about each tracked object:\n\n```python\n# Get tracking results\ntracked_objects = tracker.update(detections)\n\nfor obj in tracked_objects:\n    # Identity\n    print(f\"\ud83c\udd94 Track ID: {obj.id}\")  # Unique ID that persists across frames\n    \n    # Location & Motion\n    print(f\"\ud83d\udccd Position: {obj.position}\")  # Current [x, y] position\n    print(f\"\u27a1\ufe0f Velocity: {obj.velocity}\")  # Speed and direction [vx, vy]\n    \n    # Confidence & Quality\n    print(f\"\u2705 Confidence: {obj.confidence:.1%}\")  # How confident are we?\n    print(f\"\ud83d\udcca Track quality: {obj.hits}/{obj.age}\")  # Hits/Age ratio\n    \n    # Track Status\n    if obj.time_since_update == 0:\n        print(\"\ud83d\udfe2 Currently visible\")\n    else:\n        print(f\"\ud83d\udfe1 Lost for {obj.time_since_update} frames\")\n    \n    # Bounding Box (if available)\n    if obj.bbox is not None:\n        x1, y1, x2, y2 = obj.bbox\n        width = x2 - x1\n        height = y2 - y1\n        print(f\"\ud83d\udcd0 Size: {width:.0f}x{height:.0f} pixels\")\n```\n\n### \ud83d\udd04 Track Lifecycle Management\n\nSwarmSort provides fine control over track states - perfect for different visualization needs:\n\n```python\n# Get only tracks that are currently visible\nalive_tracks = tracker.update(detections)\nprint(f\"\ud83d\udc41\ufe0f Visible now: {len(alive_tracks)} objects\")\n\n# Get tracks that were recently lost (useful for smooth visualization)\nrecently_lost = tracker.get_recently_lost_tracks(max_frames_lost=5)\nprint(f\"\ud83d\udc7b Recently lost: {len(recently_lost)} objects\")\n\n# Get everything (visible + recently lost)\nall_active = tracker.get_all_active_tracks(max_frames_lost=5)\nprint(f\"\ud83d\udcca Total active: {len(all_active)} objects\")\n\n# Example: Different visualization for different states\nfor obj in alive_tracks:\n    draw_solid_box(frame, obj, color='green')  # Solid box for visible\n    \nfor obj in recently_lost:\n    draw_dashed_box(frame, obj, color='yellow')  # Dashed box for lost\n```\n\n\n## \u26a1 Performance & Optimization\n\n### Why SwarmSort is Fast\n\nSwarmSort is optimized for real-world performance:\n\n- **Numba JIT Compilation**: Math operations run at C speed\n- **Vectorized NumPy**: Batch operations instead of loops\n- **Smart Caching**: Reuses computed embeddings and distances\n- **Memory Pooling**: Reduces allocation overhead\n- **Early Exit Logic**: Skips unnecessary computations\n\n\n## Visualization Example\n\nSwarmSort includes built-in visualization utilities for beautiful tracking displays:\n<details>\n  <summary>Click to expand code example</summary>\n\n```python\nimport cv2\nimport numpy as np\nfrom swarmsort import SwarmSortTracker, Detection, SwarmSortConfig\nfrom swarmsort import TrackingVisualizer, VisualizationConfig\n\n# Initialize tracker with your preferred settings\nconfig = SwarmSortConfig(\n    do_embeddings=True,\n    embedding_function='cupytexture',  # or 'mega_cupytexture' for more features\n    assignment_strategy='hybrid',\n    uncertainty_weight=0.33\n)\ntracker = SwarmSortTracker(config)\n\n# Initialize visualizer with custom settings\nvis_config = VisualizationConfig(\n    show_trails=True,\n    trail_length=30,\n    show_ids=True,\n    show_confidence=True,\n    show_velocity_vectors=True,\n    id_font_scale=0.5,\n    id_thickness=2,\n    box_thickness=2\n)\nvisualizer = TrackingVisualizer(vis_config)\n\n# Example usage with video\ncap = cv2.VideoCapture('video.mp4')  # Or use 0 for webcam\n\nwhile True:\n    ret, frame = cap.read()\n    if not ret:\n        break\n    \n    # Detect objects (replace with your detector)\n    # Here's a mock detection for demonstration\n    detections = [\n        Detection(\n            position=np.array([100, 200]),\n            confidence=0.9,\n            bbox=np.array([80, 180, 120, 220])\n        ),\n        Detection(\n            position=np.array([300, 400]),\n            confidence=0.85,\n            bbox=np.array([280, 380, 320, 420])\n        )\n    ]\n    \n    # Update tracker\n    tracked_objects = tracker.update(detections)\n    \n    # Draw tracking results with built-in visualizer\n    frame = visualizer.draw_tracks(frame, tracked_objects)\n    \n    # Optionally show recently lost tracks\n    recently_lost = tracker.get_recently_lost_tracks(max_frames_lost=5)\n    frame = visualizer.draw_lost_tracks(frame, recently_lost)\n    \n    # Display frame\n    cv2.imshow('SwarmSort Tracking', frame)\n    if cv2.waitKey(1) & 0xFF == ord('q'):\n        break\n\ncap.release()\ncv2.destroyAllWindows()\n```\n\n</details>\n\n### Quick Visualization (One-liner)\n\nFor quick prototyping, use the convenience function:\n\n```python\nfrom swarmsort import quick_visualize\n\n# One line to visualize tracking results!\nframe_with_tracks = quick_visualize(frame, tracked_objects, show_trails=True)\n```\n\n### Custom Drawing (If you need more control)\n\nIf you prefer to implement custom visualization:\n\n<details>\n  <summary>Click to expand code example</summary>\n\n```python\nimport cv2\nimport numpy as np\nfrom swarmsort import SwarmSortTracker, Detection, SwarmSortConfig\n\n# Initialize tracker\ntracker = SwarmSortTracker(SwarmSortConfig(do_embeddings=True))\n\n# Function to draw tracking results\ndef draw_tracks(frame, tracked_objects, show_trails=True):\n    \"\"\"Draw bounding boxes and tracking information on frame.\"\"\"\n    # Store trail history (in production, store this outside the function)\n    if not hasattr(draw_tracks, 'trails'):\n        draw_tracks.trails = {}\n    \n    for obj in tracked_objects:\n        # Get track color (consistent color per ID)\n        color = np.random.RandomState(obj.id).randint(0, 255, 3).tolist()\n        \n        # Draw bounding box if available\n        if obj.bbox is not None:\n            x1, y1, x2, y2 = obj.bbox.astype(int)\n            cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)\n            \n            # Draw track ID and confidence\n            label = f\"ID:{obj.id} ({obj.confidence:.2f})\"\n            cv2.putText(frame, label, (x1, y1-10), \n                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)\n        \n        # Draw center point\n        cx, cy = obj.position.astype(int)\n        cv2.circle(frame, (cx, cy), 5, color, -1)\n        \n        # Update and draw trail\n        if show_trails:\n            if obj.id not in draw_tracks.trails:\n                draw_tracks.trails[obj.id] = []\n            draw_tracks.trails[obj.id].append((cx, cy))\n            \n            # Keep only last 30 points\n            draw_tracks.trails[obj.id] = draw_tracks.trails[obj.id][-30:]\n            \n            # Draw trail\n            points = draw_tracks.trails[obj.id]\n            for i in range(1, len(points)):\n                cv2.line(frame, points[i-1], points[i], color, 2)\n    \n    # Clean up old trails\n    active_ids = {obj.id for obj in tracked_objects}\n    draw_tracks.trails = {k: v for k, v in draw_tracks.trails.items() \n                         if k in active_ids}\n    \n    return frame\n```\n\n</details>\n\n### Simple Visualization with Matplotlib\n\nFor a simpler visualization or for Jupyter notebooks:\n\n<details>\n  <summary>Click to expand code example</summary>\n\n```python\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nfrom matplotlib.animation import FuncAnimation\nimport numpy as np\nfrom swarmsort import SwarmSortTracker, Detection\n\n# Create figure\nfig, ax = plt.subplots(figsize=(10, 8))\nax.set_xlim(0, 640)\nax.set_ylim(480, 0)  # Invert y-axis for image coordinates\nax.set_aspect('equal')\nax.set_title('SwarmSort Multi-Object Tracking')\n\ntracker = SwarmSortTracker()\ntrack_history = {}\n\ndef update_plot(frame_num):\n    ax.clear()\n    ax.set_xlim(0, 640)\n    ax.set_ylim(480, 0)\n    ax.set_title(f'Frame {frame_num}')\n    \n    # Generate mock detections (replace with real detections)\n    detections = [\n        Detection(\n            position=np.array([320 + 100*np.sin(frame_num/10), 240]),\n            confidence=0.9,\n            bbox=np.array([300 + 100*np.sin(frame_num/10), 220, \n                          340 + 100*np.sin(frame_num/10), 260])\n        ),\n        Detection(\n            position=np.array([200, 240 + 100*np.cos(frame_num/10)]),\n            confidence=0.85,\n            bbox=np.array([180, 220 + 100*np.cos(frame_num/10),\n                          220, 260 + 100*np.cos(frame_num/10)])\n        )\n    ]\n    \n    # Update tracker\n    tracked_objects = tracker.update(detections)\n    \n    # Plot tracked objects\n    for obj in tracked_objects:\n        # Get consistent color for track ID\n        np.random.seed(obj.id)\n        color = np.random.rand(3)\n        \n        # Draw bounding box\n        if obj.bbox is not None:\n            x1, y1, x2, y2 = obj.bbox\n            rect = patches.Rectangle((x1, y1), x2-x1, y2-y1,\n                                    linewidth=2, edgecolor=color, \n                                    facecolor='none')\n            ax.add_patch(rect)\n        \n        # Draw center point\n        ax.scatter(obj.position[0], obj.position[1], \n                  c=[color], s=100, marker='o')\n        \n        # Add ID label\n        ax.text(obj.position[0], obj.position[1]-20, f'ID:{obj.id}',\n               color=color, fontsize=12, ha='center', weight='bold')\n        \n        # Update history\n        if obj.id not in track_history:\n            track_history[obj.id] = []\n        track_history[obj.id].append(obj.position.copy())\n        \n        # Draw trail\n        if len(track_history[obj.id]) > 1:\n            trail = np.array(track_history[obj.id])\n            ax.plot(trail[:, 0], trail[:, 1], color=color, \n                   linewidth=2, alpha=0.5)\n    \n    # Clean old tracks\n    active_ids = {obj.id for obj in tracked_objects}\n    for track_id in list(track_history.keys()):\n        if track_id not in active_ids:\n            if len(track_history[track_id]) > 50:  # Remove very old tracks\n                del track_history[track_id]\n\n# Create animation\nanim = FuncAnimation(fig, update_plot, frames=200, \n                    interval=50, repeat=True)\nplt.show()\n\n# To save as video:\n# anim.save('tracking_visualization.mp4', writer='ffmpeg', fps=20)\n```\n\n</details>\n\n## \ud83d\ude80 Advanced Features\n\n### \ud83e\udde0 How SwarmSort Thinks: The Intelligence Behind the Tracking\n\n#### **Uncertainty-Aware Tracking**\nSwarmSort knows when it's confident and when it's not:\n\n```python\n# The tracker automatically adjusts behavior based on uncertainty:\n# - New tracks: \"I'm not sure yet, let me observe more\"\n# - Established tracks: \"I know this object well\"\n# - Crowded areas: \"Need to be extra careful here\"\n\nconfig = SwarmSortConfig(\n    uncertainty_weight=0.33,  # How much to consider uncertainty\n    # 0.0 = Ignore uncertainty (aggressive)\n    # 0.5 = Balanced approach\n    # 1.0 = Very conservative\n)\n\n# Example: High uncertainty for drone tracking (unpredictable motion)\ndrone_config = SwarmSortConfig(\n    uncertainty_weight=0.6,  # Be more careful with uncertain tracks\n    kalman_type='oc',       # Better motion model for erratic movement\n)\n```\n\n#### **Smart Collision Prevention**\nPrevents ID switches when objects get close:\n\n```python\n# Scenario: Tracking dancers who frequently cross paths\ndance_config = SwarmSortConfig(\n    collision_freeze_embeddings=True,  # Lock visual features when close\n    embedding_freeze_density=1,        # Freeze when anyone is within...\n    local_density_radius=100.0,        # ...100 pixels\n)\n\n# What happens:\n# 1. Two Paramecium approach each other\n# 2. SwarmSort detects they're getting close\n# 3. Visual features are \"frozen\" - relies on motion only\n# 4. Prevents mixing up their identities\n# 5. Once separated, visual matching resumes\n```\n\n#### **Hybrid Assignment Strategy**\nCombines the best of both worlds:\n\n```python\nconfig = SwarmSortConfig(\n    assignment_strategy='hybrid',  # Smart mode (default)\n    greedy_threshold=30.0,         # Fast matching for obvious cases\n)\n\n# How it works:\n# 1. Obvious matches (very close): Uses fast greedy assignment\n# 2. Ambiguous cases: Falls back to optimal Hungarian algorithm\n# 3. Best of both: Fast AND accurate\n\n\nconfig.assignment_strategy = 'hybrid'  # optimal matching\n```\n\n### \ud83d\udd0d Re-Identification: Bringing Lost Objects Back\n\nPerfect for scenarios where objects temporarily disappear:\n\n```python\n# Example: Security camera at a store entrance\nconfig = SwarmSortConfig(\n    reid_enabled=True,              # Enable re-identification\n    reid_max_distance=200.0,        # Search this far for lost tracks\n    reid_embedding_threshold=0.25,  # How similar must appearances be?\n)\n\n# What happens:\n# 1. Person walks behind a pillar (track lost)\n# 2. Person reappears on the other side\n# 3. SwarmSort compares appearance with recently lost tracks\n# 4. Same person? Same ID! Tracking continues seamlessly\n\n# Real-world usage:\ntracker = SwarmSortTracker(config)\nresults = tracker.update(detections)\n\n# The person who disappeared at frame 100 and reappeared at frame 120\n# will have the SAME track ID - perfect for counting and analytics!\n```\n\n## \ud83d\udd27 Troubleshooting & FAQ\n\n### Common Issues and Solutions\n\n**Q: My tracks keep switching IDs when objects cross paths**\n```python\n# Solution: Enable collision handling\nconfig = SwarmSortConfig(\n    collision_freeze_embeddings=True,  # Prevent ID switches\n    embedding_freeze_density=1,        # Freeze when objects are close\n    do_embeddings=True,                # Use visual features\n    embedding_weight=1.5,              # Trust appearance more\n)\n```\n\n**Q: New tracks take too long to appear**\n```python\n# Solution: Reduce initialization requirements\nconfig = SwarmSortConfig(\n    min_consecutive_detections=2,  # Was 6, now faster\n    init_conf_threshold=0.3,       # Accept lower confidence\n)\n```\n\n**Q: Too many false tracks from noise**\n```python\n# Solution: Be more strict about track creation\nconfig = SwarmSortConfig(\n    min_consecutive_detections=8,      # Require more detections\n    init_conf_threshold=0.7,           # Higher confidence needed\n    detection_conf_threshold=0.5,      # Filter out weak detections\n)\n```\n\n**Q: Tracks disappear too quickly**\n```python\n# Solution: Keep tracks alive longer\nconfig = SwarmSortConfig(\n    max_track_age=30,  # Keep for 2 seconds at 30 FPS (was 30)\n    reid_enabled=True,  # Try to re-identify lost tracks\n)\n```\n\n**Q: Performance is too slow**\nConsider processing every other frame\n\n## Examples\n\nSee the `examples/` directory for comprehensive usage examples.\n\n## Testing\n\n```bash\n# Run tests\npoetry run pytest\n\n# Run tests with coverage\npoetry run pytest --cov=swarmsort --cov-report=html\n\n# Run specific test\npoetry run pytest tests/test_basic.py::test_basic_tracking\n```\n\n## Development\n\n### Development Setup\n\nWant to contribute or modify SwarmSort? Here's how to set up a development environment:\n\n```bash\n# Clone the repository\ngit clone https://github.com/cfosseprez/swarmsort.git\ncd swarmsort\n\n# Install with Poetry (recommended for development)\npoetry install --with dev\n\n# Or use pip in editable mode\npip install -e \".[dev]\"\n```\n\n## Benchmarking\n\n```bash\n# Run benchmarks\npoetry run pytest tests/ --benchmark-only\n```\n\n## License\n\nGPL 3.0 or later - see LICENSE file for details.\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n### Development Workflow\n\n\n```bash\n# Install development dependencies\npoetry install --with dev\n\n# Run linting\npoetry run black swarmsort/\npoetry run flake8 swarmsort/\n\n# Run type checking\npoetry run mypy swarmsort/\n```\n\n",
    "bugtrack_url": null,
    "license": "GPL-3.0-or-later",
    "summary": "Standalone SwarmSort real-time multi-object tracker with integrated lightweight embeddings",
    "version": "1.1.0",
    "project_urls": {
        "Homepage": "https://github.com/cfosseprez/swarmsort",
        "Repository": "https://github.com/cfosseprez/swarmsort"
    },
    "split_keywords": [
        "tracking",
        " computer-vision",
        " multi-object-tracking",
        " embeddings",
        " real-time"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "86f63b7af98abd7d55570d8bd825a98544d9f87f5c216483d68656e0bb06d9cb",
                "md5": "02561135f47121eea9ae39eb64bb752e",
                "sha256": "8b22545827d8d90f09676632ca338efaa6742bc7f1a6ac55b2f348a527d383fa"
            },
            "downloads": -1,
            "filename": "swarmsort-1.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "02561135f47121eea9ae39eb64bb752e",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<3.12,>=3.9",
            "size": 140639,
            "upload_time": "2025-09-09T04:25:53",
            "upload_time_iso_8601": "2025-09-09T04:25:53.205881Z",
            "url": "https://files.pythonhosted.org/packages/86/f6/3b7af98abd7d55570d8bd825a98544d9f87f5c216483d68656e0bb06d9cb/swarmsort-1.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e489ac044bd5a6580c9602110c73d71dc69e6cb8c720fed1d0e11c0df16af48f",
                "md5": "427bffe3232e1662170b9e1341e21179",
                "sha256": "02d48ecb1680dda3f4f2ccd7d07419d2f1c8de17fcf6a055f62ffd0dc1fa4706"
            },
            "downloads": -1,
            "filename": "swarmsort-1.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "427bffe3232e1662170b9e1341e21179",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<3.12,>=3.9",
            "size": 143360,
            "upload_time": "2025-09-09T04:25:55",
            "upload_time_iso_8601": "2025-09-09T04:25:55.173297Z",
            "url": "https://files.pythonhosted.org/packages/e4/89/ac044bd5a6580c9602110c73d71dc69e6cb8c720fed1d0e11c0df16af48f/swarmsort-1.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-09 04:25:55",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "cfosseprez",
    "github_project": "swarmsort",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "swarmsort"
}
        
Elapsed time: 1.08206s