diff --git a/brainglobe_atlasapi/atlas_generation/validate_atlases.py b/brainglobe_atlasapi/atlas_generation/validate_atlases.py index e8524b59..230944fb 100644 --- a/brainglobe_atlasapi/atlas_generation/validate_atlases.py +++ b/brainglobe_atlasapi/atlas_generation/validate_atlases.py @@ -424,6 +424,45 @@ def validate_annotation_symmetry(atlas: BrainGlobeAtlas): return True +def validate_unique_acronyms(atlas: BrainGlobeAtlas): + """Validate that all structure acronyms in the atlas are unique. + + Duplicate acronyms are incompatible with the current implementation + of brainglobe-atlasapi as the acronym is used as a primary key to + fetch details for a region. + + Parameters + ---------- + atlas : BrainGlobeAtlas + The BrainGlobeAtlas object to validate. + + Returns + ------- + bool + True if all acronyms are unique. + + Raises + ------ + AssertionError + If any duplicate acronyms are found in the atlas structures. + """ + seen = set() + duplicates = [] + + for structure in atlas.structures: + acronym = atlas.structures[structure]["acronym"] + if acronym in seen: + name = atlas.structures[structure]["name"] + duplicates.append((acronym, name)) + else: + seen.add(acronym) + + assert ( + len(duplicates) == 0 + ), f"Duplicate acronyms found in atlas structures: {sorted(duplicates)}" + return True + + def validate_atlas_name(atlas: BrainGlobeAtlas): """Validate the naming convention of the atlas. @@ -523,6 +562,7 @@ def get_all_validation_functions(): catch_missing_structures, validate_reference_image_pixels, validate_annotation_symmetry, + validate_unique_acronyms, validate_atlas_name, ] @@ -588,6 +628,7 @@ def validate_atlas(atlas_name, version, validation_functions): catch_missing_structures, validate_reference_image_pixels, validate_annotation_symmetry, + validate_unique_acronyms, validate_atlas_name, ] diff --git a/tests/atlasgen/test_validation.py b/tests/atlasgen/test_validation.py index 232cfaa5..b06bfb97 100644 --- a/tests/atlasgen/test_validation.py +++ b/tests/atlasgen/test_validation.py @@ -21,6 +21,7 @@ validate_mesh_matches_image_extents, validate_metadata, validate_reference_image_pixels, + validate_unique_acronyms, ) from brainglobe_atlasapi.config import get_brainglobe_dir from brainglobe_atlasapi.core import AdditionalRefDict @@ -457,3 +458,31 @@ def test_validate_metadata(atlas, metadata, expected_output, error_message): validate_metadata(atlas) else: assert validate_metadata(atlas) == expected_output + + +def test_validate_unique_acronyms_fail(mocker, atlas): + """Check that an atlas with duplicate acronyms fails validation. + + Parameters + ---------- + mocker : pytest_mock.MockerFixture + Mocker fixture for patching. + atlas : BrainGlobeAtlas + A BrainGlobeAtlas instance. + """ + # Create structures with duplicate acronyms + structures_with_duplicates = { + 1: {"acronym": "root", "name": "Root"}, + 2: {"acronym": "brain", "name": "Brain"}, + 3: {"acronym": "brain", "name": "Brain Duplicate"}, # Duplicate! + 4: {"acronym": "cortex", "name": "Cortex"}, + } + mocker.patch.object(atlas, "structures", structures_with_duplicates) + + with pytest.raises(AssertionError) as exc_info: + validate_unique_acronyms(atlas) + + # Verify error contains the duplicate acronym and its name + error_message = str(exc_info.value) + assert "brain" in error_message + assert "Brain Duplicate" in error_message