Skip to content

Commit

Permalink
Fix GitHub CI and Test Cases (#143)
Browse files Browse the repository at this point in the history
* Removed old workflows

* Added run tests workflow

* Fixed tests for asserting images are the same

* Fixed error in run test workflow

* Added PyTorch installation command

* Changed Python version to 3.8 to 3.8.10

* Changed back to 3.8 and changed platform to Windows

* Switched back to Ubuntu

* Skip tests if device is CPU to see if workflow fails because hardware isn't available

* Edited test cases for detect target

* Added boolean for Half value in detect target module

* Added enable_half parameter in detect target

* Fixed test cases

* Removed enable_half from run

* Created constant for enable_half

* Added 2 spaces between top level functions

* Added precision to variable name

* Added docstring for detect target constructor parameters

* Added comment for enable_half_precision in tests for detect target

* Changed expected error to use np.sqrt function

* Fixed issues with comments

* Store if half precision is to be used or not used

* Addressed PR comments: Added override full

* Store if half precision is to be used or not used

* Addressed PR comments: Added override full

* Added override full to main_2024

* Updated comment
  • Loading branch information
mgupta27 authored Oct 14, 2023
1 parent c4c525c commit c0ce2cd
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 75 deletions.
23 changes: 0 additions & 23 deletions .github/workflows/metrics.yml

This file was deleted.

43 changes: 0 additions & 43 deletions .github/workflows/python-app.yml

This file was deleted.

41 changes: 41 additions & 0 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# This workflow will install Python dependencies and run tests with PyTest using Python 3.8
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Run tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
build:
runs-on: ubuntu-latest

steps:
# Checkout repository
- uses: actions/checkout@v3

# Set Python version
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
python-version: 3.8

# Set up submodules and submodule dependencies
- name: Set up submodule and submodule dependencies
run: |
git submodule update --init --recursive --remote
pip install -r ./modules/common/requirements.txt
# Install computer-vision-python dependencies
- name: Install project dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-pytorch.txt
# Run tests with PyTest
- name: Run tests
run: pytest -vv
3 changes: 3 additions & 0 deletions main_2023.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def main() -> int:
# Parse whether or not to force cpu from command line
parser = argparse.ArgumentParser()
parser.add_argument("--cpu", action="store_true", help="option to force cpu")
parser.add_argument("--full", action="store_true", help="option to force full precision")
args = parser.parse_args()

# Set constants
Expand All @@ -53,6 +54,7 @@ def main() -> int:
DETECT_TARGET_WORKER_COUNT = config["detect_target"]["worker_count"]
DETECT_TARGET_DEVICE = "cpu" if args.cpu else config["detect_target"]["device"]
DETECT_TARGET_MODEL_PATH = config["detect_target"]["model_path"]
DETECT_TARGET_OVERRIDE_FULL_PRECISION = args.full
DETECT_TARGET_SAVE_PREFIX = config["detect_target"]["save_prefix"]
except KeyError:
print("Config key(s) not found")
Expand Down Expand Up @@ -91,6 +93,7 @@ def main() -> int:
(
DETECT_TARGET_DEVICE,
DETECT_TARGET_MODEL_PATH,
DETECT_TARGET_OVERRIDE_FULL_PRECISION,
DETECT_TARGET_SAVE_PREFIX,
video_input_to_detect_target_queue,
detect_target_to_main_queue,
Expand Down
3 changes: 3 additions & 0 deletions main_2024.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def main() -> int:
# Parse whether or not to force cpu from command line
parser = argparse.ArgumentParser()
parser.add_argument("--cpu", action="store_true", help="option to force cpu")
parser.add_argument("--full", action="store_true", help="option to force full precision")
args = parser.parse_args()

# Set constants
Expand All @@ -55,6 +56,7 @@ def main() -> int:
DETECT_TARGET_WORKER_COUNT = config["detect_target"]["worker_count"]
DETECT_TARGET_DEVICE = "cpu" if args.cpu else config["detect_target"]["device"]
DETECT_TARGET_MODEL_PATH = config["detect_target"]["model_path"]
DETECT_TARGET_OVERRIDE_FULL_PRECISION = args.full
DETECT_TARGET_SAVE_PREFIX = config["detect_target"]["save_prefix"]

FLIGHT_INTERFACE_ADDRESS = config["flight_interface"]["address"]
Expand Down Expand Up @@ -100,6 +102,7 @@ def main() -> int:
(
DETECT_TARGET_DEVICE,
DETECT_TARGET_MODEL_PATH,
DETECT_TARGET_OVERRIDE_FULL_PRECISION,
DETECT_TARGET_SAVE_PREFIX,
video_input_to_detect_target_queue,
detect_target_to_main_queue,
Expand Down
13 changes: 11 additions & 2 deletions modules/detect_target/detect_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,19 @@ class DetectTarget:
"""
Contains the YOLOv8 model for prediction.
"""
def __init__(self, device: "str | int", model_path: str, save_name: str = ""):
def __init__(self, device: "str | int", model_path: str, override_full: bool, save_name: str = ""):
"""
device: name of target device to run inference on (i.e. "cpu" or cuda device 0, 1, 2, 3).
model_path: path to the YOLOv8 model.
override_full: Force full precision floating point calculations.
save_name: filename prefix for logging detections and annotated images.
"""
self.__device = device
self.__model = ultralytics.YOLO(model_path)
self.__counter = 0
self.__enable_half_precision = False if self.__device == "cpu" else True
if override_full:
self.__enable_half_precision = False
self.__filename_prefix = ""
if save_name != "":
self.__filename_prefix = save_name + "_" + str(int(time.time())) + "_"
Expand All @@ -33,7 +42,7 @@ def run(self, data: image_and_time.ImageAndTime) -> "tuple[bool, np.ndarray | No
image = data.image
predictions = self.__model.predict(
source=image,
half=True,
half=self.__enable_half_precision,
device=self.__device,
stream=False,
)
Expand Down
5 changes: 3 additions & 2 deletions modules/detect_target/detect_target_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@
# pylint: disable=too-many-arguments
def detect_target_worker(device: "str | int",
model_path: str,
override_full: bool,
save_name: str,
input_queue: queue_proxy_wrapper.QueueProxyWrapper,
output_queue: queue_proxy_wrapper.QueueProxyWrapper,
controller: worker_controller.WorkerController):
"""
Worker process.
model_path and save_name are initial settings.
device, model_path, override_full, and save_name are initial settings.
input_queue and output_queue are data queues.
controller is how the main process communicates to this worker process.
"""
detector = detect_target.DetectTarget(device, model_path, save_name)
detector = detect_target.DetectTarget(device, model_path, override_full, save_name)

while not controller.is_exit_requested():
controller.check_pause()
Expand Down
45 changes: 41 additions & 4 deletions tests/test_detect_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

DEVICE = 0 if torch.cuda.is_available() else "cpu"
MODEL_PATH = "tests/model_example/yolov8s_ultralytics_pretrained_default.pt"
OVERRIDE_FULL = False # Tests are able to handle both full and half precision.
IMAGE_BUS_PATH = "tests/model_example/bus.jpg"
IMAGE_BUS_ANNOTATED_PATH = "tests/model_example/bus_annotated.png"
IMAGE_ZIDANE_PATH = "tests/model_example/zidane.jpg"
Expand All @@ -25,9 +26,10 @@ def detector():
"""
Construct DetectTarget.
"""
detection = detect_target.DetectTarget(DEVICE, MODEL_PATH)
detection = detect_target.DetectTarget(DEVICE, MODEL_PATH, OVERRIDE_FULL)
yield detection


@pytest.fixture()
def image_bus():
"""
Expand All @@ -39,6 +41,7 @@ def image_bus():
assert bus_image is not None
yield bus_image


@pytest.fixture()
def image_zidane():
"""
Expand All @@ -51,11 +54,39 @@ def image_zidane():
yield zidane_image


def rmse(actual: np.ndarray,
expected: np.ndarray) -> float:
"""
Helper function to compute root mean squared error.
"""
mean_squared_error = np.square(actual - expected).mean()

return np.sqrt(mean_squared_error)


def test_rmse():
"""
Root mean squared error.
"""
# Setup
sample_actual = np.array([1, 2, 3, 4, 5])
sample_expected = np.array([1.6, 2.5, 2.9, 3, 4.1])
EXPECTED_ERROR = np.sqrt(0.486)

# Run
actual_error = rmse(sample_actual, sample_expected)

# Test
np.testing.assert_almost_equal(actual_error, EXPECTED_ERROR)


class TestDetector:
"""
Tests `DetectTarget.run()` .
"""

__IMAGE_DIFFERENCE_TOLERANCE = 1

def test_single_bus_image(self,
detector: detect_target.DetectTarget,
image_bus: image_and_time.ImageAndTime):
Expand All @@ -72,7 +103,9 @@ def test_single_bus_image(self,
# Test
assert result
assert actual is not None
np.testing.assert_array_equal(actual, expected)

error = rmse(actual, expected)
assert error < self.__IMAGE_DIFFERENCE_TOLERANCE

def test_single_zidane_image(self,
detector: detect_target.DetectTarget,
Expand All @@ -90,7 +123,9 @@ def test_single_zidane_image(self,
# Test
assert result
assert actual is not None
np.testing.assert_array_equal(actual, expected)

error = rmse(actual, expected)
assert error < self.__IMAGE_DIFFERENCE_TOLERANCE

def test_multiple_zidane_image(self,
detector: detect_target.DetectTarget,
Expand Down Expand Up @@ -121,4 +156,6 @@ def test_multiple_zidane_image(self,
result, actual = output
assert result
assert actual is not None
np.testing.assert_array_equal(actual, expected)

error = rmse(actual, expected)
assert error < self.__IMAGE_DIFFERENCE_TOLERANCE
3 changes: 2 additions & 1 deletion tests/test_detect_target_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
IMAGE_ZIDANE_PATH = "tests/model_example/zidane.jpg"

WORK_COUNT = 3
OVERRIDE_FULL = False


def simulate_previous_worker(image_path: str, in_queue: queue_proxy_wrapper.QueueProxyWrapper):
Expand All @@ -46,7 +47,7 @@ def simulate_previous_worker(image_path: str, in_queue: queue_proxy_wrapper.Queu

worker = mp.Process(
target=detect_target_worker.detect_target_worker,
args=(device, MODEL_PATH, "", image_in_queue, image_out_queue, controller),
args=(device, MODEL_PATH, OVERRIDE_FULL, "", image_in_queue, image_out_queue, controller),
)

# Run
Expand Down

0 comments on commit c0ce2cd

Please sign in to comment.