diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index f1fba58..f78f7df 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - model: [yolo11n] + model: [yolo11n,yolov8s-worldv2] steps: - name: Checkout uses: actions/checkout@v4 @@ -31,7 +31,7 @@ jobs: uses: replicate/setup-cog@v2 with: token: ${{ secrets.REPLICATE_API_TOKEN }} - - name: Download YOLO11n weights + - name: Download model weights run: python ${{ matrix.model }}/download.py - name: Build model image working-directory: ${{ matrix.model }} diff --git a/yolov8s-worldv2/README.md b/yolov8s-worldv2/README.md new file mode 100644 index 0000000..6ca8df1 --- /dev/null +++ b/yolov8s-worldv2/README.md @@ -0,0 +1,30 @@ +# YOLOv8s WorldV2 Demo Deployment + +Deploy the official YOLOv8s WorldV2 model to Replicate with PyTorch inference at https://replicate.com/ultralytics/yolov8s-worldv2. + +## Setup + +1. **Deploy to Replicate:** + + ```bash + cog push r8.im/ultralytics/yolov8s-worldv2 + ``` + +## Model Details + +- **Model**: YOLOv8s WorldV2 (Small) +- **Parameters**: 12.7M +- **Format**: PyTorch (.pt) +- **Use Case**: Demonstration of official Ultralytics model deployment + +## Model Files + +**Note:** The model weights (`yolov8s-worldv2.pt`) will be automatically downloaded by ultralytics when the container starts. + +## Configuration + +- **GPU**: Disabled by default (CPU inference) +- **Python**: 3.12 with PyTorch 2.3.1+ +- **Framework**: Ultralytics 8.3+ +- **Input**: Single image with configurable confidence/IoU thresholds +- **Output**: Annotated image with detected objects diff --git a/yolov8s-worldv2/cog.yaml b/yolov8s-worldv2/cog.yaml new file mode 100644 index 0000000..f01edbf --- /dev/null +++ b/yolov8s-worldv2/cog.yaml @@ -0,0 +1,15 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +build: + gpu: false + python_version: "3.12" + system_packages: + - "libgl1-mesa-glx" + - "libglib2.0-0" + - "ffmpeg" + python_packages: + - "ultralytics>=8.3.0" + - "clip @ git+https://github.com/ultralytics/CLIP.git@main#egg=clip" + +predict: predict.py:Predictor +image: r8.im/ultralytics/yolov8s-worldv2 diff --git a/yolov8s-worldv2/download.py b/yolov8s-worldv2/download.py new file mode 100644 index 0000000..eec4bc4 --- /dev/null +++ b/yolov8s-worldv2/download.py @@ -0,0 +1,20 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from pathlib import Path + +from ultralytics import YOLOWorld + + +def main(): + """Download YOLOv8s-worldv2 weights and move to model directory.""" + current_dir = Path(__file__).parent + YOLOWorld(current_dir / "yolov8s-worldv2.pt") + + # List files in model directory + print(f"Files in {current_dir.name} directory:") + for file in sorted(current_dir.iterdir()): + print(f" {file.stat().st_size:>10} {file.name}") + + +if __name__ == "__main__": + main() diff --git a/yolov8s-worldv2/predict.py b/yolov8s-worldv2/predict.py new file mode 100644 index 0000000..3288bb8 --- /dev/null +++ b/yolov8s-worldv2/predict.py @@ -0,0 +1,50 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from typing import Optional + +from cog import BaseModel, BasePredictor, Input, Path +from ultralytics import YOLOWorld + + +class Output(BaseModel): + """Output model for predictions.""" + + image: Optional[Path] = None + json_str: Optional[str] = None + + +class Predictor(BasePredictor): + """YOLOv8s WorldV2 model predictor for Replicate deployment.""" + + def setup(self) -> None: + """Load YOLOWorld model into memory.""" + self.model = YOLOWorld("yolov8s-worldv2.pt") + + def re_init_model(self, class_names: str) -> None: + """Re-Initialize model with class names.""" + self.model = YOLOWorld("yolov8s-worldv2.pt") + class_list = class_names.split(", ") + self.model.set_classes(class_list) + + def predict( + self, + image: Path = Input(description="Input image"), + conf: float = Input(description="Confidence threshold", default=0.25, ge=0.0, le=1.0), + iou: float = Input(description="IoU threshold for NMS", default=0.45, ge=0.0, le=1.0), + imgsz: int = Input(description="Image size", default=640, choices=[320, 416, 512, 640, 832, 1024, 1280]), + class_names: str = Input( + description="Comma-separated list of class names to filter results (e.g., 'person, bus, sign') You can also leave it empty to detect classes automatically.", + default="person, bus, sign", + ), + return_json: bool = Input(description="Return detection results as JSON", default=False), + ) -> Output: + """Run inference and return annotated image with optional JSON results.""" + self.re_init_model(class_names) + result = self.model(str(image), conf=conf, iou=iou, imgsz=imgsz)[0] + image_path = "output.png" + result.save(image_path) + + if return_json: + return Output(image=Path(image_path), json_str=result.to_json()) + else: + return Output(image=Path(image_path))