From 7d8648837ef83e37d256c15e840a5f17e0700d55 Mon Sep 17 00:00:00 2001 From: Richard Christie Date: Thu, 12 May 2022 11:50:23 +1200 Subject: [PATCH 1/5] Improve epicardial fat layer Shift fat pad nodes around inlets to reduce distortion. Define outer surface of epicardium for fitting. --- src/scaffoldmaker/annotation/heart_terms.py | 1 + .../meshtypes/meshtype_3d_heartatria1.py | 60 ++++++++++++++++--- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/scaffoldmaker/annotation/heart_terms.py b/src/scaffoldmaker/annotation/heart_terms.py index a88aa4d5..5d49316b 100644 --- a/src/scaffoldmaker/annotation/heart_terms.py +++ b/src/scaffoldmaker/annotation/heart_terms.py @@ -13,6 +13,7 @@ ( "endocardium of right ventricle", "FMA:9536" ), ( "epicardial fat", "UBERON:0015129"), ( "epicardium", "FMA:9461", "UBERON:0002348"), + ( "outer surface of epicardium", "None"), #( "epicardium of ventricle", "FMA:12150", "UBERON:0001082" ), # ventricles with base ( "conus arteriosus", "UBERON:0003983" ), diff --git a/src/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py b/src/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py index a587e481..7bed2ee6 100644 --- a/src/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py +++ b/src/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py @@ -2057,12 +2057,11 @@ def generateBaseMesh(cls, region, options): fpx = [] fpd1 = [] fpd2 = [] - fpd3 = [] - proportionAcross = 0.1 + proportionAcross = 0.2 for i in range(2): if i == 0: - nx, nd2, nd1, nd3 = laTrackSurface.createHermiteCurvePoints( - 0.0, proportionAcross, 1.0, proportionAcross, elementsCountAcrossTrackSurface)[0:4] + nx, nd2, nd1, nd3, nProportions = laTrackSurface.createHermiteCurvePoints( + 0.0, proportionAcross, 1.0, proportionAcross, elementsCountAcrossTrackSurface) else: nx, nd2, nd1, nd3 = raTrackSurface.createHermiteCurvePoints( 1.0, proportionAcross, 0.0, proportionAcross, elementsCountAcrossTrackSurface)[0:4] @@ -2072,7 +2071,6 @@ def generateBaseMesh(cls, region, options): fpx.append(nx) fpd1.append(nd1) fpd2.append(nd2) - fpd3.append(nd3) # put into single arrays cycling left to right fastest, smoothing each d1 row nx = [] @@ -2082,7 +2080,8 @@ def generateBaseMesh(cls, region, options): scale = interp.computeCubicHermiteDerivativeScaling(fpx[0][n], fpd1[0][n], fpx[1][n], fpd1[1][n]) for i in range(2): nx.append(fpx[i][n]) - nd1.append(mult(fpd1[i][n], scale)) + fpd1[i][n] = mult(fpd1[i][n], scale) + nd1.append(fpd1[i][n]) nd2.append(fpd2[i][n]) fpTrackSurface = TrackSurface(1, elementsCountAcrossTrackSurface, nx, nd1, nd2) @@ -3315,6 +3314,20 @@ def generateBaseMesh(cls, region, options): bridgeNodes = bridgeGroup.createFieldNodeGroup(nodes).getNodesetGroup() bridgeNodeTangents = {} + # parameters for shifting centre of fatpad to spread elements around PV, VC inlets + # have ivcPositionOver, svcPositionOver + rpvPositionOver = lpvOstiumPositionOver if commonLeftRightPvOstium else rpvOstiumPositionOver + # GRC fudge factors + svcSpread = ivcSpread = ivcPositionOver + rpvSpread = ivcSpread + ivcMag = 0.15 + svcMag = 0.0 + rpvMag = 0.15 + + for n in range(elementsCountAcrossTrackSurface + 1): + xi_ivc = max(1.0, math.fabs(ivcPositionOver - nProportions[n][1])) + xi_svc = max(1.0, math.fabs(ivcPositionOver - nProportions[n][1])) + for epiNodeIdentifier in epiFatPadNodeIdentifiersMap.keys(): epiNode = nodes.findNodeByIdentifier(epiNodeIdentifier) cache.setNode(epiNode) @@ -3328,8 +3341,35 @@ def generateBaseMesh(cls, region, options): epifx = None epifPosition = fpTrackSurface.findNearestPosition(epix, startPosition=None) - if 0.0001 < epifPosition.xi1 < 0.9999: - epifx, epifd1, epifd2 = fpTrackSurface.evaluateCoordinates(epifPosition, derivatives=True) + if not (((epifPosition.e1 == 0) and (epifPosition.xi1 < 0.0001)) or + ((epifPosition.e1 == (fpTrackSurface.elementsCount1 - 1)) and (0.9999 < epifPosition.xi1))): + # shift proportion 1 around inlet + xi_centre = 1.0 - (2.0 * math.fabs(0.5 - epifPosition.xi1)) + proportion1, proportion2 = fpTrackSurface.getProportion(epifPosition) + # scale to spread out centre + # proportion1Scaled = interp.interpolateCubicHermite([0.0], [0.5], [0.5], [0.6], xi_centre)[0] + # if proportion1 < 0.5: + # proportion1 = proportion1Scaled + # else: + # proportion1 = 1.0 - proportion1Scaled + proportion1Shift = 0.0 + xi_ivc = math.fabs((ivcPositionOver - proportion2) / ivcSpread) + if xi_ivc < 1.0: + proportion1Shift -= interp.interpolateCubicHermite([ivcMag], [0.0], [0.0], [0.0], xi_ivc)[0] + xi_svc = math.fabs((svcPositionOver - proportion2) / svcSpread) + if xi_svc < 1.0: + proportion1Shift -= interp.interpolateCubicHermite([svcMag], [0.0], [0.0], [0.0], xi_svc)[0] + xi_rpv = math.fabs((rpvPositionOver - proportion2) / rpvSpread) + if xi_rpv < 1.0: + proportion1Shift += interp.interpolateCubicHermite([rpvMag], [0.0], [0.0], [0.0], xi_rpv)[0] + proportion1Shift *= xi_centre + # proportion1Shift = interp.interpolateCubicHermite( + # [0.0], [0.0], [proportion1Shift], [0.0], xi_centre)[0] + proportion1 += proportion1Shift + + # convert shifted proportions to shifted position on fpTrackSurface + epifPosition2 = fpTrackSurface.createPositionProportion(proportion1, proportion2) + epifx, epifd1, epifd2 = fpTrackSurface.evaluateCoordinates(epifPosition2, derivatives=True) delta_epi = sub(epifx, epix) # epifx must be above the epicardium surface # and at least epicardialFatMinimumThickness away from epix @@ -3580,6 +3620,10 @@ def defineFaceAnnotations(cls, region, options, annotationGroups): # add non-exterior surfaces between myocardium and epicardial fat to epicardium is_epicardial_fat = epicardialFatGroup.getFieldElementGroup(mesh2d) epiGroup.getMeshGroup(mesh2d).addElementsConditional(fm.createFieldAnd(is_myocardium, is_epicardial_fat)) + is_epicardium_outer_surface = fm.createFieldAnd(is_epicardial_fat, is_exterior_face_xi3_1) + epicardiumSurfaceGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, + get_heart_term("outer surface of epicardium")) + epicardiumSurfaceGroup.getMeshGroup(mesh2d).addElementsConditional(is_epicardium_outer_surface) laEndoGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("endocardium of left atrium")) laEndoGroup.getMeshGroup(mesh2d).addElementsConditional(is_lam_endo) raEndoGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("endocardium of right atrium")) From f344de27853814f319fc388ac89f186153332ef2 Mon Sep 17 00:00:00 2001 From: Richard Christie Date: Tue, 24 May 2022 16:08:06 +1200 Subject: [PATCH 2/5] Use UBERON terms for heart --- src/scaffoldmaker/annotation/heart_terms.py | 34 ++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/scaffoldmaker/annotation/heart_terms.py b/src/scaffoldmaker/annotation/heart_terms.py index 5d49316b..433cbbbd 100644 --- a/src/scaffoldmaker/annotation/heart_terms.py +++ b/src/scaffoldmaker/annotation/heart_terms.py @@ -6,13 +6,13 @@ heart_terms = [ ( "heart", "UBERON:0000948", "FMA:7088" ), # 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" ), + ( "left ventricle myocardium", "UBERON:0006566", "FMA:9558" ), + ( "right ventricle myocardium", "UBERON:0006567", "FMA:9535" ), + ( "interventricular septum", "UBERON:0002094", "FMA:7133" ), + ( "endocardium of left ventricle", "UBERON:0009713", "FMA:9559" ), + ( "endocardium of right ventricle", "UBERON:0009712", "FMA:9536" ), ( "epicardial fat", "UBERON:0015129"), - ( "epicardium", "FMA:9461", "UBERON:0002348"), + ( "epicardium", "UBERON:0002348", "FMA:9461"), ( "outer surface of epicardium", "None"), #( "epicardium of ventricle", "FMA:12150", "UBERON:0001082" ), # ventricles with base @@ -22,17 +22,17 @@ # 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" ), + ( "endocardium of left atrium", "UBERON:0034903", "FMA:7286" ), + ( "endocardium of right atrium", "UBERON:0009129", "FMA:7281" ), + ( "interatrial septum", "UBERON:0002085", "FMA:7108" ), + ( "fossa ovalis", "UBERON:0003369", "FMA:9246" ), + ( "left auricle", "UBERON:0006630", "FMA:7219" ), # uncertain if just the tissue like myocardium + ( "right auricle", "UBERON:0006631", "FMA:7218" ), # uncertain if just the tissue like myocardium + ( "endocardium of left auricle", "UBERON:0011006", "FMA:13236" ), + ( "endocardium of right auricle", "UBERON:0011007", "FMA:13235" ), ( "epicardium of left auricle", "FMA:13233" ), ( "epicardium of right auricle", "FMA:13232" ), - ( "pulmonary vein", "FMA:66643", "UBERON:0002016" ), + ( "pulmonary vein", "UBERON:0002016", "FMA:66643" ), ( "left pulmonary vein", "UBERON:0009030" ), ( "left inferior pulmonary vein", "FMA:49913" ), ( "left superior pulmonary vein", "FMA:49916" ), @@ -40,9 +40,9 @@ ( "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", "UBERON:0001072", "FMA:10951", "posterior vena cava" ), ( "inferior vena cava inlet", "ILX:0738358" ), - ( "superior vena cava", "FMA:4720", "UBERON:0001585", "anterior vena cava" ), + ( "superior vena cava", "UBERON:0001585", "FMA:4720", "anterior vena cava" ), ( "superior vena cava inlet", "ILX:0738367" ), # arterial root ( "root of aorta", "FMA:3740" ), From f95a5cebb68c5530256c08b73d44b520e63543e0 Mon Sep 17 00:00:00 2001 From: Richard Christie Date: Fri, 3 Jun 2022 10:10:33 +1200 Subject: [PATCH 3/5] Make all heart parameter sets unit scale Remove unit-named parameter sets as same as non-unit. "Unit scale" parameter is still supported. --- .../meshtypes/meshtype_3d_heart1.py | 6 +---- .../meshtypes/meshtype_3d_heartatria1.py | 18 +++------------ .../meshtypes/meshtype_3d_heartventricles1.py | 22 ++----------------- .../meshtype_3d_heartventriclesbase1.py | 7 +----- tests/test_heart.py | 6 ++--- 5 files changed, 10 insertions(+), 49 deletions(-) diff --git a/src/scaffoldmaker/meshtypes/meshtype_3d_heart1.py b/src/scaffoldmaker/meshtypes/meshtype_3d_heart1.py index f5cb1568..e67f5123 100644 --- a/src/scaffoldmaker/meshtypes/meshtype_3d_heart1.py +++ b/src/scaffoldmaker/meshtypes/meshtype_3d_heart1.py @@ -36,11 +36,7 @@ def getParameterSetNames(): 'Human 1', 'Mouse 1', 'Pig 1', - 'Rat 1', - 'Unit Human 1', - 'Unit Mouse 1', - 'Unit Pig 1', - 'Unit Rat 1'] + 'Rat 1'] @staticmethod def getDefaultOptions(parameterSetName='Default'): diff --git a/src/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py b/src/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py index 7bed2ee6..2f8d8767 100644 --- a/src/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py +++ b/src/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py @@ -203,11 +203,7 @@ def getParameterSetNames(): 'Human 1', 'Mouse 1', 'Pig 1', - 'Rat 1', - 'Unit Human 1', - 'Unit Mouse 1', - 'Unit Pig 1', - 'Unit Rat 1'] + 'Rat 1'] @classmethod def getDefaultOptions(cls, parameterSetName='Default'): @@ -215,7 +211,6 @@ def getDefaultOptions(cls, parameterSetName='Default'): isMouse = 'Mouse' in parameterSetName isPig = 'Pig' in parameterSetName isRat = 'Rat' in parameterSetName - notUnitScale = 'Unit' not in parameterSetName if isPig: lpvOstium = cls.lpvOstiumDefaultScaffoldPackages['LPV Pig 1'] rpvOstium = cls.rpvOstiumDefaultScaffoldPackages['RPV Pig 1'] @@ -322,12 +317,7 @@ def getDefaultOptions(cls, parameterSetName='Default'): options['Refine number of elements through epicardial fat layer'] = 1 options['Use cross derivatives'] = False - if isHuman: - if notUnitScale: - options['Unit scale'] = 80.0 - elif isMouse or isRat: - if notUnitScale: - options['Unit scale'] = 6.0 if isMouse else 12.0 + if isMouse or isRat: options['Atria base inner major axis length'] = 0.32 options['Atria base inner minor axis length'] = 0.26 options['Atria major axis rotation degrees'] = 30.0 @@ -394,9 +384,7 @@ def getDefaultOptions(cls, parameterSetName='Default'): options['Superior vena cava inlet position over'] = 0.62 options['Superior vena cava inlet position right'] = 0.25 options['Superior vena cava inlet wall thickness'] = 0.012 - elif 'Pig' in parameterSetName: - if notUnitScale: - options['Unit scale'] = 80.0 + elif isPig: options['Atrial base side incline degrees'] = 0.0 options['Left atrial appendage angle axial degrees'] = -10.0 options['Left atrial appendage angle left degrees'] = 20.0 diff --git a/src/scaffoldmaker/meshtypes/meshtype_3d_heartventricles1.py b/src/scaffoldmaker/meshtypes/meshtype_3d_heartventricles1.py index 125633e2..893a0295 100644 --- a/src/scaffoldmaker/meshtypes/meshtype_3d_heartventricles1.py +++ b/src/scaffoldmaker/meshtypes/meshtype_3d_heartventricles1.py @@ -39,11 +39,7 @@ def getParameterSetNames(): 'Human 1', 'Mouse 1', 'Pig 1', - 'Rat 1', - 'Unit Human 1', - 'Unit Mouse 1', - 'Unit Pig 1', - 'Unit Rat 1'] + 'Rat 1'] @staticmethod def getDefaultOptions(parameterSetName='Default'): @@ -51,7 +47,6 @@ def getDefaultOptions(parameterSetName='Default'): isMouse = 'Mouse' in parameterSetName isPig = 'Pig' in parameterSetName isRat = 'Rat' in parameterSetName - notUnitScale = 'Unit' not in parameterSetName options = {} options['Number of elements around LV free wall'] = 5 options['Number of elements around RV free wall'] = 7 @@ -79,12 +74,7 @@ def getDefaultOptions(parameterSetName='Default'): options['Refine number of elements surface'] = 4 options['Refine number of elements through LV wall'] = 1 options['Refine number of elements through wall'] = 1 - if isHuman: - if 'Unit' not in parameterSetName: - options['Unit scale'] = 80.0 - elif isMouse or isRat: - if notUnitScale: - options['Unit scale'] = 5.0 if isMouse else 12.0 + if isMouse or isRat: options['Interventricular sulcus derivative factor'] = 0.8 options['LV outer height'] = 0.9 options['LV outer diameter'] = 0.85 @@ -103,8 +93,6 @@ def getDefaultOptions(parameterSetName='Default'): elif isPig: options['Number of elements up LV apex'] = 1 options['Number of elements up RV'] = 3 - if 'Unit' not in parameterSetName: - options['Unit scale'] = 80.0 options['LV outer height'] = 0.9 options['LV free wall thickness'] = 0.17 options['LV apex thickness'] = 0.07 @@ -115,12 +103,6 @@ def getDefaultOptions(parameterSetName='Default'): options['RV side extension'] = 0.1 options['RV side extension growth factor'] = 0.4 options['Ventricular septum thickness'] = 0.13 - elif 'Rat' in parameterSetName: - if 'Unit' not in parameterSetName: - options['Unit scale'] = 12.0 - options['LV outer height'] = 0.9 - options['LV apex thickness'] = 0.08 - options['RV width'] = 0.35 return options @staticmethod diff --git a/src/scaffoldmaker/meshtypes/meshtype_3d_heartventriclesbase1.py b/src/scaffoldmaker/meshtypes/meshtype_3d_heartventriclesbase1.py index 440009fc..ebf7c2a6 100644 --- a/src/scaffoldmaker/meshtypes/meshtype_3d_heartventriclesbase1.py +++ b/src/scaffoldmaker/meshtypes/meshtype_3d_heartventriclesbase1.py @@ -46,11 +46,7 @@ def getParameterSetNames(): 'Human 1', 'Mouse 1', 'Pig 1', - 'Rat 1', - 'Unit Human 1', - 'Unit Mouse 1', - 'Unit Pig 1', - 'Unit Rat 1'] + 'Rat 1'] @staticmethod def getDefaultOptions(parameterSetName='Default'): @@ -60,7 +56,6 @@ def getDefaultOptions(parameterSetName='Default'): isMouse = 'Mouse' in parameterSetName isPig = 'Pig' in parameterSetName isRat = 'Rat' in parameterSetName - notUnitScale = 'Unit' not in parameterSetName # only works with particular numbers of elements around options['Number of elements around LV free wall'] = 7 options['Number of elements around RV free wall'] = 7 diff --git a/tests/test_heart.py b/tests/test_heart.py index fb99cac4..6bf090c2 100644 --- a/tests/test_heart.py +++ b/tests/test_heart.py @@ -22,12 +22,12 @@ def test_heart1(self): """ scaffold = MeshType_3d_heart1 parameterSetNames = scaffold.getParameterSetNames() - self.assertEqual(parameterSetNames, [ "Default", "Human 1", "Mouse 1", "Pig 1", "Rat 1", - "Unit Human 1", "Unit Mouse 1", "Unit Pig 1", "Unit Rat 1" ]); + self.assertEqual(parameterSetNames, ["Default", "Human 1", "Mouse 1", "Pig 1", "Rat 1"]); options = scaffold.getDefaultOptions("Human 1") self.assertEqual(123, len(options)) self.assertEqual(0.9, options.get("LV outer height")) - self.assertEqual(80.0, options.get("Unit scale")) + self.assertEqual(1.0, options.get("Unit scale")) + options["Unit scale"] = 80.0 self.assertEqual(7, options.get("Number of elements around LV free wall")) self.assertEqual(7, options.get("Number of elements around RV free wall")) # simplify atria From 4478f9a79b67c6955f6263aeea15ea6db537c265 Mon Sep 17 00:00:00 2001 From: Richard Christie Date: Wed, 8 Jun 2022 22:32:00 +1200 Subject: [PATCH 4/5] Update heart annotation group terms Add new luminal and outer surface terms for digitising. Use preferred annotation term names and identifiers. --- src/scaffoldmaker/annotation/heart_terms.py | 140 +++++---- .../meshtypes/meshtype_3d_heart1.py | 32 +-- .../meshtypes/meshtype_3d_heartatria1.py | 271 ++++++++++-------- .../meshtypes/meshtype_3d_heartventricles1.py | 57 ++-- .../meshtype_3d_heartventriclesbase1.py | 146 ++++------ tests/test_general.py | 2 +- tests/test_heart.py | 42 +-- 7 files changed, 366 insertions(+), 324 deletions(-) diff --git a/src/scaffoldmaker/annotation/heart_terms.py b/src/scaffoldmaker/annotation/heart_terms.py index 433cbbbd..04961f42 100644 --- a/src/scaffoldmaker/annotation/heart_terms.py +++ b/src/scaffoldmaker/annotation/heart_terms.py @@ -4,61 +4,93 @@ # convention: preferred name, preferred id, followed by any other ids and alternative names heart_terms = [ - ( "heart", "UBERON:0000948", "FMA:7088" ), - # ventricles - ( "left ventricle myocardium", "UBERON:0006566", "FMA:9558" ), - ( "right ventricle myocardium", "UBERON:0006567", "FMA:9535" ), - ( "interventricular septum", "UBERON:0002094", "FMA:7133" ), - ( "endocardium of left ventricle", "UBERON:0009713", "FMA:9559" ), - ( "endocardium of right ventricle", "UBERON:0009712", "FMA:9536" ), - ( "epicardial fat", "UBERON:0015129"), - ( "epicardium", "UBERON:0002348", "FMA:9461"), - ( "outer surface of epicardium", "None"), - #( "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", "UBERON:0034903", "FMA:7286" ), - ( "endocardium of right atrium", "UBERON:0009129", "FMA:7281" ), - ( "interatrial septum", "UBERON:0002085", "FMA:7108" ), - ( "fossa ovalis", "UBERON:0003369", "FMA:9246" ), - ( "left auricle", "UBERON:0006630", "FMA:7219" ), # uncertain if just the tissue like myocardium - ( "right auricle", "UBERON:0006631", "FMA:7218" ), # uncertain if just the tissue like myocardium - ( "endocardium of left auricle", "UBERON:0011006", "FMA:13236" ), - ( "endocardium of right auricle", "UBERON:0011007", "FMA:13235" ), - ( "epicardium of left auricle", "FMA:13233" ), - ( "epicardium of right auricle", "FMA:13232" ), - ( "pulmonary vein", "UBERON:0002016", "FMA:66643" ), - ( "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", "UBERON:0001072", "FMA:10951", "posterior vena cava" ), - ( "inferior vena cava inlet", "ILX:0738358" ), - ( "superior vena cava", "UBERON:0001585", "FMA:4720", "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" ), + # heart - volume terms + ("heart", "UBERON:0000948", "FMA:7088"), # group of the entire heart + ("epicardial fat", "UBERON:0015129"), # not used + ("epicardium", "UBERON:0002348", "FMA:9461"), # volumetric layer outside myocardium to pericardial cavity + # heart - surface terms + ("outer surface of epicardium", "ILX:0793548"), + ("outer surface of myocardium", "ILX:0793530"), + + # ventricles - volume terms + ("conus arteriosus", "UBERON:0003983"), + ("endocardium of left ventricle", "UBERON:0009713", "FMA:9559"), + ("endocardium of right ventricle", "UBERON:0009712", "FMA:9536"), + ("heart left ventricle", "UBERON:0002084"), # the whole left ventricle + ("heart right ventricle", "UBERON:0002080"), # the whole right ventricle + ("interventricular septum", "UBERON:0002094", "FMA:7133"), + ("left fibrous ring", "FMA:77124"), + ("left ventricle myocardium", "UBERON:0006566", "FMA:9558"), + ("right fibrous ring", "FMA:77125"), + ("right ventricle myocardium", "UBERON:0006567", "FMA:9535"), + # ventricles - surface terms + ("luminal surface of left ventricle", "ILX:0793537"), + ("luminal surface of right ventricle", "ILX:0793538"), + ("outer surface of myocardium of left ventricle", "ILX:0793533"), + ("outer surface of myocardium of right ventricle", "ILX:0793534"), + + # atria - volume terms + ("left atrium endocardium", "UBERON:0034903", "FMA:7286"), + ("endocardium of left auricle", "UBERON:0011006", "FMA:13236"), + ("right atrium endocardium", "UBERON:0009129", "FMA:7281"), + ("endocardium of right auricle", "UBERON:0011007", "FMA:13235"), + ("epicardium of left auricle", "FMA:13233"), + ("epicardium of right auricle", "FMA:13232"), + ("fossa ovalis", "UBERON:0003369", "FMA:9246"), + ("inferior vena cava", "UBERON:0001072", "FMA:10951", "posterior vena cava"), + ("inferior vena cava inlet", "ILX:0738358"), + ("interatrial septum", "UBERON:0002085", "FMA:7108"), + ("left atrium myocardium", "FMA:7285"), + ("left auricle", "UBERON:0006630", "FMA:7219"), + ("left cardiac atrium", "UBERON:0002079"), # the whole left atrium + ("left inferior pulmonary vein", "FMA:49913"), + ("left pulmonary vein", "UBERON:0009030"), + ("left superior pulmonary vein", "FMA:49916"), + ("middle pulmonary vein", "ILX:0739222"), # in mouse, rat, rabbit + ("pulmonary vein", "UBERON:0002016", "FMA:66643"), + ("right atrium myocardium", "FMA:7282"), + ("right auricle", "UBERON:0006631", "FMA:7218"), + ("right cardiac atrium", "UBERON:0002078"), # the whole right atrium + ("right inferior pulmonary vein", "FMA:49911"), + ("right pulmonary vein", "UBERON:0009032"), + ("right superior pulmonary vein", "FMA:49914"), + ("superior vena cava", "UBERON:0001585", "FMA:4720", "anterior vena cava"), + ("superior vena cava inlet", "ILX:0738367"), + # atria - surface terms + ("luminal surface of left atrium", "ILX:0793535"), + ("luminal surface of right atrium", "ILX:0793536"), + ("luminal surface of inferior vena cava", "ILX:0793542"), + ("luminal surface of left pulmonary vein", "ILX:0793539"), + ("luminal surface of middle pulmonary vein", "ILX:0793540"), + ("luminal surface of right pulmonary vein", "ILX:0793541"), + ("luminal surface of superior vena cava", "ILX:0793543"), + ("outer surface of myocardium of left atrium", "ILX:0793531"), + ("outer surface of myocardium of right atrium", "ILX:0793532"), + + # arterial valves and great vessels - volume terms + ("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"), + # arterial valves and great vessels - surface terms + ("luminal surface of aorta", "ILX:0793544"), + ("luminal surface of pulmonary trunk", "ILX:0793546"), + ("luminal surface of root of aorta", "ILX:0793545"), + ("luminal surface of root of pulmonary trunk", "ILX:0793547"), + # fiducial markers - ( "apex of heart", "UBERON:0002098", "FMA:7164"), - ( "crux of heart", "ILX:0777104", "FMA:7220" ), # point on posterior surface where the four chambers meet on interventricular, atrio-ventricular and interatrial sulci - ( "left atrium epicardium venous midpoint", "ILX:0778116"), # point at centre of pulmonary vein ostia on left atrium epicardium, on anterior/ventral side for rodents - ( "right atrium epicardium venous midpoint", "ILX:0778117") # point at centre of inferior & superior vena cavae on right atrium epicardium - ] + ("apex of heart", "UBERON:0002098", "FMA:7164"), + # point on posterior surface where the four chambers meet on interventricular, A-V and interatrial sulci + ("crux cordis", "ILX:0777104", "FMA:7220"), + # point at centre of pulmonary vein ostia on left atrium epicardium, on anterior/ventral side for rodents + ("left atrium epicardium venous midpoint", "ILX:0778116"), + # point at centre of inferior & superior vena cavae on right atrium epicardium + ("right atrium epicardium venous midpoint", "ILX:0778117") +] def get_heart_term(name : str): """ diff --git a/src/scaffoldmaker/meshtypes/meshtype_3d_heart1.py b/src/scaffoldmaker/meshtypes/meshtype_3d_heart1.py index e67f5123..5beb4b5c 100644 --- a/src/scaffoldmaker/meshtypes/meshtype_3d_heart1.py +++ b/src/scaffoldmaker/meshtypes/meshtype_3d_heart1.py @@ -79,7 +79,7 @@ def getOrderedOptionNames(): 'Refine number of elements surface', 'Refine number of elements through LV wall', 'Refine number of elements through wall', - 'Refine number of elements through epicardial fat layer']: + 'Refine number of elements through epicardium layer']: optionNames.remove(optionName) optionNames.append(optionName) return optionNames @@ -129,32 +129,20 @@ def generateBaseMesh(cls, region, options): coordinates = findOrCreateFieldCoordinates(fm) cache = fm.createFieldcache() - mesh = fm.findMeshByDimension(3) - # generate heartventriclesbase1 model and put atria1 on it ventriclesAnnotationGroups = MeshType_3d_heartventriclesbase1.generateBaseMesh(region, options) atriaAnnotationGroups = MeshType_3d_heartatria1.generateBaseMesh(region, options) annotationGroups = mergeAnnotationGroups(ventriclesAnnotationGroups, atriaAnnotationGroups) heartGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("heart")) - cruxGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("crux of heart")) + cruxGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("crux cordis")) lFibrousRingGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("left fibrous ring")) rFibrousRingGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("right fibrous ring")) - # annotation fiducial points - markerGroup = findOrCreateFieldGroup(fm, "marker") - markerName = findOrCreateFieldStoredString(fm, name="marker_name") - markerLocation = findOrCreateFieldStoredMeshLocation(fm, mesh, name="marker_location") - - nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) - markerPoints = findOrCreateFieldNodeGroup(markerGroup, nodes).getNodesetGroup() - markerTemplateInternal = nodes.createNodetemplate() - markerTemplateInternal.defineField(markerName) - markerTemplateInternal.defineField(markerLocation) - ############## # Create nodes ############## + nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) nodeIdentifier = max(1, getMaximumNodeIdentifier(nodes) + 1) # discover left and right fibrous ring nodes from ventricles and atria @@ -215,6 +203,7 @@ def generateBaseMesh(cls, region, options): # Create elements ################# + mesh = fm.findMeshByDimension(3) heartMeshGroup = heartGroup.getMeshGroup(mesh) lFibrousRingMeshGroup = lFibrousRingGroup.getMeshGroup(mesh) rFibrousRingMeshGroup = rFibrousRingGroup.getMeshGroup(mesh) @@ -393,16 +382,13 @@ def generateBaseMesh(cls, region, options): for meshGroup in meshGroups: meshGroup.addElement(element) - # annotation fiducial points + # crux cordis annotation point cruxElement = mesh.findElementByIdentifier(cruxElementId) cruxXi = [ 0.5, 0.5, 1.0 ] - markerPoint = markerPoints.createNode(nodeIdentifier, markerTemplateInternal) - nodeIdentifier += 1 - cache.setNode(markerPoint) - markerName.assignString(cache, cruxGroup.getName()) - markerLocation.assignMeshLocation(cache, cruxElement, cruxXi) - for group in [ heartGroup, cruxGroup ]: - group.getNodesetGroup(nodes).addNode(markerPoint) + markerNode = cruxGroup.createMarkerNode(nodeIdentifier, element=cruxElement, xi=cruxXi) + nodeIdentifier = markerNode.getIdentifier() + 1 + for group in [ heartGroup ]: + group.getNodesetGroup(nodes).addNode(markerNode) return annotationGroups diff --git a/src/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py b/src/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py index 2f8d8767..0610cb84 100644 --- a/src/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py +++ b/src/scaffoldmaker/meshtypes/meshtype_3d_heartatria1.py @@ -16,8 +16,8 @@ from opencmiss.zinc.field import Field, FieldGroup from opencmiss.zinc.node import Node from opencmiss.zinc.result import RESULT_OK -from scaffoldmaker.annotation.annotationgroup import AnnotationGroup, findOrCreateAnnotationGroupForTerm, \ - getAnnotationGroupForTerm +from scaffoldmaker.annotation.annotationgroup import AnnotationGroup, findAnnotationGroupByName, \ + findOrCreateAnnotationGroupForTerm, getAnnotationGroupForTerm from scaffoldmaker.annotation.heart_terms import get_heart_term from scaffoldmaker.meshtypes.meshtype_3d_ostium1 import MeshType_3d_ostium1, generateOstiumMesh from scaffoldmaker.meshtypes.scaffold_base import Scaffold_base @@ -309,12 +309,12 @@ def getDefaultOptions(cls, parameterSetName='Default'): options['Superior vena cava inlet length'] = 0.1 options['Superior vena cava inlet inner diameter'] = 0.18 options['Superior vena cava inlet wall thickness'] = 0.015 - options['Define epicardial fat layer'] = False - options['Epicardial fat minimum thickness'] = 0.01 + options['Define epicardium layer'] = False + options['Epicardium layer minimum thickness'] = 0.01 options['Refine'] = False options['Refine number of elements surface'] = 4 options['Refine number of elements through wall'] = 1 - options['Refine number of elements through epicardial fat layer'] = 1 + options['Refine number of elements through epicardium layer'] = 1 options['Use cross derivatives'] = False if isMouse or isRat: @@ -505,12 +505,12 @@ def getOrderedOptionNames(): 'Superior vena cava inlet length', 'Superior vena cava inlet inner diameter', 'Superior vena cava inlet wall thickness', - 'Define epicardial fat layer', - 'Epicardial fat minimum thickness', + 'Define epicardium layer', + 'Epicardium layer minimum thickness', 'Refine', 'Refine number of elements surface', 'Refine number of elements through wall', - 'Refine number of elements through epicardial fat layer' + 'Refine number of elements through epicardium layer' #,'Use cross derivatives' ] @@ -610,7 +610,7 @@ def checkOptions(cls, options): 'Superior vena cava inlet length', 'Superior vena cava inlet inner diameter', 'Superior vena cava inlet wall thickness', - 'Epicardial fat minimum thickness']: + 'Epicardium layer minimum thickness']: if options[key] < 0.0: options[key] = 0.0 for key in [ @@ -793,13 +793,11 @@ def generateBaseMesh(cls, region, options): svcLength = unitScale*options['Superior vena cava inlet length'] svcInnerRadius = unitScale*0.5*options['Superior vena cava inlet inner diameter'] svcWallThickness = unitScale*options['Superior vena cava inlet wall thickness'] - defineEpicardialFatLayer = options['Define epicardial fat layer'] - epicardialFatMinimumThickness = unitScale*options['Epicardial fat minimum thickness'] + defineEpicardiumLayer = options['Define epicardium layer'] + epicardiumLayerMinimumThickness = unitScale*options['Epicardium layer minimum thickness'] useCrossDerivatives = options['Use cross derivatives'] fm = region.getFieldmodule() - mesh = fm.findMeshByDimension(3) - nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) coordinates = findOrCreateFieldCoordinates(fm) cache = fm.createFieldcache() @@ -811,9 +809,9 @@ def generateBaseMesh(cls, region, options): laaGroup = AnnotationGroup(region, get_heart_term("left auricle")) raaGroup = AnnotationGroup(region, get_heart_term("right auricle")) annotationGroups = [heartGroup, lamGroup, ramGroup, aSeptumGroup, fossaGroup, laaGroup, raaGroup] - if defineEpicardialFatLayer: - epicardialFatGroup = AnnotationGroup(region, get_heart_term("epicardial fat")) - annotationGroups.append(epicardialFatGroup) + if defineEpicardiumLayer: + epicardiumGroup = AnnotationGroup(region, get_heart_term("epicardium")) + annotationGroups.append(epicardiumGroup) lpvOstiumSettings = lpvOstium.getScaffoldSettings() lpvCount = lpvOstiumSettings['Number of vessels'] @@ -853,20 +851,11 @@ def generateBaseMesh(cls, region, options): annotationGroups += [laeVenousMidpointGroup, ivcGroup, ivcInletGroup, raeVenousMidpointGroup, svcGroup, svcInletGroup, lFibrousRingGroup, rFibrousRingGroup] - # annotation fiducial points - markerGroup = findOrCreateFieldGroup(fm, "marker") - markerName = findOrCreateFieldStoredString(fm, name="marker_name") - markerLocation = findOrCreateFieldStoredMeshLocation(fm, mesh, name="marker_location") - - markerPoints = findOrCreateFieldNodeGroup(markerGroup, nodes).getNodesetGroup() - markerTemplateInternal = nodes.createNodetemplate() - markerTemplateInternal.defineField(markerName) - markerTemplateInternal.defineField(markerLocation) - ############## # Create nodes ############## + nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) nodetemplate = nodes.createNodetemplate() nodetemplate.defineField(coordinates) nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1) @@ -882,6 +871,7 @@ def generateBaseMesh(cls, region, options): nodeIdentifier = max(1, getMaximumNodeIdentifier(nodes) + 1) + mesh = fm.findMeshByDimension(3) elementIdentifier = max(1, getMaximumElementIdentifier(mesh) + 1) heartMeshGroup = heartGroup.getMeshGroup(mesh) @@ -2040,7 +2030,7 @@ def generateBaseMesh(cls, region, options): coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, ltBaseOuterd2[n1]) nodeIdentifier += 1 - if defineEpicardialFatLayer: + if defineEpicardiumLayer: # epicardial fat pad in RAGP region -- track surface bridging interatrial groove fpx = [] fpd1 = [] @@ -2054,7 +2044,7 @@ def generateBaseMesh(cls, region, options): nx, nd2, nd1, nd3 = raTrackSurface.createHermiteCurvePoints( 1.0, proportionAcross, 0.0, proportionAcross, elementsCountAcrossTrackSurface)[0:4] if nx: - nx = [add(nx[i], mult(nd3[i], epicardialFatMinimumThickness)) for i in range(len(nx))] + nx = [add(nx[i], mult(nd3[i], epicardiumLayerMinimumThickness)) for i in range(len(nx))] nd1 = [[-c for c in d] for d in nd1] fpx.append(nx) fpd1.append(nd1) @@ -2631,13 +2621,10 @@ def generateBaseMesh(cls, region, options): laevmElementId = elementIdentifier - elementsCountAroundLpvOstium laevmXi = [ 0.0, 1.0, 1.0 ] laevmElement = mesh.findElementByIdentifier(laevmElementId) - markerPoint = markerPoints.createNode(nodeIdentifier, markerTemplateInternal) - nodeIdentifier += 1 - cache.setNode(markerPoint) - markerName.assignString(cache, laeVenousMidpointGroup.getName()) - markerLocation.assignMeshLocation(cache, laevmElement, laevmXi) - for group in [ heartGroup, lamGroup, laeVenousMidpointGroup ]: - group.getNodesetGroup(nodes).addNode(markerPoint) + markerNode = laeVenousMidpointGroup.createMarkerNode(nodeIdentifier, element=laevmElement, xi=laevmXi) + nodeIdentifier = markerNode.getIdentifier() + 1 + for group in [ heartGroup, lamGroup ]: + group.getNodesetGroup(nodes).addNode(markerNode) if not commonLeftRightPvOstium: # create right pulmonary vein annulus @@ -2939,13 +2926,10 @@ def generateBaseMesh(cls, region, options): raevmElementId = elementIdentifier - elementsCountAroundVC + elementsCountOverRightAtriumVenous//2 + elementsCountOverSideRightAtriumVC//2 raevmXi = [ 0.5 if (elementsCountOverSideRightAtriumVC % 2) else 0.0, 1.0, 1.0 ] raevmElement = mesh.findElementByIdentifier(raevmElementId) - markerPoint = markerPoints.createNode(nodeIdentifier, markerTemplateInternal) - nodeIdentifier += 1 - cache.setNode(markerPoint) - markerName.assignString(cache, raeVenousMidpointGroup.getName()) - markerLocation.assignMeshLocation(cache, raevmElement, raevmXi) - for group in [ heartGroup, ramGroup, raeVenousMidpointGroup ]: - group.getNodesetGroup(nodes).addNode(markerPoint) + markerNode = raeVenousMidpointGroup.createMarkerNode(nodeIdentifier, element=raevmElement, xi=raevmXi) + nodeIdentifier = markerNode.getIdentifier() + 1 + for group in [heartGroup, ramGroup]: + group.getNodesetGroup(nodes).addNode(markerNode) # create left atrial appendage position = laTrackSurface.createPositionProportion(laaMidpointOver, laaMidpointLeft) @@ -3072,7 +3056,6 @@ def generateBaseMesh(cls, region, options): eft1 = eft elementtemplate1 = elementtemplate scalefactors = None - addMarker = None nc = 2 + e1 if e2 == 0: @@ -3095,7 +3078,6 @@ def generateBaseMesh(cls, region, options): #remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [1] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) remapEftNodeValueLabel(eft1, [ 3, 7 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS2, [1] ) ]) - addMarker = { "name" : "SVC-RA", "xi" : [ 0.0, 1.0, 0.0 ] } elif (e2 == 1) and (e1 < (elementsCountOverSideRightAtriumPouch - 1)): pass # regular elements else: @@ -3126,13 +3108,6 @@ def generateBaseMesh(cls, region, options): #print('create element raa plain', element.isValid(), elementIdentifier, result2, result3, nids) elementIdentifier += 1 - if addMarker: - markerPoint = markerPoints.createNode(nodeIdentifier, markerTemplateInternal) - nodeIdentifier += 1 - cache.setNode(markerPoint) - markerName.assignString(cache, addMarker["name"]) - markerLocation.assignMeshLocation(cache, element, addMarker["xi"]) - for meshGroup in meshGroups: meshGroup.addElement(element) @@ -3256,7 +3231,7 @@ def generateBaseMesh(cls, region, options): elementsCountRadial = elementsCountAlongAtrialAppendages, meshGroups = [ heartMeshGroup, ramMeshGroup, raaMeshGroup ]) - if defineEpicardialFatLayer: + if defineEpicardiumLayer: # project epicardial points over atria to build fat pad epiGroup = fm.createFieldGroup() epiMesh = epiGroup.createFieldElementGroup(mesh).getMeshGroup() @@ -3325,7 +3300,7 @@ def generateBaseMesh(cls, region, options): result, epid3 = coordinates.getNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, 3) if result != RESULT_OK: epid3 = vector.crossproduct3(epid1, epid2) - fatx = add(epix, vector.setMagnitude(epid3, epicardialFatMinimumThickness)) + fatx = add(epix, vector.setMagnitude(epid3, epicardiumLayerMinimumThickness)) epifx = None epifPosition = fpTrackSurface.findNearestPosition(epix, startPosition=None) @@ -3360,8 +3335,8 @@ def generateBaseMesh(cls, region, options): epifx, epifd1, epifd2 = fpTrackSurface.evaluateCoordinates(epifPosition2, derivatives=True) delta_epi = sub(epifx, epix) # epifx must be above the epicardium surface - # and at least epicardialFatMinimumThickness away from epix - if (dot(delta_epi, epid3) > 0.0) and (magnitude(delta_epi) >= epicardialFatMinimumThickness): + # and at least epicardiumLayerMinimumThickness away from epix + if (dot(delta_epi, epid3) > 0.0) and (magnitude(delta_epi) >= epicardiumLayerMinimumThickness): epifNormal = normalize(cross(epifd1, epifd2)) # epix must be under the fatpad if dot(delta_epi, epifNormal) > 0.0: @@ -3389,8 +3364,8 @@ def generateBaseMesh(cls, region, options): elementtemplateX = mesh.createElementtemplate() elementtemplateX.setElementShapeType(Element.SHAPE_TYPE_CUBE) - epicardialFatMeshGroup = epicardialFatGroup.getMeshGroup(mesh) - meshGroups = [heartMeshGroup, epicardialFatMeshGroup] + epicardiumMeshGroup = epicardiumGroup.getMeshGroup(mesh) + meshGroups = [heartMeshGroup, epicardiumMeshGroup] elementtemplate = mesh.createElementtemplate() # iterate over list of identifiers since can't iterate over mesh while modifying it for epiElementIdentifier in epiElementIdentifiers: @@ -3504,9 +3479,9 @@ def refineMesh(cls, meshrefinement, options): elementsCountAroundRightAtriumFreeWall = options['Number of elements around right atrium free wall'] refineElementsCountSurface = options['Refine number of elements surface'] refineElementsCountThroughWall = options['Refine number of elements through wall'] - refineElementsCountThroughEpicardialFatLayer =\ - options['Refine number of elements through epicardial fat layer'] - defineEpicardialFatLayer = options['Define epicardial fat layer'] + refineElementsCountThroughEpicardiumLayer =\ + options['Refine number of elements through epicardium layer'] + defineEpicardiumLayer = options['Define epicardium layer'] sourceFm = meshrefinement._sourceFm annotationGroups = meshrefinement._sourceAnnotationGroups @@ -3516,15 +3491,15 @@ def refineMesh(cls, meshrefinement, options): raElementGroupField = raGroup.getFieldElementGroup(meshrefinement._sourceMesh) aSeptumGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("interatrial septum")) aSeptumMeshGroup = aSeptumGroup.getMeshGroup(meshrefinement._sourceMesh) - epicardialFatGroup = None - epicardialFatMeshGroup = None - if defineEpicardialFatLayer: - epicardialFatGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("epicardial fat")) - epicardialFatMeshGroup = epicardialFatGroup.getMeshGroup(meshrefinement._sourceMesh) + epicardiumGroup = None + epicardiumMeshGroup = None + if defineEpicardiumLayer: + epicardiumGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("epicardium")) + epicardiumMeshGroup = epicardiumGroup.getMeshGroup(meshrefinement._sourceMesh) coordinates = findOrCreateFieldCoordinates(meshrefinement._sourceFm) # last atria element is last element in following group: - lastGroup = epicardialFatGroup + lastGroup = epicardiumGroup if not lastGroup: lastGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("right auricle")) lastMeshGroup = lastGroup.getMeshGroup(meshrefinement._sourceMesh) @@ -3553,8 +3528,8 @@ def refineMesh(cls, meshrefinement, options): refineElements1 = refineElementsCountThroughWall else: refineElements2 = refineElementsCountThroughWall - elif epicardialFatGroup and epicardialFatMeshGroup.containsElement(element): - refineElements3 = refineElementsCountThroughEpicardialFatLayer + elif epicardiumGroup and epicardiumMeshGroup.containsElement(element): + refineElements3 = refineElementsCountThroughEpicardiumLayer meshrefinement.refineElementCubeStandard3d(element, refineElements1, refineElements2, refineElements3) if elementIdentifier == lastElementIdentifier: return # finish on last so can continue elsewhere @@ -3572,15 +3547,21 @@ def defineFaceAnnotations(cls, region, options, annotationGroups): New face annotation groups are appended to this list. """ # create endocardium and epicardium groups - defineEpicardialFatLayer = options['Define epicardial fat layer'] + defineEpicardiumLayer = options['Define epicardium layer'] fm = region.getFieldmodule() lamGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("left atrium myocardium")) ramGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("right atrium myocardium")) aSeptumGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("interatrial septum")) laaGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("left auricle")) raaGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("right auricle")) - epicardialFatGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("epicardial fat")) \ - if defineEpicardialFatLayer else None + lpvGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("left pulmonary vein")) + # middle pulmonary vein is only present in rodents: + mpvGroup = findAnnotationGroupByName(annotationGroups, "middle pulmonary vein") + rpvGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("right pulmonary vein")) + ivcGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("inferior vena cava")) + svcGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("superior vena cava")) + # following will already be defined if defineEpicardiumLayer is true + epiGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("epicardium")) mesh2d = fm.findMeshByDimension(2) is_exterior = fm.createFieldIsExterior() @@ -3591,72 +3572,114 @@ def defineFaceAnnotations(cls, region, options, annotationGroups): is_lam_endo = fm.createFieldAnd(is_lam, is_exterior_face_xi3_0) is_ram_endo = fm.createFieldOr(fm.createFieldAnd(fm.createFieldAnd(is_ram, is_exterior_face_xi3_0), fm.createFieldNot(is_lam_endo)), - fm.createFieldAnd(aSeptumGroup.getFieldElementGroup(mesh2d), is_exterior_face_xi3_1)) + fm.createFieldAnd(aSeptumGroup.getFieldElementGroup(mesh2d), + is_exterior_face_xi3_1)) is_laa = laaGroup.getFieldElementGroup(mesh2d) is_raa = raaGroup.getFieldElementGroup(mesh2d) is_laa_endo = fm.createFieldAnd(is_laa, is_exterior_face_xi3_0) is_raa_endo = fm.createFieldAnd(is_raa, is_exterior_face_xi3_0) is_laa_epi = fm.createFieldAnd(laaGroup.getFieldElementGroup(mesh2d), is_exterior_face_xi3_1) is_raa_epi = fm.createFieldAnd(raaGroup.getFieldElementGroup(mesh2d), is_exterior_face_xi3_1) - is_myocardium = fm.createFieldOr(is_lam, is_ram) - is_a_epi = fm.createFieldAnd(fm.createFieldOr(is_myocardium, fm.createFieldOr(is_laa, is_raa)), - fm.createFieldAnd(is_exterior_face_xi3_1, - fm.createFieldNot(aSeptumGroup.getFieldElementGroup(mesh2d)))) - epiGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("epicardium")) - epiGroup.getMeshGroup(mesh2d).addElementsConditional(is_a_epi) - if defineEpicardialFatLayer: - # add non-exterior surfaces between myocardium and epicardial fat to epicardium - is_epicardial_fat = epicardialFatGroup.getFieldElementGroup(mesh2d) - epiGroup.getMeshGroup(mesh2d).addElementsConditional(fm.createFieldAnd(is_myocardium, is_epicardial_fat)) - is_epicardium_outer_surface = fm.createFieldAnd(is_epicardial_fat, is_exterior_face_xi3_1) - epicardiumSurfaceGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, - get_heart_term("outer surface of epicardium")) - epicardiumSurfaceGroup.getMeshGroup(mesh2d).addElementsConditional(is_epicardium_outer_surface) - laEndoGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("endocardium of left atrium")) - laEndoGroup.getMeshGroup(mesh2d).addElementsConditional(is_lam_endo) - raEndoGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("endocardium of right atrium")) - raEndoGroup.getMeshGroup(mesh2d).addElementsConditional(is_ram_endo) - laaEndoGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("endocardium of left auricle")) + # is_myocardium = fm.createFieldOr(is_lam, is_ram) + is_ext_xi3_1_and_not_septum = fm.createFieldAnd( + is_exterior_face_xi3_1, fm.createFieldNot(aSeptumGroup.getFieldElementGroup(mesh2d))) + is_os_lam = fm.createFieldAnd(is_lam, is_ext_xi3_1_and_not_septum) + is_os_ram = fm.createFieldAnd(is_ram, is_ext_xi3_1_and_not_septum) + is_epi = epiGroup.getFieldElementGroup(mesh2d) + + # luminal surfaces of endocardium of left/right atrium + lslaEndoGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("luminal surface of left atrium")) + lslaEndoGroup.getMeshGroup(mesh2d).addElementsConditional(is_lam_endo) + lsraEndoGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("luminal surface of right atrium")) + lsraEndoGroup.getMeshGroup(mesh2d).addElementsConditional(is_ram_endo) + # endocardium groups are defined identically to luminal surfaces at scaffold scale + laEndoGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("left atrium endocardium")) + laEndoGroup.getMeshGroup(mesh2d).addElementsConditional(lslaEndoGroup.getFieldElementGroup(mesh2d)) + raEndoGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("right atrium endocardium")) + raEndoGroup.getMeshGroup(mesh2d).addElementsConditional(lsraEndoGroup.getFieldElementGroup(mesh2d)) + + laaEndoGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("endocardium of left auricle")) laaEndoGroup.getMeshGroup(mesh2d).addElementsConditional(is_laa_endo) - raaEndoGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("endocardium of right auricle")) + raaEndoGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("endocardium of right auricle")) raaEndoGroup.getMeshGroup(mesh2d).addElementsConditional(is_raa_endo) - laaEpiGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("epicardium of left auricle")) + laaEpiGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("epicardium of left auricle")) laaEpiGroup.getMeshGroup(mesh2d).addElementsConditional(is_laa_epi) - raaEpiGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("epicardium of right auricle")) + raaEpiGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("epicardium of right auricle")) raaEpiGroup.getMeshGroup(mesh2d).addElementsConditional(is_raa_epi) + oslamGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("outer surface of myocardium of left atrium")) + oslamGroup.getMeshGroup(mesh2d).addElementsConditional(is_os_lam) + osramGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("outer surface of myocardium of right atrium")) + osramGroup.getMeshGroup(mesh2d).addElementsConditional(is_os_ram) + if defineEpicardiumLayer: + oslamGroup.getMeshGroup(mesh2d).addElementsConditional(fm.createFieldAnd(is_lam, is_epi)) + osramGroup.getMeshGroup(mesh2d).addElementsConditional(fm.createFieldAnd(is_ram, is_epi)) + osmGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("outer surface of myocardium")) + osmGroup.getMeshGroup(mesh2d).addElementsConditional(oslamGroup.getFieldElementGroup(mesh2d)) + osmGroup.getMeshGroup(mesh2d).addElementsConditional(osramGroup.getFieldElementGroup(mesh2d)) + if defineEpicardiumLayer: + # future: limit to atria once ventricles have epicardium layer + is_os_epi = fm.createFieldAnd(is_epi, is_exterior_face_xi3_1) + osEpiGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region,get_heart_term("outer surface of epicardium")) + osEpiGroup.getMeshGroup(mesh2d).addElementsConditional(is_os_epi) + # note: epiGroup only contains 3-D elements in this case + else: + # if no volumetric epicardium group, add outer surface of atrial myocardium + epiGroup.getMeshGroup(mesh2d).addElementsConditional(oslamGroup.getFieldElementGroup(mesh2d)) + epiGroup.getMeshGroup(mesh2d).addElementsConditional(osramGroup.getFieldElementGroup(mesh2d)) + + lslpvGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("luminal surface of left pulmonary vein")) + lsrpvGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("luminal surface of right pulmonary vein")) + is_lpv = lpvGroup.getFieldElementGroup(mesh2d) + is_mpv = mpvGroup.getFieldElementGroup(mesh2d) if mpvGroup else None + is_rpv = rpvGroup.getFieldElementGroup(mesh2d) + lslpvGroup.getMeshGroup(mesh2d).addElementsConditional( + fm.createFieldAnd(is_exterior_face_xi3_0, is_lpv)) + if mpvGroup: + lsmpvGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("luminal surface of middle pulmonary vein")) + lsmpvGroup.getMeshGroup(mesh2d).addElementsConditional( + fm.createFieldAnd(is_exterior_face_xi3_0, is_mpv)) + lsrpvGroup.getMeshGroup(mesh2d).addElementsConditional( + fm.createFieldAnd(is_exterior_face_xi3_0, is_rpv)) + + lsivcGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("luminal surface of inferior vena cava")) + lssvcGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("luminal surface of superior vena cava")) + is_ivc = ivcGroup.getFieldElementGroup(mesh2d) + is_svc = svcGroup.getFieldElementGroup(mesh2d) + lsivcGroup.getMeshGroup(mesh2d).addElementsConditional( + fm.createFieldAnd(is_exterior_face_xi3_0, is_ivc)) + lssvcGroup.getMeshGroup(mesh2d).addElementsConditional( + fm.createFieldAnd(is_exterior_face_xi3_0, is_svc)) + lFibrousRingGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("left fibrous ring")) rFibrousRingGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("right fibrous ring")) - if (lFibrousRingGroup.getDimension() == 0) or (lFibrousRingGroup.getDimension() == 0): - # not already added by full heart scaffold - nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) - lFibrousRingNodeGroup = lFibrousRingGroup.getNodesetGroup(nodes) - rFibrousRingNodeGroup = rFibrousRingGroup.getNodesetGroup(nodes) - # make temp group containing all elements, faces etc. if have any nodes in fibrous ring groups - tmpGroup = fm.createFieldGroup() - tmpGroup.setSubelementHandlingMode(FieldGroup.SUBELEMENT_HANDLING_MODE_FULL) - mesh3d = fm.findMeshByDimension(3) - tmp3dMeshGroup = tmpGroup.createFieldElementGroup(mesh3d).getMeshGroup() - coordinates = fm.findFieldByName("coordinates").castFiniteElement() - elementIter = mesh3d.createElementiterator() - element = elementIter.next() - while element.isValid(): - eft = element.getElementfieldtemplate(coordinates, -1) - if eft.isValid(): - for n in range(eft.getNumberOfLocalNodes()): - node = element.getNode(eft, n + 1) - if lFibrousRingNodeGroup.containsNode(node) or rFibrousRingNodeGroup.containsNode(node): - tmp3dMeshGroup.addElement(element) - break - element = elementIter.next() - tmp2dElementGroup = tmpGroup.getFieldElementGroup(mesh2d) - if tmp2dElementGroup.isValid(): - is_exterior_face_xi2_0 = fm.createFieldAnd(is_exterior, fm.createFieldIsOnFace(Element.FACE_TYPE_XI2_0)) - is_fibrous_ring = fm.createFieldAnd(tmp2dElementGroup, is_exterior_face_xi2_0) - is_left_fibrous_ring = fm.createFieldAnd(is_lam, is_fibrous_ring) - lFibrousRingGroup.getMeshGroup(mesh2d).addElementsConditional(is_left_fibrous_ring) - is_right_fibrous_ring = fm.createFieldAnd(is_ram, is_fibrous_ring) - rFibrousRingGroup.getMeshGroup(mesh2d).addElementsConditional(is_right_fibrous_ring) + if (lFibrousRingGroup.getDimension() <= 0) or (rFibrousRingGroup.getDimension() <= 0): + is_exterior_face_xi2_0 = fm.createFieldAnd(is_exterior, fm.createFieldIsOnFace(Element.FACE_TYPE_XI2_0)) + is_pv = fm.createFieldOr(is_lpv, is_rpv) + if mpvGroup: + is_pv = fm.createFieldOr(is_pv, is_mpv) + lFibrousRingGroup.getMeshGroup(mesh2d).addElementsConditional( + fm.createFieldAnd(is_lam, fm.createFieldAnd(is_exterior_face_xi2_0, fm.createFieldNot(is_pv)))) + is_vc = fm.createFieldOr(is_ivc, is_svc) + rFibrousRingGroup.getMeshGroup(mesh2d).addElementsConditional( + fm.createFieldAnd(is_ram, fm.createFieldAnd(is_exterior_face_xi2_0, fm.createFieldNot(is_vc)))) def getLeftAtriumPulmonaryVeinOstiaElementsCounts(elementsCountAroundLeftAtriumFreeWall, elementsCountOverAtria, commonLeftRightPvOstium): ''' diff --git a/src/scaffoldmaker/meshtypes/meshtype_3d_heartventricles1.py b/src/scaffoldmaker/meshtypes/meshtype_3d_heartventricles1.py index 893a0295..3c1ad2bb 100644 --- a/src/scaffoldmaker/meshtypes/meshtype_3d_heartventricles1.py +++ b/src/scaffoldmaker/meshtypes/meshtype_3d_heartventricles1.py @@ -1221,28 +1221,53 @@ def defineFaceAnnotations(cls, region, options, annotationGroups): """ # create endocardium and epicardium groups fm = region.getFieldmodule() - lvGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("left ventricle myocardium")) - rvGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("right ventricle myocardium")) + lvmGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("left ventricle myocardium")) + rvmGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("right ventricle myocardium")) vSeptumGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("interventricular septum")) mesh2d = fm.findMeshByDimension(2) is_exterior = fm.createFieldIsExterior() is_exterior_face_xi3_0 = fm.createFieldAnd(is_exterior, fm.createFieldIsOnFace(Element.FACE_TYPE_XI3_0)) is_exterior_face_xi3_1 = fm.createFieldAnd(is_exterior, fm.createFieldIsOnFace(Element.FACE_TYPE_XI3_1)) - is_lv = lvGroup.getFieldElementGroup(mesh2d) - is_rv = rvGroup.getFieldElementGroup(mesh2d) - is_lv_endo = fm.createFieldAnd(is_lv, is_exterior_face_xi3_0) - is_rv_endo = fm.createFieldOr(fm.createFieldAnd(fm.createFieldAnd(is_rv, is_exterior_face_xi3_0), - fm.createFieldNot(is_lv_endo)), - fm.createFieldAnd(vSeptumGroup.getFieldElementGroup(mesh2d), is_exterior_face_xi3_1)) - is_v_epi = fm.createFieldAnd(fm.createFieldOr(is_lv, is_rv), - fm.createFieldAnd(is_exterior_face_xi3_1, - fm.createFieldNot(vSeptumGroup.getFieldElementGroup(mesh2d)))) + is_lvm = lvmGroup.getFieldElementGroup(mesh2d) + is_rvm = rvmGroup.getFieldElementGroup(mesh2d) + is_lvm_endo = fm.createFieldAnd(is_lvm, is_exterior_face_xi3_0) + is_rvm_endo = fm.createFieldOr(fm.createFieldAnd(fm.createFieldAnd(is_rvm, is_exterior_face_xi3_0), + fm.createFieldNot(is_lvm_endo)), + fm.createFieldAnd(vSeptumGroup.getFieldElementGroup(mesh2d), + is_exterior_face_xi3_1)) + is_ext_xi3_1_and_not_septum = fm.createFieldAnd( + is_exterior_face_xi3_1, fm.createFieldNot(vSeptumGroup.getFieldElementGroup(mesh2d))) + is_os_lvm = fm.createFieldAnd(is_lvm, is_ext_xi3_1_and_not_septum) + is_os_rvm = fm.createFieldAnd(is_rvm, is_ext_xi3_1_and_not_septum) + + # luminal surfaces of endocardium of left/right ventricle + lslvEndoGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("luminal surface of left ventricle")) + lslvEndoGroup.getMeshGroup(mesh2d).addElementsConditional(is_lvm_endo) + lsrvEndoGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("luminal surface of right ventricle")) + lsrvEndoGroup.getMeshGroup(mesh2d).addElementsConditional(is_rvm_endo) + # endocardium groups are defined identically to luminal surfaces at scaffold scale + lvEndoGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("endocardium of left ventricle")) + lvEndoGroup.getMeshGroup(mesh2d).addElementsConditional(lslvEndoGroup.getFieldElementGroup(mesh2d)) + rvEndoGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("endocardium of right ventricle")) + rvEndoGroup.getMeshGroup(mesh2d).addElementsConditional(lsrvEndoGroup.getFieldElementGroup(mesh2d)) + + oslvmGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("outer surface of myocardium of left ventricle")) + oslvmGroup.getMeshGroup(mesh2d).addElementsConditional(is_os_lvm) + osrvmGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("outer surface of myocardium of right ventricle")) + osrvmGroup.getMeshGroup(mesh2d).addElementsConditional(is_os_rvm) + osmGroup = findOrCreateAnnotationGroupForTerm( + annotationGroups, region, get_heart_term("outer surface of myocardium")) + osmGroup.getMeshGroup(mesh2d).addElementsConditional(oslvmGroup.getFieldElementGroup(mesh2d)) + osmGroup.getMeshGroup(mesh2d).addElementsConditional(osrvmGroup.getFieldElementGroup(mesh2d)) + # if no volumetric epicardium group, add outer surface of myocardium epiGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("epicardium")) - epiGroup.getMeshGroup(mesh2d).addElementsConditional(is_v_epi) - lvEndoGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("endocardium of left ventricle")) - lvEndoGroup.getMeshGroup(mesh2d).addElementsConditional(is_lv_endo) - rvEndoGroup = findOrCreateAnnotationGroupForTerm(annotationGroups, region, get_heart_term("endocardium of right ventricle")) - rvEndoGroup.getMeshGroup(mesh2d).addElementsConditional(is_rv_endo) + epiGroup.getMeshGroup(mesh2d).addElementsConditional(osmGroup.getFieldElementGroup(mesh2d)) def getSeptumPoints(septumArcRadians, lvRadius, radialDisplacement, elementsCountAroundLVFreeWall, elementsCountAroundVSeptum, z, n3): diff --git a/src/scaffoldmaker/meshtypes/meshtype_3d_heartventriclesbase1.py b/src/scaffoldmaker/meshtypes/meshtype_3d_heartventriclesbase1.py index ebf7c2a6..4d5d7928 100644 --- a/src/scaffoldmaker/meshtypes/meshtype_3d_heartventriclesbase1.py +++ b/src/scaffoldmaker/meshtypes/meshtype_3d_heartventriclesbase1.py @@ -311,8 +311,6 @@ def generateBaseMesh(cls, region, options): coordinates = findOrCreateFieldCoordinates(fm) cache = fm.createFieldcache() - mesh = fm.findMeshByDimension(3) - # generate heartventricles1 model to add base plane to annotationGroups = MeshType_3d_heartventricles1.generateBaseMesh(region, options) @@ -325,23 +323,21 @@ def generateBaseMesh(cls, region, options): # av boundary nodes are put in left and right fibrous ring groups only so they can be found by heart1 lFibrousRingGroup = AnnotationGroup(region, get_heart_term("left fibrous ring")) rFibrousRingGroup = AnnotationGroup(region, get_heart_term("right fibrous ring")) - annotationGroups += [conusArteriosusGroup, lFibrousRingGroup, rFibrousRingGroup] + # temporary groups for making face annotations + lvOutletGroup = AnnotationGroup(region, ("LV outlet", "None")) + mitralAorticCurtainGroup = AnnotationGroup(region, ("Mitral aortic curtain", "None")) + supraventricularCrestGroup = AnnotationGroup(region, ("Supraventricular crest", "None")) + annotationGroups += [conusArteriosusGroup, lFibrousRingGroup, rFibrousRingGroup, lvOutletGroup, + mitralAorticCurtainGroup, supraventricularCrestGroup] # annotation fiducial points markerGroup = findOrCreateFieldGroup(fm, "marker") - markerName = findOrCreateFieldStoredString(fm, name="marker_name") - markerLocation = findOrCreateFieldStoredMeshLocation(fm, mesh, name="marker_location") - - nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) - markerPoints = findOrCreateFieldNodeGroup(markerGroup, nodes).getNodesetGroup() - markerTemplateInternal = nodes.createNodetemplate() - markerTemplateInternal.defineField(markerName) - markerTemplateInternal.defineField(markerLocation) ################# # Create nodes ################# + nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) nodetemplate = nodes.createNodetemplate() nodetemplate.defineField(coordinates) nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1) @@ -759,8 +755,12 @@ def generateBaseMesh(cls, region, options): # Create elements ################# + mesh = fm.findMeshByDimension(3) heartMeshGroup = heartGroup.getMeshGroup(mesh) lvMeshGroup = lvGroup.getMeshGroup(mesh) + lvOutletMeshGroup = lvOutletGroup.getMeshGroup(mesh) + mitralAorticCurtainMeshGroup = mitralAorticCurtainGroup.getMeshGroup(mesh) + supraventricularCrestMeshGroup = supraventricularCrestGroup.getMeshGroup(mesh) rvMeshGroup = rvGroup.getMeshGroup(mesh) vSeptumMeshGroup = vSeptumGroup.getMeshGroup(mesh) conusArteriosusMeshGroup = conusArteriosusGroup.getMeshGroup(mesh) @@ -845,7 +845,6 @@ def generateBaseMesh(cls, region, options): nids = None scalefactors = None meshGroups = [ heartMeshGroup, rvMeshGroup ] - addMarker = None noa = e niv = e @@ -965,7 +964,6 @@ def generateBaseMesh(cls, region, options): # outer infundibulum 5, above septum nids = [ rvInnerNodeId[niv - 1], rvInnerNodeId[niv], rvOutletNodeId[0][5], rvOutletNodeId[0][0], avsNodeId, lvOutletNodeId[1][3], rvOutletNodeId[1][5], rvOutletNodeId[1][0] ] - addMarker = { "name" : "Pulmonary valve-RV", "xi" : [ 1.0, 1.0, 0.0 ] } eft1 = tricubichermite.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) scalefactors = [ -1.0 ] @@ -989,13 +987,6 @@ def generateBaseMesh(cls, region, options): #print('create element rv base r1', elementIdentifier, result, result2, result3, nids) elementIdentifier += 1 - if addMarker: - markerPoint = markerPoints.createNode(nodeIdentifier, markerTemplateInternal) - nodeIdentifier += 1 - cache.setNode(markerPoint) - markerName.assignString(cache, addMarker["name"]) - markerLocation.assignMeshLocation(cache, element, addMarker["xi"]) - for meshGroup in meshGroups: meshGroup.addElement(element) @@ -1107,8 +1098,7 @@ def generateBaseMesh(cls, region, options): eft1 = eft nids = None scalefactors = None - meshGroups = [ heartMeshGroup, lvMeshGroup ] - addMarker = None + meshGroups = [ heartMeshGroup, lvMeshGroup, lvOutletMeshGroup ] eft1 = tricubichermite.createEftNoCrossDerivatives() setEftScaleFactorIds(eft1, [1], []) @@ -1140,7 +1130,7 @@ def generateBaseMesh(cls, region, options): ln_map = [ 1, 2, 3, 3, 4, 5, 6, 6 ] remapEftLocalNodes(eft1, 6, ln_map) elif e == 2: - # 7 node collapsed element where lv outlet ring expands into + # 7 node collapsed element where lv outlet ring expands into LV freewall nids = [ lavNodeId[0][0][2], lavNodeId[0][0][1], lvOutletNodeId[0][4], lvOutletNodeId[0][5], lavNodeId[1][0][2], lavNodeId[1][0][1], lvOutletNodeId[1][4] ] remapEftNodeValueLabel(eft1, [ 1, 2, 5 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) @@ -1154,6 +1144,7 @@ def generateBaseMesh(cls, region, options): remapEftNodeValueLabel(eft1, [ 6 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS3, [1] ) ]) ln_map = [ 1, 2, 3, 4, 5, 6, 7, 6 ] remapEftLocalNodes(eft1, 7, ln_map) + meshGroups.append(mitralAorticCurtainMeshGroup) elif e == 3: # 6 node wedge element bridge/curtain between mitral and aortic valve orifices no = e - 4 @@ -1170,6 +1161,7 @@ def generateBaseMesh(cls, region, options): remapEftNodeValueLabel(eft1, [ 5, 6, 7, 8 ], Node.VALUE_LABEL_D_DS2, []) ln_map = [ 1, 2, 3, 4, 5, 6, 5, 6 ] remapEftLocalNodes(eft1, 6, ln_map) + meshGroups.append(mitralAorticCurtainMeshGroup) elif e == 4: # tetrahedral cfb shim-bridge connector element ni = elementsCountAroundLVFreeWall + elementsCountAroundAtrialSeptum @@ -1192,8 +1184,6 @@ def generateBaseMesh(cls, region, options): no = e - 5 ni = elementsCountAroundLVFreeWall + elementsCountAroundAtrialSeptum + no nids = [ lvInnerNodeId[ni], lvInnerNodeId[ni + 1], lvOutletNodeId[0][no], lvOutletNodeId[0][no + 1], lvOutletNodeId[1][no], lvOutletNodeId[1][no + 1] ] - if nids[2] == lvOutletNodeId[0][2]: - addMarker = { "name" : "Aortic Valve-Coronary vessel", "xi" : [ 0.0, 1.0, 0.0 ] } if no == 0: remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS2, [] ) ]) remapEftNodeValueLabel(eft1, [ 1 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS2, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) @@ -1214,13 +1204,6 @@ def generateBaseMesh(cls, region, options): #print('create element lv base r2', elementIdentifier, result, result2, result3, nids) elementIdentifier += 1 - if addMarker: - markerPoint = markerPoints.createNode(nodeIdentifier, markerTemplateInternal) - nodeIdentifier += 1 - cache.setNode(markerPoint) - markerName.assignString(cache, addMarker["name"]) - markerLocation.assignMeshLocation(cache, element, addMarker["xi"]) - for meshGroup in meshGroups: meshGroup.addElement(element) @@ -1275,6 +1258,7 @@ def generateBaseMesh(cls, region, options): remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) ln_map = [ 1, 2, 3, 4, 5, 6, 7, 7 ] remapEftLocalNodes(eft1, 7, ln_map) + meshGroups.append(supraventricularCrestMeshGroup) elif e == 2: # 8-node rv crest row 2 element 1 rvin1 = -elementsCountAroundAtrialSeptum - 2 @@ -1295,6 +1279,7 @@ def generateBaseMesh(cls, region, options): remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [1] ) ]) remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS3, [1] ) ]) remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) + meshGroups.append(supraventricularCrestMeshGroup) elif e == 3: # 8-node wedge rv crest row 2 element 2 rvin1 = -elementsCountAroundAtrialSeptum - 2 @@ -1313,7 +1298,7 @@ def generateBaseMesh(cls, region, options): remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS1, [] ), ( Node.VALUE_LABEL_D_DS3, [] ) ]) remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS3, [1] ) ]) remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D_DS3, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) - meshGroups += [ conusArteriosusMeshGroup ] + meshGroups += [conusArteriosusMeshGroup, supraventricularCrestMeshGroup] elif e == 4: # 8-node rv crest inner 4 by rv outlet rvin1 = -elementsCountAroundAtrialSeptum - 2 @@ -1342,7 +1327,7 @@ def generateBaseMesh(cls, region, options): remapEftNodeValueLabel(eft1, [ 7 ], Node.VALUE_LABEL_D2_DS1DS2, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS2, [ ( Node.VALUE_LABEL_D_DS1, [1] ) ]) remapEftNodeValueLabel(eft1, [ 8 ], Node.VALUE_LABEL_D_DS1, [ ( Node.VALUE_LABEL_D_DS2, [] ) ]) - meshGroups += [ conusArteriosusMeshGroup ] + meshGroups += [conusArteriosusMeshGroup, supraventricularCrestMeshGroup] else: continue @@ -1437,61 +1422,44 @@ def defineFaceAnnotations(cls, region, options, annotationGroups): New face annotation groups are appended to this list. """ MeshType_3d_heartventricles1.defineFaceAnnotations(region, options, annotationGroups) - - fm = region.getFieldmodule() + conusArteriosusGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("conus arteriosus")) lFibrousRingGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("left fibrous ring")) rFibrousRingGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("right fibrous ring")) - if (lFibrousRingGroup.getDimension() == 0) or (lFibrousRingGroup.getDimension() == 0): - # not already added by full heart scaffold - nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) - lFibrousRingNodeGroup = lFibrousRingGroup.getNodesetGroup(nodes) - rFibrousRingNodeGroup = rFibrousRingGroup.getNodesetGroup(nodes) - # make temp group containing all elements, faces etc. if have any nodes in fibrous ring groups - tmpGroup = fm.createFieldGroup() - tmpGroup.setSubelementHandlingMode(FieldGroup.SUBELEMENT_HANDLING_MODE_FULL) - mesh3d = fm.findMeshByDimension(3) - tmp3dMeshGroup = tmpGroup.createFieldElementGroup(mesh3d).getMeshGroup() - coordinates = fm.findFieldByName("coordinates").castFiniteElement() - elementIter = mesh3d.createElementiterator() - element = elementIter.next() - while element.isValid(): - eft = element.getElementfieldtemplate(coordinates, -1) - if eft.isValid(): - for n in range(eft.getNumberOfLocalNodes()): - node = element.getNode(eft, n + 1) - if lFibrousRingNodeGroup.containsNode(node) or rFibrousRingNodeGroup.containsNode(node): - tmp3dMeshGroup.addElement(element) - break - element = elementIter.next() + # temporary groups + lvOutletGroup = getAnnotationGroupForTerm(annotationGroups, ("LV outlet", "None")) + mitralAorticCurtainGroup = getAnnotationGroupForTerm(annotationGroups, ("Mitral aortic curtain", "None")) + supraventricularCrestGroup = getAnnotationGroupForTerm(annotationGroups, ("Supraventricular crest", "None")) + + fm = region.getFieldmodule() + if (lFibrousRingGroup.getDimension() <= 0) or (rFibrousRingGroup.getDimension() <= 0): mesh2d = fm.findMeshByDimension(2) - tmp2dElementGroup = tmpGroup.getFieldElementGroup(mesh2d) - if tmp2dElementGroup.isValid(): - lvGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("left ventricle myocardium")) - rvGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("right ventricle myocardium")) - is_lv = lvGroup.getFieldElementGroup(mesh2d) - is_rv = rvGroup.getFieldElementGroup(mesh2d) - is_exterior = fm.createFieldIsExterior() - is_face_xi1_0 = fm.createFieldIsOnFace(Element.FACE_TYPE_XI1_0) - is_face_xi2_0 = fm.createFieldIsOnFace(Element.FACE_TYPE_XI2_0) - is_face_xi2_1 = fm.createFieldIsOnFace(Element.FACE_TYPE_XI2_1) - is_face_xi1_0_or_xi2_1 = fm.createFieldOr(is_face_xi1_0, is_face_xi2_1) - is_exterior_face_xi1_0_or_xi2_1 = fm.createFieldAnd(is_exterior, is_face_xi1_0_or_xi2_1) - is_face_xi2_0_or_xi2_1 = fm.createFieldOr(is_face_xi2_0, is_face_xi2_1) - is_exterior_face_xi2_0_or_xi2_1 = fm.createFieldAnd(is_exterior, is_face_xi2_0_or_xi2_1) - is_left_fibrous_ring = fm.createFieldAnd( - is_lv, fm.createFieldAnd(tmp2dElementGroup, is_exterior_face_xi2_0_or_xi2_1)) - lFibrousRingGroup.getMeshGroup(mesh2d).addElementsConditional(is_left_fibrous_ring) - is_right_fibrous_ring = fm.createFieldAnd( - is_rv, fm.createFieldAnd(tmp2dElementGroup, is_exterior_face_xi1_0_or_xi2_1)) - rFibrousRingGroup.getMeshGroup(mesh2d).addElementsConditional(is_right_fibrous_ring) - # hacky correction around LV outflow - size = tmp3dMeshGroup.getSize() - elementIter = tmp3dMeshGroup.createElementiterator() - identifiers = [] - for i in range(size - 8): - element = elementIter.next() - identifiers.append(element.getIdentifier()) - for identifier in identifiers: - tmp3dMeshGroup.removeElement(mesh3d.findElementByIdentifier(identifier)) - is_lv_outflow = fm.createFieldAnd(tmp2dElementGroup, is_face_xi2_1) - lFibrousRingGroup.getMeshGroup(mesh2d).removeElementsConditional(is_lv_outflow) + is_exterior = fm.createFieldIsExterior() + is_face_xi1_0 = fm.createFieldIsOnFace(Element.FACE_TYPE_XI1_0) + is_face_xi2_0 = fm.createFieldIsOnFace(Element.FACE_TYPE_XI2_0) + is_face_xi2_1 = fm.createFieldIsOnFace(Element.FACE_TYPE_XI2_1) + lvGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("left ventricle myocardium")) + rvGroup = getAnnotationGroupForTerm(annotationGroups, get_heart_term("right ventricle myocardium")) + is_lv = lvGroup.getFieldElementGroup(mesh2d) + is_rv = rvGroup.getFieldElementGroup(mesh2d) + is_lvo = lvOutletGroup.getFieldElementGroup(mesh2d) + is_mac = mitralAorticCurtainGroup.getFieldElementGroup(mesh2d) + is_lfr = fm.createFieldAnd( + is_exterior, + fm.createFieldOr( + fm.createFieldAnd(fm.createFieldAnd(is_lv, fm.createFieldNot(is_lvo)), is_face_xi2_1), + fm.createFieldAnd(is_mac, is_face_xi2_0))) + lFibrousRingGroup.getMeshGroup(mesh2d).addElementsConditional(is_lfr) + is_ca = conusArteriosusGroup.getFieldElementGroup(mesh2d) + is_svc = supraventricularCrestGroup.getFieldElementGroup(mesh2d) + is_rfr = fm.createFieldAnd( + is_exterior, + fm.createFieldAnd( + is_rv, + fm.createFieldOr( + fm.createFieldAnd(is_face_xi2_1, fm.createFieldNot(is_ca)), + fm.createFieldAnd(is_face_xi1_0, is_svc)))) + rFibrousRingGroup.getMeshGroup(mesh2d).addElementsConditional(is_rfr) + + annotationGroups.remove(lvOutletGroup) + annotationGroups.remove(mitralAorticCurtainGroup) + annotationGroups.remove(supraventricularCrestGroup) diff --git a/tests/test_general.py b/tests/test_general.py index 17f0e57a..b690c4c2 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -109,7 +109,7 @@ def test_user_annotation_groups(self): annotationGroups = scaffoldPackage.getAnnotationGroups() self.assertEqual(24, len(annotationGroups)) - endocardium_of_la = scaffoldPackage.findAnnotationGroupByName('endocardium of left atrium') + endocardium_of_la = scaffoldPackage.findAnnotationGroupByName('left atrium endocardium') self.assertTrue(isinstance(endocardium_of_la, AnnotationGroup)) self.assertFalse(scaffoldPackage.isUserAnnotationGroup(endocardium_of_la)) self.assertFalse(scaffoldPackage.deleteAnnotationGroup(endocardium_of_la)) # can't delete auto annotation groups diff --git a/tests/test_heart.py b/tests/test_heart.py index 6bf090c2..430dff84 100644 --- a/tests/test_heart.py +++ b/tests/test_heart.py @@ -39,9 +39,22 @@ def test_heart1(self): context = Context("Test") region = context.getDefaultRegion() self.assertTrue(region.isValid()) - annotationGroups = scaffold.generateMesh(region, options) - self.assertEqual(32, len(annotationGroups)) fieldmodule = region.getFieldmodule() + + # Need to do the following manually to save originalAnnotationGroups which has some temporary groups + # annotationGroups = scaffold.generateMesh(region, options) + with ChangeManager(fieldmodule): + annotationGroups = scaffold.generateBaseMesh(region, options) + fieldmodule.defineAllFaces() + originalAnnotationGroups = copy.copy(annotationGroups) + for annotationGroup in annotationGroups: + annotationGroup.addSubelements() + scaffold.defineFaceAnnotations(region, options, annotationGroups) + for annotationGroup in annotationGroups: + if annotationGroup not in originalAnnotationGroups: + annotationGroup.addSubelements() + + self.assertEqual(45, len(annotationGroups)) mesh3d = fieldmodule.findMeshByDimension(3) self.assertEqual(332, mesh3d.getSize()) mesh2d = fieldmodule.findMeshByDimension(2) @@ -49,7 +62,7 @@ def test_heart1(self): mesh1d = fieldmodule.findMeshByDimension(1) self.assertEqual(1567, mesh1d.getSize()) nodes = fieldmodule.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) - self.assertEqual(614, nodes.getSize()) + self.assertEqual(611, nodes.getSize()) datapoints = fieldmodule.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_DATAPOINTS) self.assertEqual(0, datapoints.getSize()) @@ -93,8 +106,8 @@ def test_heart1(self): expectedSizes2d = { "endocardium of left ventricle" : 88, "endocardium of right ventricle" : 73, - "endocardium of left atrium" : 82, - "endocardium of right atrium" : 74, + "left atrium endocardium" : 82, + "right atrium endocardium" : 74, "epicardium" : 229 } for name in expectedSizes2d: @@ -105,7 +118,7 @@ def test_heart1(self): # test finding a marker in scaffold markerGroup = fieldmodule.findFieldByName("marker").castGroup() markerNodes = markerGroup.getFieldNodeGroup(nodes).getNodesetGroup() - self.assertEqual(7, markerNodes.getSize()) + self.assertEqual(4, markerNodes.getSize()) markerName = fieldmodule.findFieldByName("marker_name") self.assertTrue(markerName.isValid()) markerLocation = fieldmodule.findFieldByName("marker_location") @@ -122,14 +135,9 @@ def test_heart1(self): self.assertTrue(apexGroup.getNodesetGroup(nodes).containsNode(node)) # refine 2x2x2 and check result - # first remove any face (but not point) annotation groups as they are re-added by defineFaceAnnotations - removeAnnotationGroups = [] - for annotationGroup in annotationGroups: - if (not annotationGroup.hasMeshGroup(mesh3d)) and (annotationGroup.hasMeshGroup(mesh2d) or annotationGroup.hasMeshGroup(mesh1d)): - removeAnnotationGroups.append(annotationGroup) - for annotationGroup in removeAnnotationGroups: - annotationGroups.remove(annotationGroup) - self.assertEqual(23, len(annotationGroups)) + # need to use original annotation groups to get temporaries + annotationGroups = originalAnnotationGroups + self.assertEqual(26, len(annotationGroups)) # 23 + 3 temporary groups refineRegion = region.createRegion() refineFieldmodule = refineRegion.getFieldmodule() @@ -148,7 +156,7 @@ def test_heart1(self): for annotation in annotationGroups: if annotation not in oldAnnotationGroups: annotationGroup.addSubelements() - self.assertEqual(32, len(annotationGroups)) + self.assertEqual(45, len(annotationGroups)) mesh3d = refineFieldmodule.findMeshByDimension(3) self.assertEqual(2580, mesh3d.getSize()) @@ -157,7 +165,7 @@ def test_heart1(self): mesh1d = refineFieldmodule.findMeshByDimension(1) self.assertEqual(9983, mesh1d.getSize()) nodes = refineFieldmodule.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) - self.assertEqual(3693, nodes.getSize()) + self.assertEqual(3690, nodes.getSize()) datapoints = refineFieldmodule.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_DATAPOINTS) self.assertEqual(0, datapoints.getSize()) @@ -181,7 +189,7 @@ def test_heart1(self): markerGroup = refineFieldmodule.findFieldByName("marker").castGroup() refinedNodes = refineFieldmodule.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES) markerNodes = markerGroup.getFieldNodeGroup(refinedNodes).getNodesetGroup() - self.assertEqual(7, markerNodes.getSize()) + self.assertEqual(4, markerNodes.getSize()) markerName = refineFieldmodule.findFieldByName("marker_name") self.assertTrue(markerName.isValid()) markerLocation = refineFieldmodule.findFieldByName("marker_location") From 880aadf806e3efa6a40d8115cfe2dff6e385ff6b Mon Sep 17 00:00:00 2001 From: Richard Christie Date: Thu, 9 Jun 2022 20:01:23 +1200 Subject: [PATCH 5/5] Add heart scaffold documentation Improve docs index page. --- docs/_images/scaffoldmaker_human_heart.jpg | Bin 0 -> 106373 bytes docs/conf.py | 5 +- docs/index.rst | 13 ++-- docs/install.rst | 5 +- docs/scaffolds/heart.rst | 83 +++++++++++++++++++++ 5 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 docs/_images/scaffoldmaker_human_heart.jpg create mode 100644 docs/scaffolds/heart.rst diff --git a/docs/_images/scaffoldmaker_human_heart.jpg b/docs/_images/scaffoldmaker_human_heart.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6619c139fd9fff8bd0a439375f15d9ec4e93353b GIT binary patch literal 106373 zcmeEt1yCGa*XH2v9vlXDcXtRH+--mX26ssa?i$?P2{Je&xI==&;2tyt5+q2L_y52B z|F5=oxAxnu+N!O6@6_!%-F@bsexBPs^Yl4Ae^&l%18`LpRTKemaBu+2mlxpA4sL~l zAJ`56P*(@A0ssJ103w_i0RF|ods$J90s|0VY`7QOG%x?JcK{s#fE#o|0YG`#hWWBC z`O*OtCoc=szpTI8-wgcCz~2n~&A{Ib{LR4sj|}L5Z5*sTY-!{iJiwk_V9*N(aQ~?n zP+k7wW%QjDd z*UR!R0r6)C9qi!cJX{W$S7U;(M`1<;C`totQdf0REh=_=A zar1KV@^ZX*aCrK=cv<;zxOme4V*q)Or;P{L%?s@6Li5*vR@ScGUJ`WP-e6l%J1aW@ zYg=0Z4r?nuTMix`TPqGLTRt8RTRuKpApss?UOOIZx_^YXwfT?eZr&cw{}$WUh705j zf`DASJYU%0;iBXE_k;ew8dFl-)<)FE17zjp>hbU4-+3!{Ywn|OU{3vUH^~K^`Ej4Uhw-8@ z(1~zxG4V)=UXhU!k&=*8G0~D!GEkC`(s9x;Ftf0+v%R9>;^SiFWnyJx{c8|76jW4n zG;~4?3_?~4QVQ08x&7$_;Gx3D!vhfD=m79|a0qyCe}(|mFX=R-mjd2jHN1Z`ICum^ zBxDp+G<1v?hgMtwJRAZ7JR$-T65>ns4lej*KL8OA37?in2AM$H3Wd&{koSE`F)F=m zQ$LZ;>?M%T+9MPVotT7_>=gqe6Eh1dzkr~Su!yLfyn>>VvWn^(T|IpRLnC7wTacZ- z1K82i%iG7-&p#k6JR&kGIwm$X?L&G-W>$7iNoiSmMP*fWO>+yhwXMCQvuj{*Xn16F zY;4Or;^7P9{U_Mf<5089k9m&rrG14sex+hUoE;QycRY)wt`_2S%}i|kgPmc|d4 zjtz$^ll?Zc;SR?TOF@J|4GmM1l!}}hW7+C5W?3+$2=9EC*^4s>q0z|jtBXU2^X*N5 zq+y1lE>|dapRWxQDRSGJ0dwkheRa8E-f2OPgmQ2KKhP!~`UL}d#seNN-or!srz@V( z%DTCuz9e1egPLE!1hZA>po3pNrn~YdFp@k6e?$g1n!+|}qm7HUcM?O)JoEtrpeW^y zk~OilY4D-D)~jEibVLzb-B)LznyqD~KHl9&(eZc_EkRZ=|KoZ(DPMvPY0&yRlFS?5 z)q-x{Hc=OsN;Tt4%?QY&REeV&mZwIXUaXOhKU3&WV8PHgFZYVB;DgA{#A6w8q1#3c$|5zkBNeUwAQ zM`d!YneWmU`P8j5y?!D>%*A@Af9; zV8Nilue%@S`0aNP?8M~T@80R@Auj()*n8*cR|;1>+Q}g-$!sdq7uI;PxxPR7uz5_sF+Q^IQo9 zrP#S(kB$w(-_tp~M=8mmJ({pD);My8U87O0>PUOf^R25CL(wjDDR!aOfQ^HR@X|du z6tFN83QDXChYJJn#tuTLt!$;>ymBWuWSpKyk`cghy=sb2-|Ileaf!WT!)8UK8K`Yqt-idq$nc4TE1#w-Ro16;oW86n&+hneNr7pr8j&(D5LN)g*^F)5SN$SF+`{36u3}OFm!SFR->cfXz!e_7LUx-h zgqWe+hC&m5BWOX*XPgeVBCpPSh_(aG^a&Rp)j1a1a`|f^_DCgZ?!jUix)|XeO_BPO zXVyNvCEdsv2~1($UI-`}=`nrE0%~Bhq$1tqCdoce^I@Wj%I(jHD^48t3{&RRmkR@P zrxO?yuOdc@eh5uXOfoKdUgb|nkBw|EYKLuPzfFw4h*z5wiO4 zoUJA?vzbYoD$cJN9`dsQ`w$)UxQqgXOW)`-F3!@P`MLsTL)qVE8w-N{BO>mBXyf;N zU-F_2hr)}Kd9Ed>#!g5=1(Rjqv2IB)c^j3IF$$*uqD{1lDu7>~7#AY5ZBU{hr4Qn> ziY1q9pcL#?o!OZCIK=~)1|gYYLmzGY(gcqkuSb$jL;Gc|QH#TtoXRhU%5|%_vqQ0L z>9q{yeo(%~d0*dELY5s2%dK_!+wbSnQvXuT&Q-#@>+^ku46>Ue*KC5&r1(@Jgs`#x z!){csUa8&IiIslp)khU}E6a>se})WuEfew|t;HA}cr4f2dV=GevqUVj&0VMl-OwbO z66midsyD6Q-?G{hOgUlW z^c@sWWEFa-Ff-?QFf0Z@Rjk3ia~r)p8vOXqZ>cT8)7Rf;PUhOgKL#MLH)FP+$nS;X zB6k#yblSS)+KR!UKRF8u>7m?!R3xvkGP0XdV;m?t6WoQ9iR0Ichx)s zqk4>TqiHz=|}NGbhmH23c(Qc-D+5OpE%Dz-2!4C7QZ z84riNnNp1z#WaghZ4j8kLfBjfc(zvka)y7B@A=zstTP@mhio)ih-46t-78IZW5y0y zHM8b|(SR#CGwnBD&=fo+I-JziOc(5O7CyPD8Yk;D4L0mWj(VW7PF1FMCJKA~0W><2 zH~e&}1amZKO9uM873af=3zN%@XplmVqW8_4504a=g0QRzmnKWegB7>Q>XuZB63SvJ zBSF6QW`~0JAM#KOU$Un08*GEu`C7-K@yAptH41EuWkpb|{o)Vm23AW>vMQczix6+IEt$x~9Rehx8cOM-S(A^Sdw+)8$ zs@Wv4*Ni`RE{Zcjs=%2@kaIcq8+?EzI%Y7PrCytg;B4NmU=+RQ002xQ! zV--cTw_7{d6};aeoL=gt)wQdrg&yusrYB5S@8@GE&H;KVuiqg3v=Vy*b6uUPcMI8@ zvg%-#%P=K)7@L|*Unl)uJ^*%unGKg1m#?oWX5-uG8b<3s=z<$rEStivqmr9+?$B#~ z@Rwhdfi*OUbA5z6pK%<16QK3T>>4%98Ii88fPLgQHV4NH&}fnQu9_edx=1v9RIDFf zol)U;_<3z1t*r8uK`3A)lLEWxGe9Gc0uz_<4gArszNX7y`!Yx4eUkAS$4p+ZuZ(%1 zvfMN#cHLXU4RY5F(;{uwL4$soxLlyTBJyN0<8a(U%IZ!Y3tM4t)rDGPx9; zC@jV+@W~iL1U+Oj#TpPG(D%|qd^S8k!f@4RScZeUFeaX zTaO-Smh&aFR+$Zb{1#zY(~jV8+1YQl<)%=4x~aBqYO*?5a@-_&T6@Z?(1$fA;Gt}1 zou>$R)0&7U$NT+E$G~2w9kPn=Xo2Fw^*Zwv;jGd0{Q* zHs=tjY{Jwf8I1PL)*8r?Q#-IiiZ$l`0GQ#jqKxHs>Un3+y=qsy>%kp8iTy6dG`gXx z()2v*xQJQnlO!8JB1ox-0tcrnnoxZlE)>_?xOXI>XgaXPB{fI_(Zpqc-JNl1C{lnY z9jrzVZ^%56t+SZ;Ik4O;pXwX=(ly*Q$F>aRRr5VBOqP%t+X|*-%Pja~Bzm3i`Yp|m zU-kRG?!hG&l*R}2CcVbmGZLcT_P%mrn&B7U2o)9`~HQs@O&=%~Xu4Yf|& zEc9cXpeuH*m3O(5mjAL4AFaJ#T+lSo9oN_}M-ezs=`nWN0GfeU}B`--i5j^fT_=MmO@PLlJ@DNwra2f7p|j@-i#6HQdYPV`l?XH2o<`L0v91oFk) zl3HAK>gE)wJBA8?73iLW5cH{wVwfloQ17#h;WW?ICF zHAKUMk8Eh*FUv=h5=(2%X85Hz{`eQU4MS!8PqlL?BX5%`+Q9UzP>&ti`x8Q9?2b2r zL_K3E>q)!}KGzaz>fLh!XwKfKuZ8gNI(?B5anYL_$3Qfpr1+$`GBjak2I2PIRIKxB z8dpKHoEGP_@l^Ua*+)o6`lpoIH28BI|xWsIhHuG;q>E)6WV{Y!hBD9pu;T zPc-qm$Y-H!lkUc$mx7&76{P%$WYq)fu6{F@bZYedC8g@6u)fvrQsWsNhkCWCO7FpX zABU7J4YXXanL2x2KG6_>69t?NQu21MKUqp%^?N7R$)w(r!L7Z6=M1m@C0o_sr|4=Q z8eNCpv6k83xXiJ$7I>8E?ASzFqVz$spPVpNkp>PXy#G39_wf$k&a*(-Sfz<#RVu&ew{M*Hrd z{N;Z8?IR@hSHd8%c83ZD&kncJl-}dPhzLDHy;|TdV_WK$_EOPT2q$4(n|q3LVu00o z{W49;DmK2{UmnL!NO-|^*kiPh>cv|6vvxQY`l>T?J=Z<=dDXW-*o;Knal z3uD9uYRv9(b-L!Z%{0)yGp10h=W}tFkLx_Y;dvc4a%PY6b?I{m2lQO>@WkJnvE$ml zrA4W=%`R!msj6IIGTk}FmGSLsfM{BTcy{aM13cYgE8r-C_js%d2QqADjZzILw3++h zIyK5^t{W{Nfulbg^!Yr2^JZ}7OXf|jZ`|7#m4zTY_BgE5)|oSLMP6&bGmRJrbV2rM z6hSF;H$%>+>MXxc+EI7U8v`VRGhGQAlrHb>ZIj}UXq5p4e!?F)n@hkn%lsH<6|V=* z8e7YnJjXEbbT_fx%*YE+X#gzi;VC`KKCpt?mmkO9U-?DjIF|Cok zypESV+g4I|^P9#d&L4)k;kQXfwuSqSQWg=YjrHTq=e$k><7MjBPK36=d1r!G=4BaI z{zC*!bhsb07aCSo#`>uEEM&F@of_@}jS63XYVB&Bh31Zv5sIYF`RANZT)=dT6MbaI zhWG~Ym|QltBn|QnJeSHZU-Xq>?!P3kUc?wBN?1K4SHDo_GoFfT*;?+rD~&!0Hkl6d z1ovQdhVtY`vUrt5cQc$=E{fB6Bj_~m@!3)>TiYIsf%O%>hsPD)XW-sjz7W>J5N`+e zpGkN|<6wkIaoiau2b&zdL>5V}L=j}_?{#&eMC~G@TN)J)JI}4VuB02IWhN#{Y#WDm zLg+MC<@dXtN=!#e(%-|^r4wcV1BqSu+DcS(*r0Q4ksgcfI1Z{+@wGJ;P$*n-H3^Pe zZgvNib#9~b-YxD04B6Od_iGS>ySsfLuxm8BR80Q2*Yj&*PtRMkuNfcQ3zMChLMc6X zVTV#k$MX$w!N5NNhmKS+|F-Z4&CE`uB?R26_~`>@CSH0GOB>z#Zff{`&tT5pni82Z zP28T=rkL35)xB$dK);_JQWz0Zh|E>PFM(BJSi9VpJRM(?xC%A4*PKUl&;&_!r6W&? z;vT$OtN}1qe{v*ErmjwT)OTvU2##5!yY&6{dmUDIWyXETYOX|vO>4zE!@NW^wo5a zXhdRefJ+&J%VGE+m1s}HF-$9E|N?15Zts3Y3ojJ+G4@QOTUuNYAHEon+?A$IUUN-keib+v61qL zNDCte7;$Z?EaEvalMDqvTlEsdB|4i7c)_qd`MWyoJ(hgkJ9WJs}K3<(iLPY6m{ z&k>7{S`}Ly-=2csxyZk2?PAzqwMmvI-G-x&?v};9_UF%^zc^E@p8UwQcVmF|hALc#pdh6SadK;5Z};uT zTfQ>01a>X{Olj^C7>Y2`kU>hKS^6B#Z}JW*`MI9vd3W|dN9W>h{Qcb8cxyN=x9LY@kO*s8)&Wf;YptBZ zT_&al*aB}&-Ph_}QY*P;x>1Ln(Y8(;kG;%wh5^V9`wFvY@1ytR+Ec)_UN;+md&7ud zp?kvDyn8<;F+~8yfloCeE^4=A5wWEJk!WJJUvYdCxZ};jp((trtm@-BZU7RfE}ws? zqNY?+ZvJRCNMaYFh!r?6yY85&MV!r~B&6;nsGP7m5mQFnxWl=q-BW}(j-UMO{cbyn z_)h9g>b#2X4~cNTF`2oI3w*Sc5X@7&EyPk(&*XPe>_X_>+g~tYIHYRsH#Zqy)I-&> z55oQ5^yAVR?|9}BD)P~)A2AnMHkMJ72;?hXot?6khxs$!GX-<9jG0XZwe%WgO9$7W z32V30i$#f5PW%)tHOMCKl8Ri@>FQW&&3NEgZFPA2epClx zU|I8PaOO@0Hvslk8jJh;+zZ(W2&!g$+gbTz4G!(ov{fhcqOC$%*s_k%nX5VKD|@

YA98`kum01SJiFm08bHHzTM4)$L7#n;cP1oAHNCyTD!z zMKl+5SR+TAKLu`Vk-cLTQV6AZQ<5C~$TBL#g@h!z>dMeETZvR|<!4Ts9}aFYGg@MN(scoGlsdzS*DSvu`yk>R9*X@J|v&Hi3tC6LrK} zM{adOoLaU|b7*Xxiqac7pUc^!?F%S3t(BU^ae6L%ECaWjs5KpD3t${JL-imCNiJ}i z4z{}O4HtQy5>NdhMDTrR>B4Qj8E{I^F8Zf6%piQF(UPmUv!~D<5mlds!_s?Hd|G@$ z$s%0?u>a%kqt;?W#6>Qj0!Qm3Z;w7VdEo|sa$npbCAsqHXz;mI9@^@)1MKzec#^MgN)+AU z=1WE)GM{*z1R>P_x-)x0bxN-Tg=TCNbxr`kb>64Iq;1xs^ejX0N5tWMExn&fTUuM^ zzXvh8*er1BXtq|a22EeCD~^E`XXL7zo{sOJH{V)Xz;f27l0i@TWsd?`)i!%NID)Nx3(UIo5eYk0~X*#;YijE=@E#tyM;6VM>nuRC7?W2BK(4Fr<2)A4u z8qPDP+BZofRZ1*6`m)~5bDeI2F?JcA3OZ3|(!{?w`6A#$_CT&1Axg|O zBhsZvs)FmOGS?dLJ@8gS zR3K+%=NAOw&8&FE0z_3oGAK8C0~F~DFtM#tmE7o~EIa?JhBEU}wQ(8x9qs<>u{LLq z9h{3|Q60P8DE$)!E(kX0(G!mCA7hB{lB@sC6vN+XH+N3=)tj8uAc%!M)x^vtxOlbP z+Oi)ctHzt*wJU%<7^!R9s#J>pBY-ceq~Rn-vbbb}<5RWc&Bf^_UVoJ|AWFjM#5dJE z(bP%`0^M4Kf6lT04=YafNf>tl?+BcnY%s*#Yxr9xQpnKh5+_H@VssUN$i50=8#$vN zt<;5{ag&hyOBml6EbPTz>$>iSoF@Y*uHxx4F+22;$`HGnBVY+k`ax9#y&y&G*X`AJ zOaoCWp!r97U%$qIREFo2`s}hC1?D7zFv3{!+uZ9`4+2&~{8EHx+(7EzOq)V~0DX&p z0Q;=7&50ON=;o$oWz#pg;mWTzEjZHm1}X9=f`8G6-D=8(dTc4TFGxCY>L0W~opodP zz9OK}s7?hq+lfD}C$oLcz{jweu1v@^Zgw&w(3u6=e|<}N5+g_s+APTwB2em7O&>tr zwPjUi{?Q1TBKOEIorP)#ok1qcv{k)5yH$`Nch;{B^reHlGh6m9p=x# zGZFk)CkW4TP3sQtF$8bzra1oxz>%n^wM(zTvifSGE~i_tK9$TndhY=!1nXTmcC|F0 zW5(aWMKU5p@>wO7CTy{6@c7I!<2}>r8Mw>l6igHzgLz8Xk{8+KAN)TN3AC&8-J0X z?0GdD+AJw{BnG$d&Lj)>Q8ixgs2;%;wyuwX0Y2ZfSZj<1{92)O3F z#d)>d57SV^PJjz44lpfs#yjr*<1c_UG=fk#dYiQd5=+F9mP*LX>G-su??rBRj}Hu|PG&}r?Y#rQjf;Dz z9CjT>?oN>XV2cDsC8XCq8iz6tA~}W)-8%UXdvbLzSj&vjBg%f|0JMi8 zk6KP#C;LCGL5TYOJYTmL>Lp$eNHRW;^|rI|JTJ<@D_pZse^UQ8K@EV0(r8ShZK ze@mCCHtT%!qjhniY4YvT%_eQ5T?!~AsD(XoRv2F)A_lkNSjuCc&{h{q_uDY5u^Gj4 z?slyO;R*2%qi8R0e_XXd!X$U=lPN-oIFz^WL-c`jQ;?lhx{1{Am6{C*R4iwABPc_B zItgr^8fr8mehtSA^G{bKi-Rx8T^X=gxI)7vwj;%~U#Bt@o?&q(-#e1W#0>-8Eekn#(yiZuh6jFAqF262 z{GnKg6F2vLNK~~WysPJ3?>q25|?H@QCxt4+9ew zSjYOdLvd)rV7=6@+q!0dkr_`oltQ}_JZUU1M+`qCI!j@}SzCK-pBphGjO<0$1{cBn zGF5uI<~a_+bek1WG!daG+-{K+LQ}JO&*$TuubF)07*1V;N_W2DCi$Y2l{Zwz0a^*J6pS z3ZyGOar`;uzitz}S4*c`%G`!7MyIXF^b$zZ7!UNMN%IG3C-FNRN!mG|ohEA1Hn>yf zcv@|e03}={+ew*4=ml8~6jNZ!sDvUS3k*=wiss?3)s?SfVk5sT9YpW-Tw zr@cLi&%gGQ`T8qQWohwUMPQ65jVYfRXKYfX@*9e)_(Mud_G!DU=Z97w65x?jWcD8? zN;|}N3g1!HdRLT1l*~feLb42^&Q6pV1>E93?UCM_MhT0>Ahz-Ds8b0Vrk%s$zeG^! zuMi47h+vyit)d2Z_I-NMk!2k!A-JnQ zK28V^7|N9!vRU+(p0J6I-)9hx)jWX|DiT6SX4kTgVr`Cm8gqNSfZjZ zih!#M1kUS8^^i?nbykNcdSY37otM~6FI~LjO2+iaZhJhOQO^7L@4~I?rx~GL>RtD)RqL-uRBQ`YOh0I+D`56VN?v2W3*5@@WUv3-Qdn^Q zd6v{-wM+a#p`m?0@9pIYaT4jx+5wr$_XRGOo*kTsUo0lCJQS)!-^y6=exc{&>z;UX z$UGu>6;BWrHf#nFpN{CfJl|+UfLBvUqkpO}5^@<(67yW&i3Sj)#a6&hZ(npBgMw7c?Yn+vO>#7JlUg1%QkxLwjq8aTBc*DwgT3{= zN(@)m&x1Mn2|YFD^c1%QEjTO$K1&H2*eW(;z~Jv(2x5xoPZq<9Y;mPkN>7!9?Yu0B zhi`!4m`PmIq<+aLh^z#Q3wijgpJG7vyRDz+e-d`Lbftc!gH058N&C;@A1Z(4o+0#z zDq_DojxU?LP3fV@g_=ziu*RCb8R_bPrivNy*K6$B<@ijw57{%6x$sg=2}b;8yc?We5UMjXC9H#ft+D#y@l22RA<}x|S`9rs>(iCHYGR#_>VZ@REr!RU ziB-2)HD>Zb-R*YXGSqrrYitqd8kbX3vOlP~>K#2vu@=)*Tx~q2*tZift%=RLbE}aW zef9~i@`8$Gu|ZT0rLA95UmtM=(dPdFtQhBiYWSwDUZ*_inJO}1S^M&ZmhokDdOk%- znfeFt2h&s7n?C^6^HazE`YqkIPPRyVI0>i?RR9$!&G9v-jI6Xs-|2hI-y0jf1?5h4 z(XF^MZE2a(jPBiHJ(oiHpLEVKRiDG>8NlbsRw1swP)rFzl#*6^i z>Dh^)xpHPI?RTy-T}udweeI+j}2CJXm zU%oX+hIIVkxawld3xl|J>dUX)YUa$t~i!F%Cb5uKKEU69$9mBUoY%~S7NdU$r z2YO8@@T2%n{5pOod`b6`Akl8S?D@@*pN0lKb|GAk*0+nLnc*RiaOYGpwdEiCRPMhv zV|vG>!Co<-Zf|5c==CZ%51-(9+^>P*YSIg_`a;?nPrr3-gG+0H!RWlKUDt-f12%cc zT12Xa*N8f{hop$3!P<~pWNM`1-f-NGdqS@%$MA7A>pnDSm(f-jv=*6ci~HI{6O-*z z9feKjkyceDM`Du3oVU=YPha22FBVFFJ*PTc)fR+ajnbq_yTFgkG}{?2RL#5+yc*YF zQ-7+j*)lgYKf$+vyiY9MSMN<2PZe-TcMmxWL+zlSN*QQ6qPX~Ad%0AImW=`{N|kWg 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

T|*qsa1Rp3M-jVaSj47(g)m!kTT7>+_PjtG z%&o%IXiJrp9)4&83rh&+R70Ds^9rD151QOFyh0%E-0U?DZlkx{=57@n?GE-Wz}6{v zU{-Cs@c>19LS?kkQ(D%pj`{$gR@A4teE`_jqKMdtOsc~|dF+OJo8~(ki}TEYrG~zU zphJEVQq9ebL8>AB|HUHreEImK)SdA3v7@E7JxGMODvyc(yj>Pm;Oy_vp@6s?TeJ$ z#~0TAF0S>ZNLr#27~Nd^xwY{~)JlnE32sNXx@jwHo_209epDk_<7iXl=kxXDcNaWk zuM=y_fFD0j;rxluV1A{l72b18CN3#?^@$jV@rtk# z{!T&pQ7iG;zu~Sl(aOJZqABvPPFnRZQ^=fi!|z2QVil{JG$8&A<@OC_I*Y7@mNe$F zmC;uKC|&AnAQAIdgy|abWb9awxce~E24$kBbtD>w(!q$C^jOvg>W`e`E%FXEh(N#S zkLW)qkarB%AcTahPWsxB4skza$T}`SLE(VAJ=J^1}NxW@8vs%UzD1~G;7dVEOWvh-?gmx3C?5{tQ& zL0&VlH$8*%lz%Ks1SG}l5Ze^*micmG=>tant(Hao^+JTjZ73p#%3*xq0I3@j+DyNj z1f~J!-Z7F(W@d*F&X?^4Ik4{QDP)ic_aGz~XRM$|J1(_+D}@(+;cHi!VbUzQCN`PQ zDYFG}F7ojFj%=pI24nV_B0fblBTCEG)nQ5PX6^W8a-(n;292;4Bi17?y67(AIsP*K zDq0Cc-R5+8zpr6HyCq67OU-!N@f2o!v?Gi$9-?p8RUb6F7B2U@7F@XBRUZtcxp{Da znYIDE<6vRHtK9O1j122ike6swzl8HSld@XhY_;C`% literal 0 HcmV?d00001 diff --git a/docs/conf.py b/docs/conf.py index db8b0960..1e5cb046 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,7 +2,7 @@ # -- Project information -project = 'Scaffold Maker' +project = 'scaffoldmaker' copyright = '2022, University of Auckland' author = 'University of Auckland' @@ -33,3 +33,6 @@ # -- Options for EPUB output epub_show_urls = 'footnote' + +# If true, enable figure numbering +numfig = True diff --git a/docs/index.rst b/docs/index.rst index 7d83f837..ee6659e1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,14 +1,13 @@ -Scaffold Maker -============== +*scaffoldmaker library* +======================= -The **Scaffold Maker** is part of the software that is used in the collection of tools used for mapping data to scaffolds. +The *scaffoldmaker library* contains scripts for programmatically generating anatomical scaffolds for a range of organs and other structures, and includes utility code for building common geometric shapes, annotation, and refinement. The scripts generate the scaffold model in the data structures of the underlying *OpenCMISS-Zinc library*, and through the Zinc API clients can interrogate the model or export it for subsequent use. -.. note:: - - This project is under active development. +Most users will make scaffolds using the ABI Mapping Tools' **Scaffold Creator** user interface for this library, and its documentation gives a good introduction to anatomical scaffolds and some common features of them. +This documentation is intended to be a resource describing details about using individual scaffolds, as well as developing new ones. .. toctree:: install - + scaffolds/heart diff --git a/docs/install.rst b/docs/install.rst index 7b241847..dee6e930 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -1,6 +1,5 @@ - -Installing -========== +Installation +============ Install with:: diff --git a/docs/scaffolds/heart.rst b/docs/scaffolds/heart.rst new file mode 100644 index 00000000..8dca1ee1 --- /dev/null +++ b/docs/scaffolds/heart.rst @@ -0,0 +1,83 @@ +Heart Scaffold +============== + +The current recommended heart scaffold is ``3D Heart 1`` built from ``class MeshType_3d_heart1``; the human variant is shown in :numref:`fig-scaffoldmaker-human-heart`. + +.. _fig-scaffoldmaker-human-heart: + +.. figure:: ../_images/scaffoldmaker_human_heart.jpg + :align: center + + Human heart scaffold. + +The heart scaffold is a 3-D volumetric model of the heart representing solid walls of all four chambers. It has pulmonary vein and vena cava inlets, simple representations of auricles (atrial appendages), but does not have representations of the heart valves (only their orifices, or boundaries with the ventricles). Only the myocardium layer is fully represented (but see below). + +.. note:: + + Separate scaffolds are provided for the heart ventricles (below base/valve plane), ventricles with base, and atria. Beware that the heart atria scaffold is not compatible with the full heart scaffold because it uses different numbering for elements, faces and nodes; if working on a subset of the heart it is recommended to create the whole heart scaffold and delete the unwanted elements, which allows the resulting subset to be merged with the whole heart. Further to this, all scaffolds used must be built with the same parameters for numbers of elements, inlets, optional layers etc. to be able to merge together. + +Variants +-------- + +The heart scaffold is provided with parameter sets for the following four species, which differ in shape, and in particular have different numbers of pulmonary veins: + +* Human (2 left, 2 right pulmonary veins) +* Pig (1 left, 1 right pulmonary veins) +* Rat (3 pulmonary veins: left, middle, right) +* Mouse (3 pulmonary veins: left, middle, right) + +These variants' geometry and annotations are best viewed in the **Scaffold Creator** tool in the ABI Mapping Tools. On the web, the latest published generic heart scaffold variants can be viewed on the `SPARC Portal `_ by searching for ``heart``, filtering for models, selecting a variant and viewing the scaffold in its Gallery tab. + +The heart scaffold script generates the scaffold mesh and geometry from ellipsoid and cubic functions with many parameters controlling the shape. The parameters were carefully tuned for each species, and it is not recommended that these be edited. + +An advanced optional feature is to check *Define epicardium layer* (set parameter to ``true``) which adds a layer of 3-D elements outside the myocardium to represent the thick epicardium layer consisting of epicardial fat and other tissue. This is currently only implemented over the atria, excluding the auricles. + +Coordinates +----------- + +The heart scaffold only defines geometric coordinates (field ``coordinates``) which give geometry at approximately unit scale. Note that the scaffold has a *Unit scale* parameter (default value ``1.0``) which scales the entire scaffold efficiently. + +A material coordinates field is not provided, so to perform embedding at this time, it is necessary to use the generic ``coordinates`` field as material coordinates, built with the standard, *unmodified* parameter set (incl. *Unit scale* ``1.0``) for the species. + +The heart scaffold supports limited refinement/resampling by checking *Refine* (set parameter to ``true``) with chosen *Refine number of elements~* parameters. Be aware that only the ``coordinates`` field is currently defined on the refined mesh (but annotations are transferred), and the refined whole heart is only conformant if all *Refine number of elements~* parameters have the same value. + +Annotations +----------- + +Important anatomical regions of the heart are defined by groups of elements (or faces, edges and nodes/points) and annotated with standard term names and identifiers from a controlled vocabulary. + +Annotated 3-dimensional volume regions are defined by groups of 3-D elements including (using only one of the items separated by slash /): + +* left/right atrium/ventricle myocardium +* left/middle/right pulmonary vein +* inferior/superior vena cava +* left/right auricle + +**Terms for volume regions such as the above are not to be used for digitized contours!** They are used for applying different material properties in models and the strain/curvature penalty (stiffness) parameters in fitting. + +Annotated 2-dimensional surface regions are defined for matching annotated contours digitized from medical images including (where ``luminal`` means bordering the lumen or cavity of a tubular structure, ``outer`` is the outside boundary of a layer on the wall relative to the cavity): + +* luminal surface of left/right atrium/ventricle +* luminal surface of left/middle/right pulmonary vein +* luminal surface of inferior/superior vena cava +* outer surface of myocardium +* outer surface of myocardium of left/right atrium/ventricle (if need to distinguish) +* outer surface of epicardium (only if *Define epicardium layer* is checked) + +The following are proposed for future heart scaffolds including great vessels (the root variants being the subset of the luminal surface within the arterial valve up to the sinotubular junction) so should be used for annotating their contours: + +* luminal surface of [root of] aorta +* luminal surface of [root of] pulmonary trunk + +Note that luminal surfaces of atria structures are defined with an overlap in the scaffold as it is difficult to determine the exact boundary between pulmonary veins/vena cavae with the myocardium for the left/right atrium, which gives leeway for variation between digitized datasets. + +Note that at organ scale additional terms such as ``endocardium of left ventricle`` which represent microscopically thin layers are defined identically to ``luminal surface of left ventricle`` etc. surface groups. However, this is not the case on the epicardium which can be a layer of finite thickness at whole organ scale. + +The heart scaffold currently has no annotated 1-dimensional line regions. + +Several fiducial marker points are defined on the heart scaffold, of which the following two are potentially usable when digitizing: + +* apex of heart +* crux cordis + +At present these are both defined on the outer surface of myocardium, but when a volumetric epicardium layer is defined over the whole heart these will either be defined on the outer surface of epicardium, or separate points defined to distinguish distinct points on the two surfaces.