Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions atlas_scripts/whs_sd_rat.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,10 +286,6 @@ def create_atlas(working_dir):
# Clean junk from reference file
reference_stack *= annotation_stack > 0

# Create hemispheres stack
hemispheres_stack = np.full(reference_stack.shape, 2, dtype=np.uint8)
hemispheres_stack[:244] = 1

# save regions list json:
with open(download_dir_path / "structures.json", "w") as f:
json.dump(structures, f)
Expand Down Expand Up @@ -317,7 +313,7 @@ def create_atlas(working_dir):
meshes_dict=meshes_dict,
working_dir=working_dir,
atlas_packager=ATLAS_PACKAGER,
hemispheres_stack=hemispheres_stack,
hemispheres_available=False,
cleanup_files=False,
compress=True,
scale_meshes=True,
Expand Down
27 changes: 21 additions & 6 deletions brainglobe_atlasapi/atlas_generation/atlas_packaging_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,14 @@ class AtlasPackagingData:
Credit for those responsible for converting the atlas into the
BrainGlobe format.
hemispheres_stack : ValidComponentData, optional
Hemisphere stack for the atlas. If None, atlas is assumed symmetric.
Hemisphere stack for the atlas. If None (and hemispheres_available is
True), the atlas is assumed symmetric and a hemisphere stack is
generated by splitting the volume down the midline.
hemispheres_available : bool, optional
Whether hemisphere information exists for this atlas. Set to False when
the hemisphere assignment is unknown or the imaged object does not have
distinct hemispheres. When False, no hemisphere stack is generated or
written, and Atlas.hemispheres returns None. Defaults to True.
additional_references : List[Tuple[TemplateInfo, ValidComponentData]], optional
List of tuples containing metadata and arrays for secondary
templates.
Expand Down Expand Up @@ -414,6 +421,7 @@ class AtlasPackagingData:
atlas_version_underscore: Optional[str] = None
atlas_packager: str | None = None
hemispheres_stack: ValidComponentData = None
hemispheres_available: bool = True
additional_references: List[
Tuple[
TemplateInfo,
Expand Down Expand Up @@ -453,14 +461,21 @@ def __post_init__(self):
ref_stack = _reorient_stacks(ref_stack, self.space_convention)
self.additional_references[i] = (stack_tuple[0], ref_stack)

self.symmetric = self.hemispheres_stack is None

if not self.symmetric:
self.hemispheres_stack = _load_stack(self.hemispheres_stack)
else:
if not self.hemispheres_available:
# No hemisphere information: don't generate or load a stack.
if self.hemispheres_stack is not None:
raise ValueError(
"hemispheres_stack was provided but hemispheres_available is False."
)
self.symmetric = False
self.hemispheres_stack = None
self.symmetric = True
self.hemispheres_stack = _auto_generate_hemispheres(
shapes=[stack.shape for stack in self.annotation_stack],
)
else:
self.symmetric = False
self.hemispheres_stack = _load_stack(self.hemispheres_stack)

self.structures_list = filter_structures_not_present_in_annotation(
self.structures_list, self.annotation_stack[0]
Expand Down
8 changes: 7 additions & 1 deletion brainglobe_atlasapi/atlas_generation/metadata_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def generate_metadata_dict(
terminology: TerminologyInfo,
annotation_set: AnnotationInfo,
template: TemplateInfo,
hemispheres_available: bool = True,
):
"""
Generate a dictionary containing metadata for a BrainGlobe atlas.
Expand Down Expand Up @@ -79,7 +80,10 @@ def generate_metadata_dict(
Metadata for the annotation set.
template : TemplateInfo
Metadata for the template.

hemispheres_available : bool, optional
True if hemisphere information is available for the atlas, False if it
is unknown or the imaged object does not have distinct hemispheres.
Defaults to True.

Returns
-------
Expand All @@ -106,6 +110,7 @@ def generate_metadata_dict(

# Enforce correct format for symmetric, resolution and shape:
assert isinstance(symmetric, bool)
assert isinstance(hemispheres_available, bool)
assert len(resolution) == 3
assert len(shape) == 3

Expand All @@ -125,6 +130,7 @@ def generate_metadata_dict(
atlas_link=atlas_link,
species=species,
symmetric=symmetric,
hemispheres_available=hemispheres_available,
resolution=resolution,
orientation=orientation,
version=version,
Expand Down
5 changes: 4 additions & 1 deletion brainglobe_atlasapi/atlas_generation/validate_atlases.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,8 @@ def validate_metadata(atlas: BrainGlobeAtlas):
Checks that the metadata of the given atlas has the correct format.
Specifically, it ensures that all required keys from `METADATA_TEMPLATE`
are present and that the types of the values match the types specified
in `METADATA_TEMPLATE`.
in `METADATA_TEMPLATE`. The ``hemispheres_available`` key is optional for
compatibility with manifests created before that field was introduced.

Parameters
----------
Expand All @@ -594,6 +595,8 @@ def validate_metadata(atlas: BrainGlobeAtlas):
a metadata value does not match the expected type.
"""
for key, value in METADATA_TEMPLATE.items():
if key == "hemispheres_available" and key not in atlas.metadata:
continue
assert key in atlas.metadata, f"Missing key: {key}"
assert isinstance(atlas.metadata[key], type(value)), (
f"{key} should be of type {type(value).__name__}, "
Expand Down
109 changes: 65 additions & 44 deletions brainglobe_atlasapi/atlas_generation/wrapup.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,8 +363,10 @@ def _save_annotation_data(
transformations: List[List[dict]],
scale_meshes: bool,
resolution_mapping: Optional[List[int]],
) -> Tuple[nz.Multiscales, nz.Multiscales]:
) -> Tuple[nz.Multiscales, Optional[nz.Multiscales]]:
annotation_info = packaging_data.annotation_info
hemispheres_available = packaging_data.hemispheres_available
hemispheres_multiscale = None

if not (annotation_info.use_existing or annotation_info.update_existing):
dest_dir = packaging_data.working_dir / annotation_info.metadata[
Expand All @@ -379,26 +381,28 @@ def _save_annotation_data(
save_annotation,
)

hemispheres_stub = descriptors.format_hemispheres_stub(
annotation_info.name, annotation_info.version
)
dest_dir_hemi = packaging_data.working_dir / hemispheres_stub

if not dest_dir_hemi.exists():
save_hemispheres(
packaging_data.hemispheres_stack,
dest_dir,
transformations,
)
else:
print(
f"{annotation_info.metadata['name']} directory already exists,"
f" skipping: {dest_dir_hemi}"
if hemispheres_available:
hemispheres_stub = descriptors.format_hemispheres_stub(
annotation_info.name, annotation_info.version
)
dest_dir_hemi = packaging_data.working_dir / hemispheres_stub

if not dest_dir_hemi.exists():
save_hemispheres(
packaging_data.hemispheres_stack,
dest_dir,
transformations,
)
else:
print(
f"{annotation_info.metadata['name']} directory already "
f"exists, skipping: {dest_dir_hemi}"
)
hemispheres_multiscale = nz.from_ngff_zarr(dest_dir_hemi)

annotation_multiscale = nz.from_ngff_zarr(
packaging_data.working_dir / annotation_info.stub
)
hemispheres_multiscale = nz.from_ngff_zarr(dest_dir_hemi)
elif annotation_info.update_existing:
local_existing_path = (
packaging_data.working_dir / annotation_info.existing_stub
Expand All @@ -412,39 +416,46 @@ def _save_annotation_data(
working_dir=local_target_path,
)

existing_hemispheres_stub = descriptors.format_hemispheres_stub(
annotation_info.name, annotation_info.existing_version
)
local_existing_hemispheres = (
packaging_data.working_dir / existing_hemispheres_stub
)
hemispheres_multiscale = nz.from_ngff_zarr(local_existing_hemispheres)
hemispheres_stub = descriptors.format_hemispheres_stub(
annotation_info.name, annotation_info.version
)
local_target_hemispheres = (
packaging_data.working_dir / hemispheres_stub
)
if hemispheres_available:
existing_hemispheres_stub = descriptors.format_hemispheres_stub(
annotation_info.name, annotation_info.existing_version
)
local_existing_hemispheres = (
packaging_data.working_dir / existing_hemispheres_stub
)
hemispheres_multiscale = nz.from_ngff_zarr(
local_existing_hemispheres
)
hemispheres_stub = descriptors.format_hemispheres_stub(
annotation_info.name, annotation_info.version
)
local_target_hemispheres = (
packaging_data.working_dir / hemispheres_stub
)

_insert_into_multiscale(
hemispheres_multiscale,
transformations=transformations,
new_data=packaging_data.hemispheres_stack,
working_dir=local_target_hemispheres,
)
_insert_into_multiscale(
hemispheres_multiscale,
transformations=transformations,
new_data=packaging_data.hemispheres_stack,
working_dir=local_target_hemispheres,
)

hemispheres_multiscale = nz.from_ngff_zarr(
local_target_hemispheres
)

annotation_multiscale = nz.from_ngff_zarr(local_target_path)
hemispheres_multiscale = nz.from_ngff_zarr(local_target_hemispheres)
else:
hemispheres_stub = descriptors.format_hemispheres_stub(
annotation_info.name, annotation_info.version
)
annotation_multiscale = nz.from_ngff_zarr(
packaging_data.working_dir / annotation_info.stub
)
hemispheres_multiscale = nz.from_ngff_zarr(
packaging_data.working_dir / hemispheres_stub
)
if hemispheres_available:
hemispheres_stub = descriptors.format_hemispheres_stub(
annotation_info.name, annotation_info.version
)
hemispheres_multiscale = nz.from_ngff_zarr(
packaging_data.working_dir / hemispheres_stub
)

if not annotation_info.use_existing:
meshes_stub = descriptors.format_meshes_stub(
Expand Down Expand Up @@ -743,6 +754,7 @@ def _finalize_atlas_at_resolution(
atlas_link=packaging_data.atlas_link,
species=packaging_data.species,
symmetric=symmetric,
hemispheres_available=packaging_data.hemispheres_available,
resolution=resolution,
orientation=descriptors.ATLAS_ORIENTATION,
version=atlas_version,
Expand Down Expand Up @@ -820,6 +832,7 @@ def wrapup_atlas_from_data(
overwrite=False,
cleanup_files=None,
compress=None,
hemispheres_available: bool = True,
) -> Path:
"""
Finalise an atlas with truly consistent format from all the data.
Expand Down Expand Up @@ -869,7 +882,8 @@ def wrapup_atlas_from_data(
If str or Path, will be read with tifffile.
If list, should be list of stacks for each scale, ordered from highest
to lowest resolution.
If none is provided, atlas is assumed to be symmetric.
If none is provided (and hemispheres_available is True), atlas is
assumed to be symmetric.
scale_meshes: bool, optional
(Default value = False).
If True the meshes points are scaled by the resolution
Expand All @@ -893,6 +907,12 @@ def wrapup_atlas_from_data(
compress : deprecated, optional
(Default value = None).
Deprecated and has no effect.
hemispheres_available : bool, optional
(Default value = True).
Whether hemisphere information exists for this atlas. Set to False when
the hemisphere assignment is unknown or the imaged object does not have
distinct hemispheres. When False, no hemisphere data is generated or
written, and Atlas.hemispheres returns None.

Returns
-------
Expand Down Expand Up @@ -1036,6 +1056,7 @@ def wrapup_atlas_from_data(
meshes_dict=meshes_dict,
atlas_packager=atlas_packager,
hemispheres_stack=hemispheres_stack,
hemispheres_available=hemispheres_available,
additional_references=additional_template_list,
additional_metadata=additional_metadata,
)
Expand Down
4 changes: 3 additions & 1 deletion brainglobe_atlasapi/bg_atlas.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,9 @@ def download(self):
f"v{remote_version_str.replace('_', '.')}."
) from e

if not self.metadata["symmetric"]:
if not self.metadata["symmetric"] and self.metadata.get(
"hemispheres_available", True
):
root_hemisphere_path = (
annotation_location
+ f"/{V3_HEMISPHERES_NAME}/**/*.json"
Expand Down
26 changes: 21 additions & 5 deletions brainglobe_atlasapi/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import (
Dict,
List,
Optional,
Tuple,
Union,
)
Expand Down Expand Up @@ -125,7 +126,7 @@ def __init__(self, path):

# Add entry for file paths:
for struct in structures_list:
struct["mesh_filename"] = meshes_path / f'{struct["id"]}'
struct["mesh_filename"] = meshes_path / f"{struct['id']}"

self.structures = StructuresDict(structures_list)

Expand Down Expand Up @@ -293,7 +294,14 @@ def hemispheres(self):
generated by splitting the reference in half along the frontal axis.
If the reference has an odd number of voxels along the frontal axis,
the middle plane is assigned to the left hemisphere.

Returns ``None`` for atlases where hemisphere information is
unavailable (e.g. the assignment is unknown, or the imaged object
does not have distinct hemispheres).
"""
if not self.metadata.get("hemispheres_available", True):
return None

if self._hemispheres is not None:
return self._hemispheres

Expand Down Expand Up @@ -350,7 +358,7 @@ def hemisphere_from_coords(
coords: Union[Tuple, List, np.ndarray],
microns: bool = False,
as_string: bool = False,
) -> Union[int, str]:
) -> Optional[Union[int, str]]:
"""Get the hemisphere from a coordinate triplet.

Parameters
Expand All @@ -366,10 +374,18 @@ def hemisphere_from_coords(

Returns
-------
int or string
Hemisphere label.
int or string or None
Hemisphere label, or ``None`` if hemisphere information is
unavailable for this atlas.

Comment thread
PolarBean marked this conversation as resolved.
"""
if self.hemispheres is None:
warnings.warn(
"Hemisphere information is unavailable for this atlas.",
stacklevel=2,
)
return None

hem = self.hemispheres[self._idx_from_coords(coords, microns)]
if as_string:
hem = ["left", "right"][hem - 1]
Expand Down Expand Up @@ -657,7 +673,7 @@ def get_structures_at_hierarchy_level(
)
except IndexError:
raise ValueError(
f'Structure {self.structures[structure]["acronym"]} '
f"Structure {self.structures[structure]['acronym']} "
f"has no descendants at hierarchy level {hierarchy_level}"
)

Expand Down
1 change: 1 addition & 0 deletions brainglobe_atlasapi/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"atlas_link": "http://www.example.com",
"species": "Gen species",
"symmetric": False,
"hemispheres_available": True,
"resolution": [1.0, 1.0, 1.0],
"orientation": "asr",
"shape": [100, 50, 100],
Expand Down
Loading
Loading