Source code for brainglobe_atlasapi.structure_class

"""
Provide a class for representing hierarchical structures,
such as brain regions in an atlas.
"""

import os
import warnings
from collections import UserDict
from pathlib import Path

import DracoPy
import meshio as mio
import s3fs
from fsspec.callbacks import TqdmCallback

from brainglobe_atlasapi.descriptors import remote_url_s3
from brainglobe_atlasapi.structure_tree_util import get_structures_tree


[docs] class Structure(UserDict): """Class implementing the lazy loading of a mesh if the dictionary is queried for it. """ def __getitem__(self, item): """ Retrieve an item from the structure's data. If the item is `mesh` and the mesh data is currently None, it attempts to load the mesh from the `mesh_filename` if available. Parameters ---------- item : str The key of the item to retrieve. Returns ------- meshio.Mesh or None or any - If `item` is "mesh" and the mesh data is successfully loaded, returns a `meshio.Mesh` object. - If `item` is "mesh" and `mesh_filename` is None, returns `None`. - For other keys, returns the value associated with the given item, which can be of any type depending on the stored data. Raises ------ meshio.ReadError If `item` is "mesh" and the mesh cannot be read. The value associated with the given item. """ if item == "mesh" and self.data[item] is None: file_name = self.data["mesh_filename"] if file_name is None: warnings.warn( "No mesh filename for region {}".format( self.data["acronym"] ) ) return None try: if not file_name.exists(): self._download_mesh(file_name) self.data[item] = self._read_mesh(file_name) except ( TypeError, mio.ReadError, FileNotFoundError, DracoPy.FileTypeException, ) as e: raise RuntimeError( f"Failed to read mesh for region {self.data['acronym']} " f"from file {file_name}: {e}" ) from e return self.data[item] def _download_mesh(self, file_name: Path) -> None: """Download the mesh from the remote S3 bucket if it is not cached.""" root_path = "/".join(str(file_name).split(os.sep)[-6:]) remote_mesh_path = remote_url_s3.format(root_path) fs = s3fs.S3FileSystem(anon=True) if not fs.exists(remote_mesh_path): raise FileNotFoundError( f"Mesh file {file_name} not found locally or remotely." ) try: fs.get(remote_mesh_path, file_name, callback=TqdmCallback()) except BaseException: file_name.unlink(missing_ok=True) # Removes corrupt file raise @staticmethod def _read_mesh(mesh_path: Path) -> mio.Mesh: """ Read one object back into (vertices, faces). Re-orient from XYZ to ZYX and scale from nm to um. Returns ------- meshio.Mesh The mesh object reoriented and scaled. """ with open(mesh_path, "rb") as f: mesh = DracoPy.decode(f.read()) points = mesh.points / 1000.0 # scale from nm to um points = points[:, [2, 1, 0]] # reorient from XYZ to ZYX faces = mesh.faces[:, [2, 1, 0]] # reorient from XYZ to ZYX return mio.Mesh( points=points, cells=[("triangle", faces)], )
[docs] class StructuresDict(UserDict): """Class to handle dual indexing by either acronym or id. Parameters ---------- mesh_path : str or Path object path to folder containing all meshes .obj files """ def __init__(self, structures_list): super().__init__() # Acronym to id map: self.acronym_to_id_map = { r["acronym"]: r["id"] for r in structures_list } for struct in structures_list: sid = struct["id"] self.data[sid] = Structure(**struct, mesh=None) self.tree = get_structures_tree(structures_list) def __getitem__(self, item): """Core implementation of the class support for different indexing. Parameters ---------- item : str or int The acronym (str) or id (int) of the requested structure. Returns ------- Structure The Structure requested. """ try: item = int(item) except ValueError: item = self.acronym_to_id_map[item] return self.data[int(item)] def __repr__(self): """Return string representation of the class, showing all region names. """ return self.tree.show(stdout=False)