Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

test functions for validate_mesh_structure_pairs #114

Merged
merged 11 commits into from
Feb 21, 2024
Merged
44 changes: 34 additions & 10 deletions bg_atlasgen/validate_atlases.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@ def check_additional_references(atlas: BrainGlobeAtlas):
pass


def validate_mesh_structure_pairs(atlas: BrainGlobeAtlas):
"""Ensure mesh files (.obj) exist for each expected structure in the atlas."""
def catch_missing_mesh_files(atlas: BrainGlobeAtlas):
"""Checks if all the structures in the atlas have a corresponding mesh file"""

ids_from_bg_atlas_api = list(atlas.structures.keys())

atlas_path = (
Expand All @@ -126,19 +127,41 @@ def validate_mesh_structure_pairs(atlas: BrainGlobeAtlas):
if file.endswith(".obj")
]

in_mesh_not_bg = []
for id in ids_from_mesh_files:
if id not in ids_from_bg_atlas_api:
in_mesh_not_bg.append(id)

in_bg_not_mesh = []
for id in ids_from_bg_atlas_api:
if id not in ids_from_mesh_files:
in_bg_not_mesh.append(id)

if len(in_mesh_not_bg) or len(in_bg_not_mesh):
if len(in_bg_not_mesh) != 0:
raise AssertionError(
f"Structures with IDs {in_bg_not_mesh} are in the atlas, but don't have a corresponding mesh file."
)


def catch_missing_structures(atlas: BrainGlobeAtlas):
"""Checks if all the mesh files in the atlas folder are listed as a structure in the atlas"""

ids_from_bg_atlas_api = list(atlas.structures.keys())

atlas_path = (
Path(get_brainglobe_dir())
/ f"{atlas.atlas_name}_v{get_local_atlas_version(atlas.atlas_name)}"
)
obj_path = Path(atlas_path / "meshes")

ids_from_mesh_files = [
int(Path(file).stem)
for file in os.listdir(obj_path)
if file.endswith(".obj")
]

in_mesh_not_bg = []
for id in ids_from_mesh_files:
if id not in ids_from_bg_atlas_api:
in_mesh_not_bg.append(id)

if len(in_mesh_not_bg) != 0:
raise AssertionError(
f"Structures with ID {in_bg_not_mesh} are in the atlas, but don't have a corresponding mesh file; "
f"Structures with IDs {in_mesh_not_bg} have a mesh file, but are not accessible through the atlas."
)

Expand Down Expand Up @@ -176,7 +199,8 @@ def validate_atlas(atlas_name, version, validation_functions):
open_for_visual_check,
validate_checksum,
check_additional_references,
validate_mesh_structure_pairs,
catch_missing_mesh_files,
catch_missing_structures,
]

valid_atlases = []
Expand Down
48 changes: 48 additions & 0 deletions tests/test_unit/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

from bg_atlasgen.validate_atlases import (
_assert_close,
catch_missing_mesh_files,
catch_missing_structures,
validate_atlas_files,
validate_mesh_matches_image_extents,
)
Expand Down Expand Up @@ -34,6 +36,17 @@ def atlas_with_bad_reference_file():
os.rename(bad_name, good_name)


@pytest.fixture
def atlas_with_missing_structure():
atlas = BrainGlobeAtlas("osten_mouse_100um")
modified_structures = atlas.structures.copy()
modified_structures.pop(688)

modified_atlas = BrainGlobeAtlas("osten_mouse_100um")
modified_atlas.structures = modified_structures
return modified_atlas


def test_validate_mesh_matches_image_extents(atlas):
assert validate_mesh_matches_image_extents(atlas)

Expand Down Expand Up @@ -69,3 +82,38 @@ def test_assert_close_negative():
AssertionError, match="differ by more than 10 times pixel size"
):
_assert_close(99.5, 30, 2)


def test_catch_missing_mesh_files(atlas):
"""
Tests if catch_missing_mesh_files function raises an error,
when there is at least one structure in the atlas that doesn't have
a corresponding obj file.

Expected behaviour:
True for "allen_mouse_10um" (structure 545 doesn't have an obj file): fails
the validation function, raises an error --> no output from this test function
"""

with pytest.raises(
AssertionError,
match=r"Structures with IDs \[.*?\] are in the atlas, but don't have a corresponding mesh file.",
):
catch_missing_mesh_files(atlas)


def test_catch_missing_structures(atlas_with_missing_structure):
"""
Tests if catch_missing_structures function raises an error,
when there is at least one orphan obj file (doesn't have a corresponding structure in the atlas)

Expected behaviour:
Currently no atlas fails the validation function this way so the [] is always empty
--> this test function should always raise an error
Comment on lines +110 to +112
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this. Some thoughts:

  • I don't think a test function should ever raise an error
  • pytest.raises checks whether a function in the source code raises an error in the expected case - it doesn't raise an error itself (the name is slightly confusing).
  • this is a test for the catch_missing_structures function
  • in this test, we should therefore pass a different atlas with missing structures to catch_missing_structures - one that we expect to raise the expected error - so we can check that catch_missing_structures works as expected.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @alessandrofelder for clarifying this! I was overcomplicating it a bit in my head :)
So can I just mock an atlas with a missing structure and pass that to the test function?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So can I just mock an atlas with a missing structure ...

Indeed. You may not even need to use the mocking framework to do this. You can remove a key-value-pair from the atlas.structures dict.

... and pass that to the test function?

I would formulate this as "create it in the test function and pass it to the tested function".

"""

with pytest.raises(
AssertionError,
match=r"Structures with IDs \[.*?\] have a mesh file, but are not accessible through the atlas.",
):
catch_missing_structures(atlas_with_missing_structure)
Loading