def mosaic3D(imIn, imOut, imWts, grid=m3D.DEFAULT_GRID3D): """ Builds the mosaic 3D image of 'imIn' and puts the results into 'imOut'. The watershed line (pixel values set to 255) is stored in the greytone 3D image 'imWts'. A mosaic image is a simple image made of various tiles of uniform grey values. It is built using the watershed of 'imIn' gradient and original markers made of gradient minima which are labelled by the maximum value of 'imIn' pixels inside them. """ imWrk1 = m3D.image3DMb(imIn, 1) imWrk2 = m3D.image3DMb(imIn) m3D.copy3D(imIn, imWrk2) im_mark = m3D.image3DMb(imIn, 32) se = m3D.structuringElement3D(m3D.getDirections3D(grid), grid) m3D.gradient3D(imIn, imOut, se=se) m3D.minima3D(imOut, imWrk1, grid=grid) m3D.add3D(im_mark, imWrk1, im_mark) imWrk1.convert(8) m3D.build3D(imWrk1, imWrk2, grid=grid) m3D.add3D(im_mark, imWrk2, im_mark) m3D.watershedSegment3D(imOut, im_mark, grid=grid) m3D.copyBytePlane3D(im_mark, 3, imWts) m3D.subConst3D(im_mark, 1, im_mark) m3D.copyBytePlane3D(im_mark, 0, imOut)
def linearClose3D(imIn, imOut, dir, n, grid=m3D.DEFAULT_GRID3D, edge=mamba.FILLED): """ Performs a closing by a segment of size 'n' in direction 'dir'. If 'edge' is set to 'EMPTY', the operation must be modified to remain extensive. """ imWrk = m3D.image3DMb(imIn) if edge == mamba.EMPTY: m3D.copy3D(imIn, imWrk) m3D.linearDilate3D(imIn, imOut, dir, n, grid=grid) m3D.linearErode3D(imOut, imOut, m3D.transposeDirection3D(dir, grid=grid), n, edge=edge, grid=grid) if edge == mamba.EMPTY: m3D.logic3D(imOut, imWrk, imOut, "sup")
def hitOrMiss3D(imIn, imOut, dse, edge=mamba.EMPTY): """ Performs a binary Hit-or-miss operation on 3D image 'imIn' using the doubleStructuringElement3D 'dse'. Result is put in 'imOut'. WARNING! 'imIn' and 'imOut' must be different images. """ (width, height, length) = imIn.getSize() depth = imIn.getDepth() if depth != 1: mamba.raiseExceptionOnError(core.MB_ERR_BAD_DEPTH) if length != len(imOut): mamba.raiseExceptionOnError(core.MB_ERR_BAD_SIZE) zext = dse.grid.getZExtension() imWrk = m3D.image3DMb(width, height, length + zext * 2, depth) # Border handling imWrk.reset() m3D.copy3D(imIn, imWrk, firstPlaneOut=1) if edge == mamba.FILLED: m3D.negate3D(imWrk, imWrk) for i in range(zext): imWrk[i].reset() imWrk[length + zext * 2 - 1 - i].reset() dse = dse.flip() # Central point if dse.se1.hasZero(): m3D.copy3D(imWrk, imOut, firstPlaneIn=1) else: if dse.se0.hasZero(): for i in range(length): mamba.negate(imWrk[i + 1], imOut[i]) else: imOut.fill(1) # Other directions dirs = m3D.getDirections3D(dse.getGrid(), True) dirs0 = dse.se0.getDirections() dirs1 = dse.se1.getDirections() grid2D = dse.getGrid().get2DGrid() for d in dirs: if d in dirs1: for i in range(length): (planeOffset, dc) = dse.getGrid().convertFromDir(d, i) mamba.infNeighbor(imWrk[i + 1 + planeOffset], imOut[i], 1 << dc, grid=grid2D, edge=edge) elif d in dirs0: for i in range(length): (planeOffset, dc) = dse.getGrid().convertFromDir(d, i) mamba.diffNeighbor(imWrk[i + 1 + planeOffset], imOut[i], 1 << dc, grid=grid2D, edge=edge)
def buildClose3D(imIn, imOut, n=1, se=m3D.CUBOCTAHEDRON1): """ Performs a closing by dual reconstruction operation on 3D image 'imIn' and puts the result in 'imOut'. 'n' controls the size of the closing. """ imWrk = m3D.image3DMb(imIn) m3D.copy3D(imIn, imWrk) m3D.dilate3D(imIn, imOut, n, se=se) m3D.dualBuild3D(imWrk, imOut, grid=se.getGrid())
def buildOpen3D(imIn, imOut, n=1, se=m3D.CUBOCTAHEDRON1): """ Performs an opening by reconstruction operation on 3D image 'imIn' and puts the result in 'imOut'. 'n' controls the size of the opening. """ imWrk = m3D.image3DMb(imIn) m3D.copy3D(imIn, imWrk) m3D.erode3D(imIn, imOut, n, se=se) m3D.build3D(imWrk, imOut, grid=se.getGrid())
def linearDilate3D(imIn, imOut, d, n=1, grid=m3D.DEFAULT_GRID3D, edge=mamba.EMPTY): """ Dilation by a segment in direction 'd' of 3D image 'imIn', result in 'imOut'. The operation is repeated 'n' times (default is 1).This function will assume an EMPTY edge unless specified otherwise using 'edge'. The directions are defined according to the grid in use. """ se = m3D.structuringElement3D([0,d], grid) m3D.copy3D(imIn, imOut) m3D.dilate3D(imOut, imOut, n, se=se, edge=edge)
def largeLinearDilate3D(imIn, imOut, dir, size, grid=m3D.DEFAULT_GRID3D, edge=mamba.EMPTY): """ Dilation of the 3D image 'imIn' by a large segment in direction 'dir' in a reduced number of iterations. Uses the dilations by doublets of points (supposed to be faster, thanks to an enhanced shift operator). """ m3D.copy3D(imIn, imOut) for i in _sizeSplit(size): supFarNeighbor3D(imOut, imOut, dir, i, grid=grid, edge=edge)
def linearErode3D( imIn, imOut, d, n=1, grid=m3D.DEFAULT_GRID3D, edge=mamba.FILLED): """ Performs an erosion in direction 'd' of 3D image 'imIn' and puts the result in 'imOut'. The operation is repeated 'n' times (default is 1). This function will assume a FILLED edge unless specified otherwise using 'edge'. """ se = m3D.structuringElement3D([0,d], grid) m3D.copy3D(imIn, imOut) m3D.erode3D(imOut, imOut, n, se=se, edge=edge)
def hitOrMiss3D(imIn, imOut, dse, edge=mamba.EMPTY): """ Performs a binary Hit-or-miss operation on 3D image 'imIn' using the doubleStructuringElement3D 'dse'. Result is put in 'imOut'. WARNING! 'imIn' and 'imOut' must be different images. """ (width,height,length) = imIn.getSize() depth = imIn.getDepth() if depth!=1: mamba.raiseExceptionOnError(core.MB_ERR_BAD_DEPTH) if length!=len(imOut): mamba.raiseExceptionOnError(core.MB_ERR_BAD_SIZE) zext = dse.grid.getZExtension() imWrk = m3D.image3DMb(width, height, length+zext*2, depth) # Border handling imWrk.reset() m3D.copy3D(imIn, imWrk, firstPlaneOut=1) if edge==mamba.FILLED: m3D.negate3D(imWrk, imWrk) for i in range(zext): imWrk[i].reset() imWrk[length+zext*2-1-i].reset() dse = dse.flip() # Central point if dse.se1.hasZero(): m3D.copy3D(imWrk, imOut, firstPlaneIn=1) else: if dse.se0.hasZero(): for i in range(length): mamba.negate(imWrk[i+1], imOut[i]) else: imOut.fill(1) # Other directions dirs = m3D.getDirections3D(dse.getGrid(), True) dirs0 = dse.se0.getDirections() dirs1 = dse.se1.getDirections() grid2D = dse.getGrid().get2DGrid() for d in dirs: if d in dirs1: for i in range(length): (planeOffset, dc) = dse.getGrid().convertFromDir(d,i) mamba.infNeighbor(imWrk[i+1+planeOffset], imOut[i], 1<<dc, grid=grid2D, edge=edge) elif d in dirs0: for i in range(length): (planeOffset, dc) = dse.getGrid().convertFromDir(d,i) mamba.diffNeighbor(imWrk[i+1+planeOffset], imOut[i], 1<<dc, grid=grid2D, edge=edge)
def dilateByCylinder3D(imInOut, height, section): """ Dilates 3D image 'imInOut' using a cylinder with an hexagonal section of size 2x'section' and a height of 2x'height'. The image is modified by this function. The edge is always set to EMPTY. """ l = len(imInOut) for im in imInOut: mamba.dilate(im, im, section, se=mamba.HEXAGON) provIm3D = m3D.image3DMb(imInOut) for i in range(l): mamba.copy(imInOut[i], provIm3D[i]) for j in range(max(0,i-height), min(l,i+height+1)): mamba.logic(provIm3D[i], imInOut[j], provIm3D[i], "sup") m3D.copy3D(provIm3D, imInOut)
def fullAlternateFilter3D(imIn, imOut, n, openFirst, se=m3D.CUBOCTAHEDRON1): """ Performs a full alternate filter operation (successive alternate filters of increasing sizes, from 1 to 'n') on 3D image 'imIn' and puts the result in 'imOut'. 'n' controls the filter size. If 'openFirst' is True, the filter begins with an opening, a closing otherwise. """ m3D.copy3D(imIn, imOut) for i in range(1, n + 1): if openFirst: m3D.opening3D(imOut, imOut, i, se=se) m3D.closing3D(imOut, imOut, i, se=se) else: m3D.closing3D(imOut, imOut, i, se=se) m3D.opening3D(imOut, imOut, i, se=se)
def fullAlternateFilter3D(imIn, imOut, n, openFirst, se=m3D.CUBOCTAHEDRON1): """ Performs a full alternate filter operation (successive alternate filters of increasing sizes, from 1 to 'n') on 3D image 'imIn' and puts the result in 'imOut'. 'n' controls the filter size. If 'openFirst' is True, the filter begins with an opening, a closing otherwise. """ m3D.copy3D(imIn, imOut) for i in range(1,n+1): if openFirst: m3D.opening3D(imOut, imOut, i, se=se) m3D.closing3D(imOut, imOut, i, se=se) else: m3D.closing3D(imOut, imOut, i, se=se) m3D.opening3D(imOut, imOut, i, se=se)
def closing3D(imIn, imOut, n=1, se=m3D.CUBOCTAHEDRON1, edge=mamba.FILLED): """ Performs a closing operation on 3D image 'imIn' and puts the result in 'imOut'. 'n' controls the size of the closing and 'se' the structuring element used. The default edge is set to 'FILLED'. If 'edge' is set to 'EMPTY', the operation is slightly modified to avoid errors (non extensivity). """ imWrk = m3D.image3DMb(imIn) if edge == mamba.EMPTY: m3D.copy3D(imIn, imWrk) m3D.dilate3D(imIn, imOut, n, se=se) m3D.erode3D(imOut, imOut, n, se=se.transpose(), edge=edge) if edge == mamba.EMPTY: m3D.logic3D(imOut, imWrk, imOut, "sup")
def supOpen3D(imIn, imOut, n, grid=m3D.DEFAULT_GRID3D): """ Performs the supremum of directional openings. A white particle is preserved (but not entirely) if its length is larger than 'n' in at least one direction. This operator is an opening. The image edge is set to 'EMPTY' in order to take into account particles touching the edge (they are considered as entirely included in the image window). When square grid is used, the size in oblique directions are reduced to be similar to the horizontal and vertical size. """ imWrk1 = m3D.image3DMb(imIn) imWrk2 = m3D.image3DMb(imIn) imWrk1.reset() # Default grid is a proxy for an actual grid if grid == m3D.CUBIC: # First neighbors located at a sqrt(2) distance from the center size = int((1.4142 * n + 1) / 2) for d in [2, 4, 10, 12, 14, 16]: linearOpen3D(imIn, imWrk2, d, size, grid, edge=mamba.EMPTY) m3D.logic3D(imWrk1, imWrk2, imWrk1, "sup") # First neighbors located at a sqrt(3) distance from the center size = int((1.7320 * n + 1) / 2) for d in [11, 13, 15, 17]: linearOpen3D(imIn, imWrk2, d, size, grid, edge=mamba.EMPTY) m3D.logic3D(imWrk1, imWrk2, imWrk1, "sup") # Finally neighbors located at a 1 distance from the center size = n for d in [1, 3, 9]: linearOpen3D(imIn, imWrk2, d, size, grid, edge=mamba.EMPTY) m3D.logic3D(imWrk1, imWrk2, imWrk1, "sup") elif grid == m3D.FACE_CENTER_CUBIC: for d in [1, 3, 5, 7, 8, 9]: linearOpen3D(imIn, imWrk2, d, n, grid, edge=mamba.EMPTY) m3D.logic3D(imWrk1, imWrk2, imWrk1, "sup") else: mamba.raiseExceptionOnError(core.MB_ERR_BAD_PARAMETER) m3D.copy3D(imWrk1, imOut)
def infClose3D(imIn, imOut, n, grid=m3D.DEFAULT_GRID3D): """ Performs the infimum of directional closings. A black particle is preserved if its length is larger than 'n' in at least one direction. This operator is a closing. The image edge is set to 'FILLED' in order to take into account particles touching the edge (they are supposed not to extend outside the image window). When square grid is used, the size in oblique directions are reduced to be similar to the horizontal and vertical size. """ imWrk1 = m3D.image3DMb(imIn) imWrk2 = m3D.image3DMb(imIn) imWrk1.fill(m3D.computeMaxRange3D(imIn)[1]) # Default grid is a proxy for an actual grid if grid == m3D.CUBIC: # First neighbors located at a sqrt(2) distance from the center size = int((1.4142 * n + 1) / 2) for d in [2, 4, 10, 12, 14, 16]: linearClose3D(imIn, imWrk2, d, size, grid) m3D.logic3D(imWrk1, imWrk2, imWrk1, "inf") # First neighbors located at a sqrt(3) distance from the center size = int((1.7320 * n + 1) / 2) for d in [11, 13, 15, 17]: linearClose3D(imIn, imWrk2, d, size, grid) m3D.logic3D(imWrk1, imWrk2, imWrk1, "inf") # Finally neighbors located at a 1 distance from the center size = n for d in [1, 3, 9]: linearClose3D(imIn, imWrk2, d, size, grid) m3D.logic3D(imWrk1, imWrk2, imWrk1, "inf") elif grid == m3D.FACE_CENTER_CUBIC: for d in [1, 3, 5, 7, 8, 9]: linearClose3D(imIn, imWrk2, d, n, grid) m3D.logic3D(imWrk1, imWrk2, imWrk1, "inf") else: mamba.raiseExceptionOnError(core.MB_ERR_BAD_PARAMETER) m3D.copy3D(imWrk1, imOut)
def erode3D(imIn, imOut, n=1, se=CUBOCTAHEDRON1, edge=mamba.FILLED): """ This operator performs an erosion, using the structuring element 'se' (set by default as CUBOCTAHEDRON1), of 3D image 'imIn' and puts the result in 'imOut'. The operation is repeated 'n' times (default is 1). This operator assumes a 'FILLED' edge by default. This operator always considers that the origin of the structuring element in use is at position 0 even if this point does not belong to it. """ (width,height,length) = imIn.getSize() depth = imIn.getDepth() if length!=len(imOut): mamba.raiseExceptionOnError(core.MB_ERR_BAD_SIZE) zext = se.grid.getZExtension() imWrk = m3D.image3DMb(width, height, length+zext*2, depth) if edge==mamba.EMPTY: for i in range(zext): imWrk[i].reset() imWrk[length+zext*2-1-i].reset() else: value = mamba.computeMaxRange(imIn[0])[1] for i in range(zext): imWrk[i].fill(value) imWrk[length+zext*2-1-i].fill(value) m3D.copy3D(imIn, imOut) dirs = se.getDirections(withoutZero=True) for size in range(n): m3D.copy3D(imOut, imWrk, 0, 1) if not se.hasZero(): imOut.fill(m3D.computeMaxRange3D(imIn)[1]) for i in range(length): dirs_enc = se.grid.getEncodedDirs(dirs,i) mamba.infNeighbor(imWrk[i], imOut[i], dirs_enc[-1], grid=se.grid.get2DGrid(), edge=edge) mamba.infNeighbor(imWrk[i+1], imOut[i], dirs_enc[0], grid=se.grid.get2DGrid(), edge=edge) mamba.infNeighbor(imWrk[i+2], imOut[i], dirs_enc[1], grid=se.grid.get2DGrid(), edge=edge)
def autoMedian3D(imIn, imOut, n, se=m3D.CUBOCTAHEDRON1): """ Morphological automedian filter performed with alternate sequential filters. """ oc_im = m3D.image3DMb(imIn) co_im = m3D.image3DMb(imIn) imWrk = m3D.image3DMb(imIn) alternateFilter3D(imIn, oc_im, n, True, se=se) alternateFilter3D(imIn, co_im, n, False, se=se) m3D.copy3D(imIn, imOut) m3D.copy3D(oc_im, imWrk) m3D.logic3D(co_im, imWrk, imWrk, "sup") m3D.logic3D(imWrk, imOut, imOut, "inf") m3D.copy3D(oc_im, imWrk) m3D.logic3D(co_im, imWrk, imWrk, "inf") m3D.logic3D(imWrk, imOut, imOut, "sup")