Пример #1
0
    def __init__(self, region, centralPath, elementsCount):
        """
        :param region: Zinc region to define model in.
        :param centralPath: Central path subscaffold comes from meshtype_1d_path1 and used to calculate ellipse radii.
        :param elementsCount: Number of elements needs to be sampled along the central path.
        """
        tmpRegion = region.createRegion()
        centralPath.generate(tmpRegion)
        cx, cd1, cd2, cd3, cd12, cd13 = extractPathParametersFromRegion(
            tmpRegion, [
                Node.VALUE_LABEL_VALUE, Node.VALUE_LABEL_D_DS1,
                Node.VALUE_LABEL_D_DS2, Node.VALUE_LABEL_D_DS3,
                Node.VALUE_LABEL_D2_DS1DS2, Node.VALUE_LABEL_D2_DS1DS3
            ])
        del tmpRegion
        # for i in range(len(cx)):
        #     print(i, '[', cx[i], ',', cd1[i], ',', cd2[i], ',', cd12[i], ',', cd3[i], ',', cd13[i], '],')

        sx, sd1, se, sxi, ssf = sampleCubicHermiteCurves(
            cx, cd1, elementsCount)
        sd2, sd12 = interpolateSampleCubicHermite(cd2, cd12, se, sxi, ssf)
        sd3, sd13 = interpolateSampleCubicHermite(cd3, cd13, se, sxi, ssf)

        self.centres = sx
        self.majorRadii = [vector.magnitude(a) for a in sd2]
        self.majorAxis = sd2
        self.minorRadii = [vector.magnitude(a) for a in sd3]
        self.minorAxis = sd3
        self.alongAxis = sd1
Пример #2
0
    def setRimNodes(self):
        """
        Set nodes around the shell outer layer and core boundary in order needed for creating a shield mesh.
        """
        btx = self.px
        btd1 = self.pd1
        btd2 = self.pd2
        btd3 = self.pd3
        #
        elementsCountShell = self.elementsCountAcrossShell
        for n in range(self.elementsCountAround + 1):
            if self.elementsCountAcrossShell > 1:
                n1, n2 = self.__shield.convertRimIndex(n)
                n1c, n2c = self.__shield.convertRimIndex(
                    n, self.elementsCountAcrossShell)
                tx, td3, pe, pxi, psf = sampleCubicHermiteCurves(
                    [btx[n2c][n1c], btx[n2][n1]],
                    [btd3[n2c][n1c], btd3[n2][n1]],
                    elementsCountShell,
                    arcLengthDerivatives=True)

                for rx in range(1, self.elementsCountAcrossShell):
                    n1rx, n2rx = self.__shield.convertRimIndex(n, rx)
                    btx[n2rx][n1rx] = tx[-rx + elementsCountShell]
                    btd1[n2rx][n1rx] = btd1[n2][n1]
                    btd2[n2rx][n1rx] = btd2[n2][n1]
                    btd3[n2rx][n1rx] = btd3[n2][n1]

        for rx in range(1, self.elementsCountAcrossShell):
            self.__shield.smoothDerivativesAroundRim(0, n3d=None, rx=rx)
def getCenterLine(options):
    if 'Tube type' in options:
        if options['Tube type'] == 1:  # Straight tube
            cx = [[-4.0, 1.0, 3.0], [1.0, 2.0, 0.0]]
            cd1 = [[5.0, 1.0, -3.0], [5.0, 1.0, -3.0]]
        elif options['Tube type'] == 2:  # Human colon in x-y plane
            cx = [[0.0, 0.0, 0.0], [0.0, 10.0, 0.0], [5.0, 9.0, 0.0],
                  [10.0, 10.0, 0.0], [10.0, -2.0, 0.0], [7.0, -4.0, 0.0]]
            cd1 = [[0.0, 10.0, 0.0], [5.0, 5.0, 0.0], [5.0, 0.0, 0.0],
                   [5.0, -5.0, 0.0], [-3.0, -5.0, 0.0], [-3.0, 0.0, 0.0]]
        elif options['Tube type'] == 3:  # Human colon in 3D
            cx = [[0.0, 0.0, 0.0], [0.0, 10.0, 3.0], [5.0, 9.0, 0.0],
                  [10.0, 10.0, 2.0], [15.0, 15.0, 7.0], [20.0, -2.0, 0.0],
                  [10.0, -4.0, -0.0]]
            cd1 = [[0.0, 10.0, 3.0], [5.0, 5.0, 0.0], [5.0, 0.0, 0.0],
                   [10.0, -5.0, 0.0], [12.0, 12.0, 0.0], [5.0, -12.0, -5.0],
                   [-8.0, 0.0, 0.0]]
        else:
            return None
    else:
        return None
    points = sampleCubicHermiteCurves(cx, cd1, 300)[0]
    if isinstance(points, list):
        pathDict = {}
        pathDict["CameraPath"] = []
        pathDict["NumberOfPoints"] = len(points)
        for i in range(0, len(points)):
            pathDict["CameraPath"].extend(points[i])
        return pathDict

    return None
Пример #4
0
    def createRegularRowCurves(self, rscx, rscd1, rscd3):
        """
        generate curves along regular rows using the mirror curve obtained from createMirrorCurve.
        :param rscx: Coordinates of the nodes on the middle curve.
        :param rscd1: d1 derivatives of the nodes on the middle curve.
        :param rscd3: d3 derivatives of the nodes on the middle curve.
        """
        btx = self.px
        btd1 = self.pd1
        btd2 = self.pd2
        btd3 = self.pd3

        elementsCountRim = 0
        for n2 in range(elementsCountRim + 2, self.elementsCountUp + 1):
            btx[n2], btd3[n2], pe, pxi, psf = sampleCubicHermiteCurves(
                [btx[n2][0], rscx[n2], btx[n2][-1]], [
                    vector.setMagnitude(btd3[n2][0], -1.0), rscd3[n2],
                    btd3[n2][-1]
                ],
                self.elementsCountAcrossMinor,
                lengthFractionStart=1,
                lengthFractionEnd=1,
                arcLengthDerivatives=True)
            btd1[n2] = interpolateSampleCubicHermite(
                [[-btd1[n2][0][c] for c in range(3)], rscd1[n2], btd1[n2][-1]],
                [[0.0, 0.0, 0.0]] * 3, pe, pxi, psf)[0]
            if n2 == self.elementsCountUp:
                for n1 in range(1, self.elementsCountAcrossMinor):
                    btd1[n2][n1] = vector.setMagnitude(
                        btd1[self.elementsCountUp][-1], 1.0)
            btd3[n2][0] = [-btd3[n2][0][c] for c in range(3)]
            btd1[n2][0] = [-btd1[n2][0][c] for c in range(3)]
Пример #5
0
    def createRegularColumnCurves(self):
        """
        up regular columns of shield: get d1, initial d3 below regular rows
        """
        btx = self.px
        btd1 = self.pd1
        btd2 = self.pd2
        btd3 = self.pd3

        elementsCountRim = self.elementsCountAcrossRim
        n1d = elementsCountRim + 2
        n1x = self.elementsCountAcrossMinor - elementsCountRim - 1
        n2a = self.elementsCountAcrossShell
        n2b = elementsCountRim
        n2d = 2 + n2b
        n2m = self.elementsCountUp
        for n1 in range(n1d, n1x):
            txm, td1m, pe, pxi, psf = sampleCubicHermiteCurves(
                [btx[n2a][n1], btx[n2d][n1]],
                [[-btd3[n2a][n1][c] for c in range(3)], btd1[n2d][n1]],
                2 + self.elementsCountAcrossTransition - 1,
                arcLengthDerivatives=True)
            td3m = interpolateSampleCubicHermite(
                [btd1[n2a][n1], btd3[n2d][n1]], [[0.0, 0.0, 0.0]] * 2, pe, pxi,
                psf)[0]

            tx = []
            td1 = []
            for n2 in range(n2m + 1):
                if n2 <= n2a:
                    tx.append(btx[n2][n1])
                    td1.append([-btd3[n2][n1][c] for c in range(3)])
                elif (n2 > n2a) and (n2 < n2d):
                    tx.append(txm[n2 - n2a])
                    td1.append(td1m[n2 - n2a])
                else:
                    tx.append(btx[n2][n1])
                    td1.append(btd1[n2][n1])

            td1 = smoothCubicHermiteDerivativesLine(tx,
                                                    td1,
                                                    fixStartDirection=True,
                                                    fixEndDirection=True)

            for n2 in range(n2m + 1):
                if n2 <= n2a:
                    btd3[n2][n1] = [-td1[n2][c] for c in range(3)]
                elif (n2 > n2a) and (n2 < n2d):
                    btx[n2][n1] = tx[n2]
                    if n2 <= n2b:
                        btd1[n2][n1] = td3m[n2 - n2a]
                        btd3[n2][n1] = [-d for d in td1[n2]]
                    else:
                        btd1[n2][n1] = td1[n2]
                        btd3[n2][n1] = td3m[n2 - n2a]
                else:
                    btd1[n2][n1] = td1[n2]
Пример #6
0
    def smoothTriplePointsCurves(self):
        """
        Smooth row and column curves passing triple points (i.e., row 1 and columns 1 and -2).
        """
        btx = self.px
        btd1 = self.pd1
        btd2 = self.pd2
        btd3 = self.pd3

        n1c = 1 + self.elementsCountAcrossRim
        n1y = self.elementsCountAcrossMinor - self.elementsCountAcrossRim
        n1x = n1y - 1
        n2a = self.elementsCountAcrossShell
        n2c = 1 + self.elementsCountAcrossRim
        n2m = self.elementsCountUp

        # smooth shield row n2c
        btd3[n2c][n1c:n1y] = smoothCubicHermiteDerivativesLine(
            btx[n2c][n1c:n1y], btd3[n2c][n1c:n1y])

        # smooth Shield columns n1c, n1x
        for n1 in [n1c, n1x]:
            tx = []
            td1 = []
            for n2 in range(n2c, n2m + 1):
                tx.append(btx[n2][n1])
                td1.append(btd1[n2][n1])
            td1 = smoothCubicHermiteDerivativesLine(tx,
                                                    td1,
                                                    fixEndDirection=True,
                                                    fixStartDerivative=True)
            for n in range(n2m - n2c + 1):
                btd1[n + n2c][n1] = td1[n]

        # sample nodes to triple points
        for n1 in [n1c, n1x]:
            c1 = 1 if n1 == n1c else -1
            txm, td3m, pe, pxi, psf = sampleCubicHermiteCurves(
                [btx[n2a][n1], btx[n2c][n1]],
                [[-btd3[n2a][n1][c] for c in range(3)],
                 [btd1[n2c][n1][c] + c1 * btd3[n2c][n1][c] for c in range(3)]],
                1 + self.elementsCountAcrossTransition - 1,
                arcLengthDerivatives=True)
            td1m = interpolateSampleCubicHermite(
                [btd1[n2a][n1], [-c1 * d for d in btd1[n2c][n1]]],
                [[0.0, 0.0, 0.0]] * 2, pe, pxi, psf)[0]

            for n2 in range(n2a + 1, n2c):
                btx[n2][n1] = txm[n2 - n2a]
                btd1[n2][n1] = td1m[n2 - n2a]
                btd3[n2][n1] = [-d for d in td3m[n2 - n2a]]

        # smooth
        self.__shield.smoothDerivativesToTriplePoints(0, fixAllDirections=True)
Пример #7
0
    def createMirrorCurve(self):
        """
        generate coordinates and derivatives for the mirror curve
        :return: Coordinates and derivatives for the mirror curve
        """
        btx = self.px
        btd1 = self.pd1
        btd2 = self.pd2
        btd3 = self.pd3

        n2a = self.elementsCountAcrossShell
        rcx = []
        tmdx = btx[n2a][self.elementsCountAcrossMinor // 2]
        tmdd3 = btd3[n2a][self.elementsCountAcrossMinor // 2]
        tmux = [
            0.5 * (btx[self.elementsCountUp][0][c] +
                   btx[self.elementsCountUp][self.elementsCountAcrossMinor][c])
            for c in range(3)
        ]
        rcx.append(tmdx)
        rcx.append(tmux)
        rcd3 = [vector.setMagnitude(tmdd3, -1), vector.setMagnitude(tmdd3, -1)]
        rscx, rscd1 = sampleCubicHermiteCurves(rcx,
                                               rcd3,
                                               self.elementsCountUp - n2a,
                                               arcLengthDerivatives=True)[0:2]

        # get d2, d3
        rscd2 = []
        rscd3 = []
        for n in range(len(rscx)):
            d3 = vector.normalise([
                btx[self.elementsCountUp][self.elementsCountAcrossMinor][c] -
                btx[self.elementsCountUp][0][c] for c in range(3)
            ])
            d2 = vector.normalise(vector.crossproduct3(d3, rscd1[n]))
            rscd2.append(d2)
            rscd3.append(d3)

        return rscx, rscd1, rscd2, rscd3
Пример #8
0
    def createRegularColumnCurves(self):
        """
        up regular columns of shield: get d1, initial d3 below regular rows
        """
        btx = self.px
        btd1 = self.pd1
        btd2 = self.pd2
        btd3 = self.pd3

        for n1 in range(2, self.elementsCountAcrossMinor - 1):
            tx, td1, pe, pxi, psf = sampleCubicHermiteCurves(
                [btx[0][n1], btx[2][n1]],
                [[-btd3[0][n1][c] for c in range(3)], btd1[2][n1]],
                2,
                lengthFractionStart=1,
                arcLengthDerivatives=True)
            for n2 in range(3, self.elementsCountUp + 1):
                tx.append(btx[n2][n1])
                td1.append(btd1[n2][n1])
            td1 = smoothCubicHermiteDerivativesLine(tx,
                                                    td1,
                                                    fixStartDirection=True,
                                                    fixEndDirection=True)
            td3 = \
                interpolateSampleCubicHermite([btd1[0][n1], btd3[2][n1]], [[0.0, 0.0, 0.0]] * 2, pe, pxi, psf)[
                    0]
            for n2 in range(self.elementsCountUp + 1):
                if n2 < 2:
                    btx[n2][n1] = tx[n2]
                    if n2 == 0:
                        btd3[n2][n1] = [-td1[0][c] for c in range(3)]
                    else:
                        btd3[n2][n1] = td3[n2]
                if n2 == 0:
                    btd1[n2][n1] = td3[n2]
                else:
                    btd1[n2][n1] = td1[n2]
Пример #9
0
    def createRegularRowCurves(self, rscx, rscd1, rscd3):
        """
        generate curves along regular rows using the mirror curve obtained from createMirrorCurve.
        :param rscx: Coordinates of the nodes on the middle curve.
        :param rscd1: d1 derivatives of the nodes on the middle curve.
        :param rscd3: d3 derivatives of the nodes on the middle curve.
        """
        btx = self.px
        btd1 = self.pd1
        btd2 = self.pd2
        btd3 = self.pd3

        elementsCountRim = self.elementsCountAcrossRim
        n2a = self.elementsCountAcrossShell
        n2b = elementsCountRim
        n2d = n2b + 2
        n2m = self.elementsCountUp
        n1a = self.elementsCountAcrossShell
        n1b = elementsCountRim
        n1z = self.elementsCountAcrossMinor - self.elementsCountAcrossShell
        for n2 in range(n2d, n2m + 1):
            txm, td3m, pe, pxi, psf = sampleCubicHermiteCurves(
                [btx[n2][n1a], rscx[n2 - n2a], btx[n2][n1z]], [
                    vector.setMagnitude(btd3[n2][n1a], -1.0), rscd3[n2 - n2a],
                    btd3[n2][n1z]
                ],
                self.elementsCountAcrossMinor -
                2 * self.elementsCountAcrossShell,
                arcLengthDerivatives=True)
            td1m = interpolateSampleCubicHermite(
                [[-btd1[n2][n1a][c]
                  for c in range(3)], rscd1[n2 - n2a], btd1[n2][n1z]],
                [[0.0, 0.0, 0.0]] * 3, pe, pxi, psf)[0]

            tx = []
            td3 = []
            for n1 in range(self.elementsCountAcrossMinor + 1):
                if n1 <= n1a:
                    tx.append(btx[n2][n1])
                    td3.append([-btd3[n2][n1][c] for c in range(3)])
                elif (n1 > n1a) and (n1 < n1z):
                    tx.append(txm[n1 - n1a])
                    td3.append(td3m[n1 - n1a])
                else:
                    tx.append(btx[n2][n1])
                    td3.append((btd3[n2][n1]))

            td3 = smoothCubicHermiteDerivativesLine(tx,
                                                    td3,
                                                    fixStartDirection=True,
                                                    fixEndDirection=True)

            for n1 in range(self.elementsCountAcrossMinor + 1):
                if n1 <= n1a:
                    btd3[n2][n1] = [-td3[n1][c] for c in range(3)]
                elif (n1 > n1a) and (n1 < n1z):
                    btx[n2][n1] = tx[n1]
                    if n2 == n2m:
                        if n1 <= n1b:
                            btd1[n2][n1] = vector.setMagnitude(
                                btd1[n2m][-1], -1.0)
                        else:
                            btd1[n2][n1] = vector.setMagnitude(
                                btd1[n2m][-1], 1.0)
                    else:
                        if n1 <= n1b:
                            btd1[n2][n1] = [-d for d in td1m[n1 - n1a]]
                        else:
                            btd1[n2][n1] = td1m[n1 - n1a]
                    if n1 <= n1b:
                        btd3[n2][n1] = [-d for d in td3[n1]]
                    else:
                        btd3[n2][n1] = td3[n1]
                else:
                    btd3[n2][n1] = td3[n1]
def generateOstiumMesh(region, options, trackSurface, centrePosition, axis1, startNodeIdentifier = 1, startElementIdentifier = 1,
        vesselMeshGroups = None):
    '''
    :param vesselMeshGroups: List (over number of vessels) of list of mesh groups to add vessel elements to.
    :return: nextNodeIdentifier, nextElementIdentifier, Ostium points tuple
    (ox[n3][n1][c], od1[n3][n1][c], od2[n3][n1][c], od3[n3][n1][c], oNodeId[n3][n1], oPositions).
    '''
    vesselsCount = options['Number of vessels']
    elementsCountAroundOstium = options['Number of elements around ostium']
    elementsCountAcross = options['Number of elements across common']
    elementsCountsAroundVessels, elementsCountAroundMid = getOstiumElementsCountsAroundVessels(elementsCountAroundOstium, elementsCountAcross, vesselsCount)
    elementsCountAroundEnd = (elementsCountAroundOstium - 2*elementsCountAroundMid)//2
    #print('\nvesselsCount', vesselsCount, 'elementsCountsAroundOstium', elementsCountAroundOstium, 'elementsCountAcross', elementsCountAcross)
    #print('--> elementsCountsAroundVessels', elementsCountsAroundVessels, 'elementsCountAroundMid', elementsCountAroundMid)
    elementsCountAlong = options['Number of elements along']
    elementsCountThroughWall = options['Number of elements through wall']
    unitScale = options['Unit scale']

    isOutlet = options['Outlet']
    ostiumRadius = 0.5*unitScale*options['Ostium diameter']
    ostiumLength = unitScale*options['Ostium length']
    ostiumWallThickness = unitScale*options['Ostium wall thickness']
    interVesselHeight = unitScale*options['Ostium inter-vessel height']
    interVesselDistance = unitScale*options['Ostium inter-vessel distance'] if (vesselsCount > 1) else 0.0
    halfInterVesselDistance = 0.5*interVesselDistance
    useCubicHermiteThroughOstiumWall = not(options['Use linear through ostium wall'])
    vesselEndDerivative = ostiumLength*options['Vessel end length factor']/elementsCountAlong
    vesselInnerRadius = 0.5*unitScale*options['Vessel inner diameter']
    vesselWallThickness = unitScale*options['Vessel wall thickness']
    vesselOuterRadius = vesselInnerRadius + vesselWallThickness
    vesselAngle1Radians = math.radians(options['Vessel angle 1 degrees'])
    vesselAngle1SpreadRadians = math.radians(options['Vessel angle 1 spread degrees'])
    vesselAngle2Radians = math.radians(options['Vessel angle 2 degrees'])
    useCubicHermiteThroughVesselWall = not(options['Use linear through vessel wall'])
    useCrossDerivatives = False  # options['Use cross derivatives']  # not implemented

    fm = region.getFieldmodule()
    fm.beginChange()
    coordinates = findOrCreateFieldCoordinates(fm)
    cache = fm.createFieldcache()

    # track points in shape of ostium

    # get directions in plane of surface at centre:
    cx, cd1, cd2 = trackSurface.evaluateCoordinates(centrePosition, True)
    trackDirection1, trackDirection2, centreNormal = calculate_surface_axes(cd1, cd2, axis1)
    trackDirection2reverse = [ -d for d in trackDirection2 ]

    halfCircumference = math.pi*ostiumRadius
    circumference = 2.0*halfCircumference
    distance = 0.0
    elementLengthAroundOstiumMid = 0.0
    vesselsSpanAll = interVesselDistance*(vesselsCount - 1)
    vesselsSpanMid = interVesselDistance*(vesselsCount - 2)
    if vesselsCount == 1:
        elementLengthAroundOstiumEnd = circumference/elementsCountAroundOstium
        vesselOstiumPositions = [ centrePosition ]
        ocx  = [ cx ]
        ocd1 = [ trackDirection1 ]
        ocd2 = [ trackDirection2 ]
        ocd3 = [ centreNormal ]
    else:
        elementLengthAroundOstiumEnd = (circumference + 2.0*interVesselDistance)/(elementsCountAroundOstium - 2*elementsCountAroundMid)
        if elementsCountAroundMid > 0:
            elementLengthAroundOstiumMid = interVesselDistance*(vesselsCount - 2)/elementsCountAroundMid
        vesselOstiumPositions = []
        ocx  = []
        ocd1 = []
        ocd2 = []
        ocd3 = []
        for v in range(vesselsCount):
            vesselOstiumPositions.append(trackSurface.trackVector(centrePosition, trackDirection1, (v/(vesselsCount - 1) - 0.5)*vesselsSpanAll))
            x, d1, d2 = trackSurface.evaluateCoordinates(vesselOstiumPositions[-1], -1)
            d1, d2, d3 = calculate_surface_axes(d1, d2, trackDirection1)
            ocx .append(x)
            ocd1.append(d1)
            ocd2.append(d2)
            ocd3.append(d3)

    # coordinates around ostium
    ox = [ [], [] ]
    od1 = [ [], [] ]
    od2 = [ [], [] ]
    od3 = [ [], [] ]
    oPositions = []
    for n1 in range(elementsCountAroundOstium):
        elementLength = elementLengthAroundOstiumEnd
        if distance <= (vesselsSpanMid + halfInterVesselDistance):
            position = trackSurface.trackVector(centrePosition, trackDirection1, 0.5*vesselsSpanMid - distance)
            sideDirection = trackDirection2reverse
            if n1 < elementsCountAroundMid:
                elementLength = elementLengthAroundOstiumMid
        elif distance < (vesselsSpanMid + halfInterVesselDistance + halfCircumference):
            position = vesselOstiumPositions[0]
            angleRadians = (distance - (vesselsSpanMid + halfInterVesselDistance))/ostiumRadius
            w1 = -math.sin(angleRadians)
            w2 = -math.cos(angleRadians)
            sideDirection = [ (w1*trackDirection1[c] + w2*trackDirection2[c]) for c in range(3) ]
        elif distance < (2.0*vesselsSpanMid + halfInterVesselDistance + halfCircumference + interVesselDistance):
            position = trackSurface.trackVector(centrePosition, trackDirection1, distance - (1.5*vesselsSpanMid + interVesselDistance + halfCircumference))
            sideDirection = trackDirection2
            if 0 <= (n1 - elementsCountAroundEnd - elementsCountAroundMid) < elementsCountAroundMid:
                elementLength = elementLengthAroundOstiumMid
        elif distance < (2.0*vesselsSpanMid + halfInterVesselDistance + circumference + interVesselDistance):
            position = vesselOstiumPositions[-1]
            angleRadians = (distance - (2.0*vesselsSpanMid + halfInterVesselDistance + halfCircumference + interVesselDistance))/ostiumRadius
            w1 = math.sin(angleRadians)
            w2 = math.cos(angleRadians)
            sideDirection = [ (w1*trackDirection1[c] + w2*trackDirection2[c]) for c in range(3) ]
        else:
            position = trackSurface.trackVector(centrePosition, trackDirection1, 0.5*vesselsSpanMid + (circumference + 2.0*(vesselsSpanMid + interVesselDistance)) - distance)
            sideDirection = trackDirection2reverse
        position = trackSurface.trackVector(position, sideDirection, ostiumRadius)
        oPositions.append(position)
        px, d1, d2 = trackSurface.evaluateCoordinates(position, True)
        pd2, pd1, pd3 = calculate_surface_axes(d1, d2, sideDirection)
        # get outer coordinates
        opx = px
        opd1 = vector.setMagnitude([ -d for d in pd1 ], elementLengthAroundOstiumEnd)
        opd2 = vector.setMagnitude(pd2, elementLengthAroundOstiumEnd)  # smoothed later
        opd3 = vector.setMagnitude(pd3, ostiumWallThickness)
        # set inner and outer coordinates (use copy to avoid references to same list later)
        ox [0].append([ (opx[c] - opd3[c]) for c in range(3) ])
        od1[0].append(copy.copy(opd1))
        od2[0].append(copy.copy(opd2))
        ox [1].append(opx)
        od1[1].append(opd1)
        od2[1].append(opd2)
        if useCubicHermiteThroughOstiumWall:
            od3[0].append(copy.copy(opd3))
            od3[1].append(opd3)
        distance += elementLength
    for n3 in range(2):
        od1[n3] = interp.smoothCubicHermiteDerivativesLoop(ox[n3], od1[n3], fixAllDirections = True)

    xx  = []
    xd1 = []
    xd2 = []
    xd3 = []
    # coordinates across common ostium, between vessels
    nodesCountFreeEnd = elementsCountsAroundVessels[0] + 1 - elementsCountAcross
    oinc = 0 if (vesselsCount <= 2) else elementsCountAroundMid//(vesselsCount - 2)
    for iv in range(vesselsCount - 1):
        xx .append([ None, None ])
        xd1.append([ None, None ])
        xd2.append([ None, None ])
        xd3.append([ None, None ])
        oa = elementsCountAroundMid - iv*oinc
        ob = elementsCountAroundMid + nodesCountFreeEnd - 1 + iv*oinc
        nx = [ ox[1][oa], ox[1][ob] ]
        nd1 = [ [ -d for d in od1[1][oa] ], od1[1][ob] ]
        nd2 = [ [ -d for d in od2[1][oa] ], od2[1][ob] ]
        if elementsCountAcross > 1:
            # add centre point, displaced by interVesselHeight
            if vesselsCount == 2:
                position = centrePosition
            else:
                position = trackSurface.trackVector(centrePosition, trackDirection1, (iv/(vesselsCount - 2) - 0.5)*vesselsSpanMid)
            mx, d1, d2 = trackSurface.evaluateCoordinates(position, derivatives = True)
            md1, md2, md3 = calculate_surface_axes(d1, d2, trackDirection1)
            nx .insert(1, [ (mx[c] + interVesselHeight*md3[c]) for c in range(3) ])
            nd1.insert(1, vector.setMagnitude(md1, elementLengthAroundOstiumMid if (0 < iv < (vesselsCount - 2)) else elementLengthAroundOstiumEnd))
            nd2.insert(1, vector.setMagnitude(md2, ostiumRadius))
        nd2 = interp.smoothCubicHermiteDerivativesLine(nx, nd2, fixAllDirections = True)
        px, pd2, pe, pxi = interp.sampleCubicHermiteCurves(nx, nd2, elementsCountAcross)[0:4]
        pd1 = interp.interpolateSampleLinear(nd1, pe, pxi)
        pd3 = [ vector.setMagnitude(vector.crossproduct3(pd1[n2], pd2[n2]), ostiumWallThickness) for n2 in range(elementsCountAcross + 1) ]
        lx = [ ([ (px[n2][c] - pd3[n2][c]) for c in range(3) ]) for n2 in range(elementsCountAcross + 1) ]
        ld2 = interp.smoothCubicHermiteDerivativesLine(lx, pd2, fixAllDirections = True)
        xx [iv][0] = lx [1:elementsCountAcross]
        xd1[iv][0] = copy.deepcopy(pd1[1:elementsCountAcross])  # to be smoothed later
        xd2[iv][0] = ld2[1:elementsCountAcross]
        xx [iv][1] = px [1:elementsCountAcross]
        xd1[iv][1] = pd1[1:elementsCountAcross]  # to be smoothed later
        xd2[iv][1] = pd2[1:elementsCountAcross]
        if useCubicHermiteThroughOstiumWall:
            xd3[iv][0] = copy.deepcopy(pd3[1:elementsCountAcross])
            xd3[iv][1] = pd3[1:elementsCountAcross]
        # set smoothed d2 on ostium circumference
        od2[0][oa] = [ -d for d in ld2[0] ]
        od2[1][oa] = [ -d for d in pd2[0] ]
        od2[0][ob] = ld2[-1]
        od2[1][ob] = pd2[-1]

    # get positions of vessel end centres and rings
    vcx = []
    vcd1 = []
    vcd2 = []
    vcd3 = []
    vox = []
    vod1 = []
    vod2 = []
    vod3 = []
    for v in range(vesselsCount):
        elementsCountAroundVessel = elementsCountsAroundVessels[v]
        radiansPerElementVessel = 2.0*math.pi/elementsCountAroundVessel
        useVesselAngleRadians = vesselAngle1Radians
        if vesselsCount > 1:
            useVesselAngleRadians += (v/(vesselsCount - 1) - 0.5)*vesselAngle1SpreadRadians
        vx, vd1, vd2, vd3 = getCircleProjectionAxes(ocx[v], ocd1[v], ocd2[v], ocd3[v], ostiumLength, useVesselAngleRadians, vesselAngle2Radians)
        vd1 = [    vesselOuterRadius*d for d in vd1 ]
        vd2 = [   -vesselOuterRadius*d for d in vd2 ]
        vd3 = [ -vesselEndDerivative*d for d in vd3 ]
        vcx.append(vx)
        vcd1.append(vd1)
        vcd2.append(vd2)
        vcd3.append(vd3)
        vox.append([])
        vod1.append([])
        vod2.append([])
        vod3.append([])
        for n3 in range(2):
            radius = vesselInnerRadius if (n3 == 0) else vesselOuterRadius
            vAxis1 = vector.setMagnitude(vd1, radius)
            vAxis2 = vector.setMagnitude(vd2, radius)
            if vesselsCount == 1:
                startRadians = 0.5*math.pi
            else:
                startRadians = 0.5*radiansPerElementVessel*elementsCountAcross
                if v == (vesselsCount - 1):
                    startRadians -= math.pi
            px, pd1 = createCirclePoints(vx, vAxis1, vAxis2, elementsCountAroundVessel, startRadians)
            vox [-1].append(px)
            vod1[-1].append(pd1)
            vod2[-1].append([ vd3 ]*elementsCountAroundVessel)
            if useCubicHermiteThroughVesselWall:
                vod3[-1].append([ vector.setMagnitude(vector.crossproduct3(d1, vd3), vesselWallThickness) for d1 in pd1 ])

    # calculate common ostium vessel node derivatives map
    mvPointsx = [ None ]*vesselsCount
    mvPointsd1 = [ None ]*vesselsCount
    mvPointsd2 = [ None ]*vesselsCount
    mvPointsd3 = [ None ]*vesselsCount
    mvDerivativesMap = [ None ]*vesselsCount
    mvMeanCount = [ None ]*vesselsCount  # stores 1 if first reference to common point between vessels, 2 if second. Otherwise 0.
    for v in range(vesselsCount):
        if vesselsCount == 1:
            mvPointsx[v], mvPointsd1[v], mvPointsd2[v], mvPointsd3[v], mvDerivativesMap[v] = \
                ox, od1, od2, od3 if useCubicHermiteThroughOstiumWall else None, None
            mvMeanCount[v] = [ 0 ]*elementsCountsAroundVessels[v]
        else:
            iv = max(0, v - 1)
            oa = elementsCountAroundMid - iv*oinc
            ob = elementsCountAroundMid + nodesCountFreeEnd - 1 + iv*oinc
            mvPointsx [v] = []
            mvPointsd1[v] = []
            mvPointsd2[v] = []
            mvPointsd3[v] = [] if useCubicHermiteThroughOstiumWall else None
            mvDerivativesMap[v] = []
            for n3 in range(2):
                mvPointsd1[v].append([])
                mvPointsd2[v].append([])
                mvPointsx [v].append([])
                if useCubicHermiteThroughOstiumWall:
                    mvPointsd3[v].append([])
                mvDerivativesMap[v].append([])
                if v == 0:  # first end vessel
                    mvPointsd1[v][n3] += od1[n3][oa:ob + 1]
                    mvPointsd2[v][n3] += od2[n3][oa:ob + 1]
                    mvPointsx [v][n3] += ox [n3][oa:ob + 1]
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += od3[n3][oa:ob + 1]
                    mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 1, 0), None, (1, 0, 0) ) )
                    for i in range(nodesCountFreeEnd - 2):
                        mvDerivativesMap[v][n3].append( ( None, None, None ) )
                    mvDerivativesMap[v][n3].append( ( (1, 0, 0), (1, 1, 0), None, (0, -1, 0) ) )
                    mvPointsx [v][n3] += reversed(xx [iv][n3])
                    mvPointsd1[v][n3] += reversed(xd1[iv][n3])
                    mvPointsd2[v][n3] += reversed(xd2[iv][n3])
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += reversed(xd3[iv][n3])
                    for i in range(elementsCountAcross - 1):
                        mvDerivativesMap[v][n3].append( ( (0, -1, 0), (1, 0, 0), None ) )
                    if n3 == 0:
                        mvMeanCount[v] = [ 1 ] + [ 0 ]*(nodesCountFreeEnd - 2) + [ 1 ]*elementsCountAcross
                elif v < (vesselsCount - 1):  # middle vessels
                    # left:
                    mvPointsx [v][n3] += ox [n3][oa - oinc:oa + 1]
                    mvPointsd1[v][n3] += od1[n3][oa - oinc:oa + 1]
                    mvPointsd2[v][n3] += od2[n3][oa - oinc:oa + 1]
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += od3[n3][oa - oinc:oa + 1]
                    mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 1, 0), None, (1, 0, 0) ) )
                    for i in range(oinc - 1):
                        mvDerivativesMap[v][n3].append( ( None, None, None ) )
                    mvDerivativesMap[v][n3].append( ( (1, 0, 0), (1, 1, 0), None, (0, -1, 0) ) )
                    # across
                    mvPointsx [v][n3] += xx [iv][n3]
                    mvPointsd1[v][n3] += xd1[iv][n3]
                    mvPointsd2[v][n3] += xd2[iv][n3]
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += xd3[iv][n3]
                    for i in range(elementsCountAcross - 1):
                        mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 0, 0), None ) )
                    # right
                    mvPointsx [v][n3] += ox [n3][ob:ob + oinc + 1]
                    mvPointsd1[v][n3] += od1[n3][ob:ob + oinc + 1]
                    mvPointsd2[v][n3] += od2[n3][ob:ob + oinc + 1]
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += od3[n3][ob:ob + oinc + 1]
                    mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 1, 0), None, (1, 0, 0) ) )
                    for i in range(oinc - 1):
                        mvDerivativesMap[v][n3].append( ( None, None, None ) )
                    mvDerivativesMap[v][n3].append( ( (1, 0, 0), (1, 1, 0), None, (0, -1, 0) ) )
                    # across reverse
                    mvPointsx [v][n3] += reversed(xx [iv + 1][n3])
                    mvPointsd1[v][n3] += reversed(xd1[iv + 1][n3])
                    mvPointsd2[v][n3] += reversed(xd2[iv + 1][n3])
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += reversed(xd3[iv + 1][n3])
                    for i in range(elementsCountAcross - 1):
                        mvDerivativesMap[v][n3].append( ( (0, -1, 0), (1, 0, 0), None ) )
                    if n3 == 0:
                        mvMeanCount[v] = [ 1 ] + [ 0 ]*(oinc - 1) + [ 2 ]*(elementsCountAcross + 1) + [ 0 ]*(oinc - 1) + [ 1 ]*elementsCountAcross
                else:  # last end vessel
                    mvPointsx [v][n3] += ox [n3][ob:] + [ ox [n3][0] ]
                    mvPointsd1[v][n3] += od1[n3][ob:] + [ od1[n3][0] ]
                    mvPointsd2[v][n3] += od2[n3][ob:] + [ od2[n3][0] ]
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += od3[n3][ob:] + [ od3[n3][0] ]
                    mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 1, 0), None, (1, 0, 0) ) )
                    for i in range(nodesCountFreeEnd - 2):
                        mvDerivativesMap[v][n3].append( ( None, None, None ) )
                    mvDerivativesMap[v][n3].append( ( (1, 0, 0), (1, 1, 0), None, (0, -1, 0) ) )
                    mvPointsx [v][n3] += xx [iv][n3]
                    mvPointsd1[v][n3] += xd1[iv][n3]
                    mvPointsd2[v][n3] += xd2[iv][n3]
                    if useCubicHermiteThroughOstiumWall:
                        mvPointsd3[v][n3] += xd3[iv][n3]
                    for i in range(elementsCountAcross - 1):
                        mvDerivativesMap[v][n3].append( ( (0, 1, 0), (-1, 0, 0), None ) )
                    if n3 == 0:
                        mvMeanCount[v] = [ 2 ] + [ 0 ]*(nodesCountFreeEnd - 2) + [ 2 ]*elementsCountAcross

    # calculate derivative 2 around free sides of inlets to fit vessel derivatives
    for v in range(vesselsCount):
        for n3 in range(2):
            #print('v',v,'n3',n3,'elementsAround',elementsCountsAroundVessels[v])
            #print('mvPointsx [v][n3]', mvPointsx [v][n3])
            #print('mvPointsd1[v][n3]', mvPointsd1[v][n3])
            #print('mvPointsd2[v][n3]', mvPointsd2[v][n3])
            #print('mvDerivativesMap[v][n3]', mvDerivativesMap[v][n3])
            for n1 in range(elementsCountsAroundVessels[v]):
                d2Map = mvDerivativesMap[v][n3][n1][1] if (mvDerivativesMap[v] and mvDerivativesMap[v][n3][n1]) else None
                sf1 = d2Map[0] if d2Map else 0.0
                sf2 = d2Map[1] if d2Map else 1.0
                nx = [ vox[v][n3][n1], mvPointsx[v][n3][n1] ]
                nd2 = [ [ d*elementsCountAlong for d in vod2[v][n3][n1] ], [ (sf1*mvPointsd1[v][n3][n1][c] + sf2*mvPointsd2[v][n3][n1][c]) for c in range(3) ] ]
                nd2f = interp.smoothCubicHermiteDerivativesLine(nx, nd2, fixStartDerivative = True, fixEndDirection = True)
                ndf = [ d/elementsCountAlong for d in nd2f[1] ]
                # assign components to set original values:
                if sf1 == 0:
                    for c in range(3):
                        mvPointsd2[v][n3][n1][c] = sf2*ndf[c]
                elif sf2 == 0:
                    if mvMeanCount[v][n1] < 2:
                        for c in range(3):
                            mvPointsd1[v][n3][n1][c] = sf1*ndf[c]
                    else:
                        # take mean of values from this and last vessel
                        for c in range(3):
                            mvPointsd1[v][n3][n1][c] = 0.5*(mvPointsd1[v][n3][n1][c] + sf1*ndf[c])
                else:
                    #print('v', v, 'n3', n3, 'n1', n1, ':', vector.magnitude(ndf), 'vs.', vector.magnitude(nd2[1]), 'd2Map', d2Map)
                    pass

    if isOutlet:
        # reverse directions of d1 and d2 on vessels and ostium base
        for c in range(3):
            for n3 in range(2):
                for n1 in range(elementsCountAroundOstium):
                    od1[n3][n1][c] = -od1[n3][n1][c]
                    od2[n3][n1][c] = -od2[n3][n1][c]
                for iv in range(vesselsCount - 1):
                    for n1 in range(elementsCountAcross - 1):
                        xd1[iv][n3][n1][c] = -xd1[iv][n3][n1][c]
                        xd2[iv][n3][n1][c] = -xd2[iv][n3][n1][c]
                for v in range(vesselsCount):
                    for n1 in range(elementsCountsAroundVessels[v]):
                        vod1[v][n3][n1][c] = -vod1[v][n3][n1][c]
            # d2 is referenced all around, so only change once per vessel
            for v in range(vesselsCount):
                vod2[v][0][0][c] = -vod2[v][0][0][c]

    ##############
    # Create nodes
    ##############

    nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)

    nodetemplate = nodes.createNodetemplate()
    nodetemplate.defineField(coordinates)
    nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS1, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS2, 1)
    nodetemplate.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS3, 1)
    nodetemplateLinearS3 = nodes.createNodetemplate()
    nodetemplateLinearS3.defineField(coordinates)
    nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_VALUE, 1)
    nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS1, 1)
    nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1, Node.VALUE_LABEL_D_DS2, 1)

    nodeIdentifier = startNodeIdentifier

    oNodeId = []
    for n3 in range(2):
        oNodeId.append([])
        for n1 in range(elementsCountAroundOstium):
            node = nodes.createNode(nodeIdentifier, nodetemplate if useCubicHermiteThroughOstiumWall else nodetemplateLinearS3)
            cache.setNode(node)
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, ox [n3][n1])
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, od1[n3][n1])
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, od2[n3][n1])
            if useCubicHermiteThroughOstiumWall:
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, od3[n3][n1])
            oNodeId[n3].append(nodeIdentifier)
            nodeIdentifier += 1

    xNodeId = []
    for iv in range(vesselsCount - 1):
        xNodeId.append([])
        for n3 in range(2):
            xNodeId[iv].append([])
            for n2 in range(elementsCountAcross - 1):
                node = nodes.createNode(nodeIdentifier, nodetemplate if useCubicHermiteThroughOstiumWall else nodetemplateLinearS3)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, xx [iv][n3][n2])
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, xd1[iv][n3][n2])
                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, xd2[iv][n3][n2])
                if useCubicHermiteThroughOstiumWall:
                    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, xd3[iv][n3][n2])
                xNodeId[iv][n3].append(nodeIdentifier)
                nodeIdentifier += 1

    #for v in range(vesselsCount):
    #    node = nodes.createNode(nodeIdentifier, nodetemplate)
    #    cache.setNode(node)
    #    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, vcx [v])
    #    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, vcd1[v])
    #    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, vcd2[v])
    #    coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, vcd3[v])
    #    nodeIdentifier += 1
    #    for n3 in range(2):
    #        for n1 in range(elementsCountsAroundVessels[v]):
    #            node = nodes.createNode(nodeIdentifier, nodetemplate if useCubicHermiteThroughVesselWall else nodetemplateLinearS3)
    #            cache.setNode(node)
    #            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1, vox [v][n3][n1])
    #            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1, vod1[v][n3][n1])
    #            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1, vod2[v][n3][n1])
    #            if useCubicHermiteThroughVesselWall:
    #                coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS3, 1, vod3[v][n3][n1])
    #            #vNodeId.append(nodeIdentifier)
    #            nodeIdentifier += 1

    # get identifiers of nodes around each vessel at ostium end
    mvNodeId = [ None ]*vesselsCount
    for v in range(vesselsCount):
        if vesselsCount == 1:
            mvNodeId[v] = oNodeId
        else:
            iv = max(0, v - 1)
            mvNodeId[v] = [ None, None ]
            oa = elementsCountAroundMid - iv*oinc
            ob = elementsCountAroundMid + nodesCountFreeEnd - 1 + iv*oinc
            for n3 in range(2):
                if v == 0:  # first end vessel
                    mvNodeId[v][n3] = oNodeId[n3][oa:ob + 1] + (list(reversed(xNodeId[iv][n3])) if (v == 0) else xNodeId[iv][n3])
                elif v == (vesselsCount - 1):  # last end vessels
                    mvNodeId[v][n3] = oNodeId[n3][ob:] + [ oNodeId[n3][0] ] + (list(reversed(xNodeId[iv][n3])) if (v == 0) else xNodeId[iv][n3])
                else:  # mid vessels
                    mvNodeId[v][n3] = oNodeId[n3][oa - oinc:oa + 1] + xNodeId[iv][n3] + oNodeId[n3][ob:ob + oinc + 1] + list(reversed(xNodeId[iv + 1][n3]))

    #################
    # Create elementss
    #################

    mesh = fm.findMeshByDimension(3)
    elementIdentifier = startElementIdentifier

    tricubichermite = eftfactory_tricubichermite(mesh, useCrossDerivatives)
    #tricubicHermiteBasis = fm.createElementbasis(3, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)

    #eft = tricubichermite.createEftBasic()
    #elementtemplate = mesh.createElementtemplate()
    #elementtemplate.setElementShapeType(Element.SHAPE_TYPE_CUBE)
    #elementtemplate.defineField(coordinates, -1, eft)

    #elementtemplateX = mesh.createElementtemplate()
    #elementtemplateX.setElementShapeType(Element.SHAPE_TYPE_CUBE)

    for v in range(vesselsCount):
        if isOutlet:
            startPointsx, startPointsd1, startPointsd2, startPointsd3, startNodeId, startDerivativesMap = \
                mvPointsx[v], mvPointsd1[v], mvPointsd2[v], mvPointsd3[v], mvNodeId[v], mvDerivativesMap[v]
            endPointsx, endPointsd1, endPointsd2, endPointsd3, endNodeId, endDerivativesMap = \
                vox[v], vod1[v], vod2[v], vod3[v] if useCubicHermiteThroughVesselWall else None, None, None
            # reverse order of nodes around:
            for px in [ startPointsx, startPointsd1, startPointsd2, startPointsd3, startNodeId, startDerivativesMap, \
                        endPointsx, endPointsd1, endPointsd2, endPointsd3, endNodeId, endDerivativesMap ]:
                if px:
                    for n3 in range(2):
                        px[n3] = [ px[n3][0] ] + px[n3][len(px[n3]) - 1:0:-1]
            if vesselsCount > 1:
                # must switch in and out xi1 maps around corners in startDerivativesMap
                for n3 in range(2):
                    for n1 in range(elementsCountsAroundVessels[v]):
                        derivativesMap = startDerivativesMap[n3][n1]
                        if len(derivativesMap) == 4:
                            startDerivativesMap[n3][n1] = derivativesMap[3], derivativesMap[1], derivativesMap[2], derivativesMap[0]
        else:
            startPointsx, startPointsd1, startPointsd2, startPointsd3, startNodeId, startDerivativesMap = \
                vox[v], vod1[v], vod2[v], vod3[v] if useCubicHermiteThroughVesselWall else None, None, None
            endPointsx, endPointsd1, endPointsd2, endPointsd3, endNodeId, endDerivativesMap = \
                mvPointsx[v], mvPointsd1[v], mvPointsd2[v], mvPointsd3[v], mvNodeId[v], mvDerivativesMap[v]
        #print('endPointsx ', endPointsx )
        #print('endPointsd1', endPointsd1)
        #print('endPointsd2', endPointsd2)
        #print('endPointsd3', endPointsd3)
        #print('endNodeId', endNodeId)
        #print('endDerivativesMap', endDerivativesMap)
        nodeIdentifier, elementIdentifier = createAnnulusMesh3d(
            nodes, mesh, nodeIdentifier, elementIdentifier,
            startPointsx, startPointsd1, startPointsd2, startPointsd3, startNodeId, startDerivativesMap,
            endPointsx, endPointsd1, endPointsd2, endPointsd3, endNodeId, endDerivativesMap,
            forceMidLinearXi3 = not useCubicHermiteThroughVesselWall,
            elementsCountRadial = elementsCountAlong,
            meshGroups = vesselMeshGroups[v] if vesselMeshGroups else [])

    fm.endChange()
    return nodeIdentifier, elementIdentifier, (ox, od1, od2, od3, oNodeId, oPositions)
Пример #11
0
 def createHermiteCurvePoints(self,
                              aProportion1,
                              aProportion2,
                              bProportion1,
                              bProportion2,
                              elementsCount,
                              derivativeStart=None,
                              derivativeEnd=None,
                              curveMode=HermiteCurveMode.SMOOTH):
     '''
     Create hermite curve points between two points a and b on the surface, each defined
     by their proportions over the surface in directions 1 and 2.
     Also returns cross direction 2 in plane of surface with similar magnitude to curve derivative 1,
     and unit surface normals.
     :param derivativeStart, derivativeEnd: Optional derivative vectors in 3-D world coordinates
     to match at the start and end of the curves. If omitted, fits in with other derivative or is
     in a straight line from a to b.
     :param elementsCount:  Number of elements out.
     :return: nx[], nd1[], nd2[], nd3[], nProportions[]
     '''
     #print('createHermiteCurvePoints', aProportion1, aProportion2, bProportion1, bProportion2, elementsCount, derivativeStart, derivativeEnd)
     if derivativeStart:
         position = self.createPositionProportion(aProportion1,
                                                  aProportion2)
         _, sd1, sd2 = self.evaluateCoordinates(position, derivatives=True)
         delta_xi1, delta_xi2 = calculate_surface_delta_xi(
             sd1, sd2, derivativeStart)
         dp1Start = delta_xi1 / self.elementsCount1
         if self.loop1:
             dp1Start *= 2.0
         dp2Start = delta_xi2 / self.elementsCount2
         derivativeMagnitudeStart = math.sqrt(dp1Start * dp1Start +
                                              dp2Start * dp2Start)
         dp1Start *= elementsCount
         dp2Start *= elementsCount
         #print('start delta_xi1', delta_xi1, 'delta_xi2', delta_xi2)
         #print('dp1Start', dp1Start, 'dp2Start', dp2Start)
     if derivativeEnd:
         position = self.createPositionProportion(bProportion1,
                                                  bProportion2)
         _, sd1, sd2 = self.evaluateCoordinates(position, derivatives=True)
         delta_xi1, delta_xi2 = calculate_surface_delta_xi(
             sd1, sd2, derivativeEnd)
         dp1End = delta_xi1 / self.elementsCount1
         dp2End = delta_xi2 / self.elementsCount2
         derivativeMagnitudeEnd = math.sqrt(dp1End * dp1End +
                                            dp2End * dp2End)
         dp1End *= elementsCount
         if self.loop1:
             dp1End *= 2.0
         dp2End *= elementsCount
         #print('end delta_xi1', delta_xi1, 'delta_xi2', delta_xi2)
         #print('dp1End', dp1End, 'dp2End', dp2End)
     if not derivativeStart:
         if derivativeEnd:
             dp1Start, dp2Start = interp.interpolateLagrangeHermiteDerivative(
                 [aProportion1, aProportion2], [bProportion1, bProportion2],
                 [dp1End, dp2End], 0.0)
         else:
             dp1Start = bProportion1 - aProportion1
             dp2Start = bProportion2 - aProportion2
         derivativeMagnitudeStart = math.sqrt(
             dp1Start * dp1Start + dp2Start * dp2Start) / elementsCount
     if not derivativeEnd:
         if derivativeStart:
             dp1End, dp2End = interp.interpolateHermiteLagrangeDerivative(
                 [aProportion1, aProportion2], [dp1Start, dp2Start],
                 [bProportion1, bProportion2], 1.0)
         else:
             dp1End = bProportion1 - aProportion1
             dp2End = bProportion2 - aProportion2
         derivativeMagnitudeEnd = math.sqrt(dp1End * dp1End +
                                            dp2End * dp2End) / elementsCount
     maxProportion1 = 2.0 if self.loop1 else 1.0
     #print('derivativeMagnitudeStart', derivativeMagnitudeStart, 'derivativeMagnitudeEnd', derivativeMagnitudeEnd)
     proportions, dproportions = interp.sampleCubicHermiteCurvesSmooth([ [ aProportion1, aProportion2 ], [ bProportion1, bProportion2 ] ], \
         [ [ dp1Start, dp2Start ], [ dp1End, dp2End ] ], elementsCount, derivativeMagnitudeStart, derivativeMagnitudeEnd)[0:2]
     if curveMode != self.HermiteCurveMode.SMOOTH:
         if derivativeStart and (curveMode in [
                 self.HermiteCurveMode.TRANSITION_START,
                 self.HermiteCurveMode.TRANSITION_START_AND_END
         ]):
             addLengthStart = 0.5 * derivativeMagnitudeStart
             lengthFractionStart = 0.5
         else:
             addLengthStart = 0.0
             lengthFractionStart = 1.0
         if derivativeEnd and (curveMode in [
                 self.HermiteCurveMode.TRANSITION_END,
                 self.HermiteCurveMode.TRANSITION_START_AND_END
         ]):
             addLengthEnd = 0.5 * derivativeMagnitudeEnd
             lengthFractionEnd = 0.5
         else:
             addLengthEnd = 0.0
             lengthFractionEnd = 1.0
         proportions, dproportions = interp.sampleCubicHermiteCurves(
             proportions, dproportions, elementsCount, addLengthStart,
             addLengthEnd, lengthFractionStart, lengthFractionEnd)[0:2]
     #print(' proportions', proportions)
     #print('dproportions', dproportions)
     nx = []
     nd1 = []
     nd2 = []
     nd3 = []
     for n in range(0, elementsCount + 1):
         position = self.createPositionProportion(proportions[n][0],
                                                  proportions[n][1])
         x, sd1, sd2 = self.evaluateCoordinates(position, derivatives=True)
         f1 = dproportions[n][0] * self.elementsCount1 / maxProportion1
         f2 = dproportions[n][1] * self.elementsCount2
         d1 = [(f1 * sd1[c] + f2 * sd2[c]) for c in range(3)]
         d3 = vector.crossproduct3(sd1, sd2)
         # handle zero magnitude of d3
         mag = math.sqrt(sum(d3[c] * d3[c] for c in range(3)))
         if mag > 0.0:
             d3 = [(d3[c] / mag) for c in range(3)]
         d2 = vector.crossproduct3(d3, d1)
         nx.append(x)
         nd2.append(d2)
         nd1.append(d1)
         nd3.append(d3)
     #print('createHermiteCurvePoints end \n nx', nx,'\nnd1',nd1,'\nnd2',nd2,'\nnd3',nd3)
     return nx, nd1, nd2, nd3, proportions
    def generateBaseMesh(cls, region, options):
        """
        Generate the base tricubic Hermite mesh. See also generateMesh().
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: annotationGroups
        """
        centralPath = options['Central path']
        segmentCount = options['Number of segments']
        elementsCountAround = options['Number of elements around']
        elementsCountAlongSegment = options['Number of elements along segment']
        elementsCountThroughWall = options['Number of elements through wall']
        duodenumLength = options['Duodenum length']
        jejunumLength = options['Jejunum length']
        duodenumInnerRadius = options['Duodenum inner radius']
        duodenumJejunumInnerRadius = options['Duodenum-jejunum inner radius']
        jejunumIleumInnerRadius = options['Jejunum-ileum inner radius']
        ileumInnerRadius = options['Ileum inner radius']
        wallThickness = options['Wall thickness']
        useCrossDerivatives = options['Use cross derivatives']
        useCubicHermiteThroughWall = not(options['Use linear through wall'])
        elementsCountAlong = int(elementsCountAlongSegment*segmentCount)
        startPhase = 0.0

        firstNodeIdentifier = 1
        firstElementIdentifier = 1

        # Central path
        tmpRegion = region.createRegion()
        centralPath.generate(tmpRegion)
        cx, cd1, cd2, cd12 = extractPathParametersFromRegion(tmpRegion)
        # for i in range(len(cx)):
        #     print(i, '[', cx[i], ',', cd1[i], ',', cd2[i],',', cd12[i], '],')
        del tmpRegion

        # find arclength of colon
        length = 0.0
        elementsCountIn = len(cx) - 1
        sd1 = interp.smoothCubicHermiteDerivativesLine(cx, cd1, fixAllDirections = True,
            magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN)
        for e in range(elementsCountIn):
            arcLength = interp.getCubicHermiteArcLength(cx[e], sd1[e], cx[e + 1], sd1[e + 1])
            # print(e+1, arcLength)
            length += arcLength
        segmentLength = length / segmentCount
        elementAlongLength = length / elementsCountAlong
        # print('Length = ', length)

        # Sample central path
        sx, sd1, se, sxi, ssf = interp.sampleCubicHermiteCurves(cx, cd1, elementsCountAlongSegment*segmentCount)
        sd2, sd12 = interp.interpolateSampleCubicHermite(cd2, cd12, se, sxi, ssf)

        # Generate variation of radius & tc width along length
        lengthList = [0.0, duodenumLength, duodenumLength + jejunumLength, length]
        innerRadiusList = [duodenumInnerRadius, duodenumJejunumInnerRadius, jejunumIleumInnerRadius, ileumInnerRadius]
        innerRadiusSegmentList, dInnerRadiusSegmentList = interp.sampleParameterAlongLine(lengthList, innerRadiusList,
                                                                                          segmentCount)

        # Create annotation groups for small intestine sections
        elementsAlongDuodenum = round(duodenumLength / elementAlongLength)
        elementsAlongJejunum = round(jejunumLength / elementAlongLength)
        elementsAlongIleum = elementsCountAlong - elementsAlongDuodenum - elementsAlongJejunum
        elementsCountAlongGroups = [elementsAlongDuodenum, elementsAlongJejunum, elementsAlongIleum]

        smallintestineGroup = AnnotationGroup(region, get_smallintestine_term("small intestine"))
        duodenumGroup = AnnotationGroup(region, get_smallintestine_term("duodenum"))
        jejunumGroup = AnnotationGroup(region, get_smallintestine_term("jejunum"))
        ileumGroup = AnnotationGroup(region, get_smallintestine_term("ileum"))

        annotationGroupAlong = [[smallintestineGroup, duodenumGroup],
                                [smallintestineGroup, jejunumGroup],
                                [smallintestineGroup, ileumGroup]]

        annotationGroupsAlong = []
        for i in range(len(elementsCountAlongGroups)):
            elementsCount = elementsCountAlongGroups[i]
            for n in range(elementsCount):
                annotationGroupsAlong.append(annotationGroupAlong[i])

        annotationGroupsAround = []
        for i in range(elementsCountAround):
            annotationGroupsAround.append([ ])

        annotationGroupsThroughWall = []
        for i in range(elementsCountThroughWall):
            annotationGroupsThroughWall.append([ ])

        xExtrude = []
        d1Extrude = []
        d2Extrude = []
        d3UnitExtrude = []

        # Create object
        smallIntestineSegmentTubeMeshInnerPoints = CylindricalSegmentTubeMeshInnerPoints(
            elementsCountAround, elementsCountAlongSegment, segmentLength,
            wallThickness, innerRadiusSegmentList, dInnerRadiusSegmentList, startPhase)

        for nSegment in range(segmentCount):
            # Create inner points
            xInner, d1Inner, d2Inner, transitElementList, segmentAxis, radiusAlongSegmentList = \
               smallIntestineSegmentTubeMeshInnerPoints.getCylindricalSegmentTubeMeshInnerPoints(nSegment)

            # Project reference point for warping onto central path
            start = nSegment*elementsCountAlongSegment
            end = (nSegment + 1)*elementsCountAlongSegment + 1
            sxRefList, sd1RefList, sd2ProjectedListRef, zRefList = \
                tubemesh.getPlaneProjectionOnCentralPath(xInner, elementsCountAround, elementsCountAlongSegment,
                                                         segmentLength, sx[start:end], sd1[start:end], sd2[start:end],
                                                         sd12[start:end])

            # Warp segment points
            xWarpedList, d1WarpedList, d2WarpedList, d3WarpedUnitList = tubemesh.warpSegmentPoints(
                xInner, d1Inner, d2Inner, segmentAxis, sxRefList, sd1RefList, sd2ProjectedListRef,
                elementsCountAround, elementsCountAlongSegment, zRefList, radiusAlongSegmentList,
                closedProximalEnd=False)

            # Store points along length
            xExtrude = xExtrude + (xWarpedList if nSegment == 0 else xWarpedList[elementsCountAround:])
            d1Extrude = d1Extrude + (d1WarpedList if nSegment == 0 else d1WarpedList[elementsCountAround:])

            # Smooth d2 for nodes between segments and recalculate d3
            if nSegment == 0:
                d2Extrude = d2Extrude + (d2WarpedList[:-elementsCountAround])
                d3UnitExtrude = d3UnitExtrude + (d3WarpedUnitList[:-elementsCountAround])
            else:
                xSecondFace = xWarpedList[elementsCountAround:elementsCountAround*2]
                d2SecondFace = d2WarpedList[elementsCountAround:elementsCountAround*2]
                for n1 in range(elementsCountAround):
                    nx = [xLastTwoFaces[n1], xLastTwoFaces[n1 + elementsCountAround], xSecondFace[n1]]
                    nd2 = [d2LastTwoFaces[n1], d2LastTwoFaces[n1 + elementsCountAround], d2SecondFace[n1]]
                    d2 = interp.smoothCubicHermiteDerivativesLine(nx, nd2, fixStartDerivative = True,
                                                                  fixEndDerivative = True)[1]
                    d2Extrude.append(d2)
                    d3Unit = vector.normalise(vector.crossproduct3(vector.normalise(d1LastTwoFaces[n1 + elementsCountAround]),
                                                                   vector.normalise(d2)))
                    d3UnitExtrude.append(d3Unit)
                d2Extrude = d2Extrude + \
                            (d2WarpedList[elementsCountAround:-elementsCountAround] if nSegment < segmentCount - 1 else
                             d2WarpedList[elementsCountAround:])
                d3UnitExtrude = d3UnitExtrude + \
                                (d3WarpedUnitList[elementsCountAround:-elementsCountAround] if nSegment < segmentCount - 1 else
                                 d3WarpedUnitList[elementsCountAround:])
            xLastTwoFaces = xWarpedList[-elementsCountAround*2:]
            d1LastTwoFaces = d1WarpedList[-elementsCountAround*2:]
            d2LastTwoFaces = d2WarpedList[-elementsCountAround*2:]

        # Create coordinates and derivatives
        xList, d1List, d2List, d3List, curvatureList = tubemesh.getCoordinatesFromInner(xExtrude, d1Extrude,
            d2Extrude, d3UnitExtrude, [wallThickness]*(elementsCountAlong+1),
            elementsCountAround, elementsCountAlong, elementsCountThroughWall, transitElementList)

        flatWidthList, xiList = smallIntestineSegmentTubeMeshInnerPoints.getFlatWidthAndXiList()

        # Create flat and texture coordinates
        xFlat, d1Flat, d2Flat, xTexture, d1Texture, d2Texture = tubemesh.createFlatAndTextureCoordinates(
            xiList, flatWidthList, length, wallThickness, elementsCountAround,
            elementsCountAlong, elementsCountThroughWall, transitElementList)

        # Create nodes and elements
        nextNodeIdentifier, nextElementIdentifier, annotationGroups = tubemesh.createNodesAndElements(
            region, xList, d1List, d2List, d3List, xFlat, d1Flat, d2Flat, xTexture, d1Texture, d2Texture,
            elementsCountAround, elementsCountAlong, elementsCountThroughWall,
            annotationGroupsAround, annotationGroupsAlong, annotationGroupsThroughWall,
            firstNodeIdentifier, firstElementIdentifier, useCubicHermiteThroughWall, useCrossDerivatives,
            closedProximalEnd=False)

        return annotationGroups
Пример #13
0
    def getTriplePoints(self, n3):
        '''
        Compute coordinates and derivatives of points where 3 square elements merge.
        :param n3: Index of through-wall coordinates to use.
        '''
        n1a = self.elementsCountRim
        n1b = n1a + 1
        n1c = n1a + 2
        m1a = self.elementsCountAcross - self.elementsCountRim
        m1b = m1a - 1
        m1c = m1a - 2
        n2a = self.elementsCountRim
        n2b = n2a + 1
        n2c = n2a + 2
        # left
        ltx = []

        if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][n1c], self.px[n3][n2c][n1b]],
                [[(-self.pd1[n3][n2a][n1c][c] - self.pd3[n3][n2a][n1c][c])
                  for c in range(3)], self.pd1[n3][n2c][n1b]],
                2,
                arcLengthDerivatives=True)[0:2]
            ltx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][n1b], self.px[n3][n2c][n1c]],
                [[-self.pd3[n3][n2a][n1b][c] for c in range(3)],
                 [(self.pd1[n3][n2c][n1c][c] + self.pd3[n3][n2c][n1c][c])
                  for c in range(3)]],
                2,
                arcLengthDerivatives=True)[0:2]
            ltx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2c][n1a], self.px[n3][n2b][n1c]],
                [[(self.pd1[n3][n2c][n1a][c] - self.pd3[n3][n2c][n1a][c])
                  for c in range(3)], self.pd3[n3][n2b][n1c]],
                2,
                arcLengthDerivatives=True)[0:2]
            ltx.append(tx[1])
        elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][n1c], self.px[n3][n2c][n1b]],
                [[(-self.pd1[n3][n2a][n1c][c] + self.pd2[n3][n2a][n1c][c])
                  for c in range(3)], self.pd2[n3][n2c][n1b]],
                2,
                arcLengthDerivatives=True)[0:2]
            ltx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][n1b], self.px[n3][n2c][n1c]], [
                    self.pd2[n3][n2a][n1b],
                    [(self.pd1[n3][n2c][n1c][c] + self.pd2[n3][n2c][n1c][c])
                     for c in range(3)]
                ],
                2,
                arcLengthDerivatives=True)[0:2]
            ltx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2c][n1a], self.px[n3][n2b][n1c]],
                [[(self.pd1[n3][n2c][n1a][c] - self.pd2[n3][n2c][n1a][c])
                  for c in range(3)], self.pd1[n3][n2b][n1c]],
                2,
                arcLengthDerivatives=True)[0:2]
            ltx.append(tx[1])
        #x = [ (ltx[0][c] + ltx[1][c] + ltx[2][c])/3.0 for c in range(3) ]
        x = [(ltx[0][c] + ltx[2][c]) / 2.0 for c in range(3)]
        if self.trackSurface:
            p = self.trackSurface.findNearestPosition(
                x,
                startPosition=self.trackSurface.createPositionProportion(
                    *(self.pProportions[n2b][n1c])))
            self.pProportions[n2b][n1b] = self.trackSurface.getProportion(p)
            x, sd1, sd2 = self.trackSurface.evaluateCoordinates(
                p, derivatives=True)
            d1, d2, d3 = calculate_surface_axes(sd1, sd2,
                                                vector.normalise(sd1))
            self.pd3[n3][n2b][n1b] = d3
        self.px[n3][n2b][n1b] = x
        if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
            self.pd3[n3][n2b][n1b] = [
                (self.px[n3][n2b][n1c][c] - self.px[n3][n2b][n1b][c])
                for c in range(3)
            ]
            self.pd1[n3][n2b][n1b] = [
                (self.px[n3][n2c][n1b][c] - self.px[n3][n2b][n1b][c])
                for c in range(3)
            ]
        elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
            self.pd1[n3][n2b][n1b] = [
                (self.px[n3][n2b][n1c][c] - self.px[n3][n2b][n1b][c])
                for c in range(3)
            ]
            self.pd2[n3][n2b][n1b] = [
                (self.px[n3][n2c][n1b][c] - self.px[n3][n2b][n1b][c])
                for c in range(3)
            ]
        if not self.trackSurface:
            if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
                self.pd2[n3][n2b][n1b] = vector.normalise(
                    vector.crossproduct3(self.pd3[n3][n2b][n1b],
                                         self.pd1[n3][n2b][n1b]))
            elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
                self.pd3[n3][n2b][n1b] = vector.normalise(
                    vector.crossproduct3(self.pd1[n3][n2b][n1b],
                                         self.pd2[n3][n2b][n1b]))
        # right
        rtx = []
        if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][m1c], self.px[n3][n2c][m1b]],
                [[(self.pd1[n3][n2a][m1c][c] - self.pd3[n3][n2a][m1c][c])
                  for c in range(3)], self.pd1[n3][n2c][m1b]],
                2,
                arcLengthDerivatives=True)[0:2]
            rtx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][m1b], self.px[n3][n2c][m1c]],
                [[-self.pd3[n3][n2a][m1b][c] for c in range(3)],
                 [(-self.pd3[n3][n2c][m1c][c] + self.pd1[n3][n2c][m1c][c])
                  for c in range(3)]],
                2,
                arcLengthDerivatives=True)[0:2]
            rtx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2c][m1a], self.px[n3][n2b][m1c]],
                [[(-self.pd1[n3][n2c][m1a][c] - self.pd3[n3][n2c][m1a][c])
                  for c in range(3)], [-d for d in self.pd3[n3][n2b][m1c]]],
                2,
                arcLengthDerivatives=True)[0:2]
            rtx.append(tx[1])
        elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][m1c], self.px[n3][n2c][m1b]],
                [[(self.pd1[n3][n2a][m1c][c] + self.pd2[n3][n2a][m1c][c])
                  for c in range(3)], self.pd2[n3][n2c][m1b]],
                2,
                arcLengthDerivatives=True)[0:2]
            rtx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2a][m1b], self.px[n3][n2c][m1c]], [
                    self.pd2[n3][n2a][m1b],
                    [(-self.pd1[n3][n2c][m1c][c] + self.pd2[n3][n2c][m1c][c])
                     for c in range(3)]
                ],
                2,
                arcLengthDerivatives=True)[0:2]
            rtx.append(tx[1])
            tx, td1 = sampleCubicHermiteCurves(
                [self.px[n3][n2c][m1a], self.px[n3][n2b][m1c]],
                [[(-self.pd1[n3][n2c][m1a][c] - self.pd2[n3][n2c][m1a][c])
                  for c in range(3)], [-d for d in self.pd1[n3][n2b][m1c]]],
                2,
                arcLengthDerivatives=True)[0:2]
            rtx.append(tx[1])
        #x = [ (rtx[0][c] + rtx[1][c] + rtx[2][c])/3.0 for c in range(3) ]
        x = [(rtx[0][c] + rtx[2][c]) / 2.0 for c in range(3)]
        if self.trackSurface:
            p = self.trackSurface.findNearestPosition(
                x,
                startPosition=self.trackSurface.createPositionProportion(
                    *(self.pProportions[n2b][m1c])))
            self.pProportions[n2b][m1b] = self.trackSurface.getProportion(p)
            x, sd1, sd2 = self.trackSurface.evaluateCoordinates(
                p, derivatives=True)
            d1, d2, d3 = calculate_surface_axes(sd1, sd2,
                                                vector.normalise(sd1))
            self.pd3[n3][n2b][m1b] = d3
        self.px[n3][n2b][m1b] = x
        if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
            self.pd3[n3][n2b][m1b] = [
                (self.px[n3][n2b][m1b][c] - self.px[n3][n2b][m1c][c])
                for c in range(3)
            ]
            self.pd1[n3][n2b][m1b] = [
                (self.px[n3][n2c][m1b][c] - self.px[n3][n2b][m1b][c])
                for c in range(3)
            ]
        elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
            self.pd1[n3][n2b][m1b] = [
                (self.px[n3][n2b][m1b][c] - self.px[n3][n2b][m1c][c])
                for c in range(3)
            ]
            self.pd2[n3][n2b][m1b] = [
                (self.px[n3][n2c][m1b][c] - self.px[n3][n2b][m1b][c])
                for c in range(3)
            ]
        if not self.trackSurface:
            if self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_AROUND:
                self.pd2[n3][n2b][m1b] = vector.normalise(
                    vector.crossproduct3(self.pd3[n3][n2b][m1b],
                                         self.pd1[n3][n2b][m1b]))
            elif self._type == ShieldRimDerivativeMode.SHIELD_RIM_DERIVATIVE_MODE_REGULAR:
                self.pd3[n3][n2b][m1b] = vector.normalise(
                    vector.crossproduct3(self.pd1[n3][n2b][m1b],
                                         self.pd2[n3][n2b][m1b]))
Пример #14
0
    def generateBaseMesh(region, options):
        """
        Generate the base tricubic Hermite mesh. See also generateMesh().
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: annotationGroups
        """
        centralPath = options['Central path']
        segmentProfile = options['Segment profile']
        segmentCount = options['Number of segments']
        startPhase = options['Start phase'] % 360.0
        proximalLength = options['Proximal length']
        transverseLength = options['Transverse length']
        distalLength = options['Distal length']
        proximalInnerRadius = options['Proximal inner radius']
        proximalTCWidth = options['Proximal tenia coli width']
        proximalTransverseInnerRadius = options[
            'Proximal-transverse inner radius']
        proximalTransverseTCWidth = options[
            'Proximal-transverse tenia coli width']
        transverseDistalInnerRadius = options['Transverse-distal inner radius']
        transverseDistalTCWidth = options['Transverse-distal tenia coli width']
        distalInnerRadius = options['Distal inner radius']
        distalTCWidth = options['Distal tenia coli width']
        segmentSettings = segmentProfile.getScaffoldSettings()

        elementsCountAroundTC = segmentSettings[
            'Number of elements around tenia coli']
        elementsCountAroundHaustrum = segmentSettings[
            'Number of elements around haustrum']
        cornerInnerRadiusFactor = segmentSettings['Corner inner radius factor']
        haustrumInnerRadiusFactor = segmentSettings[
            'Haustrum inner radius factor']
        segmentLengthEndDerivativeFactor = segmentSettings[
            'Segment length end derivative factor']
        segmentLengthMidDerivativeFactor = segmentSettings[
            'Segment length mid derivative factor']
        tcCount = segmentSettings['Number of tenia coli']
        tcThickness = segmentSettings['Tenia coli thickness']
        elementsCountAround = (elementsCountAroundTC +
                               elementsCountAroundHaustrum) * tcCount

        elementsCountAlongSegment = segmentSettings[
            'Number of elements along segment']
        elementsCountThroughWall = segmentSettings[
            'Number of elements through wall']
        wallThickness = segmentSettings['Wall thickness']
        useCrossDerivatives = segmentSettings['Use cross derivatives']
        useCubicHermiteThroughWall = not (
            segmentSettings['Use linear through wall'])
        elementsCountAlong = int(elementsCountAlongSegment * segmentCount)

        firstNodeIdentifier = 1
        firstElementIdentifier = 1

        # Central path
        tmpRegion = region.createRegion()
        centralPath.generate(tmpRegion)
        cx, cd1, cd2, cd12 = extractPathParametersFromRegion(tmpRegion)
        # for i in range(len(cx)):
        # print(i, '[', cx[i], ',', cd1[i], ',', cd2[i], ',', cd12[i], '],')
        del tmpRegion

        # find arclength of colon
        length = 0.0
        elementsCountIn = len(cx) - 1
        sd1 = interp.smoothCubicHermiteDerivativesLine(
            cx,
            cd1,
            fixAllDirections=True,
            magnitudeScalingMode=interp.DerivativeScalingMode.HARMONIC_MEAN)
        for e in range(elementsCountIn):
            arcLength = interp.getCubicHermiteArcLength(
                cx[e], sd1[e], cx[e + 1], sd1[e + 1])
            # print(e+1, arcLength)
            length += arcLength
        segmentLength = length / segmentCount
        # print('Length = ', length)

        # Sample central path
        sx, sd1, se, sxi, ssf = interp.sampleCubicHermiteCurves(
            cx, cd1, elementsCountAlongSegment * segmentCount)
        sd2 = interp.interpolateSampleCubicHermite(cd2, cd12, se, sxi, ssf)[0]

        # Generate variation of radius & tc width along length
        lengthList = [
            0.0, proximalLength, proximalLength + transverseLength, length
        ]
        innerRadiusList = [
            proximalInnerRadius, proximalTransverseInnerRadius,
            transverseDistalInnerRadius, distalInnerRadius
        ]
        innerRadiusAlongElementList, dInnerRadiusAlongElementList = interp.sampleParameterAlongLine(
            lengthList, innerRadiusList, elementsCountAlong)

        tcWidthList = [
            proximalTCWidth, proximalTransverseTCWidth,
            transverseDistalTCWidth, distalTCWidth
        ]
        tcWidthAlongElementList, dTCWidthAlongElementList = interp.sampleParameterAlongLine(
            lengthList, tcWidthList, elementsCountAlong)

        xExtrude = []
        d1Extrude = []
        d2Extrude = []
        d3UnitExtrude = []

        # Create object
        colonSegmentTubeMeshInnerPoints = ColonSegmentTubeMeshInnerPoints(
            region, elementsCountAroundTC, elementsCountAroundHaustrum,
            elementsCountAlongSegment, tcCount,
            segmentLengthEndDerivativeFactor, segmentLengthMidDerivativeFactor,
            segmentLength, wallThickness, cornerInnerRadiusFactor,
            haustrumInnerRadiusFactor, innerRadiusAlongElementList,
            dInnerRadiusAlongElementList, tcWidthAlongElementList, startPhase)

        for nSegment in range(segmentCount):
            # Create inner points
            xInner, d1Inner, d2Inner, transitElementList, segmentAxis, annotationGroups, annotationArray, \
                faceMidPointsZ = colonSegmentTubeMeshInnerPoints.getColonSegmentTubeMeshInnerPoints(nSegment)

            # Warp segment points
            xWarpedList, d1WarpedList, d2WarpedList, d3WarpedUnitList = tubemesh.warpSegmentPoints(
                xInner, d1Inner, d2Inner, segmentAxis, segmentLength, sx, sd1,
                sd2, elementsCountAround, elementsCountAlongSegment, nSegment,
                faceMidPointsZ)

            # Store points along length
            xExtrude = xExtrude + (xWarpedList if nSegment == 0 else
                                   xWarpedList[elementsCountAround:])
            d1Extrude = d1Extrude + (d1WarpedList if nSegment == 0 else
                                     d1WarpedList[elementsCountAround:])
            d2Extrude = d2Extrude + (d2WarpedList if nSegment == 0 else
                                     d2WarpedList[elementsCountAround:])
            d3UnitExtrude = d3UnitExtrude + (
                d3WarpedUnitList
                if nSegment == 0 else d3WarpedUnitList[elementsCountAround:])

        contractedWallThicknessList = colonSegmentTubeMeshInnerPoints.getContractedWallThicknessList(
        )

        # Create coordinates and derivatives
        xList, d1List, d2List, d3List, curvatureList = tubemesh.getCoordinatesFromInner(
            xExtrude, d1Extrude, d2Extrude, d3UnitExtrude,
            contractedWallThicknessList, elementsCountAround,
            elementsCountAlong, elementsCountThroughWall, transitElementList)

        relaxedLengthList, xiList = colonSegmentTubeMeshInnerPoints.getRelaxedLengthAndXiList(
        )

        if tcThickness > 0:
            tubeTCWidthList = colonSegmentTubeMeshInnerPoints.getTubeTCWidthList(
            )
            xList, d1List, d2List, d3List, annotationGroups, annotationArray = getTeniaColi(
                region, xList, d1List, d2List, d3List, curvatureList, tcCount,
                elementsCountAroundTC, elementsCountAroundHaustrum,
                elementsCountAlong, elementsCountThroughWall, tubeTCWidthList,
                tcThickness, sx, annotationGroups, annotationArray)

            # Create flat and texture coordinates
            xFlat, d1Flat, d2Flat, xTexture, d1Texture, d2Texture = createFlatAndTextureCoordinatesTeniaColi(
                xiList, relaxedLengthList, length, wallThickness, tcCount,
                tcThickness, elementsCountAroundTC,
                elementsCountAroundHaustrum, elementsCountAlong,
                elementsCountThroughWall, transitElementList)

            # Create nodes and elements
            nextNodeIdentifier, nextElementIdentifier, annotationGroups = createNodesAndElementsTeniaColi(
                region, xList, d1List, d2List, d3List, xFlat, d1Flat, d2Flat,
                xTexture, d1Texture, d2Texture, elementsCountAroundTC,
                elementsCountAroundHaustrum, elementsCountAlong,
                elementsCountThroughWall, tcCount, annotationGroups,
                annotationArray, firstNodeIdentifier, firstElementIdentifier,
                useCubicHermiteThroughWall, useCrossDerivatives)

        else:
            # Create flat and texture coordinates
            xFlat, d1Flat, d2Flat, xTexture, d1Texture, d2Texture = tubemesh.createFlatAndTextureCoordinates(
                xiList, relaxedLengthList, length, wallThickness,
                elementsCountAround, elementsCountAlong,
                elementsCountThroughWall, transitElementList)

            # Create nodes and elements
            nextNodeIdentifier, nextElementIdentifier, annotationGroups = tubemesh.createNodesAndElements(
                region, xList, d1List, d2List, d3List, xFlat, d1Flat, d2Flat,
                xTexture, d1Texture, d2Texture, elementsCountAround,
                elementsCountAlong, elementsCountThroughWall, annotationGroups,
                annotationArray, firstNodeIdentifier, firstElementIdentifier,
                useCubicHermiteThroughWall, useCrossDerivatives)

        return annotationGroups
    def generateBaseMesh(cls, region, options):
        """
        Generate the base tricubic Hermite mesh. See also generateMesh().
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: annotationGroups
        """
        centralPath = options['Central path']
        elementsCountAround = options['Number of elements around']
        elementsCountAlong = options['Number of elements along']
        elementsCountThroughWall = options['Number of elements through wall']
        wallThickness = options['Wall thickness']
        mucosaRelThickness = options['Mucosa relative thickness']
        submucosaRelThickness = options['Submucosa relative thickness']
        circularRelThickness = options[
            'Circular muscle layer relative thickness']
        longitudinalRelThickness = options[
            'Longitudinal muscle layer relative thickness']
        useCrossDerivatives = options['Use cross derivatives']
        useCubicHermiteThroughWall = not (options['Use linear through wall'])

        firstNodeIdentifier = 1
        firstElementIdentifier = 1

        # Central path
        esophagusTermsAlong = [
            None, 'cervical part of esophagus', 'thoracic part of esophagus',
            'abdominal part of esophagus'
        ]
        arcLengthOfGroupsAlong = []
        for i in range(len(esophagusTermsAlong)):
            tmpRegion = region.createRegion()
            centralPath.generate(tmpRegion)
            cxGroup, cd1Group, cd2Group, cd3Group, cd12Group, cd13Group = \
                extractPathParametersFromRegion(tmpRegion, [Node.VALUE_LABEL_VALUE, Node.VALUE_LABEL_D_DS1,
                                                            Node.VALUE_LABEL_D_DS2, Node.VALUE_LABEL_D_DS3,
                                                            Node.VALUE_LABEL_D2_DS1DS2, Node.VALUE_LABEL_D2_DS1DS3],
                                                groupName=esophagusTermsAlong[i])
            arcLength = 0.0
            for e in range(len(cxGroup) - 1):
                arcLength += interp.getCubicHermiteArcLength(
                    cxGroup[e], cd1Group[e], cxGroup[e + 1], cd1Group[e + 1])
            arcLengthOfGroupsAlong.append(arcLength)

            if i == 0:
                cx = cxGroup
                cd1 = cd1Group
                cd2 = cd2Group
                cd3 = cd3Group
                cd12 = cd12Group
                cd13 = cd13Group

            del tmpRegion

        # Sample central path
        sx, sd1, se, sxi, ssf = interp.sampleCubicHermiteCurves(
            cx, cd1, elementsCountAlong)
        sd2, sd12 = interp.interpolateSampleCubicHermite(
            cd2, cd12, se, sxi, ssf)
        sd3, sd13 = interp.interpolateSampleCubicHermite(
            cd3, cd13, se, sxi, ssf)

        centralPathLength = arcLengthOfGroupsAlong[0]
        elementAlongLength = centralPathLength / elementsCountAlong

        elementsCountAlongGroups = []
        groupLength = 0.0
        e = 0
        elementsCount = 1
        length = elementAlongLength
        for i in range(1, len(esophagusTermsAlong)):
            groupLength += arcLengthOfGroupsAlong[i]
            if e == elementsCountAlong - 2:
                elementsCount += 1
                elementsCountAlongGroups.append(elementsCount)
            else:
                while length < groupLength:
                    elementsCount += 1
                    e += 1
                    length += elementAlongLength

                # check which end is grouplength closer to
                distToUpperEnd = abs(length - groupLength)
                distToLowerEnd = abs(groupLength -
                                     (length - elementsCountAlong))
                if distToLowerEnd < distToUpperEnd:
                    elementsCount -= 1
                    elementsCountAlongGroups.append(elementsCount)
                    e -= 1
                    length -= elementAlongLength
                else:
                    elementsCountAlongGroups.append(elementsCount)
            elementsCount = 0

        majorRadiusElementList = sd2
        minorRadiusElementList = sd3

        # Create annotation groups along esophagus
        esophagusGroup = AnnotationGroup(region,
                                         get_esophagus_term("esophagus"))
        cervicalGroup = AnnotationGroup(
            region, get_esophagus_term("cervical part of esophagus"))
        thoracicGroup = AnnotationGroup(
            region, get_esophagus_term("thoracic part of esophagus"))
        abdominalGroup = AnnotationGroup(
            region, get_esophagus_term("abdominal part of esophagus"))

        annotationGroupAlong = [[esophagusGroup, cervicalGroup],
                                [esophagusGroup, thoracicGroup],
                                [esophagusGroup, abdominalGroup]]

        annotationGroupsAlong = []
        for i in range(len(elementsCountAlongGroups)):
            elementsCount = elementsCountAlongGroups[i]
            for n in range(elementsCount):
                annotationGroupsAlong.append(annotationGroupAlong[i])

        annotationGroupsAround = []
        for i in range(elementsCountAround):
            annotationGroupsAround.append([])

        # Groups through wall
        longitudinalMuscleGroup = AnnotationGroup(
            region,
            get_esophagus_term("esophagus smooth muscle longitudinal layer"))
        circularMuscleGroup = AnnotationGroup(
            region,
            get_esophagus_term("esophagus smooth muscle circular layer"))
        submucosaGroup = AnnotationGroup(
            region, get_esophagus_term("submucosa of esophagus"))
        mucosaGroup = AnnotationGroup(region,
                                      get_esophagus_term("esophagus mucosa"))

        if elementsCountThroughWall == 1:
            relativeThicknessList = [1.0]
            annotationGroupsThroughWall = [[]]
        else:
            relativeThicknessList = [
                mucosaRelThickness, submucosaRelThickness,
                circularRelThickness, longitudinalRelThickness
            ]
            annotationGroupsThroughWall = [[mucosaGroup], [submucosaGroup],
                                           [circularMuscleGroup],
                                           [longitudinalMuscleGroup]]

        xToSample = []
        d1ToSample = []
        for n2 in range(elementsCountAlong + 1):
            # Create inner points
            cx = [0.0, 0.0, elementAlongLength * n2]
            axis1 = [vector.magnitude(majorRadiusElementList[n2]), 0.0, 0.0]
            axis2 = [0.0, vector.magnitude(minorRadiusElementList[n2]), 0.0]
            xInner, d1Inner = geometry.createEllipsePoints(cx,
                                                           2 * math.pi,
                                                           axis1,
                                                           axis2,
                                                           elementsCountAround,
                                                           startRadians=0.0)
            xToSample += xInner
            d1ToSample += d1Inner

        d2ToSample = [[0.0, 0.0, elementAlongLength]
                      ] * (elementsCountAround * (elementsCountAlong + 1))

        # Sample along length
        xInnerRaw = []
        d2InnerRaw = []
        xToWarp = []
        d1ToWarp = []
        d2ToWarp = []
        flatWidthList = []
        xiList = []

        for n1 in range(elementsCountAround):
            xForSamplingAlong = []
            d2ForSamplingAlong = []
            for n2 in range(elementsCountAlong + 1):
                idx = n2 * elementsCountAround + n1
                xForSamplingAlong.append(xToSample[idx])
                d2ForSamplingAlong.append(d2ToSample[idx])
            xSampled, d2Sampled = interp.sampleCubicHermiteCurves(
                xForSamplingAlong,
                d2ForSamplingAlong,
                elementsCountAlong,
                arcLengthDerivatives=True)[0:2]
            xInnerRaw.append(xSampled)
            d2InnerRaw.append(d2Sampled)

        # Re-arrange sample order & calculate dx_ds1 and dx_ds3 from dx_ds2
        for n2 in range(elementsCountAlong + 1):
            xAround = []
            d2Around = []

            for n1 in range(elementsCountAround):
                x = xInnerRaw[n1][n2]
                d2 = d2InnerRaw[n1][n2]
                xAround.append(x)
                d2Around.append(d2)

            d1Around = []
            for n1 in range(elementsCountAround):
                v1 = xAround[n1]
                v2 = xAround[(n1 + 1) % elementsCountAround]
                d1 = d2 = [v2[c] - v1[c] for c in range(3)]
                arcLengthAround = interp.computeCubicHermiteArcLength(
                    v1, d1, v2, d2, True)
                dx_ds1 = [c * arcLengthAround for c in vector.normalise(d1)]
                d1Around.append(dx_ds1)
            d1Smoothed = interp.smoothCubicHermiteDerivativesLoop(
                xAround, d1Around)

            xToWarp += xAround
            d1ToWarp += d1Smoothed
            d2ToWarp += d2Around

            # Flat width and xi
            flatWidth = 0.0
            xiFace = []
            for n1 in range(elementsCountAround):
                v1 = xAround[n1]
                d1 = d1Smoothed[n1]
                v2 = xAround[(n1 + 1) % elementsCountAround]
                d2 = d1Smoothed[(n1 + 1) % elementsCountAround]
                flatWidth += interp.getCubicHermiteArcLength(v1, d1, v2, d2)
            flatWidthList.append(flatWidth)

            for n1 in range(elementsCountAround + 1):
                xi = 1.0 / elementsCountAround * n1
                xiFace.append(xi)
            xiList.append(xiFace)

        # Project reference point for warping onto central path
        sxRefList, sd1RefList, sd2ProjectedListRef, zRefList = \
            tubemesh.getPlaneProjectionOnCentralPath(xToWarp, elementsCountAround, elementsCountAlong,
                                                     centralPathLength, sx, sd1, sd2, sd12)

        # Warp points
        segmentAxis = [0.0, 0.0, 1.0]
        closedProximalEnd = False

        innerRadiusAlong = []
        for n2 in range(elementsCountAlong + 1):
            firstNodeAlong = xToWarp[n2 * elementsCountAround]
            midptSegmentAxis = [0.0, 0.0, elementAlongLength * n2]
            radius = vector.magnitude(firstNodeAlong[c] - midptSegmentAxis[c]
                                      for c in range(3))
            innerRadiusAlong.append(radius)

        xWarpedList, d1WarpedList, d2WarpedList, d3WarpedUnitList = \
            tubemesh.warpSegmentPoints(xToWarp, d1ToWarp, d2ToWarp, segmentAxis, sxRefList, sd1RefList,
                                       sd2ProjectedListRef, elementsCountAround, elementsCountAlong,
                                       zRefList, innerRadiusAlong, closedProximalEnd)

        # Create coordinates and derivatives
        transitElementList = [0] * elementsCountAround
        xList, d1List, d2List, d3List, curvatureList = \
            tubemesh.getCoordinatesFromInner(xWarpedList, d1WarpedList, d2WarpedList, d3WarpedUnitList,
                                             [wallThickness]*(elementsCountAlong+1), relativeThicknessList,
                                             elementsCountAround, elementsCountAlong, elementsCountThroughWall,
                                             transitElementList)

        # Create flat coordinates
        xFlat, d1Flat, d2Flat = tubemesh.createFlatCoordinates(
            xiList, flatWidthList, length, wallThickness,
            relativeThicknessList, elementsCountAround, elementsCountAlong,
            elementsCountThroughWall, transitElementList)

        # Create nodes and elements
        xOrgan = []
        d1Organ = []
        d2Organ = []
        nodeIdentifier, elementIdentifier, annotationGroups = \
            tubemesh.createNodesAndElements(region, xList, d1List, d2List, d3List, xFlat, d1Flat, d2Flat,
                                            xOrgan, d1Organ, d2Organ, None, elementsCountAround, elementsCountAlong,
                                            elementsCountThroughWall, annotationGroupsAround, annotationGroupsAlong,
                                            annotationGroupsThroughWall, firstNodeIdentifier, firstElementIdentifier,
                                            useCubicHermiteThroughWall, useCrossDerivatives, closedProximalEnd)

        # annotation fiducial points
        fm = region.getFieldmodule()
        fm.beginChange()
        mesh = fm.findMeshByDimension(3)
        cache = fm.createFieldcache()

        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)

        markerNames = [
            "proximodorsal midpoint on serosa of upper esophageal sphincter",
            "proximoventral midpoint on serosa of upper esophageal sphincter",
            "distal point of lower esophageal sphincter serosa on the greater curvature of stomach",
            "distal point of lower esophageal sphincter serosa on the lesser curvature of stomach"
        ]

        totalElements = elementIdentifier
        radPerElementAround = math.pi * 2.0 / elementsCountAround
        elementAroundHalfPi = int(0.25 * elementsCountAround)
        xi1HalfPi = (math.pi * 0.5 - radPerElementAround *
                     elementAroundHalfPi) / radPerElementAround
        elementAroundPi = int(0.5 * elementsCountAround)
        xi1Pi = (math.pi -
                 radPerElementAround * elementAroundPi) / radPerElementAround

        markerElementIdentifiers = [
            elementsCountAround * elementsCountThroughWall -
            elementAroundHalfPi, elementAroundHalfPi + 1 +
            elementsCountAround * (elementsCountThroughWall - 1),
            totalElements - elementsCountAround,
            totalElements - elementsCountAround + elementAroundPi
        ]

        markerXis = [[1.0 - xi1HalfPi, 0.0, 1.0], [xi1HalfPi, 0.0, 1.0],
                     [0.0, 1.0, 1.0], [xi1Pi, 1.0, 1.0]]

        for n in range(len(markerNames)):
            markerGroup = findOrCreateAnnotationGroupForTerm(
                annotationGroups, region, get_esophagus_term(markerNames[n]))
            markerElement = mesh.findElementByIdentifier(
                markerElementIdentifiers[n])
            markerXi = markerXis[n]
            cache.setMeshLocation(markerElement, markerXi)
            markerPoint = markerPoints.createNode(nodeIdentifier,
                                                  markerTemplateInternal)
            nodeIdentifier += 1
            cache.setNode(markerPoint)
            markerName.assignString(cache, markerGroup.getName())
            markerLocation.assignMeshLocation(cache, markerElement, markerXi)
            for group in [esophagusGroup, markerGroup]:
                group.getNodesetGroup(nodes).addNode(markerPoint)

        fm.endChange()

        return annotationGroups
Пример #16
0
    def generateBaseMesh(cls, region, options):
        """
        Generate the base tricubic Hermite mesh. See also generateMesh().
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :return: annotationGroups
        """
        centralPath = options['Central path']
        segmentProfile = options['Segment profile']
        segmentCount = options['Number of segments']
        startPhase = options['Start phase'] % 360.0
        proximalLength = options['Proximal length']
        transverseLength = options['Transverse length']
        proximalInnerRadius = options['Proximal inner radius']
        proximalTCWidth = options['Proximal tenia coli width']
        proximalTransverseInnerRadius = options['Proximal-transverse inner radius']
        proximalTransverseTCWidth = options['Proximal-transverse tenia coli width']
        transverseDistalInnerRadius = options['Transverse-distal inner radius']
        transverseDistalTCWidth = options['Transverse-distal tenia coli width']
        distalInnerRadius = options['Distal inner radius']
        distalTCWidth = options['Distal tenia coli width']
        segmentSettings = segmentProfile.getScaffoldSettings()

        elementsCountAroundTC = segmentSettings['Number of elements around tenia coli']
        elementsCountAroundHaustrum = segmentSettings['Number of elements around haustrum']
        cornerInnerRadiusFactor = segmentSettings['Corner inner radius factor']
        haustrumInnerRadiusFactor = segmentSettings['Haustrum inner radius factor']
        segmentLengthEndDerivativeFactor = segmentSettings['Segment length end derivative factor']
        segmentLengthMidDerivativeFactor = segmentSettings['Segment length mid derivative factor']
        tcCount = segmentSettings['Number of tenia coli']
        tcThickness = segmentSettings['Tenia coli thickness']
        elementsCountAround = (elementsCountAroundTC + elementsCountAroundHaustrum)*tcCount

        elementsCountAlongSegment = segmentSettings['Number of elements along segment']
        elementsCountThroughWall = segmentSettings['Number of elements through wall']
        wallThickness = segmentSettings['Wall thickness']
        useCrossDerivatives = segmentSettings['Use cross derivatives']
        useCubicHermiteThroughWall = not(segmentSettings['Use linear through wall'])
        elementsCountAlong = int(elementsCountAlongSegment*segmentCount)

        firstNodeIdentifier = 1
        firstElementIdentifier = 1

        # Central path
        tmpRegion = region.createRegion()
        centralPath.generate(tmpRegion)
        cx, cd1, cd2, cd12 = extractPathParametersFromRegion(tmpRegion)
        # for i in range(len(cx)):
        #     print(i, '[', cx[i], ',', cd1[i], ',', cd2[i], ',', cd12[i], '],')
        del tmpRegion

        # find arclength of colon
        length = 0.0
        elementsCountIn = len(cx) - 1
        sd1 = interp.smoothCubicHermiteDerivativesLine(cx, cd1, fixAllDirections = True,
            magnitudeScalingMode = interp.DerivativeScalingMode.HARMONIC_MEAN)
        for e in range(elementsCountIn):
            arcLength = interp.getCubicHermiteArcLength(cx[e], sd1[e], cx[e + 1], sd1[e + 1])
            # print(e+1, arcLength)
            length += arcLength
        segmentLength = length / segmentCount
        # print('Length = ', length)
        elementAlongLength = length / elementsCountAlong

        # Sample central path
        sx, sd1, se, sxi, ssf = interp.sampleCubicHermiteCurves(cx, cd1, elementsCountAlong)
        sd2, sd12 = interp.interpolateSampleCubicHermite(cd2, cd12, se, sxi, ssf)

        # Generate variation of radius & tc width along length
        lengthList = [0.0, proximalLength, proximalLength + transverseLength, length]
        innerRadiusList = [proximalInnerRadius, proximalTransverseInnerRadius,
                           transverseDistalInnerRadius, distalInnerRadius]
        innerRadiusAlongElementList, dInnerRadiusAlongElementList = interp.sampleParameterAlongLine(lengthList,
                                                                                                    innerRadiusList,
                                                                                                    elementsCountAlong)

        tcWidthList = [proximalTCWidth, proximalTransverseTCWidth, transverseDistalTCWidth, distalTCWidth]
        tcWidthAlongElementList, dTCWidthAlongElementList = interp.sampleParameterAlongLine(lengthList,
                                                                                            tcWidthList,
                                                                                            elementsCountAlong)

        # Account for reduced haustrum appearance in transverse and distal pig colon
        if tcCount == 2:
            haustrumInnerRadiusFactorList = [haustrumInnerRadiusFactor, haustrumInnerRadiusFactor*0.75,
                                             haustrumInnerRadiusFactor*0.5, haustrumInnerRadiusFactor*0.2]
            haustrumInnerRadiusFactorAlongElementList = \
                interp.sampleParameterAlongLine(lengthList, haustrumInnerRadiusFactorList, elementsCountAlong)[0]
        else:
            haustrumInnerRadiusFactorAlongElementList = [haustrumInnerRadiusFactor]*(elementsCountAlong+1)

        # Create annotation groups for colon sections
        elementsAlongInProximal = round(proximalLength/elementAlongLength)
        elementsAlongInTransverse = round(transverseLength/elementAlongLength)
        elementsAlongInDistal = elementsCountAlong - elementsAlongInProximal - elementsAlongInTransverse
        elementsCountAlongGroups = [elementsAlongInProximal, elementsAlongInTransverse, elementsAlongInDistal]

        colonGroup = AnnotationGroup(region, get_colon_term("colon"))

        if tcCount == 1:
            proximalGroup = AnnotationGroup(region, get_colon_term("proximal colon"))
            transverseGroup = AnnotationGroup(region, get_colon_term("transverse colon"))
            distalGroup = AnnotationGroup(region, get_colon_term("distal colon"))
            annotationGroupAlong = [[colonGroup, proximalGroup],
                                    [colonGroup, transverseGroup],
                                    [colonGroup, distalGroup]]

        elif tcCount == 2:
            spiralGroup = AnnotationGroup(region, get_colon_term("spiral colon"))
            transverseGroup = AnnotationGroup(region, get_colon_term("transverse colon"))
            distalGroup = AnnotationGroup(region, get_colon_term("distal colon"))
            annotationGroupAlong = [[colonGroup, spiralGroup],
                                    [colonGroup, transverseGroup],
                                    [colonGroup, distalGroup]]

        elif tcCount == 3:
            ascendingGroup = AnnotationGroup(region, get_colon_term("ascending colon"))
            transverseGroup = AnnotationGroup(region, get_colon_term("transverse colon"))
            descendingGroup = AnnotationGroup(region, get_colon_term("descending colon"))
            annotationGroupAlong = [[colonGroup, ascendingGroup],
                                    [colonGroup, transverseGroup],
                                    [colonGroup, descendingGroup]]

        annotationGroupsAlong = []
        for i in range(len(elementsCountAlongGroups)):
            elementsCount = elementsCountAlongGroups[i]
            for n in range(elementsCount):
                annotationGroupsAlong.append(annotationGroupAlong[i])

        annotationGroupsThroughWall = []
        for i in range(elementsCountThroughWall):
            annotationGroupsThroughWall.append([ ])

        xExtrude = []
        d1Extrude = []
        d2Extrude = []
        d3UnitExtrude = []
        sxRefExtrudeList = []

        # Create object
        colonSegmentTubeMeshInnerPoints = ColonSegmentTubeMeshInnerPoints(
            region, elementsCountAroundTC, elementsCountAroundHaustrum, elementsCountAlongSegment,
            tcCount, segmentLengthEndDerivativeFactor, segmentLengthMidDerivativeFactor,
            segmentLength, wallThickness, cornerInnerRadiusFactor, haustrumInnerRadiusFactorAlongElementList,
            innerRadiusAlongElementList, dInnerRadiusAlongElementList, tcWidthAlongElementList,
            startPhase)

        for nSegment in range(segmentCount):
            # Create inner points
            xInner, d1Inner, d2Inner, transitElementList, segmentAxis, annotationGroupsAround \
                = colonSegmentTubeMeshInnerPoints.getColonSegmentTubeMeshInnerPoints(nSegment)

            # Project reference point for warping onto central path
            start = nSegment * elementsCountAlongSegment
            end = (nSegment + 1) * elementsCountAlongSegment + 1
            sxRefList, sd1RefList, sd2ProjectedListRef, zRefList = \
                tubemesh.getPlaneProjectionOnCentralPath(xInner, elementsCountAround, elementsCountAlongSegment,
                                                         segmentLength, sx[start:end], sd1[start:end], sd2[start:end],
                                                         sd12[start:end])

            # Warp segment points
            xWarpedList, d1WarpedList, d2WarpedList, d3WarpedUnitList = tubemesh.warpSegmentPoints(
                xInner, d1Inner, d2Inner, segmentAxis, sxRefList, sd1RefList, sd2ProjectedListRef,
                elementsCountAround, elementsCountAlongSegment, zRefList, innerRadiusAlongElementList[start:end],
                closedProximalEnd=False)

            # Store points along length
            xExtrude +=  xWarpedList if nSegment == 0 else xWarpedList[elementsCountAround:]
            d1Extrude += d1WarpedList if nSegment == 0 else d1WarpedList[elementsCountAround:]
            d2Extrude += d2WarpedList if nSegment == 0 else d2WarpedList[elementsCountAround:]
            d3UnitExtrude += d3WarpedUnitList if nSegment == 0 else d3WarpedUnitList[elementsCountAround:]
            sxRefExtrudeList += sxRefList if nSegment == 0 else sxRefList[elementsCountAround:]

        contractedWallThicknessList = colonSegmentTubeMeshInnerPoints.getContractedWallThicknessList()

        # Create coordinates and derivatives
        xList, d1List, d2List, d3List, curvatureList = tubemesh.getCoordinatesFromInner(xExtrude, d1Extrude,
            d2Extrude, d3UnitExtrude, contractedWallThicknessList,
            elementsCountAround, elementsCountAlong, elementsCountThroughWall, transitElementList)

        relaxedLengthList, xiList = colonSegmentTubeMeshInnerPoints.getRelaxedLengthAndXiList()

        closedProximalEnd = False

        if tcThickness > 0:
            tubeTCWidthList = colonSegmentTubeMeshInnerPoints.getTubeTCWidthList()
            xList, d1List, d2List, d3List, annotationArrayAround = getTeniaColi(
                region, xList, d1List, d2List, d3List, curvatureList, tcCount, elementsCountAroundTC,
                elementsCountAroundHaustrum, elementsCountAlong, elementsCountThroughWall,
                tubeTCWidthList, tcThickness, sxRefExtrudeList, annotationGroupsAround,
                closedProximalEnd)

            # Create flat and texture coordinates
            xFlat, d1Flat, d2Flat, xTexture, d1Texture, d2Texture = createFlatAndTextureCoordinatesTeniaColi(
                xiList, relaxedLengthList, length, wallThickness, tcCount, tcThickness,
                elementsCountAroundTC, elementsCountAroundHaustrum, elementsCountAlong,
                elementsCountThroughWall, transitElementList, closedProximalEnd)

            # Create nodes and elements
            nextNodeIdentifier, nextElementIdentifier, annotationGroups = createNodesAndElementsTeniaColi(
                region, xList, d1List, d2List, d3List, xFlat, d1Flat, d2Flat, xTexture, d1Texture, d2Texture,
                elementsCountAroundTC, elementsCountAroundHaustrum, elementsCountAlong, elementsCountThroughWall,
                tcCount, annotationGroupsAround, annotationGroupsAlong, annotationGroupsThroughWall,
                firstNodeIdentifier, firstElementIdentifier, useCubicHermiteThroughWall, useCrossDerivatives,
                closedProximalEnd)

        else:
            # Create flat and texture coordinates
            xFlat, d1Flat, d2Flat, xTexture, d1Texture, d2Texture = tubemesh.createFlatAndTextureCoordinates(
                xiList, relaxedLengthList, length, wallThickness, elementsCountAround,
                elementsCountAlong, elementsCountThroughWall, transitElementList)

            # Create nodes and elements
            nextNodeIdentifier, nextElementIdentifier, annotationGroups = tubemesh.createNodesAndElements(
                region, xList, d1List, d2List, d3List, xFlat, d1Flat, d2Flat, xTexture, d1Texture, d2Texture,
                elementsCountAround, elementsCountAlong, elementsCountThroughWall,
                annotationGroupsAround, annotationGroupsAlong, annotationGroupsThroughWall,
                firstNodeIdentifier, firstElementIdentifier, useCubicHermiteThroughWall, useCrossDerivatives,
                closedProximalEnd)

        return annotationGroups
    def generateBaseMesh(cls,
                         region,
                         options,
                         baseCentre=[0.0, 0.0, 0.0],
                         axisSide1=[0.0, -1.0, 0.0],
                         axisUp=[0.0, 0.0, 1.0]):
        """
        Generate the base bicubic-linear Hermite mesh. See also generateMesh().
        Optional extra parameters allow centre and axes to be set.
        :param region: Zinc region to define model in. Must be empty.
        :param options: Dict containing options. See getDefaultOptions().
        :param baseCentre: Centre of valve on ventriculo-arterial junction.
        :param axisSide: Unit vector in first side direction where angle around starts.
        :param axisUp: Unit vector in outflow direction of valve.
        :return: list of AnnotationGroup
         """
        unitScale = options['Unit scale']
        outerHeight = unitScale * options['Outer height']
        innerDepth = unitScale * options['Inner depth']
        cuspHeight = unitScale * options['Cusp height']
        innerRadius = unitScale * 0.5 * options['Inner diameter']
        sinusRadialDisplacement = unitScale * options[
            'Sinus radial displacement']
        wallThickness = unitScale * options['Wall thickness']
        cuspThickness = unitScale * options['Cusp thickness']
        aorticNotPulmonary = options['Aortic not pulmonary']
        useCrossDerivatives = False

        fm = region.getFieldmodule()
        fm.beginChange()
        coordinates = zinc_utils.getOrCreateCoordinateField(fm)
        cache = fm.createFieldcache()

        if aorticNotPulmonary:
            arterialRootGroup = AnnotationGroup(region,
                                                'root of aorta',
                                                FMANumber=3740,
                                                lyphID='Lyph ID unknown')
            cuspGroups = [
                AnnotationGroup(region,
                                'posterior cusp of aortic valve',
                                FMANumber=7253,
                                lyphID='Lyph ID unknown'),
                AnnotationGroup(region,
                                'right cusp of aortic valve',
                                FMANumber=7252,
                                lyphID='Lyph ID unknown'),
                AnnotationGroup(region,
                                'left cusp of aortic valve',
                                FMANumber=7251,
                                lyphID='Lyph ID unknown')
            ]
        else:
            arterialRootGroup = AnnotationGroup(region,
                                                'root of pulmonary trunk',
                                                FMANumber=8612,
                                                lyphID='Lyph ID unknown')
            cuspGroups = [
                AnnotationGroup(region,
                                'right cusp of pulmonary valve',
                                FMANumber=7250,
                                lyphID='Lyph ID unknown'),
                AnnotationGroup(region,
                                'anterior cusp of pulmonary valve',
                                FMANumber=7249,
                                lyphID='Lyph ID unknown'),
                AnnotationGroup(region,
                                'left cusp of pulmonary valve',
                                FMANumber=7247,
                                lyphID='Lyph ID unknown')
            ]

        allGroups = [arterialRootGroup
                     ]  # groups that all elements in scaffold will go in
        annotationGroups = allGroups + cuspGroups

        # annotation fiducial points
        fiducialGroup = zinc_utils.getOrCreateGroupField(fm, 'fiducial')
        fiducialCoordinates = zinc_utils.getOrCreateCoordinateField(
            fm, 'fiducial_coordinates')
        fiducialLabel = zinc_utils.getOrCreateLabelField(fm, 'fiducial_label')
        #fiducialElementXi = zinc_utils.getOrCreateElementXiField(fm, 'fiducial_element_xi')

        datapoints = fm.findNodesetByFieldDomainType(
            Field.DOMAIN_TYPE_DATAPOINTS)
        fiducialPoints = zinc_utils.getOrCreateNodesetGroup(
            fiducialGroup, datapoints)
        datapointTemplateExternal = datapoints.createNodetemplate()
        datapointTemplateExternal.defineField(fiducialCoordinates)
        datapointTemplateExternal.defineField(fiducialLabel)

        #################
        # Create nodes
        #################

        nodes = fm.findNodesetByFieldDomainType(Field.DOMAIN_TYPE_NODES)

        nodetemplate = nodes.createNodetemplate()
        nodetemplate.defineField(coordinates)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_VALUE, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS1, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS2, 1)
        nodetemplate.setValueNumberOfVersions(coordinates, -1,
                                              Node.VALUE_LABEL_D_DS3, 1)
        # most nodes in this scaffold do not have a DS3 derivative
        nodetemplateLinearS3 = nodes.createNodetemplate()
        nodetemplateLinearS3.defineField(coordinates)
        nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1,
                                                      Node.VALUE_LABEL_VALUE,
                                                      1)
        nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1,
                                                      Node.VALUE_LABEL_D_DS1,
                                                      1)
        nodetemplateLinearS3.setValueNumberOfVersions(coordinates, -1,
                                                      Node.VALUE_LABEL_D_DS2,
                                                      1)
        # several only have a DS1 derivative
        nodetemplateLinearS2S3 = nodes.createNodetemplate()
        nodetemplateLinearS2S3.defineField(coordinates)
        nodetemplateLinearS2S3.setValueNumberOfVersions(
            coordinates, -1, Node.VALUE_LABEL_VALUE, 1)
        nodetemplateLinearS2S3.setValueNumberOfVersions(
            coordinates, -1, Node.VALUE_LABEL_D_DS1, 1)

        nodeIdentifier = max(1, zinc_utils.getMaximumNodeIdentifier(nodes) + 1)

        elementsCountAround = 6
        radiansPerElementAround = 2.0 * math.pi / elementsCountAround
        axisSide2 = vector.crossproduct3(axisUp, axisSide1)
        outerRadius = innerRadius + wallThickness
        cuspOuterLength2 = 0.5 * getApproximateEllipsePerimeter(
            innerRadius, cuspHeight)
        cuspOuterWallArcLength = cuspOuterLength2 * innerRadius / (
            innerRadius + cuspHeight)
        noduleOuterAxialArcLength = cuspOuterLength2 - cuspOuterWallArcLength
        noduleOuterRadialArcLength = innerRadius
        cuspOuterWalld1 = interp.interpolateLagrangeHermiteDerivative(
            [innerRadius, outerHeight + innerDepth - cuspHeight], [0.0, 0.0],
            [-innerRadius, 0.0], 0.0)

        sin60 = math.sin(math.pi / 3.0)
        cuspThicknessLowerFactor = 4.5  # GRC fudge factor
        cuspInnerLength2 = 0.5 * getApproximateEllipsePerimeter(
            innerRadius - cuspThickness / sin60,
            cuspHeight - cuspThicknessLowerFactor * cuspThickness)

        noduleInnerAxialArcLength = cuspInnerLength2 * (
            cuspHeight - cuspThicknessLowerFactor * cuspThickness) / (
                innerRadius - cuspThickness / sin60 + cuspHeight -
                cuspThicknessLowerFactor * cuspThickness)
        noduleInnerRadialArcLength = innerRadius - cuspThickness / math.tan(
            math.pi / 3.0)
        nMidCusp = 0 if aorticNotPulmonary else 1

        # lower points
        ix, id1 = createCirclePoints(
            [(baseCentre[c] - axisUp[c] * innerDepth) for c in range(3)],
            [axisSide1[c] * innerRadius for c in range(3)],
            [axisSide2[c] * innerRadius
             for c in range(3)], elementsCountAround)
        ox, od1 = getSemilunarValveSinusPoints(baseCentre,
                                               axisSide1,
                                               axisSide2,
                                               outerRadius,
                                               sinusRadialDisplacement,
                                               startMidCusp=aorticNotPulmonary)
        lowerx, lowerd1 = [ix, ox], [id1, od1]

        # upper points
        topCentre = [(baseCentre[c] + axisUp[c] * outerHeight)
                     for c in range(3)]
        # twice as many on inner:
        ix, id1 = createCirclePoints(
            topCentre, [axisSide1[c] * innerRadius for c in range(3)],
            [axisSide2[c] * innerRadius
             for c in range(3)], elementsCountAround * 2)
        # tweak inner points so elements attached to cusps are narrower
        cuspRadiansFactor = 0.25  # GRC fudge factor
        midDerivativeFactor = 1.0 + 0.5 * (1.0 - cuspRadiansFactor
                                           )  # GRC test compromise
        cuspAttachmentRadians = cuspRadiansFactor * radiansPerElementAround
        cuspAttachmentRadialDisplacement = wallThickness * 0.333  # GRC fudge factor
        cuspAttachmentRadius = innerRadius - cuspAttachmentRadialDisplacement
        for cusp in range(3):
            n1 = cusp * 2 - 1 + nMidCusp
            n2 = n1 * 2
            id1[n2 + 2] = [2.0 * d for d in id1[n2 + 2]]
            # side 1
            radiansAround = n1 * radiansPerElementAround + cuspAttachmentRadians
            rcosRadiansAround = cuspAttachmentRadius * math.cos(radiansAround)
            rsinRadiansAround = cuspAttachmentRadius * math.sin(radiansAround)
            ix[n2 + 1] = [(topCentre[c] + rcosRadiansAround * axisSide1[c] +
                           rsinRadiansAround * axisSide2[c]) for c in range(3)]
            id1[n2 + 1] = interp.interpolateLagrangeHermiteDerivative(
                ix[n2 + 1], ix[n2 + 2], id1[n2 + 2], 0.0)
            # side 2
            n1 = ((cusp + 1) * 2 - 1 + nMidCusp) % elementsCountAround
            n2 = n1 * 2
            radiansAround = n1 * radiansPerElementAround - cuspAttachmentRadians
            rcosRadiansAround = cuspAttachmentRadius * math.cos(radiansAround)
            rsinRadiansAround = cuspAttachmentRadius * math.sin(radiansAround)
            ix[n2 - 1] = [(topCentre[c] + rcosRadiansAround * axisSide1[c] +
                           rsinRadiansAround * axisSide2[c]) for c in range(3)]
            id1[n2 - 1] = interp.interpolateHermiteLagrangeDerivative(
                ix[n2 - 2], id1[n2 - 2], ix[n2 - 1], 1.0)
        ox, od1 = createCirclePoints(
            topCentre, [axisSide1[c] * outerRadius for c in range(3)],
            [axisSide2[c] * outerRadius
             for c in range(3)], elementsCountAround)
        upperx, upperd1 = [ix, ox], [id1, od1]

        # get lower and upper derivative 2
        zero = [0.0, 0.0, 0.0]
        upperd2factor = outerHeight
        upd2 = [d * upperd2factor for d in axisUp]
        lowerOuterd2 = interp.smoothCubicHermiteDerivativesLine(
            [lowerx[1][nMidCusp], upperx[1][nMidCusp]], [upd2, upd2],
            fixStartDirection=True,
            fixEndDerivative=True)[0]
        lowerd2factor = 2.0 * (outerHeight + innerDepth) - upperd2factor
        lowerInnerd2 = [d * lowerd2factor for d in axisUp]
        lowerd2 = [[lowerInnerd2] * elementsCountAround,
                   [lowerOuterd2] * elementsCountAround
                   ]  # some lowerd2[0] to be fitted below
        upperd2 = [[upd2] * (elementsCountAround * 2),
                   [upd2] * elementsCountAround]

        # get lower and upper derivative 1 or 2 pointing to/from cusps
        for n1 in range(elementsCountAround):
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            if (n1 % 2) == nMidCusp:
                lowerd2[0][n1] = [
                    -cuspOuterWallArcLength *
                    (cosRadiansAround * axisSide1[c] +
                     sinRadiansAround * axisSide2[c]) for c in range(3)
                ]
            else:
                upperd1[0][n1 * 2] = [
                    (cuspOuterWalld1[0] * (cosRadiansAround * axisSide1[c] +
                                           sinRadiansAround * axisSide2[c]) +
                     cuspOuterWalld1[1] * axisUp[c]) for c in range(3)
                ]

        # inner wall and mid sinus points; only every second one is used
        sinusDepth = innerDepth - cuspThicknessLowerFactor * cuspThickness  # GRC test
        sinusCentre = [(baseCentre[c] - sinusDepth * axisUp[c])
                       for c in range(3)]
        sinusx, sinusd1 = createCirclePoints(
            sinusCentre, [axisSide1[c] * innerRadius for c in range(3)],
            [axisSide2[c] * innerRadius
             for c in range(3)], elementsCountAround)
        # get sinusd2, parallel to lower inclined lines
        sd2 = interp.smoothCubicHermiteDerivativesLine(
            [[innerRadius, -sinusDepth], [innerRadius, outerHeight]],
            [[wallThickness + sinusRadialDisplacement, innerDepth],
             [0.0, upperd2factor]],
            fixStartDirection=True,
            fixEndDerivative=True)[0]
        sinusd2 = [None] * elementsCountAround
        for cusp in range(3):
            n1 = cusp * 2 + nMidCusp
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            sinusd2[n1] = [(sd2[0] * (cosRadiansAround * axisSide1[c] +
                                      sinRadiansAround * axisSide2[c]) +
                            sd2[1] * axisUp[c]) for c in range(3)]

        # get points on arc between mid sinus and upper cusp points
        arcx = []
        arcd1 = []
        scaled1 = 2.5  # GRC fudge factor
        for cusp in range(3):
            n1 = cusp * 2 + nMidCusp
            n1m = n1 - 1
            n1p = (n1 + 1) % elementsCountAround
            n2m = n1m * 2 + 1
            n2p = n1p * 2 - 1
            ax, ad1 = interp.sampleCubicHermiteCurves(
                [upperx[0][n2m], sinusx[n1]],
                [[-scaled1 * d for d in upperd2[0][n2m]],
                 [scaled1 * d for d in sinusd1[n1]]],
                elementsCountOut=2,
                addLengthStart=0.5 * vector.magnitude(upperd2[0][n2m]),
                lengthFractionStart=0.5,
                addLengthEnd=0.5 * vector.magnitude(sinusd1[n1]),
                lengthFractionEnd=0.5,
                arcLengthDerivatives=False)[0:2]
            arcx.append(ax[1])
            arcd1.append(ad1[1])
            ax, ad1 = interp.sampleCubicHermiteCurves(
                [
                    sinusx[n1],
                    upperx[0][n2p],
                ], [[scaled1 * d for d in sinusd1[n1]],
                    [scaled1 * d for d in upperd2[0][n2p]]],
                elementsCountOut=2,
                addLengthStart=0.5 * vector.magnitude(sinusd1[n1]),
                lengthFractionStart=0.5,
                addLengthEnd=0.5 * vector.magnitude(upperd2[0][n2p]),
                lengthFractionEnd=0.5,
                arcLengthDerivatives=False)[0:2]
            arcx.append(ax[1])
            arcd1.append(ad1[1])
        if nMidCusp == 0:
            arcx.append(arcx.pop(0))
            arcd1.append(arcd1.pop(0))

        # cusp nodule points
        noduleCentre = [(baseCentre[c] + axisUp[c] * (cuspHeight - innerDepth))
                        for c in range(3)]
        nodulex = [[], []]
        noduled1 = [[], []]
        noduled2 = [[], []]
        noduled3 = [[], []]
        cuspRadialThickness = cuspThickness / sin60
        for i in range(3):
            nodulex[0].append(noduleCentre)
            n1 = i * 2 + nMidCusp
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            nodulex[1].append([(noduleCentre[c] + cuspRadialThickness *
                                (cosRadiansAround * axisSide1[c] +
                                 sinRadiansAround * axisSide2[c]))
                               for c in range(3)])
            n1 = i * 2 - 1 + nMidCusp
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            noduled1[0].append([
                noduleOuterRadialArcLength * (cosRadiansAround * axisSide1[c] +
                                              sinRadiansAround * axisSide2[c])
                for c in range(3)
            ])
            noduled1[1].append(
                vector.setMagnitude(noduled1[0][i],
                                    noduleInnerRadialArcLength))
            n1 = i * 2 + 1 + nMidCusp
            radiansAround = n1 * radiansPerElementAround
            cosRadiansAround = math.cos(radiansAround)
            sinRadiansAround = math.sin(radiansAround)
            noduled2[0].append([
                noduleOuterRadialArcLength * (cosRadiansAround * axisSide1[c] +
                                              sinRadiansAround * axisSide2[c])
                for c in range(3)
            ])
            noduled2[1].append(
                vector.setMagnitude(noduled2[0][i],
                                    noduleInnerRadialArcLength))
            noduled3[0].append(
                [noduleOuterAxialArcLength * axisUp[c] for c in range(3)])
            noduled3[1].append(
                [noduleInnerAxialArcLength * axisUp[c] for c in range(3)])

        # Create nodes

        lowerNodeId = [[], []]
        for n3 in range(2):
            for n1 in range(elementsCountAround):
                node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1,
                                              lowerx[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS1, 1,
                                              lowerd1[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS2, 1,
                                              lowerd2[n3][n1])
                lowerNodeId[n3].append(nodeIdentifier)
                nodeIdentifier += 1

        sinusNodeId = []
        for n1 in range(elementsCountAround):
            if (n1 % 2) != nMidCusp:
                sinusNodeId.append(None)
                continue
            node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3)
            cache.setNode(node)
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1,
                                          sinusx[n1])
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1,
                                          sinusd1[n1])
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS2, 1,
                                          sinusd2[n1])
            sinusNodeId.append(nodeIdentifier)
            nodeIdentifier += 1

        arcNodeId = []
        for n1 in range(elementsCountAround):
            node = nodes.createNode(nodeIdentifier, nodetemplateLinearS2S3)
            cache.setNode(node)
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_VALUE, 1,
                                          arcx[n1])
            coordinates.setNodeParameters(cache, -1, Node.VALUE_LABEL_D_DS1, 1,
                                          arcd1[n1])
            arcNodeId.append(nodeIdentifier)
            nodeIdentifier += 1

        noduleNodeId = [[], []]
        for n3 in range(2):
            for n1 in range(3):
                node = nodes.createNode(nodeIdentifier, nodetemplate)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1,
                                              nodulex[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS1, 1,
                                              noduled1[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS2, 1,
                                              noduled2[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS3, 1,
                                              noduled3[n3][n1])
                noduleNodeId[n3].append(nodeIdentifier)
                nodeIdentifier += 1

        upperNodeId = [[], []]
        for n3 in range(2):
            for n1 in range(len(upperx[n3])):
                node = nodes.createNode(nodeIdentifier, nodetemplateLinearS3)
                cache.setNode(node)
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1,
                                              upperx[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS1, 1,
                                              upperd1[n3][n1])
                coordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_D_DS2, 1,
                                              upperd2[n3][n1])
                upperNodeId[n3].append(nodeIdentifier)
                nodeIdentifier += 1

        #################
        # Create elements
        #################

        mesh = fm.findMeshByDimension(3)

        allMeshGroups = [allGroup.getMeshGroup(mesh) for allGroup in allGroups]
        cuspMeshGroups = [
            cuspGroup.getMeshGroup(mesh) for cuspGroup in cuspGroups
        ]

        linearHermiteLinearBasis = fm.createElementbasis(
            3, Elementbasis.FUNCTION_TYPE_LINEAR_LAGRANGE)
        linearHermiteLinearBasis.setFunctionType(
            2, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)

        hermiteLinearLinearBasis = fm.createElementbasis(
            3, Elementbasis.FUNCTION_TYPE_LINEAR_LAGRANGE)
        hermiteLinearLinearBasis.setFunctionType(
            1, Elementbasis.FUNCTION_TYPE_CUBIC_HERMITE)

        bicubichermitelinear = eftfactory_bicubichermitelinear(
            mesh, useCrossDerivatives)
        eftDefault = bicubichermitelinear.createEftNoCrossDerivatives()

        elementIdentifier = max(
            1,
            zinc_utils.getMaximumElementIdentifier(mesh) + 1)

        elementtemplate1 = mesh.createElementtemplate()
        elementtemplate1.setElementShapeType(Element.SHAPE_TYPE_CUBE)

        # wall elements
        for cusp in range(3):
            n1 = cusp * 2 - 1 + nMidCusp
            n2 = n1 * 2
            for e in range(6):
                eft1 = None
                scalefactors = None

                if (e == 0) or (e == 5):
                    # 6 node linear-hermite-linear collapsed wedge element expanding from zero width on outer wall of root, attaching to vertical part of cusp
                    eft1 = mesh.createElementfieldtemplate(
                        linearHermiteLinearBasis)
                    # switch mappings to use DS2 instead of default DS1
                    remapEftNodeValueLabel(eft1, [1, 2, 3, 4, 5, 6, 7, 8],
                                           Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS2, [])])
                    if e == 0:
                        nids = [
                            lowerNodeId[0][n1], arcNodeId[n1],
                            upperNodeId[0][n2], upperNodeId[0][n2 + 1],
                            lowerNodeId[1][n1], upperNodeId[1][n1]
                        ]
                        setEftScaleFactorIds(eft1, [1], [])
                        scalefactors = [-1.0]
                        remapEftNodeValueLabel(eft1, [2],
                                               Node.VALUE_LABEL_D_DS2,
                                               [(Node.VALUE_LABEL_D_DS1, [1])])
                    else:
                        nids = [
                            arcNodeId[n1 + 1], lowerNodeId[0][n1 - 4],
                            upperNodeId[0][n2 + 3], upperNodeId[0][n2 - 8],
                            lowerNodeId[1][n1 - 4], upperNodeId[1][n1 - 4]
                        ]
                        remapEftNodeValueLabel(eft1, [1],
                                               Node.VALUE_LABEL_D_DS2,
                                               [(Node.VALUE_LABEL_D_DS1, [])])
                    ln_map = [1, 2, 3, 4, 5, 5, 6, 6]
                    remapEftLocalNodes(eft1, 6, ln_map)
                elif (e == 1) or (e == 4):
                    # 6 node hermite-linear-linear collapsed wedge element on lower wall
                    eft1 = mesh.createElementfieldtemplate(
                        hermiteLinearLinearBasis)
                    if e == 1:
                        nids = [
                            lowerNodeId[0][n1], lowerNodeId[0][n1 + 1],
                            arcNodeId[n1], sinusNodeId[n1 + 1],
                            lowerNodeId[1][n1], lowerNodeId[1][n1 + 1]
                        ]
                    else:
                        nids = [
                            lowerNodeId[0][n1 + 1], lowerNodeId[0][n1 - 4],
                            sinusNodeId[n1 + 1], arcNodeId[n1 + 1],
                            lowerNodeId[1][n1 + 1], lowerNodeId[1][n1 - 4]
                        ]
                    ln_map = [1, 2, 3, 4, 5, 6, 5, 6]
                    remapEftLocalNodes(eft1, 6, ln_map)
                else:
                    # 8 node elements with wedges on two sides
                    if e == 2:
                        eft1 = bicubichermitelinear.createEftNoCrossDerivatives(
                        )
                        setEftScaleFactorIds(eft1, [1], [])
                        scalefactors = [-1.0]
                        nids = [
                            arcNodeId[n1], sinusNodeId[n1 + 1],
                            upperNodeId[0][n2 + 1], upperNodeId[0][n2 + 2],
                            lowerNodeId[1][n1], lowerNodeId[1][n1 + 1],
                            upperNodeId[1][n1], upperNodeId[1][n1 + 1]
                        ]
                        remapEftNodeValueLabel(eft1, [1],
                                               Node.VALUE_LABEL_D_DS2,
                                               [(Node.VALUE_LABEL_D_DS1, [1])])
                    else:
                        eft1 = eftDefault
                        nids = [
                            sinusNodeId[n1 + 1], arcNodeId[n1 + 1],
                            upperNodeId[0][n2 + 2], upperNodeId[0][n2 + 3],
                            lowerNodeId[1][n1 + 1], lowerNodeId[1][n1 - 4],
                            upperNodeId[1][n1 + 1], upperNodeId[1][n1 - 4]
                        ]
                        remapEftNodeValueLabel(eft1, [2],
                                               Node.VALUE_LABEL_D_DS2,
                                               [(Node.VALUE_LABEL_D_DS1, [])])

                result = elementtemplate1.defineField(coordinates, -1, eft1)
                element = mesh.createElement(elementIdentifier,
                                             elementtemplate1)
                result2 = element.setNodesByIdentifier(eft1, nids)
                if scalefactors:
                    result3 = element.setScaleFactors(eft1, scalefactors)
                else:
                    result3 = 7
                #print('create arterial root wall', cusp, e, 'element',elementIdentifier, result, result2, result3, nids)
                elementIdentifier += 1

                for meshGroup in allMeshGroups:
                    meshGroup.addElement(element)

        # cusps (leaflets)
        for cusp in range(3):
            n1 = cusp * 2 - 1 + nMidCusp
            n2 = n1 * 2
            meshGroups = allMeshGroups + [cuspMeshGroups[cusp]]
            for e in range(2):
                eft1 = bicubichermitelinear.createEftNoCrossDerivatives()
                setEftScaleFactorIds(eft1, [1], [])
                scalefactors = [-1.0]

                if e == 0:
                    nids = [
                        lowerNodeId[0][n1], lowerNodeId[0][n1 + 1],
                        upperNodeId[0][n2], noduleNodeId[0][cusp],
                        arcNodeId[n1], sinusNodeId[n1 + 1],
                        upperNodeId[0][n2 + 1], noduleNodeId[1][cusp]
                    ]
                    remapEftNodeValueLabel(eft1, [4, 8],
                                           Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS1, [1])])
                    remapEftNodeValueLabel(eft1, [4, 8],
                                           Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS3, [])])
                    remapEftNodeValueLabel(eft1, [5], Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS1, [1])])
                    remapEftNodeValueLabel(eft1, [6], Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS2, [1])])
                    remapEftNodeValueLabel(eft1, [7], Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS1, [1])])
                else:
                    nids = [
                        lowerNodeId[0][n1 + 1], lowerNodeId[0][n1 - 4],
                        noduleNodeId[0][cusp], upperNodeId[0][n2 - 8],
                        sinusNodeId[n1 + 1], arcNodeId[n1 + 1],
                        noduleNodeId[1][cusp], upperNodeId[0][n2 + 3]
                    ]
                    remapEftNodeValueLabel(eft1, [3, 7],
                                           Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS3, [])])
                    remapEftNodeValueLabel(eft1, [3, 7],
                                           Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS2, [])])
                    remapEftNodeValueLabel(eft1, [4, 8],
                                           Node.VALUE_LABEL_D_DS1,
                                           [(Node.VALUE_LABEL_D_DS1, [1])])
                    remapEftNodeValueLabel(eft1, [5], Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS2, [1])])
                    remapEftNodeValueLabel(eft1, [6], Node.VALUE_LABEL_D_DS2,
                                           [(Node.VALUE_LABEL_D_DS1, [])])

                result = elementtemplate1.defineField(coordinates, -1, eft1)
                element = mesh.createElement(elementIdentifier,
                                             elementtemplate1)
                result2 = element.setNodesByIdentifier(eft1, nids)
                if scalefactors:
                    result3 = element.setScaleFactors(eft1, scalefactors)
                else:
                    result3 = 7
                #print('create semilunar cusp', cusp, e, 'element',elementIdentifier, result, result2, result3, nids)
                elementIdentifier += 1

                for meshGroup in meshGroups:
                    meshGroup.addElement(element)

        # create annotation points

        datapoint = fiducialPoints.createNode(-1, datapointTemplateExternal)
        cache.setNode(datapoint)
        fiducialCoordinates.setNodeParameters(cache, -1,
                                              Node.VALUE_LABEL_VALUE, 1,
                                              noduleCentre)
        fiducialLabel.assignString(
            cache, 'aortic valve ctr'
            if aorticNotPulmonary else 'pulmonary valve ctr')

        fm.endChange()
        return annotationGroups