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

Improve annotations, refinement and VTK export #70

Merged
merged 15 commits into from
Apr 24, 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
103 changes: 88 additions & 15 deletions src/scaffoldmaker/annotation/annotationgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,41 @@ class AnnotationGroup(object):
Describes subdomains of a scaffold with attached names and terms.
'''

def __init__(self, region, name, FMANumber, lyphID):
def __init__(self, region, term):
'''
:param region: The Zinc region the AnnotationGroup is to be made for.
:param name: The name of an annotation group e.g. common medical term.
:param FMANumber: The FMA Number of the group.
:param lyphID: The Apinatomy Lyph ID for the group.
:param term: Identifier for anatomical term, currently a tuple of name, id.
e.g. ("heart", "FMA:7088")
'''
self._name = name
self._FMANumber = FMANumber
self._lyphID = lyphID
self._name = term[0]
self._id = term[1]
fm = region.getFieldmodule()
field = fm.findFieldByName(name)
field = fm.findFieldByName(self._name)
if field.isValid():
self._group = field.castGroup()
assert self._group.isValid(), 'AnnotationGroup found existing non-group field called ' + name
assert self._group.isValid(), 'AnnotationGroup found existing non-group field called ' + self._name
else:
# assume client is calling between fm.begin/endChange()
self._group = fm.createFieldGroup()
self._group.setName(name)
self._group.setName(self._name)
self._group.setManaged(True)

def getName(self):
return self._name

def getId(self):
return self._id

def getFMANumber(self):
return self._FMANumber
"""
:return: Integer FMA number or None.
"""
if self._id and (self.id[:4] == "FMA:"):
return int(self._id[4:])
return None

def getLyphID(self):
return self._lyphID
def getTerm(self):
return ( self._name, self._id )

def getGroup(self):
return self._group
Expand Down Expand Up @@ -69,13 +75,29 @@ def getMeshGroup(self, mesh):
'''
return self.getFieldElementGroup(mesh).getMeshGroup()

def hasMeshGroup(self, mesh):
'''
:param mesh: The Zinc mesh to query a sub group of.
:return: True if MeshGroup for mesh exists and is not empty, otherwise False.
'''
elementGroup = self._group.getFieldElementGroup(mesh)
return elementGroup.isValid() and (elementGroup.getMeshGroup().getSize() > 0)

def getNodesetGroup(self, nodeset):
'''
:param nodeset: The Zinc nodeset to manage a sub group of.
:return: The Zinc nodesetGroup for adding nodes from nodeset in this AnnotationGroup.
'''
return self.getFieldNodeGroup(nodeset).getNodesetGroup()

def hasNodesetGroup(self, nodeset):
'''
:param nodeset: The Zinc nodeset to query a sub group of.
:return: True if NodesetGroup for nodeset exists and is not empty, otherwise False.
'''
nodeGroup = self._group.getFieldNodeGroup(nodeset)
return nodeGroup.isValid() and (nodeGroup.getNodesetGroup().getSize() > 0)

def addSubelements(self):
'''
Call after group is complete and faces have been defined to add faces and
Expand All @@ -92,13 +114,64 @@ def addSubelements(self):
meshGroup.addElementsConditional(elementGroup) # use FieldElementGroup as conditional field


def findAnnotationGroupByName(annotationGroups, name):
def findAnnotationGroupByName(annotationGroups: list, name: str):
'''
Find existing annotation group for name.
:param annotationGroups: list(AnnotationGroup)
:param name: Name of group.
:return: AnnotationGroup or None.
:return: AnnotationGroup or None if not found.
'''
for annotationGroup in annotationGroups:
if annotationGroup._name == name:
return annotationGroup
return None


def findOrCreateAnnotationGroupForTerm(annotationGroups: list, region, term) -> AnnotationGroup:
'''
Find existing annotation group for term, or create it for region if not found.
If annotation group created here, append it to annotationGroups.
:param annotationGroups: list(AnnotationGroup)
:param region: Zinc region to create group for.
:param term: Identifier for anatomical term, currently a tuple of name, id.
:return: AnnotationGroup.
'''
name = term[0]
annotationGroup = findAnnotationGroupByName(annotationGroups, name)
if annotationGroup:
assert annotationGroup._id == term[1], "Annotation group '" + name + "' id '" + term[1] + "' does not match existing id '" + annotationGroup._id + "'"
else:
annotationGroup = AnnotationGroup(region, term)
annotationGroups.append(annotationGroup)
return annotationGroup


def getAnnotationGroupForTerm(annotationGroups: list, term) -> AnnotationGroup:
'''
Get existing annotation group for term. Raise exception if not found.
:param annotationGroups: list(AnnotationGroup)
:param term: Identifier for anatomical term, currently a tuple of name, id.
:return: AnnotationGroup.
'''
name = term[0]
annotationGroup = findAnnotationGroupByName(annotationGroups, name)
if annotationGroup:
assert annotationGroup._id == term[1], "Annotation group '" + name + "' id '" + term[1] + "' does not match existing id '" + annotationGroup._id + "'"
return annotationGroup
raise NameError("Annotation group '" + name + "' not found.")


def mergeAnnotationGroups(*annotationGroupsIn):
'''
Merge the supplied sequence of list(annotationGroups) to a single list,
without duplicates.
:param annotationGroupsIn: Variable number of list(AnnotationGroup) to merge.
Groups must be for the same region.
:return: Merged list(AnnotationGroup)
'''
annotationGroups = []
for agroups in annotationGroupsIn:
for agroup in agroups:
if not findAnnotationGroupByName(annotationGroups, agroup._name):
annotationGroups.append(agroup)
return annotationGroups
21 changes: 21 additions & 0 deletions src/scaffoldmaker/annotation/bladder_terms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
Common resource for bladder annotation terms.
"""

# convention: preferred name, preferred id, followed by any other ids and alternative names
bladder_terms = [
( "urinary bladder", "FMA:15900", "UBERON:0001255" ),
( "neck of urinary bladder", "FMA:15912", "UBERON:0001258"),
( "body of urinary bladder", None) # needs to be replaced with an actual term e.g. urinary bladder (which includes neck)
]

def get_bladder_term(name : str):
"""
Find term by matching name to any identifier held for a term.
Raise exception if name not found.
:return ( preferred name, preferred id )
"""
for term in bladder_terms:
if name in term:
return ( term[0], term[1] )
raise NameError("Bladder annotation term '" + name + "' not found.")
25 changes: 25 additions & 0 deletions src/scaffoldmaker/annotation/colon_terms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
Common resource for colon annotation terms.
"""

# convention: preferred name, preferred id, followed by any other ids and alternative names
colon_terms = [
( "colon", "UBERON:0001155", "FMA:14543" ),
( "mesenteric zone", None ),
( "non-mesenteric zone", None ),
( "taenia coli", "UBERON:0012419", "FMA:15041" ),
( "tenia libera", None ),
( "tenia mesocolica", None ),
( "tenia omentalis", None )
]

def get_colon_term(name : str):
"""
Find term by matching name to any identifier held for a term.
Raise exception if name not found.
:return ( preferred name, preferred id )
"""
for term in colon_terms:
if name in term:
return ( term[0], term[1] )
raise NameError("Colon annotation term '" + name + "' not found.")
67 changes: 67 additions & 0 deletions src/scaffoldmaker/annotation/heart_terms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
Common resource for heart annotation terms.
"""

# convention: preferred name, preferred id, followed by any other ids and alternative names
heart_terms = [
rchristie marked this conversation as resolved.
Show resolved Hide resolved
( "heart", "FMA:7088", "UBERON:0000948" ),
# ventricles
( "left ventricle myocardium", "FMA:9558" ),
( "right ventricle myocardium", "FMA:9535" ),
( "interventricular septum", "FMA:7133" ),
( "endocardium of left ventricle", "FMA:9559" ),
( "endocardium of right ventricle", "FMA:9536" ),
( "epicardium", "FMA:9461", "UBERON:0002348"),
#( "epicardium of ventricle", "FMA:12150", "UBERON:0001082" ),
# ventricles with base
( "conus arteriosus", "UBERON:0003983" ),
( "left fibrous ring", "FMA:77124" ),
( "right fibrous ring", "FMA:77125" ),
# atria
( "left atrium myocardium", "FMA:7285" ),
( "right atrium myocardium", "FMA:7282" ),
( "endocardium of left atrium", "FMA:7286", "UBERON:0034903" ),
( "endocardium of right atrium", "FMA:7281", "UBERON:0009129" ),
( "interatrial septum", "FMA:7108" ),
( "fossa ovalis", "FMA:9246" ),
( "left auricle", "FMA:7219", "UBERON:0006630" ), # uncertain if just the tissue like myocardium
( "right auricle", "FMA:7218", "UBERON:0006631" ), # uncertain if just the tissue like myocardium
( "endocardium of left auricle", "FMA:13236", "UBERON:0011006" ),
( "endocardium of right auricle", "FMA:13235", "UBERON:0011007" ),
( "epicardium of left auricle", "FMA:13233" ),
( "epicardium of right auricle", "FMA:13232" ),
( "pulmonary vein", "FMA:66643", "UBERON:0002016" ),
( "left pulmonary vein", "UBERON:0009030" ),
( "left inferior pulmonary vein", "FMA:49913" ),
( "left superior pulmonary vein", "FMA:49916" ),
( "middle pulmonary vein", "ILX:0739222" ), # in mouse, rat, rabbit
( "right pulmonary vein", "UBERON:0009032" ),
( "right inferior pulmonary vein", "FMA:49911" ),
( "right superior pulmonary vein", "FMA:49914" ),
( "inferior vena cava", "FMA:10951", "UBERON:0001072", "posterior vena cava" ),
( "inferior vena cava inlet", "ILX:0738358" ),
( "superior vena cava", "FMA:4720", "UBERON:0001585", "anterior vena cava" ),
( "superior vena cava inlet", "ILX:0738367" ),
# arterial root
( "root of aorta", "FMA:3740" ),
( "posterior cusp of aortic valve", "FMA:7253" ),
( "right cusp of aortic valve", "FMA:7252" ),
( "left cusp of aortic valve", "FMA:7251" ),
( "root of pulmonary trunk", "FMA:8612" ),
( "right cusp of pulmonary valve", "FMA:7250" ),
( "anterior cusp of pulmonary valve", "FMA:7249" ),
( "left cusp of pulmonary valve", "FMA:7247" ),
# future: fiducial markers
( "apex of heart", "FMA:7164", "UBERON:0002098")
]

def get_heart_term(name : str):
"""
Find term by matching name to any identifier held for a term.
Raise exception if name not found.
:return ( preferred name, preferred id )
"""
for term in heart_terms:
if name in term:
return ( term[0], term[1] )
raise NameError("Heart annotation term '" + name + "' not found.")
5 changes: 3 additions & 2 deletions src/scaffoldmaker/meshtypes/meshtype_3d_bladder1.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from opencmiss.zinc.field import Field
from opencmiss.zinc.node import Node
from scaffoldmaker.annotation.annotationgroup import AnnotationGroup
from scaffoldmaker.annotation.bladder_terms import get_bladder_term
from scaffoldmaker.meshtypes.meshtype_3d_ostium1 import MeshType_3d_ostium1, generateOstiumMesh
from scaffoldmaker.meshtypes.scaffold_base import Scaffold_base
from scaffoldmaker.scaffoldpackage import ScaffoldPackage
Expand Down Expand Up @@ -264,8 +265,8 @@ def generateBaseMesh(region, options):

cache = fm.createFieldcache()

neckGroup = AnnotationGroup(region, 'neck of bladder', FMANumber='unknown', lyphID='unknown')
bodyGroup = AnnotationGroup(region, 'body of bladder', FMANumber='unknown', lyphID='unknown')
neckGroup = AnnotationGroup(region, get_bladder_term("neck of urinary bladder"))
bodyGroup = AnnotationGroup(region, get_bladder_term("body of urinary bladder"))
annotationGroups = [neckGroup, bodyGroup]

neckMeshGroup = neckGroup.getMeshGroup(mesh)
Expand Down
15 changes: 8 additions & 7 deletions src/scaffoldmaker/meshtypes/meshtype_3d_colonsegment1.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from opencmiss.zinc.field import Field
from opencmiss.zinc.node import Node
from scaffoldmaker.annotation.annotationgroup import AnnotationGroup
from scaffoldmaker.annotation.colon_terms import get_colon_term
from scaffoldmaker.meshtypes.scaffold_base import Scaffold_base
from scaffoldmaker.utils.eftfactory_bicubichermitelinear import eftfactory_bicubichermitelinear
from scaffoldmaker.utils.eftfactory_tricubichermite import eftfactory_tricubichermite
Expand Down Expand Up @@ -537,8 +538,8 @@ def getColonSegmentInnerPoints(region, elementsCountAroundTC,
d2Final.append(d2Raw[n1][n2])

# Create annotation groups for mouse colon
mzGroup = AnnotationGroup(region, 'mesenteric zone', FMANumber = 'FMANumber unknown', lyphID = 'Lyph ID unknown')
nonmzGroup = AnnotationGroup(region, 'non-mesenteric zone', FMANumber = 'FMANumber unknown', lyphID = 'Lyph ID unknown')
mzGroup = AnnotationGroup(region, get_colon_term("mesenteric zone"))
nonmzGroup = AnnotationGroup(region, get_colon_term("non-mesenteric zone"))
annotationGroups = [mzGroup, nonmzGroup]
annotationArray = (['mesenteric zone']*int(elementsCountAroundTC*0.5) +
['non-mesenteric zone']*elementsCountAroundHaustrum +
Expand Down Expand Up @@ -1288,14 +1289,14 @@ def getTeniaColi(region, xList, d1List, d2List, d3List, curvatureList,

# Update annotation groups
if tcCount == 3:
tlGroup = AnnotationGroup(region, 'tenia libera', FMANumber = 'FMANumber unknown', lyphID = 'Lyph ID unknown')
tmGroup = AnnotationGroup(region, 'tenia mesocolica', FMANumber = 'FMANumber unknown', lyphID = 'Lyph ID unknown')
toGroup = AnnotationGroup(region, 'tenia omentalis', FMANumber = 'FMANumber unknown', lyphID = 'Lyph ID unknown')
tlGroup = AnnotationGroup(region, get_colon_term("tenia libera"))
tmGroup = AnnotationGroup(region, get_colon_term("tenia mesocolica"))
toGroup = AnnotationGroup(region, get_colon_term("tenia omentalis"))
annotationGroupsTC = [tlGroup, tmGroup, toGroup]
annotationArrayTC = (['tenia omentalis']*int(elementsCountAroundTC*0.5) +
annotationArrayTC = (['tenia omentalis']*(elementsCountAroundTC//2) +
['tenia libera']*elementsCountAroundTC +
['tenia mesocolica']*elementsCountAroundTC +
['tenia omentalis']*int(elementsCountAroundTC*0.5))
['tenia omentalis']*(elementsCountAroundTC//2))
annotationGroups += annotationGroupsTC
annotationArray += annotationArrayTC

Expand Down
Loading