Skip to content

Commit

Permalink
Ultralytics Code Refactor https://ultralytics.com/actions (ultralytic…
Browse files Browse the repository at this point in the history
…s#14109)

Signed-off-by: Glenn Jocher <[email protected]>
Co-authored-by: UltralyticsAssistant <[email protected]>
  • Loading branch information
glenn-jocher and UltralyticsAssistant authored Jun 30, 2024
1 parent ff63a56 commit 691b5da
Show file tree
Hide file tree
Showing 16 changed files with 124 additions and 113 deletions.
32 changes: 18 additions & 14 deletions examples/YOLOv8-Action-Recognition/action_recognition.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def available_model_names() -> List[str]:
"""
return list(TorchVisionVideoClassifier.model_name_to_model_and_weights.keys())

def preprocess_crops_for_video_cls(self, crops: List[np.ndarray], input_size: list = [224, 224]) -> torch.Tensor:
def preprocess_crops_for_video_cls(self, crops: List[np.ndarray], input_size: list = None) -> torch.Tensor:
"""
Preprocess a list of crops for video classification.
Expand All @@ -80,6 +80,8 @@ def preprocess_crops_for_video_cls(self, crops: List[np.ndarray], input_size: li
Returns:
torch.Tensor: Preprocessed crops as a tensor with dimensions (1, T, C, H, W).
"""
if input_size is None:
input_size = [224, 224]
from torchvision.transforms import v2

transform = v2.Compose(
Expand Down Expand Up @@ -156,7 +158,7 @@ def __init__(
model = model.half()
self.model = model.eval()

def preprocess_crops_for_video_cls(self, crops: List[np.ndarray], input_size: list = [224, 224]) -> torch.Tensor:
def preprocess_crops_for_video_cls(self, crops: List[np.ndarray], input_size: list = None) -> torch.Tensor:
"""
Preprocess a list of crops for video classification.
Expand All @@ -167,6 +169,8 @@ def preprocess_crops_for_video_cls(self, crops: List[np.ndarray], input_size: li
Returns:
torch.Tensor: Preprocessed crops as a tensor (1, T, C, H, W).
"""
if input_size is None:
input_size = [224, 224]
from torchvision.transforms import v2

transform = v2.Compose(
Expand Down Expand Up @@ -266,15 +270,7 @@ def run(
video_cls_overlap_ratio: float = 0.25,
fp16: bool = False,
video_classifier_model: str = "microsoft/xclip-base-patch32",
labels: List[str] = [
"walking",
"running",
"brushing teeth",
"looking into phone",
"weight lifting",
"cooking",
"sitting",
],
labels: List[str] = None,
) -> None:
"""
Run action recognition on a video source using YOLO for object detection and a video classifier.
Expand All @@ -295,6 +291,16 @@ def run(
Returns:
None</edit>
"""
if labels is None:
labels = [
"walking",
"running",
"brushing teeth",
"looking into phone",
"weight lifting",
"cooking",
"sitting",
]
# Initialize models and device
device = select_device(device)
yolo_model = YOLO(weights).to(device)
Expand All @@ -312,9 +318,7 @@ def run(
# Initialize video capture
if source.startswith("http") and urlparse(source).hostname in {"www.youtube.com", "youtube.com", "youtu.be"}:
source = get_best_youtube_url(source)
elif source.endswith(".mp4"):
pass
else:
elif not source.endswith(".mp4"):
raise ValueError("Invalid source. Supported sources are YouTube URLs and MP4 files.")
cap = cv2.VideoCapture(source)

Expand Down
1 change: 1 addition & 0 deletions examples/YOLOv8-OpenCV-int8-tflite-Python/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class LetterBox:
def __init__(
self, new_shape=(img_width, img_height), auto=False, scaleFill=False, scaleup=True, center=True, stride=32
):
"""Initializes LetterBox with parameters for reshaping and transforming image while maintaining aspect ratio."""
self.new_shape = new_shape
self.auto = auto
self.scaleFill = scaleFill
Expand Down
22 changes: 17 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,24 @@ def pytest_addoption(parser):
Add custom command-line options to pytest.
Args:
parser (pytest.config.Parser): The pytest parser object.
parser (pytest.config.Parser): The pytest parser object for adding custom command-line options.
Returns:
(None)
"""
parser.addoption("--slow", action="store_true", default=False, help="Run slow tests")


def pytest_collection_modifyitems(config, items):
"""
Modify the list of test items to remove tests marked as slow if the --slow option is not provided.
Modify the list of test items to exclude tests marked as slow if the --slow option is not specified.
Args:
config (pytest.config.Config): The pytest config object.
items (list): List of test items to be executed.
config (pytest.config.Config): The pytest configuration object that provides access to command-line options.
items (list): The list of collected pytest item objects to be modified based on the presence of --slow option.
Returns:
(None) The function modifies the 'items' list in place, and does not return a value.
"""
if not config.getoption("--slow"):
# Remove the item entirely from the list of test items if it's marked as 'slow'
Expand All @@ -38,6 +44,9 @@ def pytest_sessionstart(session):
Args:
session (pytest.Session): The pytest session object.
Returns:
(None)
"""
from ultralytics.utils.torch_utils import init_seeds

Expand All @@ -54,9 +63,12 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):
and directories used during testing.
Args:
terminalreporter (pytest.terminal.TerminalReporter): The terminal reporter object.
terminalreporter (pytest.terminal.TerminalReporter): The terminal reporter object used for terminal output.
exitstatus (int): The exit status of the test run.
config (pytest.config.Config): The pytest config object.
Returns:
(None)
"""
from ultralytics.utils import WEIGHTS_DIR

Expand Down
16 changes: 8 additions & 8 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def run(cmd):


def test_special_modes():
"""Test various special command modes of YOLO."""
"""Test various special command-line modes for YOLO functionality."""
run("yolo help")
run("yolo checks")
run("yolo version")
Expand All @@ -30,38 +30,38 @@ def test_special_modes():

@pytest.mark.parametrize("task,model,data", TASK_MODEL_DATA)
def test_train(task, model, data):
"""Test YOLO training for a given task, model, and data."""
"""Test YOLO training for different tasks, models, and datasets."""
run(f"yolo train {task} model={model} data={data} imgsz=32 epochs=1 cache=disk")


@pytest.mark.parametrize("task,model,data", TASK_MODEL_DATA)
def test_val(task, model, data):
"""Test YOLO validation for a given task, model, and data."""
"""Test YOLO validation process for specified task, model, and data using a shell command."""
run(f"yolo val {task} model={model} data={data} imgsz=32 save_txt save_json")


@pytest.mark.parametrize("task,model,data", TASK_MODEL_DATA)
def test_predict(task, model, data):
"""Test YOLO prediction on sample assets for a given task and model."""
"""Test YOLO prediction on provided sample assets for specified task and model."""
run(f"yolo predict model={model} source={ASSETS} imgsz=32 save save_crop save_txt")


@pytest.mark.parametrize("model", MODELS)
def test_export(model):
"""Test exporting a YOLO model to different formats."""
"""Test exporting a YOLO model to TorchScript format."""
run(f"yolo export model={model} format=torchscript imgsz=32")


def test_rtdetr(task="detect", model="yolov8n-rtdetr.yaml", data="coco8.yaml"):
"""Test the RTDETR functionality with the Ultralytics framework."""
"""Test the RTDETR functionality within Ultralytics for detection tasks using specified model and data."""
# Warning: must use imgsz=640 (note also add coma, spaces, fraction=0.25 args to test single-image training)
run(f"yolo train {task} model={model} data={data} --imgsz= 160 epochs =1, cache = disk fraction=0.25")
run(f"yolo predict {task} model={model} source={ASSETS / 'bus.jpg'} imgsz=160 save save_crop save_txt")


@pytest.mark.skipif(checks.IS_PYTHON_3_12, reason="MobileSAM with CLIP is not supported in Python 3.12")
def test_fastsam(task="segment", model=WEIGHTS_DIR / "FastSAM-s.pt", data="coco8-seg.yaml"):
"""Test FastSAM segmentation functionality within Ultralytics."""
"""Test FastSAM model for segmenting objects in images using various prompts within Ultralytics."""
source = ASSETS / "bus.jpg"

run(f"yolo segment val {task} model={model} data={data} imgsz=32")
Expand Down Expand Up @@ -99,7 +99,7 @@ def test_fastsam(task="segment", model=WEIGHTS_DIR / "FastSAM-s.pt", data="coco8


def test_mobilesam():
"""Test MobileSAM segmentation functionality using Ultralytics."""
"""Test MobileSAM segmentation with point prompts using Ultralytics."""
from ultralytics import SAM

# Load the model
Expand Down
10 changes: 5 additions & 5 deletions tests/test_cuda.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_checks():
],
)
def test_export_engine_matrix(task, dynamic, int8, half, batch):
"""Test YOLO exports to TensorRT format."""
"""Test YOLO model export to TensorRT format for various configurations and run inference."""
file = YOLO(TASK2MODEL[task]).export(
format="engine",
imgsz=32,
Expand All @@ -51,15 +51,15 @@ def test_export_engine_matrix(task, dynamic, int8, half, batch):

@pytest.mark.skipif(not CUDA_IS_AVAILABLE, reason="CUDA is not available")
def test_train():
"""Test model training on a minimal dataset."""
"""Test model training on a minimal dataset using available CUDA devices."""
device = 0 if CUDA_DEVICE_COUNT == 1 else [0, 1]
YOLO(MODEL).train(data="coco8.yaml", imgsz=64, epochs=1, device=device) # requires imgsz>=64


@pytest.mark.slow
@pytest.mark.skipif(not CUDA_IS_AVAILABLE, reason="CUDA is not available")
def test_predict_multiple_devices():
"""Validate model prediction on multiple devices."""
"""Validate model prediction consistency across CPU and CUDA devices."""
model = YOLO("yolov8n.pt")
model = model.cpu()
assert str(model.device) == "cpu"
Expand All @@ -84,7 +84,7 @@ def test_predict_multiple_devices():

@pytest.mark.skipif(not CUDA_IS_AVAILABLE, reason="CUDA is not available")
def test_autobatch():
"""Check batch size for YOLO model using autobatch."""
"""Check optimal batch size for YOLO model training using autobatch utility."""
from ultralytics.utils.autobatch import check_train_batch_size

check_train_batch_size(YOLO(MODEL).model.cuda(), imgsz=128, amp=True)
Expand All @@ -103,7 +103,7 @@ def test_utils_benchmarks():

@pytest.mark.skipif(not CUDA_IS_AVAILABLE, reason="CUDA is not available")
def test_predict_sam():
"""Test SAM model prediction with various prompts."""
"""Test SAM model predictions using different prompts, including bounding boxes and point annotations."""
from ultralytics import SAM
from ultralytics.models.sam import Predictor as SAMPredictor

Expand Down
10 changes: 5 additions & 5 deletions tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@


def test_func(*args): # noqa
"""Test function callback."""
"""Test function callback for evaluating YOLO model performance metrics."""
print("callback test passed")


def test_export():
"""Test model exporting functionality."""
"""Tests the model exporting function by adding a callback and asserting its execution."""
exporter = Exporter()
exporter.add_callback("on_export_start", test_func)
assert test_func in exporter.callbacks["on_export_start"], "callback test failed"
Expand All @@ -26,7 +26,7 @@ def test_export():


def test_detect():
"""Test object detection functionality."""
"""Test YOLO object detection training, validation, and prediction functionality."""
overrides = {"data": "coco8.yaml", "model": "yolov8n.yaml", "imgsz": 32, "epochs": 1, "save": False}
cfg = get_cfg(DEFAULT_CFG)
cfg.data = "coco8.yaml"
Expand Down Expand Up @@ -65,7 +65,7 @@ def test_detect():


def test_segment():
"""Test image segmentation functionality."""
"""Tests image segmentation training, validation, and prediction pipelines using YOLO models."""
overrides = {"data": "coco8-seg.yaml", "model": "yolov8n-seg.yaml", "imgsz": 32, "epochs": 1, "save": False}
cfg = get_cfg(DEFAULT_CFG)
cfg.data = "coco8-seg.yaml"
Expand Down Expand Up @@ -104,7 +104,7 @@ def test_segment():


def test_classify():
"""Test image classification functionality."""
"""Test image classification including training, validation, and prediction phases."""
overrides = {"data": "imagenet10", "model": "yolov8n-cls.yaml", "imgsz": 32, "epochs": 1, "save": False}
cfg = get_cfg(DEFAULT_CFG)
cfg.data = "imagenet10"
Expand Down
8 changes: 4 additions & 4 deletions tests/test_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

@pytest.mark.slow
def test_similarity():
"""Test similarity calculations and SQL queries for correctness and response length."""
"""Test the correctness and response length of similarity calculations and SQL queries in the Explorer."""
exp = Explorer(data="coco8.yaml")
exp.create_embeddings_table()
similar = exp.get_similar(idx=1)
Expand All @@ -26,7 +26,7 @@ def test_similarity():

@pytest.mark.slow
def test_det():
"""Test detection functionalities and ensure the embedding table has bounding boxes."""
"""Test detection functionalities and verify embedding table includes bounding boxes."""
exp = Explorer(data="coco8.yaml", model="yolov8n.pt")
exp.create_embeddings_table(force=True)
assert len(exp.table.head()["bboxes"]) > 0
Expand All @@ -39,7 +39,7 @@ def test_det():

@pytest.mark.slow
def test_seg():
"""Test segmentation functionalities and verify the embedding table includes masks."""
"""Test segmentation functionalities and ensure the embedding table includes segmentation masks."""
exp = Explorer(data="coco8-seg.yaml", model="yolov8n-seg.pt")
exp.create_embeddings_table(force=True)
assert len(exp.table.head()["masks"]) > 0
Expand All @@ -51,7 +51,7 @@ def test_seg():

@pytest.mark.slow
def test_pose():
"""Test pose estimation functionalities and check the embedding table for keypoints."""
"""Test pose estimation functionality and verify the embedding table includes keypoints."""
exp = Explorer(data="coco8-pose.yaml", model="yolov8n-pose.pt")
exp.create_embeddings_table(force=True)
assert len(exp.table.head()["keypoints"]) > 0
Expand Down
Loading

0 comments on commit 691b5da

Please sign in to comment.