Skip to content

Commit

Permalink
Merge pull request #265 from rchristie/body-pose
Browse files Browse the repository at this point in the history
New annotated, poseable body scaffold
  • Loading branch information
rchristie authored Oct 22, 2024
2 parents 75f71d0 + 34d22fd commit 33bf002
Show file tree
Hide file tree
Showing 19 changed files with 2,837 additions and 1,072 deletions.
50 changes: 35 additions & 15 deletions src/scaffoldmaker/annotation/body_terms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,41 @@

# convention: preferred name, preferred id, followed by any other ids and alternative names
body_terms = [
( "abdomen", "UBERON:0000916", "ILX:0725977" ),
( "thorax", "ILX:0742178" ),
( "neck", "UBERON:0000974", "ILX:0733967" ),
( "head", "UBERON:0000033", "ILX:0104909" ),
( "neck core", "" ),
( "head core", "" ),
( "skin epidermis", "UBERON:0001003", "ILX:0728574" ),
( "diaphragm", "UBERON:0001103", "ILX:0103194" ),
( "spinal cord", "UBERON:0002240", "ILX:0110909" ),
( "body", "UBERON:0000468", "ILX:0101370" ),
( "core", "" ),
( "non core", "" ),
( "core boundary", "" ),
( "arm", "UBERON:0001460"),
( "leg", "UBERON:0000978")
("abdomen", "UBERON:0000916", "ILX:0725977"),
("abdominal cavity", "UBERON:0003684"),
("abdominal cavity boundary", ""),
("abdominopelvic cavity", "UBERON:0035819"),
("arm", "UBERON:0001460"),
("left arm", "FMA:24896"),
("left arm skin epidermis", ""),
("right arm", "FMA:24895"),
("right arm skin epidermis", ""),
("body", "UBERON:0000468", "ILX:0101370"),
("core", ""),
("core boundary", ""),
("dorsal", ""),
("head", "UBERON:0000033", "ILX:0104909"),
("head core", ""),
("diaphragm", "UBERON:0001103", "ILX:0103194"),
("hand", "FMA:9712"),
("left", ""),
("leg", "UBERON:0000978"),
("left leg", "FMA:24981"),
("left leg skin epidermis", ""),
("right leg", "FMA:24980"),
("right leg skin epidermis", ""),
("foot", "FMA:9664"),
("neck", "UBERON:0000974", "ILX:0733967"),
("neck core", ""),
("non core", ""),
("right", ""),
("shell", ""),
("skin epidermis", "UBERON:0001003", "ILX:0728574"),
("spinal cord", "UBERON:0002240", "ILX:0110909"),
("thoracic cavity", "UBERON:0002224"),
("thoracic cavity boundary", ""),
("thorax", "ILX:0742178"),
("ventral", "")
]

def get_body_term(name : str):
Expand Down
15 changes: 10 additions & 5 deletions src/scaffoldmaker/annotation/uterus_terms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
("dorsal cervix junction with vagina", "None"),
("dorsal top left horn", "None"),
("dorsal top right horn", "None"),
("dorsal uterus", "None"),
("external cervical os", "UBERON:0013760", "FMA:76836", "ILX:0736534"),
("fundus of uterus", "None"),
("internal cervical os", "UBERON:0013759", "FMA:17747", "ILX:0729495"),
Expand All @@ -18,6 +19,7 @@
("left transverse cervical ligament", "None"),
("left uterine horn", "UBERON:0009020"),
("left uterine tube", "UBERON:0001303", "FMA:18484", "ILX:0734218"),
("left uterus", "None"),
("lumen of body of uterus", "None"),
("lumen of fallopian tube", "None"),
("lumen of left horn", "None"),
Expand All @@ -33,6 +35,7 @@
("right transverse cervical ligament", "None"),
("right uterine horn", "UBERON:0009022"),
("right uterine tube", "UBERON:0001302", "FMA:18483", "ILX:0724908"),
("right uterus", "None"),
("serosa of body of uterus", "None"),
("serosa of left uterine tube", "None"),
("serosa of left horn", "None"),
Expand All @@ -41,7 +44,7 @@
("serosa of uterine cervix", "None"),
("serosa of uterus", "UBERON:0001297"),
("serosa of vagina", "None"),
("uterine cervix", "UBERON:0000002","FMA:17740", "ILX:0724162"),
("uterine cervix", "UBERON:0000002", "FMA:17740", "ILX:0724162"),
("uterine horn", "UBERON:000224"),
("uterine lumen", "UBERON:0013769"),
("uterine wall", "UBERON:0000459", "FMA:17560", "ILX:0735839"),
Expand All @@ -50,15 +53,17 @@
("vagina orifice", "UBERON:0012317", "FMA:19984", "ILX:0729556"),
("ventral cervix junction with vagina", "None"),
("ventral top left horn", "None"),
("ventral top right horn", "None")]
("ventral top right horn", "None"),
("ventral uterus", "None")]


def get_uterus_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 )
:return: ( preferred name, preferred id )
"""
for term in uterus_terms:
if name in term:
return (term[0], term[1])
raise NameError("Uterus annotation term '" + name + "' not found.")
return term[0], term[1]
raise NameError("Uterus annotation term '" + name + "' not found.")
77 changes: 68 additions & 9 deletions src/scaffoldmaker/meshtypes/meshtype_1d_network_layout1.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ class MeshType_1d_network_layout1(Scaffold_base):
"Bifurcation": "1-2.1,2.2-3,2.3-4",
"Converging bifurcation": "1-3.1,2-3.2,3.3-4",
"Loop": "1-2-3-4-5-6-7-8-1",
"Snake": "1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-33",
"Sphere cube": "1.1-2.1,1.2-3.1,1.3-4.1,2.2-5.2,2.3-6.1,3.2-6.2,3.3-7.1,4.2-7.2,4.3-5.1,5.3-8.1,6.3-8.2,7.3-8.3",
"Trifurcation": "1-2.1,2.2-3,2.3-4,2.4-5",
"Trifurcation cross": "1-3.1,2-3.2,3.2-4,3.1-5"
"Trifurcation cross": "1-3.1,2-3.2,3.2-4,3.1-5",
"Vase": "1-2-3-4-5"
}

@classmethod
Expand Down Expand Up @@ -69,7 +71,7 @@ def generateBaseMesh(cls, region, options):
:param options: Dict containing options. See getDefaultOptions().
:return: [] empty list of AnnotationGroup, NetworkMesh
"""
parameterSetName = options['Base parameter set']
parameterSetName = options["Base parameter set"]
structure = options["Structure"]
defineInnerCoordinates = options["Define inner coordinates"]
networkMesh = NetworkMesh(structure)
Expand Down Expand Up @@ -101,6 +103,36 @@ def generateBaseMesh(cls, region, options):
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS2, 1, d2)
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS3, 1, d3)
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D2_DS1DS3, 1, d13)
elif "Snake" in parameterSetName:
snakeRadius = 0.5
tubeRadius = 0.1
nodesCount = nodes.getSize()
elementsCountHalfCircle = 8
elementAngle = math.pi / elementsCountHalfCircle
d1Mag = snakeRadius * elementAngle
xSign = -1.0
xOffset = -snakeRadius
for n in range(nodesCount):
halfCircle = (n % elementsCountHalfCircle) == 0
if halfCircle:
xSign = -xSign
xOffset += 2.0 * snakeRadius
angle = elementAngle * n
cosAngle = math.cos(angle)
sinAngle = math.sin(angle)
node = nodes.findNodeByIdentifier(n + 1)
fieldcache.setNode(node)
x = [xOffset - xSign * snakeRadius * cosAngle, snakeRadius * sinAngle, 0.0]
d1 = [xSign * d1Mag * sinAngle, d1Mag * cosAngle, 0.0]
d2 = [-tubeRadius * cosAngle, xSign * tubeRadius * sinAngle, 0.0]
d12Sign = 0.0 if halfCircle else xSign
d12 = mult(d1, d12Sign * elementAngle * tubeRadius / d1Mag)
d3 = [0.0, 0.0, tubeRadius]
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_VALUE, 1, x)
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS1, 1, d1)
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS2, 1, d2)
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D2_DS1DS2, 1, d12)
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS3, 1, d3)
elif "Sphere cube" in parameterSetName:
# edit node parameters
sphereRadius = 0.5
Expand Down Expand Up @@ -156,21 +188,47 @@ def generateBaseMesh(cls, region, options):
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS2, v + 1, cd2[n][v])
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS3, v + 1, cd3[n])
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D2_DS1DS3, v + 1, cd13[n][v])
elif "Vase" in parameterSetName:
midRadius = 1.0
magRadius = 0.5
nodesCount = nodes.getSize()
elementsCountWavelength = 4
elementAngle = 2.0 * math.pi / elementsCountWavelength
for n in range(nodesCount):
angle = elementAngle * n
cosAngle = math.cos(angle)
sinAngle = math.sin(angle)
node = nodes.findNodeByIdentifier(n + 1)
fieldcache.setNode(node)
x = [0.0, 0.0, n]
d1 = [0.0, 0.0, 1.0]
r = midRadius + magRadius * sinAngle
d2 = [0.0, r, 0.0]
d12 = [0.0, 2.0 * magRadius * cosAngle, 0.0]
d3 = [r, 0.0, 0.0]
d13 = [2.0 * magRadius * cosAngle, 0.0, 0.0]
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_VALUE, 1, x)
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS1, 1, d1)
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS2, 1, d2)
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D2_DS1DS2, 1, d12)
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D_DS3, 1, d3)
coordinates.setNodeParameters(fieldcache, -1, Node.VALUE_LABEL_D2_DS1DS3, 1, d13)

if defineInnerCoordinates:
cls._defineInnerCoordinates(region, coordinates, options, networkMesh)
cls.defineInnerCoordinates(region, coordinates, options, networkMesh)

return [], networkMesh

@classmethod
def _defineInnerCoordinates(cls, region, coordinates, options, networkMesh):
def defineInnerCoordinates(cls, region, coordinates, options, networkMesh, innerProportion=0.8):
"""
Copy coordinates to inner coordinates via in-memory model file.
Assign using the interactive function.
:param region: Region to define field in.
:param coordinates: Standard/outer coordinate field.
:param options: Options used to generate scaffold.
:param networkMesh: Network mesh object used to generate scaffold.
:param innerProportion: Proportion of outer coordinates to assign to inner, typically 0.0 < p < 1.0.
"""
assert options["Define inner coordinates"]
coordinates.setName("inner coordinates") # temporarily rename
Expand All @@ -186,8 +244,8 @@ def _defineInnerCoordinates(cls, region, coordinates, options, networkMesh):
"To field": {"coordinates": False, "inner coordinates": True},
"From field": {"coordinates": True, "inner coordinates": False},
"Mode": {"Scale": True, "Offset": False},
"D2 value": 0.8,
"D3 value": 0.8}
"D2 value": innerProportion,
"D3 value": innerProportion}
cls.assignCoordinates(region, options, networkMesh, functionOptions, editGroupName=None)

@classmethod
Expand All @@ -207,13 +265,14 @@ def editStructure(cls, region, options, networkMesh, functionOptions, editGroupN
with ChangeManager(fieldmodule):
clearRegion(region)
structure = options["Structure"] = functionOptions["Structure"]
options["Base parameter set"] = "Default" # to not assign coordinates for one of the special sets
networkMesh.build(structure)
networkMesh.create1DLayoutMesh(region)
coordinates = find_or_create_field_coordinates(fieldmodule).castFiniteElement()
coordinates.setManaged(True) # since cleared by clearRegion
defineInnerCoordinates = options["Define inner coordinates"]
if defineInnerCoordinates:
cls._defineInnerCoordinates(region, coordinates, options, networkMesh)
cls.defineInnerCoordinates(region, coordinates, options, networkMesh)

return True, False # settings changed, nodes not changed (since reset to original coordinates)

Expand Down Expand Up @@ -345,8 +404,8 @@ def makeSideDerivativesNormal(cls, region, options, networkMesh, functionOptions
print("Make side derivatives normal: inner coordinates field not defined")
return False, False
useCoordinates = coordinates if functionOptions["Field"]["coordinates"] else innerCoordinates
makeD2Normal = functionOptions['Make D2 normal']
makeD3Normal = functionOptions['Make D3 normal']
makeD2Normal = functionOptions["Make D2 normal"]
makeD3Normal = functionOptions["Make D3 normal"]
if not (makeD2Normal or makeD3Normal):
return False, False
nodeset = fieldmodule.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
Expand Down
26 changes: 20 additions & 6 deletions src/scaffoldmaker/meshtypes/meshtype_2d_tubenetwork1.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,21 @@ def getDefaultOptions(cls, parameterSetName="Default"):
"Number of elements around": 8,
"Annotation numbers of elements around": [0],
"Target element density along longest segment": 4.0,
"Annotation numbers of elements along": [0],
"Show trim surfaces": False
}
if parameterSetName in ["Loop", "Snake", "Vase"]:
options["Target element density along longest segment"] = 12.0
return options

@staticmethod
def getOrderedOptionNames():
@classmethod
def getOrderedOptionNames(cls):
return [
"Network layout",
"Number of elements around",
"Annotation numbers of elements around",
"Target element density along longest segment",
"Annotation numbers of elements along",
"Show trim surfaces"
]

Expand Down Expand Up @@ -88,6 +92,15 @@ def checkOptions(cls, options):
annotationElementsCountsAround[i] = 4
if options["Target element density along longest segment"] < 1.0:
options["Target element density along longest segment"] = 1.0
annotationAlongCounts = options["Annotation numbers of elements along"]
if len(annotationAlongCounts) == 0:
options["Annotation numbers of elements along"] = [0]
else:
for i in range(len(annotationAlongCounts)):
if annotationAlongCounts[i] <= 0:
annotationAlongCounts[i] = 0
elif annotationAlongCounts[i] < 1:
annotationAlongCounts[i] = 1
return dependentChanges

@classmethod
Expand All @@ -106,14 +119,15 @@ def generateBaseMesh(cls, region, options):
tubeNetworkMeshBuilder = TubeNetworkMeshBuilder(
networkMesh,
targetElementDensityAlongLongestSegment=options["Target element density along longest segment"],
defaultElementsCountAround=options["Number of elements around"],
elementsCountThroughWall=1,
layoutAnnotationGroups=networkLayout.getAnnotationGroups(),
annotationElementsCountsAround=options["Annotation numbers of elements around"])
annotationElementsCountsAlong=options["Annotation numbers of elements along"],
defaultElementsCountAround=options["Number of elements around"],
annotationElementsCountsAround=options["Annotation numbers of elements around"],
elementsCountThroughShell=1)
tubeNetworkMeshBuilder.build()
generateData = TubeNetworkMeshGenerateData(
region, 2,
isLinearThroughWall=True,
isLinearThroughShell=True,
isShowTrimSurfaces=options["Show trim surfaces"])
tubeNetworkMeshBuilder.generateMesh(generateData)
annotationGroups = generateData.getAnnotationGroups()
Expand Down
18 changes: 15 additions & 3 deletions src/scaffoldmaker/meshtypes/meshtype_3d_boxnetwork1.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@ def getParameterSetNames(cls):
def getDefaultOptions(cls, parameterSetName="Default"):
options = {
"Network layout": ScaffoldPackage(MeshType_1d_network_layout1, defaultParameterSetName=parameterSetName),
"Target element density along longest segment": 4.0
"Target element density along longest segment": 4.0,
"Annotation numbers of elements along": [0]
}
return options

@classmethod
def getOrderedOptionNames(cls):
return [
"Network layout",
"Target element density along longest segment"
"Target element density along longest segment",
"Annotation numbers of elements along"
]

@classmethod
Expand Down Expand Up @@ -70,6 +72,15 @@ def checkOptions(cls, options):
options["Network layout"] = cls.getOptionScaffoldPackage("Network layout", MeshType_1d_network_layout1)
if options["Target element density along longest segment"] < 1.0:
options["Target element density along longest segment"] = 1.0
annotationAlongCounts = options["Annotation numbers of elements along"]
if len(annotationAlongCounts) == 0:
options["Annotation numbers of elements along"] = [0]
else:
for i in range(len(annotationAlongCounts)):
if annotationAlongCounts[i] <= 0:
annotationAlongCounts[i] = 0
elif annotationAlongCounts[i] < 1:
annotationAlongCounts[i] = 1
dependentChanges = False
return dependentChanges

Expand All @@ -83,14 +94,15 @@ def generateBaseMesh(cls, region, options):
"""
networkLayout = options["Network layout"]
targetElementDensityAlongLongestSegment = options["Target element density along longest segment"]
annotationElementsCountsAlong = options["Annotation numbers of elements along"]

layoutRegion = region.createRegion()
networkLayout.generate(layoutRegion) # ask scaffold to generate to get user-edited parameters
layoutAnnotationGroups = networkLayout.getAnnotationGroups()
networkMesh = networkLayout.getConstructionObject()

boxNetworkMeshBuilder = BoxNetworkMeshBuilder(
networkMesh, targetElementDensityAlongLongestSegment, layoutAnnotationGroups)
networkMesh, targetElementDensityAlongLongestSegment, layoutAnnotationGroups, annotationElementsCountsAlong)
boxNetworkMeshBuilder.build()
generateData = BoxNetworkMeshGenerateData(region)
boxNetworkMeshBuilder.generateMesh(generateData)
Expand Down
Loading

0 comments on commit 33bf002

Please sign in to comment.