Skip to content

Commit

Permalink
Merge pull request #83 from rchristie/annotation
Browse files Browse the repository at this point in the history
Add user-defined annotation groups
  • Loading branch information
rchristie authored Aug 12, 2020
2 parents 006f037 + e70aa68 commit f1c6e8b
Show file tree
Hide file tree
Showing 4 changed files with 455 additions and 13 deletions.
90 changes: 83 additions & 7 deletions src/scaffoldmaker/annotation/annotationgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
Describes subdomains of a scaffold with attached names and terms.
"""

from opencmiss.zinc.field import FieldGroup
from opencmiss.zinc.field import Field, FieldGroup
from opencmiss.utils.zinc.general import ChangeManager
from scaffoldmaker.utils.zinc_utils import group_get_highest_dimension, \
identifier_ranges_from_string, identifier_ranges_to_string, \
mesh_group_add_identifier_ranges, mesh_group_to_identifier_ranges, \
nodeset_group_add_identifier_ranges, nodeset_group_to_identifier_ranges

class AnnotationGroup(object):
'''
Expand All @@ -17,23 +22,88 @@ def __init__(self, region, term):
'''
self._name = term[0]
self._id = term[1]
fm = region.getFieldmodule()
field = fm.findFieldByName(self._name)
fieldmodule = region.getFieldmodule()
field = fieldmodule.findFieldByName(self._name)
if field.isValid():
self._group = field.castGroup()
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(self._name)
self._group.setManaged(True)
with ChangeManager(fieldmodule):
self._group = fieldmodule.createFieldGroup()
self._group.setName(self._name)
self._group.setManaged(True)

def toDict(self):
'''
Encodes object into a dictionary for JSON serialisation.
Used only for user-defined annotation groups.
:return: Dictionary containing object encoding.
'''
# get identifier ranges from highest dimension domain in group
dimension = self.getDimension()
fieldmodule = self._group.getFieldmodule()
if dimension > 0:
mesh = fieldmodule.findMeshByDimension(dimension)
meshGroup = self._group.getFieldElementGroup(mesh).getMeshGroup()
identifierRanges = mesh_group_to_identifier_ranges(meshGroup)
else:
nodes = fieldmodule.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
nodesetGroup = self._group.getFieldNodeGroup(nodes).getNodesetGroup()
if nodesetGroup.isValid():
identifierRanges = nodeset_group_to_identifier_ranges(nodesetGroup)
else:
identifierRanges = []
dct = {
'_AnnotationGroup' : True,
'name' : self._name,
'ontId' : self._id,
'dimension' : dimension,
'identifierRanges' : identifier_ranges_to_string(identifierRanges)
}
return dct

@classmethod
def fromDict(cls, dct, region):
'''
Instantiate from dict. See toDict()
:param region: Zinc region.
:return: AnnotationGroup
'''
assert dct['_AnnotationGroup']
name = dct['name']
ontId = dct['ontId']
dimension = dct['dimension']
identifierRangesString = dct['identifierRanges']
identifierRanges = identifier_ranges_from_string(identifierRangesString)
fieldmodule = region.getFieldmodule()
with ChangeManager(fieldmodule):
annotationGroup = cls(region, (name, ontId))
if dimension > 0:
meshGroup = annotationGroup.getMeshGroup(fieldmodule.findMeshByDimension(dimension))
mesh_group_add_identifier_ranges(meshGroup, identifierRanges)
else:
nodesetGroup = annotationGroup.getNodesetGroup(fieldmodule.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES))
nodeset_group_add_identifier_ranges(nodesetGroup, identifierRanges)
return annotationGroup

def getName(self):
return self._name

def setName(self, name):
'''
Client must ensure name is unique for all annotation groups.
'''
self._name = name

def getId(self):
return self._id

def setId(self, id):
'''
Client must ensure id is unique for all annotation groups.
'''
self._id = id

def getFMANumber(self):
"""
:return: Integer FMA number or None.
Expand All @@ -48,6 +118,12 @@ def getTerm(self):
def getGroup(self):
return self._group

def getDimension(self):
'''
Get dimension 3, 2 or 1 of mesh which group is annotating, 0 if nodes or -1 if empty.
'''
return group_get_highest_dimension(self._group)

def getFieldElementGroup(self, mesh):
'''
:param mesh: The Zinc mesh to manage a sub group of.
Expand Down
85 changes: 80 additions & 5 deletions src/scaffoldmaker/scaffoldpackage.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from opencmiss.utils.zinc.field import createFieldEulerAnglesRotationMatrix
from opencmiss.utils.zinc.general import ChangeManager
from opencmiss.utils.maths.vectorops import euler_to_rotation_matrix
from scaffoldmaker.annotation.annotationgroup import AnnotationGroup, findAnnotationGroupByName
from scaffoldmaker.meshtypes.scaffold_base import Scaffold_base

class ScaffoldPackage:
Expand Down Expand Up @@ -43,6 +44,15 @@ def __init__(self, scaffoldType, dct={}, defaultParameterSetName='Default'):
if not self._translation:
self._translation = [ 0.0, 0.0, 0.0 ]
self._meshEdits = copy.deepcopy(dct.get('meshEdits'))
self._autoAnnotationGroups = []
# read user AnnotationGroups dict:
userAnnotationGroupsDict = dct.get('userAnnotationGroups')
# serialised form of user annotation groups, read from serialisation before generate(), updated before writing
self._userAnnotationGroupsDict = copy.deepcopy(userAnnotationGroupsDict) if userAnnotationGroupsDict else []
# can only have the actual user annotation groups once generate() is called
self._userAnnotationGroups = []
# region is set in generate(); can only instantiate user AnnotationGroups then
self._region = None

def deepcopy(self, other):
'''
Expand Down Expand Up @@ -78,6 +88,9 @@ def toDict(self):
}
if self._meshEdits:
dct['meshEdits'] = self._meshEdits
if self._userAnnotationGroups:
self._userAnnotationGroupsDict = [ annotationGroup.toDict() for annotationGroup in self._userAnnotationGroups ]
dct['userAnnotationGroups'] = self._userAnnotationGroupsDict
return dct

def getMeshEdits(self):
Expand Down Expand Up @@ -155,11 +168,13 @@ def getTransformationMatrix(self):
[ 0.0, 0.0, 0.0, 1.0 ] ]
return None

def applyTransformation(self, region):
def applyTransformation(self):
'''
If rotation, scale or transformation are set, transform node coordinates.
Only call after generate().
'''
fieldmodule = region.getFieldmodule()
assert self._region
fieldmodule = self._region.getFieldmodule()
coordinates = fieldmodule.findFieldByName('coordinates').castFiniteElement()
if not coordinates.isValid():
print('Warning: ScaffoldPackage.applyTransformation: Missing coordinates field')
Expand Down Expand Up @@ -191,17 +206,77 @@ def applyTransformation(self, region):

def generate(self, region, applyTransformation=True):
'''
Generate the finite element scaffold and define annotation groups.
:param applyTransformation: If True (default) apply scale, rotation and translation to
node coordinates. Specify False if client will transform, e.g. with graphics transformations.
'''
#print('\nScaffoldPackage.generate: ', self.toDict())
annotationGroups = self._scaffoldType.generateMesh(region, self._scaffoldSettings)
self._region = region
autoAnnotationGroups = self._scaffoldType.generateMesh(region, self._scaffoldSettings)
self._autoAnnotationGroups = autoAnnotationGroups if autoAnnotationGroups else []
if self._meshEdits:
# apply mesh edits, a Zinc-readable model file containing node edits
# Note: these are untransformed coordinates
sir = region.createStreaminformationRegion()
srm = sir.createStreamresourceMemoryBuffer(self._meshEdits)
region.read(sir)
# define user AnnotationGroups from serialised Dict
self._userAnnotationGroups = [ AnnotationGroup.fromDict(dct, self._region) for dct in self._userAnnotationGroupsDict ]
if applyTransformation:
self.applyTransformation(region)
return annotationGroups
self.applyTransformation()

def getAnnotationGroups(self):
'''
Empty until after call to generate().
:return: Alphabetically sorted list of annotation groups.
'''
return sorted(self._autoAnnotationGroups + self._userAnnotationGroups, key=AnnotationGroup.getName)

def findAnnotationGroupByName(self, name):
'''
Invalid until after call to generate().
:return: Annotation group with the given name or None.
'''
return findAnnotationGroupByName(self._autoAnnotationGroups + self._userAnnotationGroups, name)

def createUserAnnotationGroup(self, term=None):
'''
Create a new, empty user annotation group.
Only call after generate().
:param term: Identifier for anatomical term, currently a tuple of name, id.
e.g. ('heart', 'FMA:7088'). Or None to generate a unique name. Name must be
unique if supplied; id should be unique but may be None.
:return: New AnnotationGroup.
'''
assert self._region
if term:
assert not self.findAnnotationGroupByName(term[0])
useTerm = term
else:
number = 1
while True:
name = "group" + str(number)
if not self.findAnnotationGroupByName(name):
break
number += 1
useTerm = (name, None)
annotationGroup = AnnotationGroup(self._region, useTerm)
self._userAnnotationGroups.append(annotationGroup)
return annotationGroup

def deleteAnnotationGroup(self, annotationGroup):
'''
Delete the annotation group. Must be a user annotation group.
:return: True on success, otherwise False
'''
if annotationGroup and self.isUserAnnotationGroup(annotationGroup):
self._userAnnotationGroups.remove(annotationGroup)
return True
return False

def isUserAnnotationGroup(self, annotationGroup):
'''
Invalid until after call to generate().
:return: True if annotationGroup is user-created and editable.
'''
return annotationGroup in self._userAnnotationGroups
Loading

0 comments on commit f1c6e8b

Please sign in to comment.