chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:05:14 +08:00
commit 2a547be7fe
7904 changed files with 1000926 additions and 0 deletions
@@ -0,0 +1,2 @@
cache/
dataset/
@@ -0,0 +1,172 @@
<!--[metadata]
title = "Detect and track objects"
tags = ["2D", "Hugging face", "Object detection", "Object tracking", "OpenCV"]
thumbnail = "https://static.rerun.io/detect-and-track-objects/63d7684ab1504c86a5375cb5db0fc515af433e08/480w.png"
thumbnail_dimensions = [480, 480]
channel = "release"
include_in_manifest = true
allow_warnings = true # TODO(emilk): torch produces a warning because of `transformers` (I think?). We should fix that, if we can.
-->
Visualize object detection and segmentation using the [Huggingface's Transformers](https://huggingface.co/docs/transformers/index) and optical flow tracking from OpenCV.
<picture data-inline-viewer="examples/detect_and_track_objects">
<img src="https://static.rerun.io/detact_and_track_objects/ce1939b8f2d22b36c4ca8b36dc0441e106b51da5/full.png" alt="">
<source media="(max-width: 480px)" srcset="https://static.rerun.io/detact_and_track_objects/ce1939b8f2d22b36c4ca8b36dc0441e106b51da5/480w.png">
<source media="(max-width: 768px)" srcset="https://static.rerun.io/detact_and_track_objects/ce1939b8f2d22b36c4ca8b36dc0441e106b51da5/768w.png">
<source media="(max-width: 1024px)" srcset="https://static.rerun.io/detact_and_track_objects/ce1939b8f2d22b36c4ca8b36dc0441e106b51da5/1024w.png">
<source media="(max-width: 1200px)" srcset="https://static.rerun.io/detact_and_track_objects/ce1939b8f2d22b36c4ca8b36dc0441e106b51da5/1200w.png">
</picture>
## Used Rerun types
[`Image`](https://www.rerun.io/docs/reference/types/archetypes/image), [`AssetVideo`](https://www.rerun.io/docs/reference/types/archetypes/asset_video), [`VideoFrameReference`](https://rerun.io/docs/reference/types/archetypes/video_frame_reference), [`SegmentationImage`](https://www.rerun.io/docs/reference/types/archetypes/segmentation_image), [`AnnotationContext`](https://www.rerun.io/docs/reference/types/archetypes/annotation_context), [`Boxes2D`](https://www.rerun.io/docs/reference/types/archetypes/boxes2d), [`TextLog`](https://www.rerun.io/docs/reference/types/archetypes/text_log)
## Background
In this example, optical flow tracking from OpenCV is employed for tracking objects across frames.
Additionally, the example showcases basic object detection and segmentation on a video using the Huggingface transformers library.
## Logging and visualizing with Rerun
The visualizations in this example were created with the following Rerun code.
### Timelines
For each processed video frame, all data sent to Rerun is associated with the [`timelines`](https://www.rerun.io/docs/concepts/logging-and-ingestion/timelines) `frame_idx`.
```python
rr.set_time("frame", sequence=frame_idx)
```
### Video
The input video is logged as a static [`AssetVideo`](https://www.rerun.io/docs/reference/types/archetypes/asset_video) to the `video` entity.
```python
video_asset = rr.AssetVideo(path=video_path)
frame_timestamps_ns = video_asset.read_frame_timestamps_nanos()
rr.log("video", video_asset, static=True)
```
Each frame is processed and the timestamp is logged to the `frame` timeline using a [`VideoFrameReference`](https://www.rerun.io/docs/reference/types/archetypes/video_frame_reference).
```python
rr.log("video", rr.VideoFrameReference(nanoseconds=frame_timestamps_ns[frame_idx]))
```
Since the detection and segmentation model operates on smaller images the resized images are logged to the separate `segmentation/rgb_scaled` entity.
This allows us to subsequently visualize the segmentation mask on top of the video.
```python
rr.log("segmentation/rgb_scaled", rr.Image(rgb_scaled).compress(jpeg_quality=85))
```
### Segmentations
The segmentation results is logged through a combination of two archetypes.
The segmentation image itself is logged as an
[`SegmentationImage`](https://www.rerun.io/docs/reference/types/archetypes/segmentation_image) and
contains the id for each pixel. It is logged to the `segmentation` entity.
```python
rr.log("segmentation", rr.SegmentationImage(mask))
```
The color and label for each class is determined by the
[`AnnotationContext`](https://www.rerun.io/docs/reference/types/archetypes/annotation_context) which is
logged to the root entity using `rr.log("/", …, static=True)` as it should apply to the whole sequence and all
entities that have a class id.
```python
class_descriptions = [rr.AnnotationInfo(id=cat["id"], color=cat["color"], label=cat["name"]) for cat in coco_categories]
rr.log("/", rr.AnnotationContext(class_descriptions), static=True)
```
### Detections
The detections and tracked bounding boxes are visualized by logging the [`Boxes2D`](https://www.rerun.io/docs/reference/types/archetypes/boxes2d) to Rerun.
#### Detections
```python
rr.log(
"segmentation/detections/things",
rr.Boxes2D(
array=thing_boxes,
array_format=rr.Box2DFormat.XYXY,
class_ids=thing_class_ids,
),
)
```
```python
rr.log(
f"image/tracked/{self.tracking_id}",
rr.Boxes2D(
array=self.tracked.bbox_xywh,
array_format=rr.Box2DFormat.XYWH,
class_ids=self.tracked.class_id,
),
)
```
#### Tracked bounding boxes
```python
rr.log(
"segmentation/detections/background",
rr.Boxes2D(
array=background_boxes,
array_format=rr.Box2DFormat.XYXY,
class_ids=background_class_ids,
),
)
```
The color and label of the bounding boxes is determined by their class id, relying on the same
[`AnnotationContext`](https://www.rerun.io/docs/reference/types/archetypes/annotation_context) as the
segmentation images. This ensures that a bounding box and a segmentation image with the same class id will also have the
same color.
Note that it is also possible to log multiple annotation contexts should different colors and / or labels be desired.
The annotation context is resolved by seeking up the entity hierarchy.
### Text log
Rerun integrates with the [Python logging module](https://docs.python.org/3/library/logging.html).
Through the [`TextLog`](https://www.rerun.io/docs/reference/types/archetypes/text_log#textlogintegration) text at different importance level can be logged. After an initial setup that is described on the
[`TextLog`](https://www.rerun.io/docs/reference/types/archetypes/text_log#textlogintegration), statements
such as `logging.info("…")`, `logging.debug("…")`, etc. will show up in the Rerun viewer.
```python
def setup_logging() -> None:
logger = logging.getLogger()
rerun_handler = rr.LoggingHandler("logs")
rerun_handler.setLevel(-1)
logger.addHandler(rerun_handler)
def main() -> None:
# … existing code …
setup_logging() # setup logging
track_objects(video_path, max_frame_count=args.max_frame) # start tracking
```
In the Viewer you can adjust the filter level and look at the messages time-synchronized with respect to other logged data.
## Run the code
To run this example, make sure you have the Rerun repository checked out and the latest SDK installed:
```bash
pip install --upgrade rerun-sdk # install the latest Rerun SDK
git clone git@github.com:rerun-io/rerun.git # Clone the repository
cd rerun
git checkout latest # Check out the commit matching the latest SDK release
```
Install the necessary libraries specified in the requirements file:
```bash
pip install -e examples/python/detect_and_track_objects
```
To experiment with the provided example, simply execute the main Python script:
```bash
python -m detect_and_track_objects # run the example
```
If you wish to customize it for various videos, adjust the maximum frames, explore additional features, or save it use the CLI with the `--help` option for guidance:
```bash
python -m detect_and_track_objects --help
```
@@ -0,0 +1,506 @@
#!/usr/bin/env python3
"""Example applying simple object detection and tracking on a video."""
from __future__ import annotations
import argparse
import json
import logging
import os
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Any, Final
import cv2
import numpy as np
import numpy.typing as npt
import requests
from PIL import Image
import rerun as rr # pip install rerun-sdk
DESCRIPTION = """
# Detect and track objects
This is a more elaborate example applying simple object detection and segmentation on a video using the Huggingface
`transformers` library. Tracking across frames is performed using optical flow from OpenCV. The results are
visualized using Rerun.
The full source code for this example is available
[on GitHub](https://github.com/rerun-io/rerun/blob/latest/examples/python/detect_and_track_objects).
""".strip()
EXAMPLE_DIR: Final = Path(os.path.dirname(__file__))
DATASET_DIR: Final = EXAMPLE_DIR / "dataset" / "tracking_sequences"
DATASET_URL_BASE: Final = "https://storage.googleapis.com/rerun-example-datasets/tracking_sequences"
CACHE_DIR: Final = EXAMPLE_DIR / "cache"
# panoptic_coco_categories.json comes from:
# https://github.com/cocodataset/panopticapi/blob/7bb4655548f98f3fedc07bf37e9040a992b054b0/panoptic_coco_categories.json
# License: https://github.com/cocodataset/panopticapi/blob/7bb4655548f98f3fedc07bf37e9040a992b054b0/license.txt
COCO_CATEGORIES_PATH = EXAMPLE_DIR / "panoptic_coco_categories.json"
DOWNSCALE_FACTOR = 2
DETECTION_SCORE_THRESHOLD = 0.8
os.environ["HF_HOME"] = str(CACHE_DIR.absolute())
from transformers import (
DetrForSegmentation,
DetrImageProcessor,
)
if TYPE_CHECKING:
from collections.abc import Sequence
@dataclass
class Detection:
"""Information about a detected object."""
class_id: int
bbox_xywh: list[float]
image_width: int
image_height: int
def scaled_to_fit_image(self, target_image: npt.NDArray[Any]) -> Detection:
"""Rescales detection to fit to target image."""
target_height, target_width = target_image.shape[:2]
return self.scaled_to_fit_size(target_width=target_width, target_height=target_height)
def scaled_to_fit_size(self, target_width: int, target_height: int) -> Detection:
"""Rescales detection to fit to target image with given size."""
if target_height == self.image_height and target_width == self.image_width:
return self
width_scale = target_width / self.image_width
height_scale = target_height / self.image_height
target_bbox = [
self.bbox_xywh[0] * width_scale,
self.bbox_xywh[1] * height_scale,
self.bbox_xywh[2] * width_scale,
self.bbox_xywh[3] * height_scale,
]
return Detection(self.class_id, target_bbox, target_width, target_height)
class Detector:
"""Detects objects to track."""
def __init__(self, coco_categories: list[dict[str, Any]]) -> None:
logging.info("Initializing neural net for detection and segmentation.")
self.feature_extractor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50-panoptic")
self.model = DetrForSegmentation.from_pretrained("facebook/detr-resnet-50-panoptic")
self.is_thing_from_id: dict[int, bool] = {cat["id"]: bool(cat["isthing"]) for cat in coco_categories}
def detect_objects_to_track(self, rgb: cv2.typing.MatLike, frame_idx: int) -> list[Detection]:
logging.info("Looking for things to track on frame %d", frame_idx)
logging.debug("Preprocess image for detection network")
pil_im_small = Image.fromarray(rgb)
inputs = self.feature_extractor(images=pil_im_small, return_tensors="pt")
_, _, scaled_height, scaled_width = inputs["pixel_values"].shape
scaled_size = (scaled_width, scaled_height)
rgb_scaled = cv2.resize(rgb, scaled_size)
rr.log("segmentation/rgb_scaled", rr.Image(rgb_scaled).compress(jpeg_quality=85))
logging.debug("Pass image to detection network")
outputs = self.model(**inputs)
logging.debug("Extracting detections and segmentations from network output")
processed_sizes = [(scaled_height, scaled_width)]
segmentation_mask = self.feature_extractor.post_process_semantic_segmentation(outputs, processed_sizes)[0]
detections = self.feature_extractor.post_process_object_detection(
outputs,
threshold=0.8,
target_sizes=processed_sizes,
)[0]
mask = segmentation_mask.detach().cpu().numpy().astype(np.uint8)
rr.log("segmentation", rr.SegmentationImage(mask))
boxes = detections["boxes"].detach().cpu().numpy()
class_ids = detections["labels"].detach().cpu().numpy()
things = [self.is_thing_from_id[id] for id in class_ids]
self.log_detections(boxes, class_ids, things)
objects_to_track: list[Detection] = []
for idx, (class_id, is_thing) in enumerate(zip(class_ids, things, strict=False)):
if is_thing:
x_min, y_min, x_max, y_max = boxes[idx, :]
bbox_xywh = [x_min, y_min, x_max - x_min, y_max - y_min]
objects_to_track.append(
Detection(
class_id=class_id,
bbox_xywh=bbox_xywh,
image_width=scaled_width,
image_height=scaled_height,
),
)
return objects_to_track
def log_detections(self, boxes: npt.NDArray[np.float32], class_ids: list[int], things: list[bool]) -> None:
things_np = np.array(things)
class_ids_np = np.array(class_ids, dtype=np.uint16)
thing_boxes = boxes[things_np, :]
thing_class_ids = class_ids_np[things_np]
rr.log(
"segmentation/detections/things",
rr.Boxes2D(
array=thing_boxes,
array_format=rr.Box2DFormat.XYXY,
class_ids=thing_class_ids,
),
)
background_boxes = boxes[~things_np, :]
background_class_ids = class_ids[~things_np]
rr.log(
"segmentation/detections/background",
rr.Boxes2D(
array=background_boxes,
array_format=rr.Box2DFormat.XYXY,
class_ids=background_class_ids,
),
)
class Tracker:
"""
Each instance takes care of tracking a single object using optical flow.
The factory class method `create_new_tracker` is used to give unique tracking id's per instance.
"""
next_tracking_id = 0
MAX_TIMES_UNDETECTED = 2
def __init__(self, tracking_id: int, detection: Detection, bgr: cv2.typing.MatLike) -> None:
self.tracking_id = tracking_id
self.tracked = detection.scaled_to_fit_image(bgr)
self.num_recent_undetected_frames = 0
# Store the previous frame and points for optical flow tracking
self.prev_gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
self.is_active = True
self.prev_points: npt.NDArray[np.float32] = np.array([]) # Will be initialized below
self._init_tracking_points()
self.log_tracked()
def _init_tracking_points(self) -> None:
"""Initialize corner points within the bounding box for tracking."""
x, y, w, h = [int(v) for v in self.tracked.bbox_xywh]
# Create a grid of points within the bounding box
points = []
grid_size = 5
for i in range(grid_size):
for j in range(grid_size):
px = x + (w * (i + 1)) // (grid_size + 1)
py = y + (h * (j + 1)) // (grid_size + 1)
points.append([[px, py]])
self.prev_points = np.array(points, dtype=np.float32)
@classmethod
def create_new_tracker(cls, detection: Detection, bgr: cv2.typing.MatLike) -> Tracker:
new_tracker = cls(cls.next_tracking_id, detection, bgr)
cls.next_tracking_id += 1
return new_tracker
def update(self, bgr: cv2.typing.MatLike) -> None:
if not self.is_tracking:
return
curr_gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
# Calculate optical flow
next_points, status, _error = cv2.calcOpticalFlowPyrLK( # type: ignore[call-overload]
self.prev_gray,
curr_gray,
self.prev_points,
None,
winSize=(15, 15),
maxLevel=2,
)
if next_points is None or status is None:
logging.info("Optical flow failed for tracker with id #%d", self.tracking_id)
self.is_active = False
self.log_tracked()
return
# Filter good points
status_mask = status.flatten() == 1
good_new = next_points[status_mask].reshape(-1, 2)
good_old = self.prev_points[status_mask].reshape(-1, 2)
if len(good_new) < 3:
logging.info("Too few points tracked for tracker with id #%d", self.tracking_id)
self.is_active = False
self.log_tracked()
return
# Calculate displacement to adjust bbox
displacement_x = np.median(good_new[:, 0] - good_old[:, 0])
displacement_y = np.median(good_new[:, 1] - good_old[:, 1])
x, y, w, h = self.tracked.bbox_xywh
new_x = x + displacement_x
new_y = y + displacement_y
self.tracked.bbox_xywh = clip_bbox_to_image(
bbox_xywh=[new_x, new_y, w, h],
image_width=self.tracked.image_width,
image_height=self.tracked.image_height,
)
# Update for next iteration
self.prev_gray = curr_gray.copy()
self.prev_points = good_new.reshape(-1, 1, 2)
self.log_tracked()
def log_tracked(self) -> None:
if self.is_tracking:
rr.log(
f"video/tracked/{self.tracking_id}",
rr.Boxes2D(
array=self.tracked.bbox_xywh,
array_format=rr.Box2DFormat.XYWH,
class_ids=self.tracked.class_id,
),
)
else:
rr.log(f"video/tracked/{self.tracking_id}", rr.Boxes2D.cleared())
def update_with_detection(self, detection: Detection, bgr: cv2.typing.MatLike) -> None:
self.num_recent_undetected_frames = 0
self.tracked = detection.scaled_to_fit_image(bgr)
self.prev_gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
self.is_active = True
self._init_tracking_points()
self.log_tracked()
def set_not_detected_in_frame(self) -> None:
self.num_recent_undetected_frames += 1
if self.num_recent_undetected_frames >= Tracker.MAX_TIMES_UNDETECTED:
logging.info(
"Dropping tracker with id #%d after not being detected %d times",
self.tracking_id,
self.num_recent_undetected_frames,
)
self.is_active = False
self.log_tracked()
@property
def is_tracking(self) -> bool:
return self.is_active
def match_score(self, other: Detection) -> float:
"""Returns bbox IoU if classes match, otherwise 0."""
if self.tracked.class_id != other.class_id:
return 0.0
if not self.is_tracking:
return 0.0
other = other.scaled_to_fit_size(target_width=self.tracked.image_width, target_height=self.tracked.image_height)
tracked_bbox = self.tracked.bbox_xywh
other_bbox = other.bbox_xywh
return box_iou(tracked_bbox, other_bbox)
def box_iou(first: list[float], second: list[float]) -> float:
"""Calculate Intersection over Union (IoU) between two 2D rectangles in XYWH format."""
left = max(first[0], second[0])
right = min(first[0] + first[2], second[0] + second[2])
top = min(first[1] + first[3], second[1] + second[3])
bottom = max(first[1], second[1])
overlap_width = max(0.0, right - left)
overlap_height = max(0.0, top - bottom)
intersection_area = overlap_width * overlap_height
tracked_area = first[2] * first[3]
other_area = second[2] * second[3]
union_area = tracked_area + other_area - intersection_area
return intersection_area / union_area
def clip_bbox_to_image(bbox_xywh: list[float], image_width: int, image_height: int) -> list[float]:
x_min = max(0, bbox_xywh[0])
y_min = max(0, bbox_xywh[1])
x_max = min(image_width - 1, bbox_xywh[0] + bbox_xywh[2])
y_max = min(image_height - 1, bbox_xywh[1] + bbox_xywh[3])
return [x_min, y_min, x_max - x_min, y_max - y_min]
def update_trackers_with_detections(
trackers: list[Tracker],
detections: Sequence[Detection],
label_strs: Sequence[str],
bgr: cv2.typing.MatLike,
) -> list[Tracker]:
"""
Tries to match detections to existing trackers and updates the trackers if they match.
Any detections that don't match existing trackers will generate new trackers.
Returns the new set of trackers.
"""
non_updated_trackers = list(trackers) # shallow copy
updated_trackers: list[Tracker] = []
logging.debug("Updating %d trackers with %d new detections", len(trackers), len(detections))
for detection in detections:
top_match_score = 0.0
best_match_idx = -1
if non_updated_trackers:
scores = [tracker.match_score(detection) for tracker in non_updated_trackers]
best_match_idx = int(np.argmax(scores))
top_match_score = scores[best_match_idx]
if top_match_score > 0.0 and best_match_idx >= 0:
best_tracker = non_updated_trackers.pop(best_match_idx)
best_tracker.update_with_detection(detection, bgr)
updated_trackers.append(best_tracker)
else:
updated_trackers.append(Tracker.create_new_tracker(detection, bgr))
logging.info(
"Tracking newly detected %s with tracking id #%d",
label_strs[detection.class_id],
Tracker.next_tracking_id,
)
logging.debug("Updating %d trackers without matching detections", len(non_updated_trackers))
for tracker in non_updated_trackers:
tracker.set_not_detected_in_frame()
tracker.update(bgr)
if tracker.is_tracking:
updated_trackers.append(tracker)
logging.info("Tracking %d objects after updating with %d new detections", len(updated_trackers), len(detections))
return updated_trackers
def track_objects(video_path: str, *, max_frame_count: int | None) -> None:
with open(COCO_CATEGORIES_PATH, encoding="utf8") as f:
coco_categories = json.load(f)
class_descriptions = [
rr.AnnotationInfo(id=cat["id"], color=cat["color"], label=cat["name"]) for cat in coco_categories
]
rr.log("/", rr.AnnotationContext(class_descriptions), static=True)
logging.info("Initializing detector…")
# This call has a tendency to hard exit on failure (no exceptions):
detector = Detector(coco_categories=coco_categories)
logging.info("Detector initialized.")
video_asset = rr.AssetVideo(path=video_path)
frame_timestamps_ns = video_asset.read_frame_timestamps_nanos()
rr.log("video", video_asset, static=True)
logging.info("Loading input video: %s", str(video_path))
cap = cv2.VideoCapture(video_path)
frame_idx = 0
label_strs = [cat["name"] or str(cat["id"]) for cat in coco_categories]
trackers: list[Tracker] = []
while cap.isOpened():
if max_frame_count is not None and frame_idx >= max_frame_count:
break
ret, bgr = cap.read()
rr.set_time("frame", sequence=frame_idx)
if not ret:
logging.info("End of video")
break
rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
rr.log("video", rr.VideoFrameReference(nanoseconds=frame_timestamps_ns[frame_idx]))
if not trackers or frame_idx % 40 == 0:
detections = detector.detect_objects_to_track(rgb=rgb, frame_idx=frame_idx)
trackers = update_trackers_with_detections(trackers, detections, label_strs, bgr)
else:
if frame_idx % 10 == 0:
logging.debug("Running tracking update step for frame %d", frame_idx)
for tracker in trackers:
tracker.update(bgr)
trackers = [tracker for tracker in trackers if tracker.is_tracking]
frame_idx += 1
def get_downloaded_path(dataset_dir: Path, video_name: str) -> str:
video_file_name = f"{video_name}.mp4"
destination_path = dataset_dir / video_file_name
if destination_path.exists():
logging.info("%s already exists. No need to download", destination_path)
return str(destination_path)
source_path = f"{DATASET_URL_BASE}/{video_file_name}"
logging.info("Downloading video from %s to %s", source_path, destination_path)
os.makedirs(dataset_dir.absolute(), exist_ok=True)
with requests.get(source_path, stream=True) as req:
req.raise_for_status()
with open(destination_path, "wb") as f:
f.writelines(req.iter_content(chunk_size=8192))
return str(destination_path)
def setup_logging() -> None:
logger = logging.getLogger()
rerun_handler = rr.LoggingHandler("logs")
rerun_handler.setLevel(-1)
logger.addHandler(rerun_handler)
def main() -> None:
# Ensure the logging gets written to stderr:
logging.getLogger().addHandler(logging.StreamHandler())
logging.getLogger().setLevel("DEBUG")
parser = argparse.ArgumentParser(description="Example applying simple object detection and tracking on a video.")
parser.add_argument(
"--video",
type=str,
default="horses",
choices=["horses", "driving", "boats"],
help="The example video to run on.",
)
parser.add_argument("--dataset-dir", type=Path, default=DATASET_DIR, help="Directory to save example videos to.")
parser.add_argument("--video-path", type=str, default="", help="Full path to video to run on. Overrides `--video`.")
parser.add_argument(
"--max-frame",
type=int,
help="Stop after processing this many frames. If not specified, will run until interrupted.",
)
rr.script_add_args(parser)
args = parser.parse_args()
rr.script_setup(args, "rerun_example_detect_and_track_objects")
setup_logging()
rr.log("description", rr.TextDocument(DESCRIPTION, media_type=rr.MediaType.MARKDOWN), static=True)
video_path: str = args.video_path
if not video_path:
video_path = get_downloaded_path(args.dataset_dir, args.video)
track_objects(video_path, max_frame_count=args.max_frame)
rr.script_teardown(args)
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,25 @@
[project]
name = "detect_and_track_objects"
version = "0.1.0"
# requires-python = "<3.12"
readme = "README.md"
dependencies = [
"numpy",
"opencv-python>4.9",
"pillow",
"requests>=2.31,<3",
"rerun-sdk",
"timm==1.0.19",
"torch", # this will use the version defined in the uv workspace
"transformers>=4.55.0",
]
[project.scripts]
detect_and_track_objects = "detect_and_track_objects:main"
[tool.rerun-example]
extra-args = "--max-frame=10"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"