# YOLOv5 🚀 by Ultralytics, AGPL-3.0 license
# YOLOv5 Continuous Integration (CI) GitHub Actions tests

name: YOLOv5 CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]
  schedule:
    - cron: '0 0 * * *'  # runs at 00:00 UTC every day

jobs:
  Benchmarks:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ ubuntu-latest ]
        python-version: [ '3.10' ]  # requires python<=3.10
        model: [ yolov5n ]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
          cache: 'pip' # caching pip dependencies
      - name: Install requirements
        run: |
          python -m pip install --upgrade pip wheel
          pip install -r requirements.txt coremltools openvino-dev tensorflow-cpu --extra-index-url https://download.pytorch.org/whl/cpu
          python --version
          pip --version
          pip list
      - name: Benchmark DetectionModel
        run: |
          python benchmarks.py --data coco128.yaml --weights ${{ matrix.model }}.pt --img 320 --hard-fail 0.29
      - name: Benchmark SegmentationModel
        run: |
          python benchmarks.py --data coco128-seg.yaml --weights ${{ matrix.model }}-seg.pt --img 320 --hard-fail 0.22
      - name: Test predictions
        run: |
          python export.py --weights ${{ matrix.model }}-cls.pt --include onnx --img 224
          python detect.py --weights ${{ matrix.model }}.onnx --img 320
          python segment/predict.py --weights ${{ matrix.model }}-seg.onnx --img 320
          python classify/predict.py --weights ${{ matrix.model }}-cls.onnx --img 224

  Tests:
    timeout-minutes: 60
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ ubuntu-latest, windows-latest ]  # macos-latest bug https://github.com/ultralytics/yolov5/pull/9049
        python-version: [ '3.10' ]
        model: [ yolov5n ]
        include:
          - os: ubuntu-latest
            python-version: '3.8'  # '3.6.8' min
            model: yolov5n
          - os: ubuntu-latest
            python-version: '3.9'
            model: yolov5n
          - os: ubuntu-latest
            python-version: '3.8'  # torch 1.7.0 requires python >=3.6, <=3.8
            model: yolov5n
            torch: '1.7.0'  # min torch version CI https://pypi.org/project/torchvision/
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
          cache: 'pip' # caching pip dependencies
      - name: Install requirements
        run: |
          python -m pip install --upgrade pip wheel
          if [ "${{ matrix.torch }}" == "1.7.0" ]; then
              pip install -r requirements.txt torch==1.7.0 torchvision==0.8.1 --extra-index-url https://download.pytorch.org/whl/cpu
          else
              pip install -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu
          fi
        shell: bash  # for Windows compatibility
      - name: Check environment
        run: |
          python -c "import utils; utils.notebook_init()"
          echo "RUNNER_OS is ${{ runner.os }}"
          echo "GITHUB_EVENT_NAME is ${{ github.event_name }}"
          echo "GITHUB_WORKFLOW is ${{ github.workflow }}"
          echo "GITHUB_ACTOR is ${{ github.actor }}"
          echo "GITHUB_REPOSITORY is ${{ github.repository }}"
          echo "GITHUB_REPOSITORY_OWNER is ${{ github.repository_owner }}"
          python --version
          pip --version
          pip list
      - name: Test detection
        shell: bash  # for Windows compatibility
        run: |
          # export PYTHONPATH="$PWD"  # to run '$ python *.py' files in subdirectories
          m=${{ matrix.model }}  # official weights
          b=runs/train/exp/weights/best  # best.pt checkpoint
          python train.py --imgsz 64 --batch 32 --weights $m.pt --cfg $m.yaml --epochs 1 --device cpu  # train
          for d in cpu; do  # devices
            for w in $m $b; do  # weights
              python val.py --imgsz 64 --batch 32 --weights $w.pt --device $d  # val
              python detect.py --imgsz 64 --weights $w.pt --device $d  # detect
            done
          done
          python hubconf.py --model $m  # hub
          # python models/tf.py --weights $m.pt  # build TF model
          python models/yolo.py --cfg $m.yaml  # build PyTorch model
          python export.py --weights $m.pt --img 64 --include torchscript  # export
          python - <<EOF
          import torch
          im = torch.zeros([1, 3, 64, 64])
          for path in '$m', '$b':
              model = torch.hub.load('.', 'custom', path=path, source='local')
              print(model('data/images/bus.jpg'))
              model(im)  # warmup, build grids for trace
              torch.jit.trace(model, [im])
          EOF
      - name: Test segmentation
        shell: bash  # for Windows compatibility
        run: |
          m=${{ matrix.model }}-seg  # official weights
          b=runs/train-seg/exp/weights/best  # best.pt checkpoint
          python segment/train.py --imgsz 64 --batch 32 --weights $m.pt --cfg $m.yaml --epochs 1 --device cpu  # train
          python segment/train.py --imgsz 64 --batch 32 --weights '' --cfg $m.yaml --epochs 1 --device cpu  # train
          for d in cpu; do  # devices
            for w in $m $b; do  # weights
              python segment/val.py --imgsz 64 --batch 32 --weights $w.pt --device $d  # val
              python segment/predict.py --imgsz 64 --weights $w.pt --device $d  # predict
              python export.py --weights $w.pt --img 64 --include torchscript --device $d  # export
            done
          done
      - name: Test classification
        shell: bash  # for Windows compatibility
        run: |
          m=${{ matrix.model }}-cls.pt  # official weights
          b=runs/train-cls/exp/weights/best.pt  # best.pt checkpoint
          python classify/train.py --imgsz 32 --model $m --data mnist160 --epochs 1  # train
          python classify/val.py --imgsz 32 --weights $b --data ../datasets/mnist160  # val
          python classify/predict.py --imgsz 32 --weights $b --source ../datasets/mnist160/test/7/60.png  # predict
          python classify/predict.py --imgsz 32 --weights $m --source data/images/bus.jpg  # predict
          python export.py --weights $b --img 64 --include torchscript  # export
          python - <<EOF
          import torch
          for path in '$m', '$b':
              model = torch.hub.load('.', 'custom', path=path, source='local')
          EOF

  Summary:
    runs-on: ubuntu-latest
    needs: [Benchmarks, Tests] # Add job names that you want to check for failure
    if: always() # This ensures the job runs even if previous jobs fail
    steps:
      - name: Check for failure and notify
        if: (needs.Benchmarks.result == 'failure' || needs.Tests.result == 'failure' || needs.Benchmarks.result == 'cancelled' || needs.Tests.result == 'cancelled') && github.repository == 'ultralytics/yolov5' && (github.event_name == 'schedule' || github.event_name == 'push')
        uses: slackapi/slack-github-action@v1.24.0
        with:
          payload: |
            {"text": "<!channel> GitHub Actions error for ${{ github.workflow }} ❌\n\n\n*Repository:* https://github.com/${{ github.repository }}\n*Action:* https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\n*Author:* ${{ github.actor }}\n*Event:* ${{ github.event_name }}\n"}
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL_YOLO }}
