-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Cuttlebase cuttlefish atlas #338
Open
kjungwoo5
wants to merge
45
commits into
brainglobe:main
Choose a base branch
from
kjungwoo5:cuttlefish-atlas
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 40 commits
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
45834a1
Make empty cuttlefish.py file
kjungwoo5 2ace3d2
Download cuttlefish hierarchy file from github and amend format
kjungwoo5 1edcf82
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 4f16ca5
Tweaked region ID structure, loaded in region colour map from cuttlebase
kjungwoo5 947d056
Merge branch 'cuttlefish-atlas' of https://github.com/kjungwoo5/brain…
kjungwoo5 e440e5b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 09ed09f
Applied RGB triplets and fixed acronyms for all regions
kjungwoo5 93dcd64
Merge branch 'cuttlefish-atlas' of https://github.com/kjungwoo5/brain…
kjungwoo5 6d5244d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 74d47c1
Revised hierarchy creation using annotation data, and added both left…
kjungwoo5 57d7f5b
Fixed structure ID path of all regions using the new IDs found from a…
kjungwoo5 ab5256d
Merge branch 'cuttlefish-atlas' of https://github.com/kjungwoo5/brain…
kjungwoo5 ede9d78
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] b42a409
add wrapup function + minor tweaks
alessandrofelder 6dd72b9
draft mesh creation
alessandrofelder 0bae2f6
Added validation script to brainglobe main folder.
kjungwoo5 f2b7d02
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] ce5fecd
Updated mesh generation code to store meshes by correct ID. (In previ…
kjungwoo5 411fe4d
Scaled, rotated and inverted mesh to match the size of the annotation…
kjungwoo5 a8c23aa
Corrected meshes_dict in wrapup function, changed mesh generation to …
kjungwoo5 934d156
Merge branch 'cuttlefish-atlas' of https://github.com/kjungwoo5/brain…
kjungwoo5 6c45b7c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 98b3b3a
Edited validation script
kjungwoo5 407c874
Merge branch 'cuttlefish-atlas' of https://github.com/kjungwoo5/brain…
kjungwoo5 bbf96d4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 84f9d47
Corrected the orientation of the meshes using map_points_to rather th…
kjungwoo5 80f188e
Merge branch 'cuttlefish-atlas' of https://github.com/kjungwoo5/brain…
kjungwoo5 ee46a9c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] f215042
Edited the atlas generation to be in 50um resolution, and changed sca…
kjungwoo5 82195e5
Merge branch 'cuttlefish-atlas' of https://github.com/kjungwoo5/brain…
kjungwoo5 92736a3
test to check for functionality of new branch
kjungwoo5 2101b6b
Remove test of branch functionality
kjungwoo5 13cb394
Remove mesh generation code from atlas generation script.
kjungwoo5 64c1fd3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 0b74776
Added mesh generation code to create our own meshes, rather than usin…
kjungwoo5 cd46ba5
Merge branch 'cuttlefish-atlas' of https://github.com/kjungwoo5/brain…
kjungwoo5 d7e5e3c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 8f5f5f7
Removed temporary validation script, and cleaned up comments on atlas…
kjungwoo5 d61f5ab
Merge branch 'cuttlefish-atlas' of https://github.com/kjungwoo5/brain…
kjungwoo5 7d9fac5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 3051ae3
Removed mesh url, and added code to rescale template image to uint16
kjungwoo5 1136559
Merge branch 'cuttlefish-atlas' of https://github.com/kjungwoo5/brain…
kjungwoo5 5e1bf8e
rename script to match api name
PolarBean f33e83c
Merge branch 'main' into cuttlefish-atlas
alessandrofelder 8cf0d38
symmetrise annotations
alessandrofelder File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
357 changes: 357 additions & 0 deletions
357
brainglobe_atlasapi/atlas_generation/atlas_scripts/cuttlefish.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,357 @@ | ||
__version__ = "0" | ||
|
||
import csv | ||
import glob as glob | ||
import time | ||
from pathlib import Path | ||
|
||
import nrrd | ||
import numpy as np | ||
import pooch | ||
from brainglobe_utils.IO.image import load | ||
from rich.progress import track | ||
|
||
from brainglobe_atlasapi import utils | ||
from brainglobe_atlasapi.atlas_generation.mesh_utils import ( | ||
Region, | ||
create_region_mesh, | ||
) | ||
from brainglobe_atlasapi.atlas_generation.wrapup import wrapup_atlas_from_data | ||
from brainglobe_atlasapi.structure_tree_util import get_structures_tree | ||
|
||
|
||
def hex_to_rgb(hex): | ||
hex = hex.lstrip("#") | ||
rgb = [] | ||
for i in (0, 2, 4): | ||
decimal = int(hex[i : i + 2], 16) | ||
rgb.append(decimal) | ||
|
||
return rgb | ||
|
||
|
||
def create_atlas(working_dir, resolution): | ||
ATLAS_NAME = "columbia_cuttlefish" | ||
SPECIES = "Sepia bandensis" | ||
ATLAS_LINK = "https://www.cuttlebase.org/" | ||
CITATION = ( | ||
"Montague et al, 2023, https://doi.org/10.1016/j.cub.2023.06.007" | ||
) | ||
ORIENTATION = "srp" | ||
ATLAS_PACKAGER = "Jung Woo Kim" | ||
ADDITIONAL_METADATA = {} | ||
ROOT_ID = 999 | ||
HIERARCHY_FILE_URL = "https://raw.githubusercontent.com/noisyneuron/cuttlebase-util/main/data/brain-hierarchy.csv" # noqa E501 | ||
TEMPLATE_URL = r"https://www.dropbox.com/scl/fo/fz8gnpt4xqduf0dnmgrat/ABflM0-v-b4_2WthGaeYM4s/Averaged%2C%20template%20brain/2023_FINAL-Cuttlebase_warped_template.nii.gz?rlkey=eklemeh57slu7v6j1gphqup4z&dl=1" # noqa E501 | ||
ANNOTATION_URL = r"https://www.dropbox.com/scl/fo/fz8gnpt4xqduf0dnmgrat/ALfSeAj81IM0v56bEeoTfUQ/Averaged%2C%20template%20brain/2023_FINAL-Cuttlebase_warped_template_lobe-labels.nii.seg.nrrd?rlkey=eklemeh57slu7v6j1gphqup4z&dl=1" # noqa E501 | ||
MESH_URL = r"https://www.cuttlebase.org/assets/models/cuttlefish_brain.glb" | ||
|
||
download_dir_path = working_dir / "downloads" | ||
download_dir_path.mkdir(exist_ok=True) | ||
|
||
# download hierarchy files | ||
utils.check_internet_connection() | ||
hierarchy_path = pooch.retrieve( | ||
HIERARCHY_FILE_URL, | ||
known_hash="023418e626bdefbd177d4bb8c08661bd63a95ccff47720e64bb7a71546935b77", | ||
progressbar=True, | ||
) | ||
|
||
# import cuttlefish .nrrd file | ||
annotation_path = pooch.retrieve( | ||
ANNOTATION_URL, | ||
known_hash="768973251b179902ab48499093a4cc870cb6507c09ce46ff76b8203daf243f82", | ||
progressbar=True, | ||
) | ||
|
||
# process brain annotation file. There are a total of 70 segments. | ||
print("Processing brain annotations:") | ||
readdata, header = nrrd.read(annotation_path) | ||
|
||
# Extract annotation mapping information from nrrd headers, | ||
# to be applied to hierarchy file later. | ||
mapping = [] | ||
for n in range(0, 70): | ||
mapping.append( | ||
{ | ||
"color": header[f"Segment{n}_Color"], | ||
"id": header[f"Segment{n}_LabelValue"], | ||
"acronym": header[f"Segment{n}_Name"], | ||
} | ||
) | ||
|
||
# convert the color information stored as a string of 3 RGB floats | ||
# into a list of 3 RGB integers from 0 to 255. | ||
for index, Map in enumerate(mapping): | ||
mapping[index]["color"] = Map["color"].split(" ") | ||
mapping[index]["color"] = list(map(float, mapping[index]["color"])) | ||
mapping[index]["color"] = [ | ||
int(255 * x) for x in mapping[index]["color"] | ||
] | ||
|
||
# create dictionaries for regions hierarchy | ||
print("Creating structure tree") | ||
with open( | ||
hierarchy_path, mode="r", encoding="utf-8-sig" | ||
) as cuttlefish_file: | ||
cuttlefish_dict_reader = csv.DictReader(cuttlefish_file) | ||
|
||
# empty list to populate with dictionaries | ||
hierarchy = [] | ||
|
||
# parse through csv file and populate hierarchy list | ||
for row in cuttlefish_dict_reader: | ||
if row["hasSides"] == "Y": | ||
leftSide = dict(row) | ||
leftSide["abbreviation"] = leftSide["abbreviation"] + "l" | ||
leftSide["name"] = leftSide["name"] + " (left)" | ||
|
||
rightSide = dict(row) | ||
rightSide["abbreviation"] = rightSide["abbreviation"] + "r" | ||
rightSide["name"] = rightSide["name"] + " (right)" | ||
|
||
hierarchy.append(leftSide) | ||
hierarchy.append(rightSide) | ||
else: | ||
hierarchy.append(row) | ||
|
||
# use layers to give IDs to regions which do not have existing IDs. | ||
layer1 = 100 | ||
layer2 = 200 | ||
# remove 'hasSides' and 'function' keys, | ||
# reorder and rename the remaining keys | ||
for i in range(0, len(hierarchy)): | ||
hierarchy[i]["acronym"] = hierarchy[i].pop("abbreviation") | ||
hierarchy[i].pop("hasSides") | ||
hierarchy[i].pop("function") | ||
hierarchy[i]["structure_id_path"] = list( | ||
(map(int, (hierarchy[i]["index"].split("-")))) | ||
) | ||
hierarchy[i]["structure_id_path"].insert(0, 999) | ||
hierarchy[i].pop("index") | ||
if ( | ||
len(hierarchy[i]["structure_id_path"]) < 4 | ||
and hierarchy[i]["structure_id_path"][-2] != 3 | ||
): | ||
if len(hierarchy[i]["structure_id_path"]) == 3: | ||
hierarchy[i]["id"] = layer2 | ||
layer2 += 1 | ||
elif len(hierarchy[i]["structure_id_path"]) == 2: | ||
hierarchy[i]["id"] = layer1 | ||
layer1 += 1 | ||
if hierarchy[i]["acronym"] == "SB": | ||
hierarchy[i]["id"] = 71 | ||
elif hierarchy[i]["acronym"] == "IB": | ||
hierarchy[i]["id"] = 72 | ||
|
||
# remove erroneous key for the VS region | ||
# (error due to commas being included in the 'function' column) | ||
hierarchy[-3].pop(None) | ||
hierarchy[-4].pop(None) | ||
|
||
# add the 'root' structure | ||
hierarchy.append( | ||
{ | ||
"name": "root", | ||
"acronym": "root", | ||
"structure_id_path": [999], | ||
"id": ROOT_ID, | ||
} | ||
) | ||
|
||
# apply colour and id map to each region | ||
for index, region in enumerate(hierarchy): | ||
for Map in mapping: | ||
if region["acronym"] == Map["acronym"]: | ||
hierarchy[index]["rgb_triplet"] = Map["color"] | ||
hierarchy[index]["id"] = int(Map["id"]) | ||
|
||
# amend each region's structure_id_path by iterating through entire list, | ||
# and replacing dummy values with actual ID values. | ||
for i in range(0, len(hierarchy)): | ||
if len(hierarchy[i]["structure_id_path"]) == 2: | ||
hierarchy[i]["structure_id_path"][1] = hierarchy[i]["id"] | ||
len2_shortest_index = i | ||
|
||
elif len(hierarchy[i]["structure_id_path"]) == 3: | ||
hierarchy[i]["structure_id_path"][1] = hierarchy[ | ||
len2_shortest_index | ||
]["id"] | ||
hierarchy[i]["structure_id_path"][2] = hierarchy[i]["id"] | ||
len3_shortest_index = i | ||
|
||
elif len(hierarchy[i]["structure_id_path"]) == 4: | ||
hierarchy[i]["structure_id_path"][1] = hierarchy[ | ||
len2_shortest_index | ||
]["id"] | ||
hierarchy[i]["structure_id_path"][2] = hierarchy[ | ||
len3_shortest_index | ||
]["id"] | ||
hierarchy[i]["structure_id_path"][3] = hierarchy[i]["id"] | ||
|
||
# original atlas does not give colours to some regions, so we give | ||
# random RGB triplets to regions without specified RGB triplet values | ||
random_rgb_triplets = [ | ||
[156, 23, 189], | ||
[45, 178, 75], | ||
[231, 98, 50], | ||
[12, 200, 155], | ||
[87, 34, 255], | ||
[190, 145, 66], | ||
[64, 199, 225], | ||
[255, 120, 5], | ||
[10, 45, 90], | ||
[145, 222, 33], | ||
[35, 167, 204], | ||
[76, 0, 89], | ||
[27, 237, 236], | ||
[255, 255, 255], | ||
] | ||
|
||
n = 0 | ||
for index, region in enumerate(hierarchy): | ||
if "rgb_triplet" not in region: | ||
hierarchy[index]["rgb_triplet"] = random_rgb_triplets[n] | ||
n = n + 1 | ||
|
||
# give filler acronyms for regions without specified acronyms | ||
missing_acronyms = [ | ||
"SpEM", | ||
"VLC", | ||
"BsLC", | ||
"SbEM", | ||
"PLC", | ||
"McLC", | ||
"PvLC", | ||
"BLC", | ||
"PeM", | ||
"OTC", | ||
"NF", | ||
] | ||
n = 0 | ||
for index, region in enumerate(hierarchy): | ||
if hierarchy[index]["acronym"] == "": | ||
hierarchy[index]["acronym"] = missing_acronyms[n] | ||
n = n + 1 | ||
|
||
# generate hierarchy tree | ||
tree = get_structures_tree(hierarchy) | ||
print( | ||
f"Number of brain regions: {tree.size()}, " | ||
f"max tree depth: {tree.depth()}" | ||
) | ||
|
||
# import cuttlefish .nii file | ||
template_path = pooch.retrieve( | ||
TEMPLATE_URL, | ||
known_hash="195125305a11abe6786be1b32830a8aed1bc8f68948ad53fa84bf74efe7cbe9c", # noqa E501 | ||
progressbar=True, | ||
) | ||
|
||
# process brain template MRI file | ||
print("Processing brain template:") | ||
brain_template = load.load_nii(template_path, as_array=True) | ||
|
||
# generate binary mask for mesh creation | ||
# generate binary mask for mesh creation | ||
labels = np.unique(readdata).astype(np.int_) | ||
for key, node in tree.nodes.items(): | ||
if key in labels: | ||
is_label = True | ||
else: | ||
is_label = False | ||
|
||
node.data = Region(is_label) | ||
|
||
# write meshes | ||
atlas_dir_name = f"{ATLAS_NAME}_{resolution[0]}um_v1.{__version__}" | ||
mesh_dir = Path(working_dir) / ATLAS_NAME / atlas_dir_name / "meshes" | ||
mesh_dir.mkdir(exist_ok=True, parents=True) | ||
|
||
# define smoothing information for meshes | ||
closing_n_iters = 2 | ||
decimate_fraction = 0.3 | ||
smooth = True | ||
|
||
start = time.time() | ||
|
||
for node in track( | ||
tree.nodes.values(), | ||
total=tree.size(), | ||
description="Creating meshes", | ||
): | ||
create_region_mesh( | ||
( | ||
mesh_dir, | ||
node, | ||
tree, | ||
labels, | ||
readdata, | ||
ROOT_ID, | ||
closing_n_iters, | ||
decimate_fraction, | ||
smooth, | ||
) | ||
) | ||
|
||
print( | ||
"Finished mesh extraction in : ", | ||
round((time.time() - start) / 60, 2), | ||
" minutes", | ||
) | ||
|
||
# create meshes_dict | ||
meshes_dict = dict() | ||
structures_with_mesh = [] | ||
for s in hierarchy: | ||
# check if a mesh was created | ||
mesh_path = mesh_dir / f"{s['id']}.obj" | ||
if not mesh_path.exists(): | ||
print(f"No mesh file exists for: {s}, ignoring it.") | ||
continue | ||
else: | ||
# check that the mesh actually exists and isn't empty | ||
if mesh_path.stat().st_size < 512: | ||
print(f"obj file for {s} is too small, ignoring it.") | ||
continue | ||
structures_with_mesh.append(s) | ||
meshes_dict[s["id"]] = mesh_path | ||
|
||
print( | ||
f"In the end, {len(structures_with_mesh)} " | ||
"structures with mesh are kept" | ||
) | ||
|
||
output_filename = wrapup_atlas_from_data( | ||
atlas_name=ATLAS_NAME, | ||
atlas_minor_version=__version__, | ||
citation=CITATION, | ||
atlas_link=ATLAS_LINK, | ||
species=SPECIES, | ||
resolution=resolution, | ||
orientation=ORIENTATION, | ||
root_id=ROOT_ID, | ||
reference_stack=brain_template, | ||
annotation_stack=readdata, | ||
structures_list=hierarchy, | ||
meshes_dict=meshes_dict, | ||
scale_meshes=True, | ||
working_dir=working_dir, | ||
hemispheres_stack=None, | ||
cleanup_files=False, | ||
compress=True, | ||
atlas_packager=ATLAS_PACKAGER, | ||
additional_metadata=ADDITIONAL_METADATA, | ||
additional_references={}, | ||
) | ||
|
||
return output_filename | ||
|
||
|
||
if __name__ == "__main__": | ||
res = 50, 50, 50 | ||
home = str(Path.home()) | ||
bg_root_dir = Path.home() / "brainglobe_workingdir" | ||
bg_root_dir.mkdir(exist_ok=True, parents=True) | ||
|
||
create_atlas(bg_root_dir, res) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to rescale the template image here, because otherwise it get naively converted from
np.float64
tonp.uint16
. For an example of how this is done, seebrainglobe-atlasapi/brainglobe_atlasapi/atlas_generation/atlas_scripts/kim_devccf_mouse.py
Line 94 in a1cbccc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, I'll try to get working on this soon!