Skip to content

Commit

Permalink
Merge pull request #70 from rchristie/fiducial
Browse files Browse the repository at this point in the history
Improve annotations, refinement and VTK export
  • Loading branch information
rchristie authored Apr 24, 2020
2 parents 3550ddb + 7bc9f34 commit 42d56c2
Show file tree
Hide file tree
Showing 21 changed files with 835 additions and 422 deletions.
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 = [
( "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

0 comments on commit 42d56c2

Please sign in to comment.