Skip to content

Commit

Permalink
Add automated testing (#55)
Browse files Browse the repository at this point in the history
* Test install
* Test tools
* Clean up after tests

Co-authored-by: Elizabeth Campolongo <[email protected]>

---------

Co-authored-by: Elizabeth Campolongo <[email protected]>
  • Loading branch information
zhong-al and egrace479 authored Dec 10, 2024
1 parent ce98b25 commit 08db6f8
Show file tree
Hide file tree
Showing 15 changed files with 827 additions and 11 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Test

on:
push:

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10"]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
pip install -r requirements.txt
python -m pip install hatchling
python -m pip install --no-build-isolation .
- name: Running unit tests
env:
HF_TOKEN: ${{ secrets.HF_TOKEN_TESTING }}
run: |
python -m unittest tests/test_cvat2slowfast.py
python -m unittest tests/test_cvat2ultralytics.py
python -m unittest tests/test_detector2cvat.py
python -m unittest tests/test_miniscene2behavior.py
python -m unittest tests/test_player.py
python -m unittest tests/test_tracks_extractor.py
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -177,5 +177,18 @@ cython_debug/
# Mac System
.DS_Store

# Tool output
*.json
*.xml
*.jpg
*.yaml
*.csv
*.txt

# Model files
*.pyth
*.pyth.zip
*.yml


helper_scripts/mini-scenes
6 changes: 6 additions & 0 deletions ethogram/label2index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"Grevy": 0,
"Zebra": 0,
"Baboon": 1,
"Giraffe": 2
}
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ ultralytics~=8.0.36
pandas>=1.3.5
pillow==10.4.0
scikit-learn==1.5.1
huggingface_hub
28 changes: 17 additions & 11 deletions src/kabr_tools/cvat2ultralytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import argparse
import json
import cv2
import ruamel.yaml as yaml
from ruamel.yaml import YAML
from lxml import etree
from collections import OrderedDict
from tqdm import tqdm
Expand Down Expand Up @@ -39,8 +39,10 @@ def cvat2ultralytics(video_path: str, annotation_path: str,
shutil.rmtree(f"{dataset}")

with open(f"{dataset}.yaml", "w") as file:
yaml.dump(yaml.load(dataset_file, Loader=yaml.RoundTripLoader, preserve_quotes=True),
file, Dumper=yaml.RoundTripDumper)
yaml = YAML(typ='rt')
yaml.preserve_quotes = True
data = yaml.load(dataset_file)
yaml.dump(data, file)

if not os.path.exists(f"{dataset}/images/train"):
os.makedirs(f"{dataset}/images/train")
Expand All @@ -57,6 +59,7 @@ def cvat2ultralytics(video_path: str, annotation_path: str,

if label2index is None:
label2index = {
"Grevy": 0,
"Zebra": 0,
"Baboon": 1,
"Giraffe": 2
Expand All @@ -69,21 +72,24 @@ def cvat2ultralytics(video_path: str, annotation_path: str,
for root, dirs, files in os.walk(annotation_path):
for file in files:
video_name = os.path.join(video_path + root[len(annotation_path):], os.path.splitext(file)[0])

if os.path.exists(video_name + ".MP4"):
videos.append(video_name + ".MP4")
else:
videos.append(video_name + ".mp4")

annotations.append(os.path.join(root, file))
if file.endswith(".xml"):
if os.path.exists(video_name + ".MP4"):
videos.append(video_name + ".MP4")
else:
videos.append(video_name + ".mp4")
annotations.append(os.path.join(root, file))

for i, (video, annotation) in enumerate(zip(videos, annotations)):
print(f"{i + 1}/{len(annotations)}:")
print(f"{i + 1}/{len(annotations)}:", flush=True)

if not os.path.exists(video):
print(f"Path {video} does not exist.")
continue

if not os.path.exists(annotation):
print(f"Path {annotation} does not exist.")
continue

# Parse CVAT for video 1.1 annotation file.
root = etree.parse(annotation).getroot()
name = os.path.splitext(video.split("/")[-1])[0]
Expand Down
Empty file added tests/__init__.py
Empty file.
49 changes: 49 additions & 0 deletions tests/examples/MINISCENE1/metadata/DJI_tracks.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version='1.0' encoding='UTF-8'?>
<annotations>
<track id="0" label="Grevy" source="manual">
<box frame="0" outside="0" occluded="0" keyframe="1" xtl="2011.00" ytl="1883.00" xbr="2133.00" ybr="1978.00" z_order="0"/>
<box frame="1" outside="0" occluded="0" keyframe="0" xtl="2011.00" ytl="1883.00" xbr="2133.00" ybr="1978.00" z_order="0"/>
<box frame="2" outside="0" occluded="0" keyframe="0" xtl="2011.00" ytl="1883.00" xbr="2133.00" ybr="1978.00" z_order="0"/>
<box frame="3" outside="0" occluded="0" keyframe="0" xtl="2011.00" ytl="1883.00" xbr="2133.00" ybr="1978.00" z_order="0"/>
<box frame="4" outside="0" occluded="0" keyframe="0" xtl="2011.00" ytl="1883.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="5" outside="0" occluded="0" keyframe="0" xtl="2011.00" ytl="1883.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="6" outside="0" occluded="0" keyframe="0" xtl="2011.00" ytl="1883.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="7" outside="0" occluded="0" keyframe="0" xtl="2012.00" ytl="1883.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="8" outside="0" occluded="0" keyframe="0" xtl="2012.00" ytl="1883.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="9" outside="0" occluded="0" keyframe="0" xtl="2012.00" ytl="1883.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="10" outside="0" occluded="0" keyframe="0" xtl="2012.00" ytl="1882.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="11" outside="0" occluded="0" keyframe="0" xtl="2012.00" ytl="1882.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="12" outside="0" occluded="0" keyframe="0" xtl="2012.00" ytl="1882.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="13" outside="0" occluded="0" keyframe="0" xtl="2012.00" ytl="1882.00" xbr="2135.00" ybr="1978.00" z_order="0"/>
<box frame="14" outside="0" occluded="0" keyframe="0" xtl="2012.00" ytl="1882.00" xbr="2135.00" ybr="1977.00" z_order="0"/>
<box frame="15" outside="0" occluded="0" keyframe="0" xtl="2012.00" ytl="1882.00" xbr="2135.00" ybr="1977.00" z_order="0"/>
<box frame="16" outside="0" occluded="0" keyframe="0" xtl="2013.00" ytl="1882.00" xbr="2135.00" ybr="1977.00" z_order="0"/>
<box frame="17" outside="0" occluded="0" keyframe="0" xtl="2013.00" ytl="1882.00" xbr="2135.00" ybr="1977.00" z_order="0"/>
<box frame="18" outside="0" occluded="0" keyframe="0" xtl="2013.00" ytl="1882.00" xbr="2135.00" ybr="1977.00" z_order="0"/>
<box frame="19" outside="0" occluded="0" keyframe="0" xtl="2013.00" ytl="1882.00" xbr="2135.00" ybr="1977.00" z_order="0"/>
<box frame="20" outside="0" occluded="0" keyframe="0" xtl="2013.00" ytl="1882.00" xbr="2135.00" ybr="1977.00" z_order="0"/>
</track>
<track id="1" label="Grevy" source="manual">
<box frame="0" outside="0" occluded="0" keyframe="1" xtl="2513.00" ytl="1921.00" xbr="2615.00" ybr="1991.00" z_order="0"/>
<box frame="1" outside="0" occluded="0" keyframe="0" xtl="2513.00" ytl="1921.00" xbr="2615.00" ybr="1991.00" z_order="0"/>
<box frame="2" outside="0" occluded="0" keyframe="0" xtl="2512.00" ytl="1921.00" xbr="2614.00" ybr="1991.00" z_order="0"/>
<box frame="3" outside="0" occluded="0" keyframe="0" xtl="2512.00" ytl="1921.00" xbr="2613.00" ybr="1991.00" z_order="0"/>
<box frame="4" outside="0" occluded="0" keyframe="0" xtl="2511.00" ytl="1921.00" xbr="2613.00" ybr="1991.00" z_order="0"/>
<box frame="5" outside="0" occluded="0" keyframe="0" xtl="2510.00" ytl="1921.00" xbr="2612.00" ybr="1991.00" z_order="0"/>
<box frame="6" outside="0" occluded="0" keyframe="0" xtl="2510.00" ytl="1921.00" xbr="2612.00" ybr="1991.00" z_order="0"/>
<box frame="7" outside="0" occluded="0" keyframe="0" xtl="2509.00" ytl="1921.00" xbr="2611.00" ybr="1991.00" z_order="0"/>
<box frame="8" outside="0" occluded="0" keyframe="0" xtl="2508.00" ytl="1921.00" xbr="2610.00" ybr="1991.00" z_order="0"/>
<box frame="9" outside="0" occluded="0" keyframe="0" xtl="2508.00" ytl="1921.00" xbr="2610.00" ybr="1991.00" z_order="0"/>
<box frame="10" outside="0" occluded="0" keyframe="0" xtl="2507.00" ytl="1921.00" xbr="2609.00" ybr="1991.00" z_order="0"/>
<box frame="11" outside="0" occluded="0" keyframe="0" xtl="2507.00" ytl="1921.00" xbr="2608.00" ybr="1991.00" z_order="0"/>
<box frame="12" outside="0" occluded="0" keyframe="0" xtl="2506.00" ytl="1921.00" xbr="2608.00" ybr="1991.00" z_order="0"/>
<box frame="13" outside="0" occluded="0" keyframe="0" xtl="2505.00" ytl="1921.00" xbr="2607.00" ybr="1991.00" z_order="0"/>
<box frame="14" outside="0" occluded="0" keyframe="0" xtl="2505.00" ytl="1921.00" xbr="2607.00" ybr="1991.00" z_order="0"/>
<box frame="15" outside="0" occluded="0" keyframe="0" xtl="2504.00" ytl="1920.00" xbr="2606.00" ybr="1991.00" z_order="0"/>
<box frame="16" outside="0" occluded="0" keyframe="0" xtl="2503.00" ytl="1920.00" xbr="2605.00" ybr="1991.00" z_order="0"/>
<box frame="17" outside="0" occluded="0" keyframe="0" xtl="2503.00" ytl="1920.00" xbr="2605.00" ybr="1991.00" z_order="0"/>
<box frame="18" outside="0" occluded="0" keyframe="0" xtl="2502.00" ytl="1920.00" xbr="2604.00" ybr="1991.00" z_order="0"/>
<box frame="19" outside="0" occluded="0" keyframe="0" xtl="2502.00" ytl="1920.00" xbr="2603.00" ybr="1990.00" z_order="0"/>
<box frame="20" outside="0" occluded="0" keyframe="0" xtl="2501.00" ytl="1920.00" xbr="2603.00" ybr="1990.00" z_order="0"/>
</track>
</annotations>
29 changes: 29 additions & 0 deletions tests/examples/MINISCENE2/metadata/DJI_tracks.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version='1.0' encoding='UTF-8'?>
<annotations>
<track id="0" label="Grevy" source="manual">
<box frame="0" outside="0" occluded="0" keyframe="1" xtl="2011.00" ytl="1883.00" xbr="2133.00" ybr="1978.00" z_order="0"/>
<box frame="1" outside="0" occluded="0" keyframe="0" xtl="2011.00" ytl="1883.00" xbr="2133.00" ybr="1978.00" z_order="0"/>
<box frame="2" outside="0" occluded="0" keyframe="0" xtl="2011.00" ytl="1883.00" xbr="2133.00" ybr="1978.00" z_order="0"/>
<box frame="3" outside="0" occluded="0" keyframe="0" xtl="2011.00" ytl="1883.00" xbr="2133.00" ybr="1978.00" z_order="0"/>
<box frame="4" outside="0" occluded="0" keyframe="0" xtl="2011.00" ytl="1883.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="5" outside="0" occluded="0" keyframe="0" xtl="2011.00" ytl="1883.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="6" outside="0" occluded="0" keyframe="0" xtl="2011.00" ytl="1883.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="7" outside="0" occluded="0" keyframe="0" xtl="2012.00" ytl="1883.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="8" outside="0" occluded="0" keyframe="0" xtl="2012.00" ytl="1883.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="9" outside="0" occluded="0" keyframe="0" xtl="2012.00" ytl="1883.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
<box frame="10" outside="0" occluded="0" keyframe="0" xtl="2012.00" ytl="1882.00" xbr="2134.00" ybr="1978.00" z_order="0"/>
</track>
<track id="1" label="Grevy" source="manual">
<box frame="40" outside="0" occluded="0" keyframe="1" xtl="2513.00" ytl="1921.00" xbr="2615.00" ybr="1991.00" z_order="0"/>
<box frame="41" outside="0" occluded="0" keyframe="0" xtl="2513.00" ytl="1921.00" xbr="2615.00" ybr="1991.00" z_order="0"/>
<box frame="42" outside="0" occluded="0" keyframe="0" xtl="2512.00" ytl="1921.00" xbr="2614.00" ybr="1991.00" z_order="0"/>
<box frame="43" outside="0" occluded="0" keyframe="0" xtl="2512.00" ytl="1921.00" xbr="2613.00" ybr="1991.00" z_order="0"/>
<box frame="44" outside="0" occluded="0" keyframe="0" xtl="2511.00" ytl="1921.00" xbr="2613.00" ybr="1991.00" z_order="0"/>
<box frame="45" outside="0" occluded="0" keyframe="0" xtl="2510.00" ytl="1921.00" xbr="2612.00" ybr="1991.00" z_order="0"/>
<box frame="46" outside="0" occluded="0" keyframe="0" xtl="2510.00" ytl="1921.00" xbr="2612.00" ybr="1991.00" z_order="0"/>
<box frame="47" outside="0" occluded="0" keyframe="0" xtl="2509.00" ytl="1921.00" xbr="2611.00" ybr="1991.00" z_order="0"/>
<box frame="48" outside="0" occluded="0" keyframe="0" xtl="2508.00" ytl="1921.00" xbr="2610.00" ybr="1991.00" z_order="0"/>
<box frame="49" outside="0" occluded="0" keyframe="0" xtl="2508.00" ytl="1921.00" xbr="2610.00" ybr="1991.00" z_order="0"/>
<box frame="50" outside="0" occluded="0" keyframe="0" xtl="2507.00" ytl="1921.00" xbr="2609.00" ybr="1991.00" z_order="0"/>
</track>
</annotations>
88 changes: 88 additions & 0 deletions tests/test_cvat2slowfast.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import unittest
import sys
import os
from kabr_tools import cvat2slowfast
from tests.utils import (
get_behavior,
del_dir,
del_file
)


def run():
cvat2slowfast.main()


class TestCvat2Slowfast(unittest.TestCase):

@classmethod
def setUpClass(cls):
# download data
cls.video, cls.miniscene, cls.annotation, cls.metadata = get_behavior()
cls.dir = os.path.dirname(os.path.dirname(cls.video))

@classmethod
def tearDownClass(cls):
# delete data
del_file(cls.video)
del_file(cls.miniscene)
del_file(cls.annotation)
del_file(cls.metadata)
del_dir(cls.dir)

def setUp(self):
# set params
self.tool = "cvat2slowfast.py"
self.miniscene = TestCvat2Slowfast.dir
self.dataset = "tests/slowfast"
self.classes = "ethogram/classes.json"
self.old2new = "ethogram/old2new.json"

def tearDown(self):
# delete outputs
del_dir(self.dataset)

def test_run(self):
# run cvat2slowfast
sys.argv = [self.tool,
"--miniscene", self.miniscene,
"--dataset", self.dataset,
"--classes", self.classes]
run()

def test_parse_arg_min(self):
# parse arguments
sys.argv = [self.tool,
"--miniscene", self.miniscene,
"--dataset", self.dataset,
"--classes", self.classes]
args = cvat2slowfast.parse_args()

# check parsed argument values
self.assertEqual(args.miniscene, self.miniscene)
self.assertEqual(args.dataset, self.dataset)
self.assertEqual(args.classes, self.classes)

# check default argument values
self.assertEqual(args.old2new, None)

# run cvat2slowfast
run()

def test_parse_arg_full(self):
# parse arguments
sys.argv = ["cvat2slowfast.py",
"--miniscene", self.miniscene,
"--dataset", self.dataset,
"--classes", self.classes,
"--old2new", self.old2new]
args = cvat2slowfast.parse_args()

# check parsed argument values
self.assertEqual(args.miniscene, self.miniscene)
self.assertEqual(args.dataset, self.dataset)
self.assertEqual(args.classes, self.classes)
self.assertEqual(args.old2new, self.old2new)

# run cvat2slowfast
run()
88 changes: 88 additions & 0 deletions tests/test_cvat2ultralytics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import unittest
import sys
import os
from kabr_tools import cvat2ultralytics
from tests.utils import (
del_dir,
del_file,
get_detection
)


def run():
cvat2ultralytics.main()


class TestCvat2Ultralytics(unittest.TestCase):

@classmethod
def setUpClass(cls):
# download data
cls.video, cls.annotation = get_detection()
cls.dir = os.path.dirname(os.path.dirname(cls.video))

@classmethod
def tearDownClass(cls):
# delete data
del_file(cls.video)
del_file(cls.annotation)

def setUp(self):
self.tool = "cvat2ultralytics.py"
self.video = TestCvat2Ultralytics.dir
self.annotation = TestCvat2Ultralytics.dir
self.dataset = "tests/ultralytics"
self.skip = "5"
self.label2index = "ethogram/label2index.json"

def tearDown(self):
# delete outputs
del_dir(self.dataset)

def test_run(self):
# run cvat2ultralytics
sys.argv = [self.tool,
"--video", self.video,
"--annotation", self.annotation,
"--dataset", "tests/ultralytics"]
run()

def test_parse_arg_min(self):
# parse arguments
sys.argv = [self.tool,
"--video", self.video,
"--annotation", self.annotation,
"--dataset", self.dataset]
args = cvat2ultralytics.parse_args()

# check parsed argument values
self.assertEqual(args.video, self.video)
self.assertEqual(args.annotation, self.annotation)
self.assertEqual(args.dataset, self.dataset)

# check default argument values
self.assertEqual(args.skip, 10)
self.assertEqual(args.label2index, None)

# run cvat2ultralytics
run()

def test_parse_arg_full(self):
# parse arguments
sys.argv = [self.tool,
"--video", self.video,
"--annotation", self.annotation,
"--dataset", self.dataset,
"--skip", self.skip,
"--label2index", self.label2index]
args = cvat2ultralytics.parse_args()

# check parsed argument values
self.assertEqual(args.video, self.video)
self.assertEqual(args.annotation, self.annotation)
self.assertEqual(args.dataset, self.dataset)
self.assertEqual(args.skip, 5)
self.assertEqual(args.label2index, self.label2index)

# run cvat2ultralytics
run()
Loading

0 comments on commit 08db6f8

Please sign in to comment.