-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Integration test for detect+track video (#242)
* Basic test passes * Added tests for save frames and save video functionality * Factor out test inputs as fixture * Fix fixture name and parametrisation * Rename integration test modules and refactor fixture * Parametrise output_dir * Clarify CLI help * Remove home mocking commented out fixture * Add caching to testing on CI * Small additions * Dummy commit to check if cache is shared * Remove zip from GIN repo and pooch registry * Fix tests by forcing download of all mlflow files * Replace macos-13 for macos-12 as intel macos * Revert "Replace macos-13 for macos-12 as intel macos" This reverts commit ec69f8e. * Make parametrisation of output_dir_name more explicit * Remove output directory parametrisation (cover as a unit test instead) * Fix deprecation warnings * Revert "Remove zip from GIN repo and pooch registry" This reverts commit 46daa32. * Fix docstring fixture * Remove timestamp * Add no timestamp flag parametrisation * Mark integration tests as slow * Skip slow tests in macos-13 * Fix optional type for py3.9 * Correctly pass tox parameters to pytest * Fix not-equal
- Loading branch information
Showing
7 changed files
with
235 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
"""Pytest configuration file.""" | ||
|
||
pytest_plugins = [ | ||
"tests.fixtures.integration", | ||
"tests.fixtures.frame_extraction", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
"""Pytest fixtures for integration tests.""" | ||
|
||
from pathlib import Path | ||
|
||
import pooch | ||
import pytest | ||
|
||
GIN_TEST_DATA_REPO = "https://gin.g-node.org/SainsburyWellcomeCentre/crabs-exploration-test-data" | ||
|
||
|
||
@pytest.fixture(scope="session") | ||
def pooch_registry() -> dict: | ||
"""Pooch registry for the test data. | ||
This fixture is common to the entire test session. The | ||
file registry is downloaded fresh for every test session. | ||
Returns | ||
------- | ||
dict | ||
URL and hash of the GIN repository with the test data | ||
""" | ||
# Initialise pooch registry | ||
registry = pooch.create( | ||
Path.home() / ".crabs-exploration-test-data", | ||
base_url=f"{GIN_TEST_DATA_REPO}/raw/master/test_data", | ||
) | ||
|
||
# Download only the registry file from GIN | ||
# if known_hash = None, the file is always downloaded. | ||
file_registry = pooch.retrieve( | ||
url=f"{GIN_TEST_DATA_REPO}/raw/master/files-registry.txt", | ||
known_hash=None, | ||
path=Path.home() / ".crabs-exploration-test-data", | ||
) | ||
|
||
# Load registry file onto pooch registry | ||
registry.load_registry(file_registry) | ||
|
||
return registry |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import re | ||
import subprocess | ||
from pathlib import Path | ||
from typing import Optional | ||
|
||
import cv2 | ||
import pooch | ||
import pytest | ||
|
||
from crabs.tracker.utils.io import open_video | ||
|
||
|
||
@pytest.fixture() | ||
def input_data_paths(pooch_registry: pooch.Pooch): | ||
"""Input data for a detector+tracking run. | ||
The data is fetched from the pooch registry. | ||
Returns | ||
------- | ||
dict | ||
Dictionary with the paths to the input video, annotations, | ||
tracking configuration and trained model. | ||
""" | ||
input_data_paths = {} | ||
video_root_name = "04.09.2023-04-Right_RE_test_3_frames" | ||
input_data_paths["video_root_name"] = video_root_name | ||
|
||
# get trained model from pooch registry | ||
# download and unzip ml-runs | ||
list_files_ml_runs = pooch_registry.fetch( | ||
"ml-runs.zip", | ||
processor=pooch.Unzip(extract_dir=""), | ||
progressbar=True, | ||
) | ||
# get path to the last checkpoint | ||
input_data_paths["ckpt"] = next( | ||
x for x in list_files_ml_runs if x.endswith("last.ckpt") | ||
) | ||
|
||
# get input video, annotations and config from registry | ||
map_key_to_filepath = { | ||
"video": f"{video_root_name}/{video_root_name}.mp4", | ||
"annotations": f"{video_root_name}/{video_root_name}_ground_truth.csv", | ||
"tracking_config": f"{video_root_name}/tracking_config.yaml", | ||
} | ||
for key, filepath in map_key_to_filepath.items(): | ||
input_data_paths[key] = pooch_registry.fetch(filepath) | ||
|
||
return input_data_paths | ||
|
||
|
||
# mark integration test as slow | ||
@pytest.mark.slow | ||
@pytest.mark.parametrize( | ||
"no_timestamp_flag", | ||
[ | ||
None, | ||
"--output_dir_no_timestamp", | ||
], | ||
) | ||
@pytest.mark.parametrize( | ||
"flags_to_append", | ||
[ | ||
[], | ||
["--save_video"], | ||
["--save_frames"], | ||
["--save_video", "--save_frames"], | ||
], | ||
) | ||
def test_detect_and_track_video( | ||
input_data_paths: dict, | ||
tmp_path: Path, | ||
flags_to_append: list, | ||
no_timestamp_flag: Optional[str], | ||
): | ||
"""Test the detect-and-track-video entry point when groundtruth is passed. | ||
The test checks: | ||
- the exit code of the detect-and-track-video command | ||
- the existence of csv file with predictions | ||
- the existence of csv file with tracking metrics | ||
- the existence of video file if requested | ||
- the existence of exported frames if requested | ||
""" | ||
# Define main detect-and-track-video command | ||
main_command = [ | ||
"detect-and-track-video", | ||
f"--trained_model_path={input_data_paths['ckpt']}", | ||
f"--video_path={input_data_paths['video']}", | ||
f"--config_file={input_data_paths['tracking_config']}", | ||
f"--annotations_file={input_data_paths['annotations']}", | ||
"--accelerator=cpu", | ||
] | ||
# append required flags | ||
main_command.extend(flags_to_append) | ||
if no_timestamp_flag: | ||
main_command.append(no_timestamp_flag) | ||
|
||
# run command | ||
completed_process = subprocess.run( | ||
main_command, | ||
check=True, | ||
cwd=tmp_path, | ||
# set cwd to Pytest's temporary directory | ||
# so the output is saved there | ||
) | ||
|
||
# check the command runs successfully | ||
assert completed_process.returncode == 0 | ||
|
||
# check the tracking output directory is created and has expected name | ||
output_dir_name_expected = "tracking_output" | ||
if no_timestamp_flag: | ||
expected_pattern = re.compile(rf"{output_dir_name_expected}$") | ||
else: | ||
expected_pattern = re.compile( | ||
rf"{output_dir_name_expected}_\d{{8}}_\d{{6}}$" | ||
) | ||
list_cwd_subdirs = [x for x in tmp_path.iterdir() if x.is_dir()] | ||
tracking_output_dir = list_cwd_subdirs[0] | ||
assert len(list_cwd_subdirs) == 1 | ||
assert expected_pattern.match(tracking_output_dir.stem) | ||
|
||
# check csv with predictions exists | ||
predictions_csv = ( | ||
tmp_path | ||
/ tracking_output_dir | ||
/ f"{input_data_paths['video_root_name']}_tracks.csv" | ||
) | ||
assert (predictions_csv).exists() | ||
|
||
# check csv with tracking metrics exists | ||
tracking_metrics_csv = ( | ||
tmp_path / tracking_output_dir / "tracking_metrics_output.csv" | ||
) | ||
assert (tracking_metrics_csv).exists() | ||
|
||
# if the video is requested: check it exists | ||
if "--save_video" in flags_to_append: | ||
assert ( | ||
tmp_path | ||
/ tracking_output_dir | ||
/ f"{input_data_paths['video_root_name']}_tracks.mp4" | ||
).exists() | ||
|
||
# if the frames are requested: check they exist | ||
if "--save_frames" in flags_to_append: | ||
input_video_object = open_video(input_data_paths["video"]) | ||
total_n_frames = int(input_video_object.get(cv2.CAP_PROP_FRAME_COUNT)) | ||
|
||
# check frames subdirectory exists | ||
frames_subdir = ( | ||
tmp_path | ||
/ tracking_output_dir | ||
/ f"{input_data_paths['video_root_name']}_frames" | ||
) | ||
assert frames_subdir.exists() | ||
|
||
# check files are named as expected | ||
expected_pattern = re.compile(r"frame_\d{8}.png") | ||
list_files = [x for x in frames_subdir.iterdir() if x.is_file()] | ||
|
||
assert len(list_files) == total_n_frames | ||
assert all(expected_pattern.match(x.name) for x in list_files) |