Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 6 additions & 53 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,54 +1,7 @@
# Git
.git
.gitignore
# Exclude everything by default
*

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Virtual Environment
venv/
env/
ENV/

# IDE
.idea/
.vscode/
*.swp
*.swo

# Docker
Dockerfile
.dockerignore

# Documentation
docs/
*.md
*.rst

# Tests
tests/
.pytest_cache/
.coverage
htmlcov/

# Misc
.DS_Store
# Allow only what the Dockerfile needs
!collab_env/
!scripts/
!pyproject.toml
13 changes: 13 additions & 0 deletions .gcloudignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Upload only what the Docker build needs
# .gcloudignore controls what gcloud builds submit uploads

# Start by ignoring everything
*

# Allow only what Dockerfile.tracking-studio needs
!collab_env/
!scripts/
!pyproject.toml
!Dockerfile.tracking-studio
!cloudbuild.yaml
!.dockerignore
48 changes: 48 additions & 0 deletions Dockerfile.tracking-studio
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
FROM python:3.10-slim

# Install system dependencies
RUN apt-get update && apt-get install -y \
ffmpeg \
libgl1 \
libglib2.0-0 \
libsm6 \
libxext6 \
libxrender-dev \
&& rm -rf /var/lib/apt/lists/*

# Install uv for fast dependency resolution
RUN pip install uv

WORKDIR /workspace

# Install CPU-only PyTorch first (prevents ultralytics pulling 900MB CUDA version)
RUN uv pip install --system --no-cache \
torch torchvision --index-url https://download.pytorch.org/whl/cpu

# Install Python dependencies (Roboflow models load via .pt download, no inference SDK needed)
COPY pyproject.toml .
RUN uv pip install --system --no-cache \
nicegui \
ultralytics \
supervision \
google-cloud-storage \
gcsfs \
opencv-python-headless \
pandas \
numpy \
loguru

# Copy application code
COPY collab_env/ collab_env/
COPY scripts/ scripts/

# Create model cache and tmp directories
RUN mkdir -p /workspace/models
RUN mkdir -p /tmp/videos /tmp/outputs /tmp/uploads

ENV PORT=8080
ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH=/workspace
ENV NICEGUI_RELOAD=false

CMD ["python", "scripts/tracking/run_tracking_studio.py"]
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,6 @@ Detailed documentation for specific modules:
* `GNN Training <docs/gnn/GNNReadMe.md>`_ - Graph Neural Network training and rollouts
* `Simulation <docs/sim/README.md>`_ - Boids simulation and output format
* `Tracking <docs/tracking/README.md>`_ - Animal tracking and thermal video processing
* `Tracking Studio <docs/tracking/tracking_web_gui.md>`_ - Interactive web GUI for video object detection and tracking

For contributing guidelines, see `CONTRIBUTING.md <CONTRIBUTING.md>`_.
35 changes: 35 additions & 0 deletions cloudbuild.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
steps:
- name: 'gcr.io/cloud-builders/docker'
args:
- 'build'
- '-t'
- 'gcr.io/$PROJECT_ID/tracking-studio:latest'
- '-f'
- 'Dockerfile.tracking-studio'
- '.'

- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'gcr.io/$PROJECT_ID/tracking-studio:latest']

- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- 'run'
- 'deploy'
- 'tracking-studio'
- '--image=gcr.io/$PROJECT_ID/tracking-studio:latest'
- '--platform=managed'
- '--region=us-central1'
- '--memory=4Gi'
- '--cpu=2'
- '--timeout=900'
- '--concurrency=1'
- '--max-instances=5'
- '--set-secrets=ROBOFLOW_API_KEY=roboflow-api-key:latest'
- '--no-allow-unauthenticated'

images:
- 'gcr.io/$PROJECT_ID/tracking-studio:latest'

options:
machineType: 'N1_HIGHCPU_8'
45 changes: 44 additions & 1 deletion collab_env/dashboard/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ def __init__(
self.clear_cache_button = pn.widgets.Button(name="Clear Cache", width=100)
self.cache_info_pane = pn.pane.HTML("", width=300)

# Data browser refresh button
self.refresh_browser_button = pn.widgets.Button(
name="🔄 Refresh", button_type="primary", width=100
)

# PLY viewer state
self.current_ply_viewer = None

Expand All @@ -235,6 +240,7 @@ def __init__(
self.save_button.on_click(self._save_edit)
self.cancel_edit_button.on_click(self._cancel_edit)
self.clear_cache_button.on_click(self._clear_cache)
self.refresh_browser_button.on_click(self._refresh_data_browser)
self.convert_video_button.on_click(self._convert_video)
self.bbox_viewer_button.on_click(self._open_bbox_viewer)
self.mesh_3d_viewer_button.on_click(self._open_mesh_3d_viewer)
Expand Down Expand Up @@ -271,6 +277,41 @@ def _load_sessions(self):
f"<p style='color:red'>Error loading sessions: {e}</p>"
)

def _refresh_data_browser(self, event=None):
"""Refresh data browser by re-reading buckets and resetting app state."""
try:
logger.info("Refreshing data browser...")
self.status_pane.object = "<p>🔄 Refreshing data browser...</p>"

# Reset app state
self.selected_session = ""
self.selected_file = ""
self.current_bucket_type = "curated"
self.session_select.value = ""

# Clear current session data
self.current_session_files = []
self.current_file_content = None
self.current_file_info = {}
self.display_to_path_map = {}

# Hide UI elements
self.bucket_type_toggle.visible = False
self.file_tree.visible = False
self.file_tree.options = []
self.file_viewer.object = "<p>Select a file to view its contents</p>"
self.file_name_header.object = "<h3>No file selected</h3>"
self.file_management_controls.visible = False

# Re-load sessions from both buckets
self._load_sessions()

logger.info("Data browser refreshed successfully")

except Exception as e:
logger.error(f"Error refreshing data browser: {e}")
self.status_pane.object = f"<p style='color:red'>Error refreshing: {e}</p>"

def _on_session_change(self, event):
"""Handle session selection change."""
session_name = event.new
Expand Down Expand Up @@ -2110,7 +2151,9 @@ def create_layout(self):
cache_controls = pn.Column(self.cache_info_pane, self.clear_cache_button)

nav_panel = pn.Column(
"## Data Browser",
pn.Row(
"## Data Browser", pn.Spacer(width=100), self.refresh_browser_button
),
self.session_select,
self.bucket_type_toggle,
self.file_tree,
Expand Down
3 changes: 3 additions & 0 deletions collab_env/tracking/visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ def overlay_tracks_on_video(

# Get frame size
sample_frame = cv2.imread(str(frame_paths[0]))
assert sample_frame is not None, f"Failed to read frame: {frame_paths[0]}"
h, w = sample_frame.shape[:2]
writer = cv2.VideoWriter(
str(output_video),
Expand All @@ -146,6 +147,8 @@ def overlay_tracks_on_video(
for frame_path in frame_paths:
frame_idx = int(frame_path.stem.split("_")[-1])
frame = cv2.imread(str(frame_path))
if frame is None:
continue

frame_tracks = df[df["frame"] == frame_idx]
for _, row in frame_tracks.iterrows():
Expand Down
12 changes: 12 additions & 0 deletions collab_env/tracking_studio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""
NiceGUI-based Video Tracking Studio

A web application for interactive video tracking with support for:
- GCS bucket video browsing and upload
- YOLO and Roboflow model selection
- Real-time tracking visualization
- ByteTrack with Re-ID support
- CSV output export
"""

__version__ = "0.1.0"
Loading
Loading