Skip to content
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

Refactoring structures/.. #14

Merged
merged 5 commits into from
May 28, 2020
Merged
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
63 changes: 0 additions & 63 deletions brainatlas_api/core.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import numpy as np
from pathlib import Path

from treelib import Tree

from brainatlas_api.utils import open_json, read_tiff, make_hemispheres_stack
from brainatlas_api.structures.structure_tree import StructureTree
from brainatlas_api.obj_utils import read_obj
Expand Down Expand Up @@ -190,67 +188,6 @@ def get_structure_descendants(self, regions):
def get_structure_parent(self, acronyms):
pass

def print_structures(self):
"""
Prints the name of every structure in the structure tree to the console.
"""
acronyms, names = self.structures_acronyms, self.structures_names
sort_idx = np.argsort(acronyms)
acronyms, names = (
np.array(acronyms)[sort_idx],
np.array(names)[sort_idx],
)
[print("({}) - {}".format(a, n)) for a, n in zip(acronyms, names)]

def print_structures_tree(self, to_file=False, save_filepath=None):
"""
Prints a 'tree' graph with the hierarchical organisation of all structures

:param to_file: bool, default False. If True the tree structure is saved to
a file (at save_filepath) instead of printd to REPL
:param save_filepath: str, if to_file = True, pass the path to a .txt file
where the tree structure will be saved.
"""

def add_descendants_to_tree(atlas, tree, structure_id, parent_id):
"""
Recursively goes through all the the descendants of a region and adds them to the tree
"""
tree.create_node(
tag=atlas.id_to_acronym_map[structure_id],
identifier=structure_id,
parent=parent_id,
)
descendants = atlas.structures.child_ids([structure_id])[0]

if len(descendants):
for child in descendants:
add_descendants_to_tree(atlas, tree, child, structure_id)

# Create a Tree structure and initialise with root
root = self.acronym_to_id_map["root"]
tree = Tree()
tree.create_node(tag="root", identifier=root)

# Recursively iterate through hierarchy#
for child in self.structures.child_ids([root])[0]:
add_descendants_to_tree(self, tree, child, root)

if not to_file:
tree.show()
else:
if save_filepath is None:
raise ValueError(
"If setting to_file as True, you need to pass the path to \
a .txt file where the tree will be saved"
)
elif not save_filepath.endswith(".txt"):
raise ValueError(
f"save_filepath should point to a .txt file, not: {save_filepath}"
)

tree.save2file(save_filepath)

# # functions to create oriented planes that can be used to slice actors etc
# def get_plane_at_point(self, pos, norm, sx, sy,
# color='lightgray', alpha=.25,
Expand Down
43 changes: 19 additions & 24 deletions brainatlas_api/structures/simple_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@
from collections import defaultdict
from six import iteritems

from allensdk.deprecated import deprecated


class SimpleTree(object):
def __init__(self, nodes, node_id_cb, parent_id_cb):
Expand Down Expand Up @@ -180,10 +178,6 @@ def node_ids(self):

return list(self._nodes)

@deprecated("Use SimpleTree.parent_ids instead.")
def parent_id(self, node_ids):
return self.parent_ids(node_ids)

def parent_ids(self, node_ids):
"""Obtain the ids of one or more nodes' parents

Expand All @@ -198,8 +192,10 @@ def parent_ids(self, node_ids):
Items are ids of input nodes' parents in order.

"""

return [self._parent_ids[nid] for nid in node_ids]
if isinstance(node_ids, list):
return [self._parent_ids[nid] for nid in node_ids]
else:
return self._parent_ids[node_ids]

def child_ids(self, node_ids):
"""Obtain the ids of one or more nodes' children
Expand All @@ -215,8 +211,10 @@ def child_ids(self, node_ids):
Items are lists of input nodes' children's ids.

"""

return [self._child_ids[nid] for nid in node_ids]
if isinstance(node_ids, list):
return [self._child_ids[nid] for nid in node_ids]
else:
return self._child_ids[node_ids]

def ancestor_ids(self, node_ids):
"""Obtain the ids of one or more nodes' ancestors
Expand Down Expand Up @@ -291,10 +289,6 @@ def descendant_ids(self, node_ids):
out.append(current)
return out

@deprecated("Use SimpleTree.nodes instead")
def node(self, node_ids=None):
return self.nodes(node_ids)

def nodes(self, node_ids=None):
"""Get one or more nodes' full dictionaries from their ids.

Expand All @@ -312,14 +306,13 @@ def nodes(self, node_ids=None):
if node_ids is None:
node_ids = self.node_ids()

return [
self._nodes[nid] if nid in self._nodes else None
for nid in node_ids
]

@deprecated("Use SimpleTree.parents instead")
def parent(self, node_ids):
return self.parents(node_ids)
if isinstance(node_ids, list):
return [
self._nodes[nid] if nid in self._nodes else None
for nid in node_ids
]
else:
return self._nodes[node_ids] if node_ids in self._nodes else None

def parents(self, node_ids):
"""Get one or mode nodes' parent nodes
Expand All @@ -335,8 +328,10 @@ def parents(self, node_ids):
Items are parents of nodes corresponding to argued ids.

"""

return self.nodes([self._parent_ids[nid] for nid in node_ids])
if isinstance(node_ids, list):
return self.nodes([self._parent_ids[nid] for nid in node_ids])
else:
return self.nodes(self._parent_ids[node_ids])

def children(self, node_ids):
"""Get one or mode nodes' child nodes
Expand Down
109 changes: 103 additions & 6 deletions brainatlas_api/structures/structure_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

import numpy as np
import pandas as pd
from treelib import Tree

from .simple_tree import SimpleTree

Expand Down Expand Up @@ -97,8 +98,10 @@ def get_structures_by_id(self, structure_ids):
Each item describes a structure.

"""

return self.nodes(structure_ids)
if isinstance(structure_ids, list):
return self.nodes(structure_ids)
else:
return self.nodes([structure_ids])[0]

def get_structures_by_name(self, names):
"""Obtain a list of brain structures from their names,
Expand All @@ -114,8 +117,10 @@ def get_structures_by_name(self, names):
Each item describes a structure.

"""

return self.nodes_by_property("name", names)
if isinstance(names, list):
return self.nodes_by_property("name", names)
else:
return self.nodes_by_property("name", [names])[0]

def get_structures_by_acronym(self, acronyms):
"""Obtain a list of brain structures from their acronyms
Expand All @@ -131,8 +136,10 @@ def get_structures_by_acronym(self, acronyms):
Each item describes a structure.

"""

return self.nodes_by_property("acronym", acronyms)
if isinstance(acronyms, list):
return self.nodes_by_property("acronym", acronyms)
else:
return self.nodes_by_property("acronym", [acronyms])[0]

def get_colormap(self):
"""Get a dictionary mapping structure ids to colors across all nodes.
Expand Down Expand Up @@ -389,3 +396,93 @@ def path_to_list(path):
return list(path)

return [int(stid) for stid in path.split("/") if stid != ""]

def print_structures(self, to_file=False, save_filepath=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a convenient function and I see the point of having it here; I am a splitting maniac and I would keep it as a separate function then used here, put it's a matter of taste - as long as this class does not get too long

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a splitting maniac

You really are :P

I prefer keeping the number of files / classes low at the cost of slightly longer classes. Especially for stuff like this which is behind the scenes and we won't have to edit it often if at all.

I guess we can find a compromise? :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would agree on a maximum number of lines per class 😄 500?

"""
Prints the name of every structure in the structure tree to the console.
:param to_file: bool, default False. If True the tree structure is saved to
a file (at save_filepath) instead of printd to REPL
:param save_filepath: str, if to_file = True, pass the path to a .txt file
where the tree structure will be saved.
"""
names = [n["name"] for n in self.nodes()]
acronyms = [n["acronym"] for n in self.nodes()]

sort_idx = np.argsort(acronyms)
acronyms, names = (
np.array(acronyms)[sort_idx],
np.array(names)[sort_idx],
)

if not to_file:
[print("({}) - {}".format(a, n)) for a, n in zip(acronyms, names)]
else:
if save_filepath is None:
raise ValueError(
"If setting to_file as True, you need to pass the path to \
a .txt file where the tree will be saved"
)
elif not save_filepath.endswith(".txt"):
raise ValueError(
f"save_filepath should point to a .txt file, not: {save_filepath}"
)

with open(save_filepath, "w") as out:
for a, n in zip(acronyms, names):
out.write("({}) - {}\n".format(a, n))

def print_structures_tree(self, to_file=False, save_filepath=None):
"""
Prints a 'tree' graph with the hierarchical organisation of all structures

:param to_file: bool, default False. If True the tree structure is saved to
a file (at save_filepath) instead of printd to REPL
:param save_filepath: str, if to_file = True, pass the path to a .txt file
where the tree structure will be saved.
"""

def add_descendants_to_tree(
self, id_to_acronym_map, tree, structure_id, parent_id
):
"""
Recursively goes through all the the descendants of a region and adds them to the tree
"""
tree.create_node(
tag=id_to_acronym_map[structure_id],
identifier=structure_id,
parent=parent_id,
)
descendants = self.child_ids([structure_id])[0]

if len(descendants):
for child in descendants:
add_descendants_to_tree(
self, id_to_acronym_map, tree, child, structure_id
)

# Create a Tree structure and initialise with root
acronym_to_id_map = self.get_id_acronym_map()
id_to_acronym_map = {v: k for k, v in acronym_to_id_map.items()}

root = acronym_to_id_map["root"]
tree = Tree()
tree.create_node(tag="root", identifier=root)

# Recursively iterate through hierarchy#
for child in self.child_ids([root])[0]:
add_descendants_to_tree(self, id_to_acronym_map, tree, child, root)

if not to_file:
tree.show()
else:
if save_filepath is None:
raise ValueError(
"If setting to_file as True, you need to pass the path to \
a .txt file where the tree will be saved"
)
elif not save_filepath.endswith(".txt"):
raise ValueError(
f"save_filepath should point to a .txt file, not: {save_filepath}"
)

tree.save2file(save_filepath)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import setup, find_namespace_packages

requirements = ["allensdk", "tqdm", "numpy", "tifffile"]
requirements = ["tqdm", "numpy", "tifffile", "treelib"]


setup(
Expand Down