diff --git a/atlas_gen/atlas_scripts/mpin_zfish_atlas.py b/atlas_gen/atlas_scripts/mpin_zfish_atlas.py index f7e9997..f7b5586 100644 --- a/atlas_gen/atlas_scripts/mpin_zfish_atlas.py +++ b/atlas_gen/atlas_scripts/mpin_zfish_atlas.py @@ -1,5 +1,4 @@ from pathlib import Path -import tempfile import warnings import nrrd @@ -12,13 +11,13 @@ from brainatlas_api.structures import StructureTree # Specify information about the atlas: -RES_UM = 1.0 +RES_UM = 1 VERSION = 2 ATLAS_NAME = f"mpin_zfish" -SPECIES = "Mus musculus" -ATLAS_LINK = "http://www.brain-map.org.com" +SPECIES = "Danio rerio" +ATLAS_LINK = "http://fishatlas.neuro.mpg.de" CITATION = "Kunst et al 2019, https://doi.org/10.1016/j.neuron.2019.04.034" -ORIENTATION = "ial" +ORIENTATION = "lai" BASE_URL = r"https://fishatlas.neuro.mpg.de" @@ -96,25 +95,21 @@ def collect_all_inplace( collect_all_inplace(region, traversing_list, download_path, mesh_dict) -# Temporary folder for nrrd files download: -temp_path = Path(tempfile.mkdtemp()) -download_dir_path = temp_path / "downloading_path" -download_dir_path.mkdir() - # Download reference: ##################### reference_url = f"{BASE_URL}/media/brain_browser/Brain/MovieViewBrain/standard_brain_fixed_SYP_T_GAD1b.nrrd" -out_file_path = download_dir_path / "reference.nrrd" +out_file_path = bg_root_dir / "reference.nrrd" retrieve_over_http(reference_url, out_file_path) refstack, h = nrrd.read(str(out_file_path)) +print(refstack.shape) # Download structures tree and meshes: ###################################### regions_url = f"{BASE_URL}/neurons/get_brain_regions" -meshes_dir_path = download_dir_path / "meshes_temp_download" +meshes_dir_path = bg_root_dir / "meshes_temp_download" meshes_dir_path.mkdir(exist_ok=True) # Download structures hierarchy: @@ -143,7 +138,6 @@ def collect_all_inplace( structures_dict, structures_list, meshes_dir_path, meshes_dict ) -print(structures_list) # Wrap up, compress, and remove file:0 print(f"Finalising atlas") wrapup_atlas_from_data( diff --git a/brainatlas_api/bg_atlas.py b/brainatlas_api/bg_atlas.py index 0f96c03..286ed2c 100644 --- a/brainatlas_api/bg_atlas.py +++ b/brainatlas_api/bg_atlas.py @@ -94,9 +94,9 @@ def download_extract_file(self): destination_path.unlink() -class TestAtlas(BrainGlobeAtlas): - atlas_name = "test_allen_100um" - version = "0.1" +class ExampleAtlas(BrainGlobeAtlas): + atlas_name = "example_mouse_100um" + version = "0.2" class FishAtlas(BrainGlobeAtlas): diff --git a/brainatlas_api/core.py b/brainatlas_api/core.py index 1bfa7cb..5748624 100644 --- a/brainatlas_api/core.py +++ b/brainatlas_api/core.py @@ -30,11 +30,15 @@ def __init__(self, path): self.root_dir = Path(path) self.metadata = read_json(self.root_dir / METADATA_FILENAME) - # Class for structures: + # Load structures list: structures_list = read_json(self.root_dir / STRUCTURES_FILENAME) - self.structures = StructuresDict( - structures_list, self.root_dir / MESHES_DIRNAME - ) + # Add entry for file paths: + for struct in structures_list: + struct["mesh_filename"] = ( + self.root_dir / MESHES_DIRNAME / "{}.obj".format(struct["id"]) + ) + + self.structures = StructuresDict(structures_list,) self._reference = None self._annotation = None @@ -57,7 +61,9 @@ def hemispheres(self): if self._hemispheres is None: # If reference is symmetric generate hemispheres block: if self.metadata["symmetric"]: - self._hemispheres = make_hemispheres_stack(self.shape) + self._hemispheres = make_hemispheres_stack( + self.metadata["shape"] + ) else: self._hemispheres = read_tiff( self.root_dir / HEMISPHERES_FILENAME @@ -67,15 +73,29 @@ def hemispheres(self): def hemisphere_from_coords(self, coords): return self.hemispheres[_idx_from_coords(coords)] - def structure_from_coords(self, coords): - return self.annotation[_idx_from_coords(coords)] + def structure_from_coords(self, coords, as_acronym=False): + rid = self.annotation[_idx_from_coords(coords)] + if as_acronym: + d = self.structures[rid] + return d["acronym"] + else: + return rid # Meshes-related methods: - def get_mesh(self, region_id): - return self.structure_mesh_dict[region_id] + def get_from_structure(self, structure, key): + if isinstance(structure, list) or isinstance(structure, tuple): + return [self.get_from_structure(s, key) for s in structure] + else: + return self.structures[structure][key] + + def mesh_from_structure(self, region_id): + return self.structures[region_id]["mesh"] + + def meshfile_from_structure(self, region_id): + return self.structures[region_id]["mesh_filename"] - def get_brain_mesh(self): - return self.get_mesh_from_name("root") + def root_mesh(self): + return self.mesh_from_structure("root") # ------- BrainRender methods, might be useful to implement here ------- # diff --git a/brainatlas_api/descriptors.py b/brainatlas_api/descriptors.py index 6f63bc9..da7d906 100644 --- a/brainatlas_api/descriptors.py +++ b/brainatlas_api/descriptors.py @@ -37,4 +37,4 @@ ANNOTATION_DTYPE = np.int32 # Standard orientation origin: Anterior, Left, Superior -ATLAS_ORIENTATION = "als" +ATLAS_ORIENTATION = "asl" diff --git a/brainatlas_api/structure_class.py b/brainatlas_api/structure_class.py index f358560..e723ebe 100644 --- a/brainatlas_api/structure_class.py +++ b/brainatlas_api/structure_class.py @@ -1,14 +1,18 @@ import meshio as mio -from collections import UserDict -class Structure(UserDict): +class Structure(dict): def __getitem__(self, item): - if item == "mesh" and self.data[item] is None: + if item == "mesh" and super().__getitem__(item) is None: # TODO gracefully fail with warning if no mesh: - self.data[item] = mio.read(self["mesh_filename"]) + try: + super().__setitem__(item, mio.read(self["mesh_filename"])) + except (TypeError, mio.ReadError): + raise mio.ReadError( + "No valid mesh for region: {}".format(self.data["acronym"]) + ) - return self.data[item] + return super().__getitem__(item) def get_center_of_mass(self): pass @@ -20,7 +24,7 @@ def descendants(self): pass -class StructuresDict(UserDict): +class StructuresDict(dict): """ Class to handle dual indexing by either acronym or id. Parameters @@ -29,7 +33,7 @@ class StructuresDict(UserDict): path to folder containing all meshes .obj files """ - def __init__(self, structures_list, mesh_dir_path): + def __init__(self, structures_list): super().__init__() # Acronym to id map: @@ -37,17 +41,24 @@ def __init__(self, structures_list, mesh_dir_path): r["acronym"]: r["id"] for r in structures_list } - for struct_dict in structures_list: - sid = struct_dict["id"] - mesh_filename = mesh_dir_path / f"{sid}.obj" - self.data[sid] = Structure( - mesh_filename=mesh_filename, mesh=None, **struct_dict - ) + for struct in structures_list: + sid = struct["id"] + super().__setitem__(sid, Structure(**struct, mesh=None)) def __getitem__(self, item): - if isinstance(item, int): - return self.data[item] - elif isinstance(item, str): - return self.data[self.acronym_to_id_map[item]] - elif isinstance(item, list): - return [self.__getitem__(i) for i in item] + """ Core implementation of the class support for different indexing. + + Parameters + ---------- + item : + + Returns + ------- + + """ + try: + item = int(item) + except ValueError: + item = self.acronym_to_id_map[item] + + return super().__getitem__(int(item)) diff --git a/requirements.txt b/requirements.txt index cdf502b..9ea3ec6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ tifffile treelib pandas requests +meshio \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index d2d04c0..556ff10 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,10 @@ import pytest -from brainatlas_api.bg_atlas import TestAtlas -import tempfile +from brainatlas_api.bg_atlas import ExampleAtlas + +# import tempfile @pytest.fixture(scope="module") def atlas_path(): - return TestAtlas(brainglobe_path=tempfile.mkdtemp()).root_dir + # brainglobe_path=tempfile.mkdtemp() + return ExampleAtlas().root_dir diff --git a/tests/test_atlas.py b/tests/test_atlas.py index afd6c43..6335408 100644 --- a/tests/test_atlas.py +++ b/tests/test_atlas.py @@ -2,45 +2,38 @@ import numpy as np -from brainatlas_api.bg_atlas import TestAtlas +from brainatlas_api.bg_atlas import ExampleAtlas @pytest.fixture() def atlas(): - return TestAtlas() + return ExampleAtlas() def test_initialization(atlas): assert atlas.metadata == { - "name": "test_allen_100um", + "name": "example_mouse", "citation": "Wang et al 2020, https://doi.org/10.1016/j.cell.2020.04.007", "atlas_link": "http://www.brain-map.org.com", "symmetric": True, "resolution": [100, 100, 100], - "species": "mouse (Mus musculus)", - "version": "0.1", + "species": "Mus musculus", + "version": "0.2", "shape": [132, 80, 114], + "trasform_to_bg": [ + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + ], } -@pytest.mark.parametrize( - "attribute, val", - [ - ("shape", [132, 80, 114]), - ("resolution", [100, 100, 100]), - ("name", "test_allen_100um"), - ("symmetric", True), - ], -) -def test_attributes(atlas, attribute, val): - assert getattr(atlas, attribute) == val - - @pytest.mark.parametrize( "stack_name, val", [ ("reference", [[[146, 155], [153, 157]], [[148, 150], [153, 153]]]), - ("annotated", [[[59, 362], [59, 362]], [[59, 362], [59, 362]]]), + ("annotation", [[[59, 362], [59, 362]], [[59, 362], [59, 362]]]), ("hemispheres", [[[0, 0], [0, 0]], [[1, 1], [1, 1]]]), ], ) @@ -49,30 +42,37 @@ def test_stacks(atlas, stack_name, val): assert np.allclose(loaded_stack[65:67, 39:41, 57:59], val) -def test_maps(atlas): - assert atlas.acronym_to_id_map == {"root": 997, "grey": 8, "CH": 567} - - assert atlas.id_to_acronym_map == {997: "root", 8: "grey", 567: "CH"} +def test_structures(atlas): + assert {s["acronym"]: k for k, s in atlas.structures.items()} == { + "root": 997, + "grey": 8, + "CH": 567, + } + assert atlas.get_from_structure([997, 8, 567], "acronym") == [ + "root", + "grey", + "CH", + ] @pytest.mark.parametrize( "coords", [[39.0, 36.0, 57.0], (39, 36, 57), np.array([39.0, 36.0, 57.0])] ) def test_data_from_coords(atlas, coords): - assert atlas.get_structure_id_from_coords(coords) == 997 - assert atlas.get_structure_name_from_coords(coords) == "root" - assert atlas.get_hemisphere_from_coords(coords) == 0 + assert atlas.structure_from_coords(coords) == 997 + assert atlas.structure_from_coords(coords, as_acronym=True) == "root" + assert atlas.hemisphere_from_coords(coords) == 0 def test_meshfile_from_id(atlas): assert ( - atlas.get_mesh_file_from_acronym("CH") + atlas.meshfile_from_structure("CH") == atlas.root_dir / "meshes/567.obj" ) def test_mesh_from_id(atlas): - # TODO will change depeding on mesh loading package - vert, vnorms, faces, fnorms = atlas.get_mesh_from_id(567) - assert np.allclose(vert[0], [8019.52, 3444.48, 507.104]) - assert np.allclose(faces[0], [0, 1, 2]) + # TODO will change depending on mesh loading package + mesh = atlas.structures[567]["mesh"] + assert np.allclose(mesh.points[0], [8019.52, 3444.48, 507.104]) + assert np.allclose(mesh.cells[0].data[0], [0, 1, 2]) diff --git a/tests/test_structure_dict.py b/tests/test_structure_dict.py new file mode 100644 index 0000000..d2d5c77 --- /dev/null +++ b/tests/test_structure_dict.py @@ -0,0 +1,36 @@ +from brainatlas_api.structure_class import StructuresDict + + +struct_list = [ + { + "acronym": "root", + "id": 997, + "name": "root", + "structure_id_path": [997], + "rgb_triplet": [255, 255, 255], + "mesh_filename": None, + }, + { + "acronym": "grey", + "id": 8, + "name": "Basic cell groups and regions", + "structure_id_path": [997, 8], + "rgb_triplet": [191, 218, 227], + "mesh_filename": None, + }, + { + "acronym": "CH", + "id": 567, + "name": "Cerebrum", + "structure_id_path": [997, 8, 567], + "rgb_triplet": [176, 240, 255], + "mesh_filename": None, + }, +] + + +def test_structure_indexing(): + struct_dict = StructuresDict(struct_list) + assert struct_dict[997] == struct_dict["root"] + assert struct_dict[997.0] == struct_dict["root"] + assert struct_dict["997"] == struct_dict["root"]