From 7c0b12cdaa2c0f4a0fd0ad0c4cd5a046bb776f6c Mon Sep 17 00:00:00 2001 From: lochhh Date: Fri, 24 Jan 2025 16:59:54 +0000 Subject: [PATCH] Refactor fixtures --- tests/fixtures/datasets.py | 27 +- tests/fixtures/files.py | 389 ++++++++---------- tests/test_unit/test_save_poses.py | 2 +- .../test_validators/test_files_validators.py | 26 +- 4 files changed, 195 insertions(+), 249 deletions(-) diff --git a/tests/fixtures/datasets.py b/tests/fixtures/datasets.py index 207f590c..02d0d32e 100644 --- a/tests/fixtures/datasets.py +++ b/tests/fixtures/datasets.py @@ -1,12 +1,20 @@ -"""Valid and invalid movement datasets and arrays fixtures.""" +"""Valid and invalid data fixtures.""" import numpy as np +import pandas as pd import pytest import xarray as xr from movement.validators.datasets import ValidBboxesDataset, ValidPosesDataset +# -------------------- Valid DLC-style DataFrame -------------------- +@pytest.fixture +def dlc_style_df(): + """Return a valid DLC-style DataFrame.""" + return pd.read_hdf(pytest.DATA_PATHS.get("DLC_single-wasp.predictions.h5")) + + # -------------------- Valid bboxes datasets and arrays -------------------- @pytest.fixture def valid_bboxes_arrays_all_zeros(): @@ -285,24 +293,13 @@ def valid_poses_dataset_with_nan(valid_poses_dataset): - Individual "id_1" has no missing values. """ valid_poses_dataset.position.loc[ - { - "individuals": "id_0", - "keypoints": "centroid", - "time": 0, - } + {"individuals": "id_0", "keypoints": "centroid", "time": 0} ] = np.nan valid_poses_dataset.position.loc[ - { - "individuals": "id_0", - "keypoints": "left", - "time": [3, 7, 8], - } + {"individuals": "id_0", "keypoints": "left", "time": [3, 7, 8]} ] = np.nan valid_poses_dataset.position.loc[ - { - "individuals": "id_0", - "keypoints": "right", - } + {"individuals": "id_0", "keypoints": "right"} ] = np.nan return valid_poses_dataset diff --git a/tests/fixtures/files.py b/tests/fixtures/files.py index 7806ddff..8c04e80a 100644 --- a/tests/fixtures/files.py +++ b/tests/fixtures/files.py @@ -5,11 +5,10 @@ from unittest.mock import mock_open, patch import h5py -import pandas as pd import pytest -# --------- File validator fixtures --------------------------------- +# ------------------ Generic file fixtures ---------------------- @pytest.fixture def unreadable_file(tmp_path): """Return a dictionary containing the file path and @@ -81,20 +80,7 @@ def nonexistent_file(tmp_path): @pytest.fixture -def directory(tmp_path): - """Return a dictionary containing the file path and - expected permission for a directory. - """ - file_path = tmp_path / "directory" - file_path.mkdir() - return { - "file_path": file_path, - "expected_permission": "r", - } - - -@pytest.fixture -def h5_file_no_dataframe(tmp_path): +def no_dataframe_h5_file(tmp_path): """Return a dictionary containing the file path and expected datasets for a .h5 file with no dataframe. """ @@ -110,7 +96,7 @@ def h5_file_no_dataframe(tmp_path): @pytest.fixture def fake_h5_file(tmp_path): """Return a dictionary containing the file path, - expected exception, and expected datasets for + expected permission, and expected datasets for a file with .h5 extension that is not in HDF5 format. """ file_path = tmp_path / "fake.h5" @@ -146,9 +132,22 @@ def invalid_multi_individual_csv_file(tmp_path): @pytest.fixture -def new_file_wrong_ext(tmp_path): +def wrong_ext_new_file(tmp_path): """Return the file path for a new file with the wrong extension.""" - return tmp_path / "new_file_wrong_ext.txt" + return tmp_path / "wrong_ext_new_file.txt" + + +@pytest.fixture +def directory(tmp_path): + """Return a dictionary containing the file path and + expected permission for a directory. + """ + file_path = tmp_path / "directory" + file_path.mkdir() + return { + "file_path": file_path, + "expected_permission": "r", + } @pytest.fixture @@ -163,12 +162,7 @@ def new_csv_file(tmp_path): return tmp_path / "new_file.csv" -@pytest.fixture -def dlc_style_df(): - """Return a valid DLC-style DataFrame.""" - return pd.read_hdf(pytest.DATA_PATHS.get("DLC_single-wasp.predictions.h5")) - - +# ---------------- Anipose file fixtures ---------------------------- @pytest.fixture def missing_keypoint_columns_anipose_csv_file(tmp_path): """Return the file path for a fake single-individual .csv file.""" @@ -224,6 +218,7 @@ def spurious_column_anipose_csv_file(tmp_path): return file_path +# ---------------- SLEAP file fixtures ---------------------------- @pytest.fixture( params=[ "SLEAP_single-mouse_EPM.analysis.h5", @@ -240,247 +235,201 @@ def sleap_file(request): # ---------------- VIA tracks CSV file fixtures ---------------------------- +via_tracks_csv_file_valid_header = ( + "filename,file_size,file_attributes,region_count," + "region_id,region_shape_attributes,region_attributes\n" +) + + @pytest.fixture -def via_tracks_csv_with_invalid_header(tmp_path): - """Return the file path for a file with invalid header.""" - file_path = tmp_path / "invalid_via_tracks.csv" - with open(file_path, "w") as f: - f.write("filename,file_size,file_attributes\n") - f.write("1,2,3") - return file_path +def invalid_via_tracks_csv_file(tmp_path, request): + """Return the file path for an invalid VIA tracks .csv file.""" + + def _invalid_via_tracks_csv_file(invalid_content): + file_path = tmp_path / "invalid_via_tracks.csv" + with open(file_path, "w") as f: + f.write(request.getfixturevalue(invalid_content)) + return file_path + + return _invalid_via_tracks_csv_file @pytest.fixture -def via_tracks_csv_with_valid_header(tmp_path): - file_path = tmp_path / "sample_via_tracks.csv" - with open(file_path, "w") as f: - f.write( - "filename," - "file_size," - "file_attributes," - "region_count," - "region_id," - "region_shape_attributes," - "region_attributes" - ) - f.write("\n") - return file_path +def via_invalid_header(): + """Return the content of a VIA tracks .csv file with invalid header.""" + return "filename,file_size,file_attributes\n1,2,3" @pytest.fixture -def frame_number_in_file_attribute_not_integer( - via_tracks_csv_with_valid_header, -): - """Return the file path for a VIA tracks .csv file with invalid frame +def via_frame_number_in_file_attribute_not_integer(): + """Return the content of a VIA tracks .csv file with invalid frame number defined as file_attribute. """ - file_path = via_tracks_csv_with_valid_header - with open(file_path, "a") as f: - f.write( - "04.09.2023-04-Right_RE_test_frame_A.png," - "26542080," - '"{""clip"":123, ""frame"":""FOO""}",' # frame number is a string - "1," - "0," - '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' - '"{""track"":""71""}"' - ) - return file_path + return ( + via_tracks_csv_file_valid_header + + "04.09.2023-04-Right_RE_test_frame_A.png," + "26542080," + '"{""clip"":123, ""frame"":""FOO""}",' # frame number is a string + "1," + "0," + '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' + '"{""track"":""71""}"' + ) @pytest.fixture -def frame_number_in_filename_wrong_pattern( - via_tracks_csv_with_valid_header, -): - """Return the file path for a VIA tracks .csv file with invalid frame +def via_frame_number_in_filename_wrong_pattern(): + """Return the content of a VIA tracks .csv file with invalid frame number defined in the frame's filename. """ - file_path = via_tracks_csv_with_valid_header - with open(file_path, "a") as f: - f.write( - "04.09.2023-04-Right_RE_test_frame_1.png," # frame not zero-padded - "26542080," - '"{""clip"":123}",' - "1," - "0," - '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' - '"{""track"":""71""}"' - ) - return file_path + return ( + via_tracks_csv_file_valid_header + + "04.09.2023-04-Right_RE_test_frame_1.png," # frame not zero-padded + "26542080," + '"{""clip"":123}",' + "1," + "0," + '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' + '"{""track"":""71""}"' + ) @pytest.fixture -def more_frame_numbers_than_filenames( - via_tracks_csv_with_valid_header, -): - """Return the file path for a VIA tracks .csv file with more +def via_more_frame_numbers_than_filenames(): + """Return the content of a VIA tracks .csv file with more frame numbers than filenames. """ - file_path = via_tracks_csv_with_valid_header - with open(file_path, "a") as f: - f.write( - "04.09.2023-04-Right_RE_test.png," - "26542080," - '"{""clip"":123, ""frame"":24}",' - "1," - "0," - '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' - '"{""track"":""71""}"' - ) - f.write("\n") - f.write( - "04.09.2023-04-Right_RE_test.png," # same filename as previous row - "26542080," - '"{""clip"":123, ""frame"":25}",' # different frame number - "1," - "0," - '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' - '"{""track"":""71""}"' - ) - return file_path + return ( + via_tracks_csv_file_valid_header + "04.09.2023-04-Right_RE_test.png," + "26542080," + '"{""clip"":123, ""frame"":24}",' + "1," + "0," + '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' + '"{""track"":""71""}"' + "\n" + "04.09.2023-04-Right_RE_test.png," # same filename as previous row + "26542080," + '"{""clip"":123, ""frame"":25}",' # different frame number + "1," + "0," + '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' + '"{""track"":""71""}"' + ) @pytest.fixture -def less_frame_numbers_than_filenames( - via_tracks_csv_with_valid_header, -): - """Return the file path for a VIA tracks .csv file with with less +def via_less_frame_numbers_than_filenames(): + """Return the content of a VIA tracks .csv file with with less frame numbers than filenames. """ - file_path = via_tracks_csv_with_valid_header - with open(file_path, "a") as f: - f.write( - "04.09.2023-04-Right_RE_test_A.png," - "26542080," - '"{""clip"":123, ""frame"":24}",' - "1," - "0," - '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' - '"{""track"":""71""}"' - ) - f.write("\n") - f.write( - "04.09.2023-04-Right_RE_test_B.png," # different filename - "26542080," - '"{""clip"":123, ""frame"":24}",' # same frame as previous row - "1," - "0," - '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' - '"{""track"":""71""}"' - ) - return file_path + return ( + via_tracks_csv_file_valid_header + "04.09.2023-04-Right_RE_test_A.png," + "26542080," + '"{""clip"":123, ""frame"":24}",' + "1," + "0," + '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' + '"{""track"":""71""}"' + "\n" + "04.09.2023-04-Right_RE_test_B.png," # different filename + "26542080," + '"{""clip"":123, ""frame"":24}",' # same frame as previous row + "1," + "0," + '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' + '"{""track"":""71""}"' + ) @pytest.fixture -def region_shape_attribute_not_rect( - via_tracks_csv_with_valid_header, -): - """Return the file path for a VIA tracks .csv file with invalid shape in +def via_region_shape_attribute_not_rect(): + """Return the content of a VIA tracks .csv file with invalid shape in region_shape_attributes. """ - file_path = via_tracks_csv_with_valid_header - with open(file_path, "a") as f: - f.write( - "04.09.2023-04-Right_RE_test_frame_01.png," - "26542080," - '"{""clip"":123}",' - "1," - "0," - '"{""name"":""circle"",""cx"":1049,""cy"":1006,""r"":125}",' - '"{""track"":""71""}"' - ) # annotation of circular shape - return file_path + return ( + via_tracks_csv_file_valid_header + + "04.09.2023-04-Right_RE_test_frame_01.png," + "26542080," + '"{""clip"":123}",' + "1," + "0," + '"{""name"":""circle"",""cx"":1049,""cy"":1006,""r"":125}",' + '"{""track"":""71""}"' + ) # annotation of circular shape @pytest.fixture -def region_shape_attribute_missing_x( - via_tracks_csv_with_valid_header, -): - """Return the file path for a VIA tracks .csv file with missing `x` key in +def via_region_shape_attribute_missing_x(): + """Return the content of a VIA tracks .csv file with missing `x` key in region_shape_attributes. """ - file_path = via_tracks_csv_with_valid_header - with open(file_path, "a") as f: - f.write( - "04.09.2023-04-Right_RE_test_frame_01.png," - "26542080," - '"{""clip"":123}",' - "1," - "0," - '"{""name"":""rect"",""y"":393.281,""width"":46,""height"":38}",' - '"{""track"":""71""}"' - ) # region_shape_attributes is missing ""x"" key - return file_path + return ( + via_tracks_csv_file_valid_header + + "04.09.2023-04-Right_RE_test_frame_01.png," + "26542080," + '"{""clip"":123}",' + "1," + "0," + '"{""name"":""rect"",""y"":393.281,""width"":46,""height"":38}",' + '"{""track"":""71""}"' + ) # region_shape_attributes is missing ""x"" key @pytest.fixture -def region_attribute_missing_track( - via_tracks_csv_with_valid_header, -): - """Return the file path for a VIA tracks .csv file with missing track +def via_region_attribute_missing_track(): + """Return the content of a VIA tracks .csv file with missing track attribute in region_attributes. """ - file_path = via_tracks_csv_with_valid_header - with open(file_path, "a") as f: - f.write( - "04.09.2023-04-Right_RE_test_frame_01.png," - "26542080," - '"{""clip"":123}",' - "1," - "0," - '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' - '"{""foo"":""71""}"' # missing ""track"" - ) - return file_path + return ( + via_tracks_csv_file_valid_header + + "04.09.2023-04-Right_RE_test_frame_01.png," + "26542080," + '"{""clip"":123}",' + "1," + "0," + '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' + '"{""foo"":""71""}"' # missing ""track"" + ) @pytest.fixture -def track_id_not_castable_as_int( - via_tracks_csv_with_valid_header, -): - """Return the file path for a VIA tracks .csv file with a track ID +def via_track_id_not_castable_as_int(): + """Return the content of a VIA tracks .csv file with a track ID attribute not castable as an integer. """ - file_path = via_tracks_csv_with_valid_header - with open(file_path, "a") as f: - f.write( - "04.09.2023-04-Right_RE_test_frame_01.png," - "26542080," - '"{""clip"":123}",' - "1," - "0," - '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' - '"{""track"":""FOO""}"' # ""track"" not castable as int - ) - return file_path + return ( + via_tracks_csv_file_valid_header + + "04.09.2023-04-Right_RE_test_frame_01.png," + "26542080," + '"{""clip"":123}",' + "1," + "0," + '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' + '"{""track"":""FOO""}"' # ""track"" not castable as int + ) @pytest.fixture -def track_ids_not_unique_per_frame( - via_tracks_csv_with_valid_header, -): - """Return the file path for a VIA tracks .csv file with a track ID +def via_track_ids_not_unique_per_frame(): + """Return the content of a VIA tracks .csv file with a track ID that appears twice in the same frame. """ - file_path = via_tracks_csv_with_valid_header - with open(file_path, "a") as f: - f.write( - "04.09.2023-04-Right_RE_test_frame_01.png," - "26542080," - '"{""clip"":123}",' - "1," - "0," - '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' - '"{""track"":""71""}"' - ) - f.write("\n") - f.write( - "04.09.2023-04-Right_RE_test_frame_01.png," - "26542080," - '"{""clip"":123}",' - "1," - "0," - '"{""name"":""rect"",""x"":2567.627,""y"":466.888,""width"":40,""height"":37}",' - '"{""track"":""71""}"' # same track ID as the previous row - ) - return file_path + return ( + via_tracks_csv_file_valid_header + + "04.09.2023-04-Right_RE_test_frame_01.png," + "26542080," + '"{""clip"":123}",' + "1," + "0," + '"{""name"":""rect"",""x"":526.236,""y"":393.281,""width"":46,""height"":38}",' + '"{""track"":""71""}"' + "\n" + "04.09.2023-04-Right_RE_test_frame_01.png," + "26542080," + '"{""clip"":123}",' + "1," + "0," + '"{""name"":""rect"",""x"":2567.627,""y"":466.888,""width"":40,""height"":37}",' + '"{""track"":""71""}"' # same track ID as the previous row + ) diff --git a/tests/test_unit/test_save_poses.py b/tests/test_unit/test_save_poses.py index ae812f89..ca313a32 100644 --- a/tests/test_unit/test_save_poses.py +++ b/tests/test_unit/test_save_poses.py @@ -31,7 +31,7 @@ class TestSavePoses: # invalid file path }, { - "file_fixture": "new_file_wrong_ext", + "file_fixture": "wrong_ext_new_file", "to_dlc_file_expected_exception": pytest.raises(ValueError), "to_sleap_file_expected_exception": pytest.raises(ValueError), "to_lp_file_expected_exception": pytest.raises(ValueError), diff --git a/tests/test_unit/test_validators/test_files_validators.py b/tests/test_unit/test_validators/test_files_validators.py index b3149d64..86c868df 100644 --- a/tests/test_unit/test_validators/test_files_validators.py +++ b/tests/test_unit/test_validators/test_files_validators.py @@ -36,7 +36,7 @@ def test_file_validator_with_invalid_input( @pytest.mark.parametrize( "invalid_input, expected_exception", [ - ("h5_file_no_dataframe", pytest.raises(ValueError)), + ("no_dataframe_h5_file", pytest.raises(ValueError)), ("fake_h5_file", pytest.raises(ValueError)), ], ) @@ -72,7 +72,7 @@ def test_deeplabcut_csv_validator_with_invalid_input( "invalid_input, error_type, log_message", [ ( - "via_tracks_csv_with_invalid_header", + "via_invalid_header", ValueError, ".csv header row does not match the known format for " "VIA tracks .csv files. " @@ -83,7 +83,7 @@ def test_deeplabcut_csv_validator_with_invalid_input( "but got ['filename', 'file_size', 'file_attributes'].", ), ( - "frame_number_in_file_attribute_not_integer", + "via_frame_number_in_file_attribute_not_integer", ValueError, "04.09.2023-04-Right_RE_test_frame_A.png (row 0): " "'frame' file attribute cannot be cast as an integer. " @@ -91,7 +91,7 @@ def test_deeplabcut_csv_validator_with_invalid_input( "{'clip': 123, 'frame': 'FOO'}.", ), ( - "frame_number_in_filename_wrong_pattern", + "via_frame_number_in_filename_wrong_pattern", AttributeError, "04.09.2023-04-Right_RE_test_frame_1.png (row 0): " "The provided frame regexp ((0\d*)\.\w+$) did not return " @@ -100,28 +100,28 @@ def test_deeplabcut_csv_validator_with_invalid_input( "filename.", ), ( - "more_frame_numbers_than_filenames", + "via_more_frame_numbers_than_filenames", ValueError, "The number of unique frame numbers does not match the number " "of unique image files. Please review the VIA tracks .csv file " "and ensure a unique frame number is defined for each file. ", ), ( - "less_frame_numbers_than_filenames", + "via_less_frame_numbers_than_filenames", ValueError, "The number of unique frame numbers does not match the number " "of unique image files. Please review the VIA tracks .csv file " "and ensure a unique frame number is defined for each file. ", ), ( - "region_shape_attribute_not_rect", + "via_region_shape_attribute_not_rect", ValueError, "04.09.2023-04-Right_RE_test_frame_01.png (row 0): " "bounding box shape must be 'rect' (rectangular) " "but instead got 'circle'.", ), ( - "region_shape_attribute_missing_x", + "via_region_shape_attribute_missing_x", ValueError, "04.09.2023-04-Right_RE_test_frame_01.png (row 0): " "at least one bounding box shape parameter is missing. " @@ -130,7 +130,7 @@ def test_deeplabcut_csv_validator_with_invalid_input( "'['name', 'y', 'width', 'height']'.", ), ( - "region_attribute_missing_track", + "via_region_attribute_missing_track", ValueError, "04.09.2023-04-Right_RE_test_frame_01.png (row 0): " "bounding box does not have a 'track' attribute defined " @@ -138,7 +138,7 @@ def test_deeplabcut_csv_validator_with_invalid_input( "Please review the VIA tracks .csv file.", ), ( - "track_id_not_castable_as_int", + "via_track_id_not_castable_as_int", ValueError, "04.09.2023-04-Right_RE_test_frame_01.png (row 0): " "the track ID for the bounding box cannot be cast " @@ -146,7 +146,7 @@ def test_deeplabcut_csv_validator_with_invalid_input( "Please review the VIA tracks .csv file.", ), ( - "track_ids_not_unique_per_frame", + "via_track_ids_not_unique_per_frame", ValueError, "04.09.2023-04-Right_RE_test_frame_01.png: " "multiple bounding boxes in this file have the same track ID. " @@ -155,7 +155,7 @@ def test_deeplabcut_csv_validator_with_invalid_input( ], ) def test_via_tracks_csv_validator_with_invalid_input( - invalid_input, error_type, log_message, request + invalid_via_tracks_csv_file, invalid_input, error_type, log_message ): """Test that invalid VIA tracks .csv files raise the appropriate errors. @@ -171,7 +171,7 @@ def test_via_tracks_csv_validator_with_invalid_input( (i.e., bboxes IDs must exist only once per frame) - error if bboxes IDs are not 1-based integers """ - file_path = request.getfixturevalue(invalid_input) + file_path = invalid_via_tracks_csv_file(invalid_input) with pytest.raises(error_type) as excinfo: ValidVIATracksCSV(file_path)