Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stomach #132

Merged
merged 40 commits into from
May 6, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
94eaf10
Initial commit for stomach scaffold
mlin865 Mar 4, 2021
efd7386
Space out elements at triple point
mlin865 Apr 1, 2021
a782aab
Create parameter sets for rat and human
mlin865 Apr 2, 2021
db65d6f
Spread out elements along more evenly
mlin865 Apr 7, 2021
861e3c6
Update parameters for rat and human
mlin865 Apr 8, 2021
cdbd95e
Add annotation groups
mlin865 Apr 13, 2021
1ec8235
Create annotation groups
mlin865 Apr 15, 2021
c2945a5
Remove redundant code
mlin865 Apr 16, 2021
849119e
Allow for linear through wall
mlin865 Apr 16, 2021
84b7e7f
Add unit test for stomach and update tests for colon and small intestine
mlin865 Apr 16, 2021
3a79f61
Add markers
mlin865 Apr 16, 2021
e2103d8
Calculate position of 6 point junction using triple points function
mlin865 Apr 21, 2021
59e3875
Add annotation groups for limiting ridge
mlin865 Apr 21, 2021
7081afb
Add third marker on duodenum end on greater curvature
mlin865 Apr 21, 2021
7ecffce
Update unit tests
mlin865 Apr 21, 2021
a9adadf
Merge remote-tracking branch 'abi/master' into stomach
mlin865 Apr 21, 2021
cd703d5
Remove redundant lines and edit comments to improve clarity
mlin865 Apr 21, 2021
70bc33c
Add scale factor for wedges
mlin865 Apr 22, 2021
8c4aa1e
Annotate part of esophagus as stomach
mlin865 Apr 23, 2021
a850565
Update central path for rat stomach to show duodenum group
mlin865 Apr 23, 2021
5ee25ac
Make None into string for colon annotation terms without termIDs
mlin865 May 3, 2021
e9364b9
Update description of functions for creating wedges with collapse nodes
mlin865 May 3, 2021
d776722
Merge createEftWedgeCollapseXi2 with createEFTWedgeCollapseXi2Quadrant
mlin865 May 3, 2021
9a2b482
Replace eft.validate assert with print calls
mlin865 May 3, 2021
17d0ab0
Add description for argument collapseNodes
mlin865 May 3, 2021
7c7f869
Merge parameters to Refine number of elements surface
mlin865 May 3, 2021
92f089f
Add marker for pylorus on greater curvature
mlin865 May 3, 2021
795f073
Create annotation groups for markers
mlin865 May 4, 2021
8bd8bd6
Apply smoothing tools to central path for human stomach
mlin865 May 4, 2021
90deefc
Apply smoothing tools to central path of rat stomach
mlin865 May 4, 2021
b2c3714
Update refine number of elements surface to 4
mlin865 May 4, 2021
ff89dea
Remove number of elements through wall option until support for multi…
mlin865 May 4, 2021
2aa5557
Replace option names with annulus with cardia
mlin865 May 4, 2021
6d96760
Add esophagus to stomach annotation group
mlin865 May 4, 2021
81b54f2
Add markers to stomach group
mlin865 May 4, 2021
5e7809e
Vary cardia derivative factor to smooth transition around annulus fro…
mlin865 May 5, 2021
5914a70
Remove redundant steps
mlin865 May 5, 2021
89b9714
Correct error to improve greater curvature near fundus end
mlin865 May 5, 2021
a28f7dc
Merge remote-tracking branch 'abi/master' into stomach
mlin865 May 6, 2021
07cfc79
Update unit test for stomach
mlin865 May 6, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/scaffoldmaker/annotation/stomach_terms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
Common resource for stomach annotation terms.
"""

# convention: preferred name, preferred id, followed by any other ids and alternative names
stomach_terms = [
( "body of stomach", "UBERON:0001161", " FMA:14560", "ILX:0724929"),
( "cardia of stomach", "UBERON:0001162", " FMA:14561", "ILX:0729096"),
( "duodenum", "UBERON:0002114", " FMA:7206", "ILX:0726125"),
( "esophagus", "UBERON:0001043", "FMA: 7131", "ILX:0735017"),
( "esophagogastric junction", "UBERON:0007650", "FMA: 9434", "ILX:0733910"),
( "forestomach-glandular stomach junction", "UBERON:0012270", "ILX:0729974"),
( "forestomach-glandular stomach junction on inner wall", None),
( "forestomach-glandular stomach junction on outer wall", None),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is mucosa a layer of the multi-wall stomach or can it be used for the inner wall here? Or it is the lumen boundary? I'm just wondering if there are more technical terms.
Also make sure new terms get added to the appropriate list for the SAWG to review.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest we keep the terms as-is now but I will update the terminologies and submit the new terms for SAWG review after discussion with the investigators.

( "fundus of stomach", "UBERON:0001160", " FMA:14559", "ILX:0724443"),
( "pyloric antrum", "UBERON:0001165", " FMA:14579", "ILX:0728672"),
( "pylorus", "UBERON:0001166", " FMA:14581", "ILX:0734150"),
( "stomach", "UBERON:0000945", "FMA:7148", "ILX:0736697")
]

def get_stomach_term(name : str):
"""
Find term by matching name to any identifier held for a term.
Raise exception if name not found.
:return ( preferred name, preferred id )
"""
for term in stomach_terms:
if name in term:
return ( term[0], term[1] )
raise NameError("Stomach annotation term '" + name + "' not found.")
2,473 changes: 2,473 additions & 0 deletions src/scaffoldmaker/meshtypes/meshtype_3d_stomach1.py

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/scaffoldmaker/scaffolds.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from scaffoldmaker.meshtypes.meshtype_3d_sphereshell1 import MeshType_3d_sphereshell1
from scaffoldmaker.meshtypes.meshtype_3d_sphereshellseptum1 import MeshType_3d_sphereshellseptum1
from scaffoldmaker.meshtypes.meshtype_3d_stellate1 import MeshType_3d_stellate1
from scaffoldmaker.meshtypes.meshtype_3d_stomach1 import MeshType_3d_stomach1
from scaffoldmaker.meshtypes.meshtype_3d_stomachhuman1 import MeshType_3d_stomachhuman1
from scaffoldmaker.meshtypes.meshtype_3d_tube1 import MeshType_3d_tube1
from scaffoldmaker.meshtypes.meshtype_3d_tubeseptum1 import MeshType_3d_tubeseptum1
Expand Down Expand Up @@ -84,6 +85,7 @@ def __init__(self):
MeshType_3d_sphereshell1,
MeshType_3d_sphereshellseptum1,
MeshType_3d_stellate1,
MeshType_3d_stomach1,
MeshType_3d_stomachhuman1,
MeshType_3d_tube1,
MeshType_3d_tubeseptum1,
Expand Down
91 changes: 91 additions & 0 deletions src/scaffoldmaker/utils/eftfactory_bicubichermitelinear.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,70 @@ def createEftWedgeXi1Zero(self):
assert eft.validate(), 'eftfactory_tricubichermite.createEftWedgeXi1Zero: Failed to validate eft'
return eft

def createEftWedgeCollapseXi1Quadrant(self, collapseNodes):
'''
Create a bicubic hermite linear element field for a wedge element, where xi1 collapsed on xi3 = 0 or xi3 = 1.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not limited to xi3 = 0 or xi3 = 1. Can also be xi2 = 0 and xi2 = 1 depending on collapseNodes. Document param collapseNodes (see my later comments).

:return: Element field template
'''
eft = self.createEftBasic()
setEftScaleFactorIds(eft, [1], [])

valid = True
if collapseNodes in [[1, 3], [2, 4]]:
nodes = [1, 2, 3, 4]
if collapseNodes == [1, 3]:
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, [])
elif collapseNodes == [2, 4]:
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, [])
else:
valid = False
ln_map = [1, 1, 2, 2, 3, 4, 5, 6]
elif collapseNodes in [[5, 7], [6, 8]]:
nodes = [5, 6, 7, 8]
if collapseNodes == [5, 7]:
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, [])
elif collapseNodes == [6, 8]:
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, [])
else:
valid = False
ln_map = [1, 2, 3, 4, 5, 5, 6, 6]
elif collapseNodes in [[1, 5], [2, 6]]:
nodes = [1, 2, 5, 6]
# remap parameters on xi2 = 0 before collapsing nodes
if collapseNodes == [1, 5]:
remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [1])])
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, [])
elif collapseNodes == [2, 6]:
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, [])
remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [])])
else:
valid = False
ln_map = [1, 1, 2, 3, 4, 4, 5, 6]
elif collapseNodes in [[3, 7], [4, 8]]:
nodes = [3, 4, 7, 8]
# remap parameters on xi2 = 1 before collapsing nodes
if collapseNodes == [3, 7]:
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, [])
remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [])])
elif collapseNodes == [4, 8]:
remapEftNodeValueLabel(eft, collapseNodes, Node.VALUE_LABEL_D_DS2, [(Node.VALUE_LABEL_D_DS1, [1])])
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS1, [])
else:
valid = False
ln_map = [1, 2, 3, 3, 4, 5, 6, 6]
else:
valid = False

if not valid:
assert False, "createEftWedgeCollapseXi1Quadrant. Not implemented for collapse nodes " + str(collapseNodes)

# zero cross derivative parameters
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D2_DS1DS2, [])

remapEftLocalNodes(eft, 6, ln_map)
assert eft.validate(), 'eftfactory_bicubichermitelinear.createEftWedgeCollapseXi1Quadrant: Failed to validate eft'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been changing these last asserts into print calls as it's more helpful to see the hole in the mesh - please also change your other util functions. Example:

if not eft.validate():
    print("eftfactory_bicubichermitelinear.createEftWedgeCollapseXi1Quadrant:  Failed to validate eft")

return eft

def createEftWedgeXi1ZeroOpenTube(self):
'''
Create a basic bicubic hermite linear element template for elements
Expand Down Expand Up @@ -431,3 +495,30 @@ def createEftPyramidBottomSimple(self, nodeScaleFactorOffset0, nodeScaleFactorOf

assert eft.validate(), 'eftfactory_bicubichermitelinear.createEftPyramidBottomSimple: Failed to validate eft'
return eft

def createEftWedgeCollapseXi2(self, collapseNodes):
'''
Create a bicubic hermite linear element field for a wedge element, where xi2 collapsed on xi1 = 1.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note just xi1 = 1. see my comments for bicubic linear function.

:return: Element field template
'''
eft = self.createEftBasic()

if collapseNodes in [[4, 8]]:
setEftScaleFactorIds(eft, [1], [])
nodes = [2, 4, 6, 8]
remapEftNodeValueLabel(eft, [4, 8], Node.VALUE_LABEL_D_DS1, [(Node.VALUE_LABEL_D_DS2, [1])])
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS2, [])
ln_map = [1, 2, 3, 2, 4, 5, 6, 5]

elif collapseNodes in [[3, 7]]:
nodes = [1, 3, 5, 7]
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS2, [])
remapEftNodeValueLabel(eft, [3, 7], Node.VALUE_LABEL_D_DS1, [(Node.VALUE_LABEL_D_DS2, [])])
ln_map = [1, 2, 1, 3, 4, 5, 4, 6]

remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D2_DS1DS2, [])

remapEftLocalNodes(eft, 6, ln_map)
assert eft.validate(), 'eftfactory_bicubichermitelinear.createEftWedgeCollapseXi2: Failed to validate eft'

return eft
28 changes: 28 additions & 0 deletions src/scaffoldmaker/utils/eftfactory_tricubichermite.py
Original file line number Diff line number Diff line change
Expand Up @@ -1663,3 +1663,31 @@ def replaceTwoElementWithInlet6(self, origElement1, origElement2, startElementId
self._mesh.destroyElement(origElement1)
self._mesh.destroyElement(origElement2)
fm.endChange()

def createEftWedgeCollapseXi2(self, collapseNodes):
'''
Create a tricubic hermite element field for a wedge element, where xi2 collapsed on xi1 = 1.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to "Create a tricubic hermite element field for a wedge element collapsed in xi2."

... it can be collapsed in xi2 at all other ends of xi1 and xi3 depending on collapseNodes and future implementations; describe this with param collapseNodes.

:return: Element field template
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Describe argument collapseNodes, for this and other functions, with an example.

'''
eft = self.createEftBasic()

if collapseNodes in [[4, 8]]:
setEftScaleFactorIds(eft, [1], [])
nodes = [2, 4, 6, 8]
remapEftNodeValueLabel(eft, [4, 8], Node.VALUE_LABEL_D_DS1, [(Node.VALUE_LABEL_D_DS2, [1])])
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS2, [])
ln_map = [1, 2, 3, 2, 4, 5, 6, 5]

elif collapseNodes in [[3, 7]]:
nodes = [1, 3, 5, 7]
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D_DS2, [])
remapEftNodeValueLabel(eft, [3, 7], Node.VALUE_LABEL_D_DS1, [(Node.VALUE_LABEL_D_DS2, [])])
ln_map = [1, 2, 1, 3, 4, 5, 4, 6]

remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D2_DS1DS2, [])
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D2_DS2DS3, [])
remapEftNodeValueLabel(eft, nodes, Node.VALUE_LABEL_D3_DS1DS2DS3, [])

remapEftLocalNodes(eft, 6, ln_map)
assert eft.validate(), 'eftfactory_tricubichermite.createEftWedgeCollapseXi2: Failed to validate eft'
return eft
10 changes: 5 additions & 5 deletions tests/test_colon.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,14 @@ def test_colon1(self):
coordinates = fieldmodule.findFieldByName("coordinates").castFiniteElement()
self.assertTrue(coordinates.isValid())
minimums, maximums = evaluateFieldNodesetRange(coordinates, nodes)
assertAlmostEqualList(self, minimums, [ 108.03959866945482, -36.876103983560014, -25.93217903595996 ], 1.0E-6)
assertAlmostEqualList(self, maximums, [ 185.43446761757468, 48.07433517752282, 34.995316052158934 ], 1.0E-6)
assertAlmostEqualList(self, minimums, [ 108.02506479907721, -36.876103983560014, -25.89741158484918 ], 1.0E-6)
assertAlmostEqualList(self, maximums, [ 185.46457506076914, 48.1011574894518, 34.995316052158934 ], 1.0E-6)

flatCoordinates = fieldmodule.findFieldByName("flat coordinates").castFiniteElement()
self.assertTrue(flatCoordinates.isValid())
minimums, maximums = evaluateFieldNodesetRange(flatCoordinates, nodes)
assertAlmostEqualList(self, minimums, [ 0.0, 0.0, 0.0 ], 1.0E-6)
assertAlmostEqualList(self, maximums, [ 186.72988844629867, 75.79769125771575, 3.2000000000000006 ], 1.0E-6)
assertAlmostEqualList(self, maximums, [ 186.72988844629867, 77.41781871321301, 3.2000000000000006 ], 1.0E-6)

textureCoordinates = fieldmodule.findFieldByName("texture coordinates").castFiniteElement()
minimums, maximums = evaluateFieldNodesetRange(textureCoordinates, nodes)
Expand All @@ -136,10 +136,10 @@ def test_colon1(self):
fieldcache = fieldmodule.createFieldcache()
result, surfaceArea = surfaceAreaField.evaluateReal(fieldcache, 1)
self.assertEqual(result, RESULT_OK)
self.assertAlmostEqual(surfaceArea, 14623.340352853866, delta=1.0E-6)
self.assertAlmostEqual(surfaceArea, 14612.416789097502, delta=1.0E-6)
result, volume = volumeField.evaluateReal(fieldcache, 1)
self.assertEqual(result, RESULT_OK)
self.assertAlmostEqual(volume, 26869.74823158621, delta=1.0E-6)
self.assertAlmostEqual(volume, 26826.06954301569, delta=1.0E-6)

def test_mousecolon1(self):
"""
Expand Down
2 changes: 1 addition & 1 deletion tests/test_smallintestine.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def test_smallintestine1(self):
Test creation of small intestine scaffold.
"""
parameterSetNames = MeshType_3d_smallintestine1.getParameterSetNames()
self.assertEqual(parameterSetNames, ["Default", "Mouse 1"])
self.assertEqual(parameterSetNames, ["Default", "Cattle 1", "Mouse 1"])
centralPathDefaultScaffoldPackages = {
'Test line': ScaffoldPackage(MeshType_1d_path1, {
'scaffoldSettings': {
Expand Down
84 changes: 84 additions & 0 deletions tests/test_stomach.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import unittest
from opencmiss.utils.zinc.finiteelement import evaluateFieldNodesetRange
from opencmiss.utils.zinc.general import ChangeManager
from opencmiss.zinc.context import Context
from opencmiss.zinc.element import Element
from opencmiss.zinc.field import Field
from opencmiss.zinc.result import RESULT_OK
from scaffoldmaker.meshtypes.meshtype_3d_stomach1 import MeshType_3d_stomach1
from scaffoldmaker.utils.zinc_utils import createFaceMeshGroupExteriorOnFace
from testutils import assertAlmostEqualList

class StomachScaffoldTestCase(unittest.TestCase):

def test_stomach1(self):
"""
Test creation of stomach scaffold.
"""
parameterSetNames = MeshType_3d_stomach1.getParameterSetNames()
self.assertEqual(parameterSetNames, [ "Default", "Human 1", "Rat 1" ])
options = MeshType_3d_stomach1.getDefaultOptions("Rat 1")
self.assertEqual(17, len(options))
self.assertEqual(12, options.get("Number of elements around esophagus"))
self.assertEqual(14, options.get("Number of elements around duodenum"))
self.assertEqual(2, options.get("Number of elements between annulus and duodenum"))
self.assertEqual(1, options.get("Number of elements through wall"))
self.assertEqual(1, options.get("Number of radial elements in annulus"))
self.assertEqual(0.5, options.get("Wall thickness"))
self.assertEqual(True, options.get("Limiting ridge"))
ostiumOptions = options['Gastro-esophagal junction']
ostiumSettings = ostiumOptions.getScaffoldSettings()
self.assertEqual(1, ostiumSettings.get("Number of vessels"))
self.assertEqual(8, ostiumSettings.get("Number of elements around ostium"))
self.assertEqual(1, ostiumSettings.get("Number of elements through wall"))
self.assertEqual(4.0, ostiumSettings.get("Ostium diameter"))
self.assertEqual(3.5, ostiumSettings.get("Ostium length"))
self.assertEqual(0.5, ostiumSettings.get("Ostium wall thickness"))
self.assertEqual(1.25, ostiumSettings.get("Vessel inner diameter"))
self.assertEqual(0.5, ostiumSettings.get("Vessel wall thickness"))
self.assertEqual(0.0, ostiumSettings.get("Vessel angle 1 degrees"))
self.assertEqual(0.55, options.get("Gastro-esophagal junction position along factor"))
self.assertEqual(0.2, options.get("Annulus derivative factor"))

context = Context("Test")
region = context.getDefaultRegion()
self.assertTrue(region.isValid())
annotationGroups = MeshType_3d_stomach1.generateBaseMesh(region, options)
self.assertEqual(8, len(annotationGroups))

fieldmodule = region.getFieldmodule()
self.assertEqual(RESULT_OK, fieldmodule.defineAllFaces())
mesh3d = fieldmodule.findMeshByDimension(3)
self.assertEqual(158, mesh3d.getSize())
mesh2d = fieldmodule.findMeshByDimension(2)
self.assertEqual(643, mesh2d.getSize())
mesh1d = fieldmodule.findMeshByDimension(1)
self.assertEqual(823, mesh1d.getSize())
nodes = fieldmodule.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)
self.assertEqual(341, nodes.getSize())
datapoints = fieldmodule.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_DATAPOINTS)
self.assertEqual(0, datapoints.getSize())

coordinates = fieldmodule.findFieldByName("coordinates").castFiniteElement()
self.assertTrue(coordinates.isValid())
minimums, maximums = evaluateFieldNodesetRange(coordinates, nodes)
assertAlmostEqualList(self, minimums, [-17.977095466754566, -15.437578891036395, -8.706694306455596], 1.0E-6)
assertAlmostEqualList(self, maximums, [17.943222678234513, 15.191836205539767, 8.725549026319936], 1.0E-6)

with ChangeManager(fieldmodule):
one = fieldmodule.createFieldConstant(1.0)
faceMeshGroup = createFaceMeshGroupExteriorOnFace(fieldmodule, Element.FACE_TYPE_XI3_1)
surfaceAreaField = fieldmodule.createFieldMeshIntegral(one, coordinates, faceMeshGroup)
surfaceAreaField.setNumbersOfPoints(4)
volumeField = fieldmodule.createFieldMeshIntegral(one, coordinates, mesh3d)
volumeField.setNumbersOfPoints(3)
fieldcache = fieldmodule.createFieldcache()
result, surfaceArea = surfaceAreaField.evaluateReal(fieldcache, 1)
self.assertEqual(result, RESULT_OK)
self.assertAlmostEqual(surfaceArea, 2436.4955183926895, delta=1.0E-6)
result, volume = volumeField.evaluateReal(fieldcache, 1)
self.assertEqual(result, RESULT_OK)
self.assertAlmostEqual(volume, 1168.9418891341106, delta=1.0E-6)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to test number of annotation groups, checking numbers of elements in each of them. See test_heart.py


if __name__ == "__main__":
unittest.main()