##############################################################################
# Braviz, Brain Data interactive visualization #
# Copyright (C) 2014 Diego Angulo #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU Lesser General Public License as #
# published by the Free Software Foundation, either version 3 of the #
# License, or (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public License#
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
##############################################################################
from __future__ import division
__author__ = 'Diego'
import PyQt4.QtCore as QtCore
from PyQt4.QtCore import QAbstractItemModel
from braviz.readAndFilter.link_with_rdf import cached_get_free_surfer_dict
from braviz.interaction.structural_hierarchy import get_structural_hierarchy_with_names
from braviz.readAndFilter import config_file, tabular_data
import logging
[docs]class StructureTreeNode(object):
"""
Node for the freesurfer structures tree
Args:
parent (braviz.interaction.qt_structures_model.StructureTreeNode) : Reference to the parent node
name (str) : Name of this node
son_number (int) : How many siblings are before this node
"""
def __init__(self, parent=None, name="", son_number=0):
self.parent_id = id(parent)
self.parent = parent
self.children = []
self.tooltip = name
self.name = name
self.checked = QtCore.Qt.Unchecked
self.leaf_name = None
self.son_number = son_number
[docs] def is_leaf(self):
"""
Returns ``True`` if this node is a leaf
"""
return len(self.children) == 0
[docs] def add_child(self, name):
"""
Add a child to this node
Args:
name (str) : Name for the child node
"""
child = StructureTreeNode(self, name, len(self.children))
self.children.append(child)
return child
[docs] def check_and_update_tree(self, check=True):
"""
Add a checkmark to this node
If it has childs, they are also checked (recursively)
Predecessor boxes are also updated
"""
self.__check_and_propagate_down(check)
self.__propagate_up()
def __propagate_up(self):
if self.parent is not None:
self.parent.__update_from_children()
def __check_and_propagate_down(self, check=True):
self.checked = QtCore.Qt.Checked if check is True else QtCore.Qt.Unchecked
for k in self.children:
k.__check_and_propagate_down(check)
def __update_from_children(self):
# self.name += "*"
previus_state = self.checked
if len(self.children) == 0:
return
found_checked = False
found_unchecked = False
for k in self.children:
state = k.checked
if state == QtCore.Qt.Checked:
found_checked = True
if state == QtCore.Qt.Unchecked:
found_unchecked = True
if (state == QtCore.Qt.PartiallyChecked) or (found_checked and found_unchecked):
self.checked = QtCore.Qt.PartiallyChecked
if self.checked != previus_state:
self.__propagate_up()
return
if not found_unchecked:
self.checked = QtCore.Qt.Checked
else:
self.checked = QtCore.Qt.Unchecked
if self.checked != previus_state:
self.__propagate_up()
[docs] def delete_children(self):
"""
Delete all children from this node
"""
for i in self.children:
i.parent = None
i.delete_children()
del self.children
[docs] def get_leaf_names(self):
"""
Get names of leafs that are under the current node
"""
if self.is_leaf():
return {self.leaf_name}
else:
return set.union(*(c.get_leaf_names() for c in self.children))
[docs]class StructureTreeModel(QAbstractItemModel):
"""
A tree of freesurfer segmented structures
Args:
reader (braviz.readAndFilter.base_reader.BaseReader) : A reader from which to get available models
subj : Id of a subject to use as reference for getting available models
dominant (bool) : If ``False`` the tree will have left and right hemispheres, otherwise it will
have dominant and non-dominant hemispheres
"""
selection_changed = QtCore.pyqtSignal()
def __init__(self, reader, subj=None, dominant=False):
super(StructureTreeModel, self).__init__()
self.reader = reader
self.pretty_names = cached_get_free_surfer_dict(reader)
self.hierarchy = None
self.__id_index = {}
self.__root = None
self.leaf_ids = set()
self.leaf_inverse_ids = dict()
self.__dominant = None
self.optimistic_reload_hierarchy(dominant)
if subj is None:
subj = reader.get("ids", None)[0]
self.subj = subj
[docs] def optimistic_reload_hierarchy(self, dominant):
"""
Rebuilds the tree from the models of the first subject with models
Args:
dominant (bool) : If ``False`` the tree will have left and right hemispheres, otherwise it will
have dominant and non-dominant hemispheres
"""
possibles = self.reader.get("ids", None)
favorite = config_file.get_apps_config().get_default_subject()
possibles.insert(0, favorite)
for subj in possibles:
try:
self.reload_hierarchy(subj, dominant)
except Exception as e:
log = logging.getLogger(__name__)
log.exception(e)
self.hierarchy = tuple()
if len(self.hierarchy) > 0:
return
raise Exception("Couldnt build structures index")
[docs] def reload_hierarchy(self, subj=None, dominant=False):
"""
Reload tree based on a specific subject
Args:
subj : Subject to load available models from
dominant (bool) : If ``False`` the tree will have left and right hemispheres, otherwise it will
have dominant and non-dominant hemispheres
"""
if dominant == self.__dominant:
return
if subj is None:
subj = config_file.get_apps_config().get_default_subject()
log = logging.getLogger(__name__)
log.debug("reloading hierarchy")
self.modelAboutToBeReset.emit()
if self.__root is None:
# We are building the tree from scratch
if dominant is True:
self.hierarchy = get_structural_hierarchy_with_names(
self.reader, subj, True, False, False)
else:
self.hierarchy = get_structural_hierarchy_with_names(
self.reader, subj, False, True, False)
self.__root = StructureTreeNode(None, "root")
self.leaf_ids = set()
self.__id_index = {id(self.__root): self.__root}
self.__load_sub_tree(self.__root, self.hierarchy)
else:
# We are transforming an existing tree
laterality = tabular_data.get_laterality(subj)
if dominant:
# changing from left, right to dominant, non-dominant
if laterality == "l":
mapping = {"r": "d",
"l": "n",
"Right": "Dominant",
"Left": "Nondominant",
}
else:
mapping = {"l": "d",
"r": "n",
"Right": "Nondominant",
"Left": "Dominant"}
else:
# changing from dominant, non-dominant to left, right
if laterality == "l":
mapping = {"d": "r",
"n": "l",
"Dominant": "Right",
"Nondominant": "Left"}
else:
mapping = {"d": "l",
"n": "r",
"Dominant": "Left",
"Nondominant": "Right"}
self.__switch_sub_tree(self.__root, mapping)
self.modelReset.emit()
def __load_sub_tree(self, sub_root, hierarchy_dict):
for k, v in sorted(hierarchy_dict.items(), key=lambda x: x[0]):
new_node = sub_root.add_child(k)
new_node_id = id(new_node)
self.__id_index[new_node_id] = new_node
if isinstance(v, dict):
self.__load_sub_tree(new_node, v)
else:
# is a leaf
new_node.leaf_name = v
new_node.tooltip = self.pretty_names.get(v, v)
self.leaf_ids.add(new_node_id)
self.leaf_inverse_ids.setdefault(v, set()).add(new_node_id)
def __switch_sub_tree(self, sub_root, mapping):
if self.__transform_node(sub_root, mapping):
# Recursion can be stopped by returning False
for c in sub_root.children:
self.__switch_sub_tree(c, mapping)
def __transform_node(self, node, mapping):
leaf_name = node.leaf_name
if leaf_name is None:
# Not a leaf
name = node.name
new_name = None
if name.endswith("Hemisphere"):
words = name.split()
words[0] = mapping[words[0]]
new_name = " ".join(words)
elif name == "Base":
return False
elif name == "Corpus Callosum":
return False
if new_name is not None:
node.name = new_name
node.tooltip =new_name
else:
# is a leaf
if leaf_name.startswith("ctx"):
try:
del self.leaf_inverse_ids[leaf_name]
except KeyError:
pass
hemi = leaf_name[4]
new_hemi = mapping[hemi]
leaf_name = leaf_name[:4] + new_hemi + leaf_name[5:]
node.leaf_name = leaf_name
node.tooltip = self.pretty_names.get(leaf_name,leaf_name)
elif leaf_name.startswith("wm"):
try:
del self.leaf_inverse_ids[leaf_name]
except KeyError:
pass
hemi = leaf_name[3]
new_hemi = mapping[hemi]
leaf_name = leaf_name[:3] + new_hemi + leaf_name[4:]
node.leaf_name = leaf_name
node.tooltip = self.pretty_names.get(leaf_name,leaf_name)
self.leaf_inverse_ids.setdefault(leaf_name,set()).add(id(node))
return False
return True
def parent(self, QModelIndex=None):
if QModelIndex.isValid():
nid = QModelIndex.internalId()
node = self.__id_index[nid]
parent = node.parent
if parent is None:
# root node
return QtCore.QModelIndex()
# print "parent of %s is %s"%(node.name,parent.name)
index = self.__get_node_index(parent)
return index
assert True
def rowCount(self, QModelIndex_parent=None, *args, **kwargs):
if QModelIndex_parent.isValid():
nid = QModelIndex_parent.internalId()
parent = self.__id_index[nid]
return len(parent.children)
# Start at second level
return len(self.__root.children)
def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
return 1
def data(self, QModelIndex, int_role=None):
nid = QModelIndex.internalId()
row = QModelIndex.row()
node = self.__id_index[nid]
assert node.son_number == row
# print "data", node.name
if int_role == QtCore.Qt.DisplayRole:
return node.name
elif int_role == QtCore.Qt.ToolTipRole:
return node.tooltip
elif int_role == QtCore.Qt.CheckStateRole:
# print node.name, node.checked
return node.checked
return None
def index(self, p_int, p_int_1, QModelIndex_parent=None, *args, **kwargs):
if QModelIndex_parent.isValid():
nid = QModelIndex_parent.internalId()
parent = self.__id_index[nid]
if p_int_1 == 0:
if 0 <= p_int < len(parent.children):
child = parent.children[p_int]
# print "index", child.name
index = self.__get_node_index(child)
return index
else:
# asking for root nodes
meta_root = self.__root
if (0 <= p_int < len(meta_root.children)) and (p_int_1 == 0):
root = meta_root.children[p_int]
index = self.__get_node_index(root)
return index
def hasChildren(self, QModelIndex_parent=None, *args, **kwargs):
if QModelIndex_parent.isValid():
nid = QModelIndex_parent.internalId()
node = self.__id_index[nid]
return not node.is_leaf()
return True
def setData(self, QModelIndex, QVariant, int_role=None):
if int_role == QtCore.Qt.CheckStateRole:
nid = QModelIndex.internalId()
node = self.__id_index[nid]
assert node.son_number == QModelIndex.row()
check = QVariant == QtCore.Qt.Checked
leaf_names = node.get_leaf_names()
leaf_ids = set.union(
*(self.leaf_inverse_ids.get(n, set()) for n in leaf_names))
for n_i in leaf_ids:
node = self.__id_index[n_i]
node.check_and_update_tree(check)
self.__notify_parents(node)
self.dataChanged.emit(QModelIndex, QModelIndex)
# self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
# print "signaling"
self.emit(
QtCore.SIGNAL("DataChanged(QModelIndex,QModelIndex)"), QModelIndex, QModelIndex)
# parents
self.__notify_parents(node)
self.__notify_children(node)
self.selection_changed.emit()
return True
return False
def __notify_parents(self, node):
parent = node.parent
if parent is None:
return
index = self.__get_node_index(parent)
# print "notifying", parent.name
self.dataChanged.emit(index, index)
self.emit(
QtCore.SIGNAL("DataChanged(QModelIndex,QModelIndex)"), index, index)
self.__notify_parents(parent)
def __notify_children(self, node):
if node.is_leaf():
return
first_children = node.children[0]
last_children = node.children[-1]
first_index = self.__get_node_index(first_children)
last_index = self.__get_node_index(last_children)
self.emit(
QtCore.SIGNAL("DataChanged(QModelIndex,QModelIndex)"), first_index, last_index)
def flags(self, QModelIndex):
if QModelIndex.isValid():
flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable
return flags
return QtCore.Qt.NoItemFlags
def __get_node_index(self, node):
index = self.createIndex(node.son_number, 0, id(node))
assert index.isValid()
return index
[docs] def get_selected_structures(self):
"""
Get a list of currently checked structures
"""
selected_leaf_names = {self.__id_index[leaf].leaf_name for leaf in self.leaf_ids if
self.__id_index[leaf].checked == QtCore.Qt.Checked}
# print "selected leafs names",selected_leaf_names
return sorted(selected_leaf_names)
[docs] def set_selected_structures(self, selected_list):
"""
Selects the structures given in a list
Args:
selected_list (list) : New set of selected structures' names
"""
for leaf in self.leaf_ids:
node = self.__id_index[leaf]
if node.leaf_name in selected_list:
node.check_and_update_tree(True)
else:
node.check_and_update_tree(False)
self.selection_changed.emit()
self.modelReset.emit()