def infClose(imIn, imOut, n, grid=mamba.DEFAULT_GRID): """ 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 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk1.fill(mamba.computeMaxRange(imIn)[1]) if grid == mamba.SQUARE: size = int((1.4142 * n + 1) / 2) linearClose(imIn, imWrk2, 2, size, grid=grid) mamba.logic(imWrk1, imWrk2, imWrk1, "inf") linearClose(imIn, imWrk2, 4, size, grid=grid) mamba.logic(imWrk1, imWrk2, imWrk1, "inf") d = 4 else: d = 6 for i in range(1, d, 2): linearClose(imIn, imWrk2, i, n, grid=grid) mamba.logic(imWrk1, imWrk2, imWrk1, "inf") mamba.copy(imWrk1, imOut)
def rotatingThin(imIn, imOut, dse, edge=mamba.FILLED): """ Performs a complete rotation of thinnings , the initial 'dse' double structuring element being turned one step clockwise after each thinning. At each rotation step, the previous result is used as input for the next thinning (chained thinnings). Depending on the grid where 'dse' is defined, 6 or 8 rotations are performed. 'imIn' and 'imOut' are binary images. 'edge' is set to FILLED by default (default value is EMPTY in simple thin). """ imWrk = mamba.imageMb(imIn) if edge == mamba.FILLED: mamba.negate(imIn, imOut) for d in mamba.getDirections(dse.getGrid(), True): hitOrMiss(imOut, imWrk, dse.flip(), edge=mamba.EMPTY) mamba.logic(imWrk, imOut, imOut, "sup") dse = dse.rotate() mamba.negate(imOut, imOut) else: mamba.copy(imIn, imOut) for d in mamba.getDirections(dse.getGrid(), True): hitOrMiss(imOut, imWrk, dse, edge=mamba.EMPTY) mamba.diff(imOut, imWrk, imOut) dse = dse.rotate()
def infClose(imIn, imOut, n, grid=mamba.DEFAULT_GRID): """ 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 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk1.fill(mamba.computeMaxRange(imIn)[1]) if grid == mamba.SQUARE: size = int((1.4142 * n + 1)/2) linearClose(imIn, imWrk2, 2, size, grid=grid) mamba.logic(imWrk1, imWrk2, imWrk1, "inf") linearClose(imIn, imWrk2, 4, size, grid=grid) mamba.logic(imWrk1, imWrk2, imWrk1, "inf") d = 4 else: d = 6 for i in range(1, d, 2): linearClose(imIn, imWrk2, i, n, grid=grid) mamba.logic(imWrk1, imWrk2, imWrk1, "inf") mamba.copy(imWrk1, imOut)
def mosaic(imIn, imOut, imWts, grid=mamba.DEFAULT_GRID): """ Builds the mosaic image of 'imIn' and puts the results into 'imOut'. The watershed line (pixel values set to 255) is stored in the greytone 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 = mamba.imageMb(imIn, 1) imWrk2 = mamba.imageMb(imIn) mamba.copy(imIn, imWrk2) im_mark = mamba.imageMb(imIn, 32) se = mamba.structuringElement(mamba.getDirections(grid), grid) mamba.gradient(imIn, imOut, se=se) mamba.minima(imOut, imWrk1, grid=grid) mamba.add(im_mark, imWrk1, im_mark) imWrk1.convert(8) mamba.build(imWrk1, imWrk2, grid=grid) mamba.add(im_mark, imWrk2, im_mark) watershedSegment(imOut, im_mark, grid=grid) mamba.copyBytePlane(im_mark, 3, imWts) mamba.subConst(im_mark, 1, im_mark) mamba.copyBytePlane(im_mark, 0, imOut)
def nonEqualNeighbors(imIn, imOut, nb, grid=mamba.DEFAULT_GRID, edge=mamba.FILLED): """ This operator compares the value of each pixel of image 'imIn' with the value of its neighbors encoded in 'nb'. If all the neighbor values are different, the pixel is unchanged. Otherwise, it takes value 0. This operator works on hexagonal or square 'grid' and 'edge' is set to FILLED by default. This operator works for 8-bit and 32-bit images. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn, 1) imWrk4 = mamba.imageMb(imIn, 1) for d in mamba.getDirections(grid): ed = 1 << d if (nb & ed): mamba.copy(imIn, imWrk1) mamba.copy(imIn, imWrk2) mamba.supNeighbor(imWrk1, imWrk1, ed, grid=grid, edge=edge) mamba.infNeighbor(imWrk2, imWrk2, ed, grid=grid, edge=edge) mamba.generateSupMask(imWrk2, imWrk1, imWrk3, False) mamba.logic(imWrk4, imWrk3, imWrk4, "or") mamba.convertByMask(imWrk4, imWrk1, mamba.computeMaxRange(imIn)[1], 0) mamba.logic(imIn, imWrk1, imOut, "inf")
def _sparseConjugateHexagonDilate(imIn, imOut, size, edge=mamba.EMPTY): """ Dilation by a conjugate hexagon. The structuring element used by this operation is not complete. Some holes appear inside the structuring element. Therefore, this operation should not be used to obtain true conjugate hexagons dilations (for internal use only). """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) mamba.copy(imIn, imOut) val = mamba.computeMaxRange(imIn)[1]*int(edge!=mamba.EMPTY) for i in _sizeSplit(size): mamba.copy(imOut, imWrk1) j = 2*i supFarNeighbor(imWrk1, imOut, 1, j, grid=mamba.SQUARE, edge=edge) mamba.shift(imWrk1, imWrk2, 2, i, val, grid=mamba.HEXAGONAL) supFarNeighbor(imWrk2, imOut, 4, i, grid=mamba.HEXAGONAL, edge=edge) supFarNeighbor(imWrk2, imOut, 6, i, grid=mamba.HEXAGONAL, edge=edge) supFarNeighbor(imWrk1, imOut, 5, j, grid=mamba.SQUARE, edge=edge) mamba.shift(imWrk1, imWrk2, 5, i, val, grid=mamba.HEXAGONAL) supFarNeighbor(imWrk2, imOut, 1, i, grid=mamba.HEXAGONAL, edge=edge) supFarNeighbor(imWrk2, imOut, 3, i, grid=mamba.HEXAGONAL, edge=edge) j = 3*i//2 supFarNeighbor(imWrk1, imOut, 2, j, grid=mamba.HEXAGONAL, edge=edge) supFarNeighbor(imWrk1, imOut, 5, j, grid=mamba.HEXAGONAL, edge=edge)
def computeDiameter(imIn, dir, scale=(1.0, 1.0), grid=mamba.DEFAULT_GRID): """ Computes the diameter (diametral variation) of binary image 'imIn' in direction 'dir'. 'scale' is a tuple defining the horizontal and vertical scale factors (default is 1.0). Beware, if the input image 'imIn' is not a binary image, the function raises an error. """ if imIn.getDepth() != 1: mamba.raiseExceptionOnError(core.MB_ERR_BAD_DEPTH) if dir == 0: return 0.0 dir = ((dir - 1)%(mamba.gridNeighbors(grid)//2)) +1 imWrk = mamba.imageMb(imIn) mamba.copy(imIn, imWrk) mamba.diffNeighbor(imIn, imWrk, 1<<dir, grid=grid) if grid == mamba.HEXAGONAL: l = scale[1] if dir != 2: l = 2*l*scale[0]/math.sqrt(scale[0]*scale[0] + 4*scale[1]*scale[1]) else: if dir == 1: l = scale[0] elif dir == 3: l = scale[1] else: l = scale[0]*scale[1]/math.sqrt(scale[0]*scale[0] + scale[1]*scale[1]) l = l*mamba.computeVolume(imWrk) return l
def largeHexagonalErode(imIn, imOut, size, edge=mamba.FILLED): """ Erosion by large hexagons using erosions by large segments and the Steiner decomposition property of the hexagon. Edge effects are corrected by erosions with transposed decompositions combined with inf operations (see documentation for further details). This operator is quite complex to avoid edge effects. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) sizemax = min(imIn.getSize())//2 # If size larger than sizemax, the operation must be iterated to prevent edge effects. n = size mamba.copy(imIn, imOut) while n > 0: s = min(n, sizemax) largeLinearErode(imOut, imWrk1, 6, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imWrk1, imWrk1, 4, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imOut, imWrk2, 4, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imWrk2, imWrk2, 6, s, grid=mamba.HEXAGONAL, edge=edge) mamba.logic(imWrk1, imWrk2, imWrk1, "inf") largeLinearErode(imWrk1, imWrk2, 2, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imOut, imWrk1, 1, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imWrk1, imWrk1, 3, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imOut, imOut, 3, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imOut, imOut, 1, s, grid=mamba.HEXAGONAL, edge=edge) mamba.logic(imWrk1, imOut, imWrk1, "inf") largeLinearErode(imWrk1, imOut, 5, s, grid=mamba.HEXAGONAL, edge=edge) mamba.logic(imOut, imWrk2, imOut, "inf") n = n - s
def _sparseConjugateHexagonDilate(imIn, imOut, size, edge=mamba.EMPTY): """ Dilation by a conjugate hexagon. The structuring element used by this operation is not complete. Some holes appear inside the structuring element. Therefore, this operation should not be used to obtain true conjugate hexagons dilations (for internal use only). """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) mamba.copy(imIn, imOut) val = mamba.computeMaxRange(imIn)[1] * int(edge != mamba.EMPTY) for i in _sizeSplit(size): mamba.copy(imOut, imWrk1) j = 2 * i supFarNeighbor(imWrk1, imOut, 1, j, grid=mamba.SQUARE, edge=edge) mamba.shift(imWrk1, imWrk2, 2, i, val, grid=mamba.HEXAGONAL) supFarNeighbor(imWrk2, imOut, 4, i, grid=mamba.HEXAGONAL, edge=edge) supFarNeighbor(imWrk2, imOut, 6, i, grid=mamba.HEXAGONAL, edge=edge) supFarNeighbor(imWrk1, imOut, 5, j, grid=mamba.SQUARE, edge=edge) mamba.shift(imWrk1, imWrk2, 5, i, val, grid=mamba.HEXAGONAL) supFarNeighbor(imWrk2, imOut, 1, i, grid=mamba.HEXAGONAL, edge=edge) supFarNeighbor(imWrk2, imOut, 3, i, grid=mamba.HEXAGONAL, edge=edge) j = 3 * i // 2 supFarNeighbor(imWrk1, imOut, 2, j, grid=mamba.HEXAGONAL, edge=edge) supFarNeighbor(imWrk1, imOut, 5, j, grid=mamba.HEXAGONAL, edge=edge)
def largeHexagonalErode(imIn, imOut, size, edge=mamba.FILLED): """ Erosion by large hexagons using erosions by large segments and the Steiner decomposition property of the hexagon. Edge effects are corrected by erosions with transposed decompositions combined with inf operations (see documentation for further details). This operator is quite complex to avoid edge effects. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) sizemax = min(imIn.getSize()) // 2 # If size larger than sizemax, the operation must be iterated to prevent edge effects. n = size mamba.copy(imIn, imOut) while n > 0: s = min(n, sizemax) largeLinearErode(imOut, imWrk1, 6, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imWrk1, imWrk1, 4, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imOut, imWrk2, 4, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imWrk2, imWrk2, 6, s, grid=mamba.HEXAGONAL, edge=edge) mamba.logic(imWrk1, imWrk2, imWrk1, "inf") largeLinearErode(imWrk1, imWrk2, 2, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imOut, imWrk1, 1, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imWrk1, imWrk1, 3, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imOut, imOut, 3, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearErode(imOut, imOut, 1, s, grid=mamba.HEXAGONAL, edge=edge) mamba.logic(imWrk1, imOut, imWrk1, "inf") largeLinearErode(imWrk1, imOut, 5, s, grid=mamba.HEXAGONAL, edge=edge) mamba.logic(imOut, imWrk2, imOut, "inf") n = n - s
def supOpen(imIn, imOut, n, grid=mamba.DEFAULT_GRID): """ 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 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk1.reset() if grid == mamba.SQUARE: size = int((1.4142 * n + 1)/2) linearOpen(imIn, imWrk2, 2, size, edge=mamba.EMPTY, grid=grid) mamba.logic(imWrk1, imWrk2, imWrk1, "sup") linearOpen(imIn, imWrk2, 4, size, edge=mamba.EMPTY, grid=grid) mamba.logic(imWrk1, imWrk2, imWrk1, "sup") d = 4 else: d = 6 for i in range(1, d, 2): linearOpen(imIn, imWrk2, i, n, edge=mamba.EMPTY, grid=grid) mamba.logic(imWrk1, imWrk2, imWrk1, "sup") mamba.copy(imWrk1, imOut)
def vectorGradient(imIn, imModul, imAzim, size=1): """ Computes modulus (result in 'imModul') and azimut (in 'imAzim') of image 'imIn'. The 'size' of each directional gradient is set to 1 by default. This operator is defined on the hexagonal grid (12 directions are used). Note that this operator belongs to the residual operators class. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn) imWrk4 = mamba.imageMb(imIn, 1) mamba.copy(imIn, imWrk1) imModul.reset() imAzim.reset() for i in range(12): d = i + 1 # Directional gradient obtained with linear erosions and dilations. directionalDilate(imWrk1, imWrk2, d, size) directionalErode(imWrk1, imWrk3, d, size) mamba.sub(imWrk2, imWrk3, imWrk2) # For each direction, maximal pixels are indexed in a mask image. mamba.generateSupMask(imWrk2, imModul, imWrk4, True) mamba.convertByMask(imWrk4, imWrk3, 0, d) # Modulus and azimut are calculated. mamba.logic(imWrk2, imModul, imModul, "sup") mamba.logic(imWrk3, imAzim, imAzim, "sup")
def mulRealConst(imIn, v, imOut, nearest=False, precision=2): """ Multiplies image 'imIn' by a real positive constant value 'v' and puts the result in image 'imOut'. 'imIn' and 'imOut' can be 8-bit or 32-bit images. If 'imOut' is greyscale (8-bit), the result is saturated (results of the multiplication greater than 255 are limited to this value). 'precision' indicates the number of decimal digits taken into account for the constant 'v' (default is 2). If 'nearest' is true, the result is rounded to the nearest integer value. If not (default), the result is simply truncated. """ if imIn.getDepth()==1 or imOut.getDepth()==1: mamba.raiseExceptionOnError(core.MB_ERR_BAD_DEPTH) imWrk1 = mamba.imageMb(imIn, 32) imWrk2 = mamba.imageMb(imIn, 1) precVal = (10 ** precision) v1 = int(v * precVal) if imIn.getDepth()==8: imWrk1.reset() mamba.copyBytePlane(imIn, 0, imWrk1) else: mamba.copy(imIn, imWrk1) mulConst(imWrk1, v1, imWrk1) if nearest: adjVal = int(5 * (10 ** (precision - 1))) addConst(imWrk1, adjVal , imWrk1) divConst(imWrk1, precVal, imWrk1) if imOut.getDepth()==8: mamba.threshold(imWrk1, imWrk2, 255, mamba.computeMaxRange(imWrk1)[1]) mamba.copyBytePlane(imWrk1, 0, imOut) imWrk2.convert(8) mamba.logic(imOut, imWrk2, imOut, "sup") else: mamba.copy(imWrk1, imOut)
def cellsBuild(imIn, imInOut, grid=mamba.DEFAULT_GRID): """ Geodesic reconstruction of the cells of the partition image 'imIn' which are marked by the image 'imInOut'. The marked cells take the value of their corresponding marker. Note that the background cells (labelled by 0) are also modified if they are marked. The result is stored in 'imInOut'. The images can be 8-bit or 32-bit images. 'grid' can be set to HEXAGONAL or SQUARE. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn, 1) vol = 0 prec_vol = -1 dirs = mamba.getDirections(grid)[1:] while (prec_vol!=vol): prec_vol = vol for d in dirs: ed = 1<<d mamba.copy(imIn, imWrk1) mamba.copy(imIn, imWrk2) mamba.supNeighbor(imWrk1, imWrk1, ed, grid=grid) mamba.infNeighbor(imWrk2, imWrk2, ed, grid=grid) mamba.generateSupMask(imWrk2, imWrk1, imWrk3, False) mamba.convertByMask(imWrk3, imWrk1, 0, mamba.computeMaxRange(imIn)[1]) mamba.linearDilate(imInOut, imWrk2, d, 1, grid=grid) mamba.logic(imWrk2, imWrk1, imWrk2, "inf") v = mamba.buildNeighbor(imWrk1, imWrk2, d, grid=grid) mamba.logic(imWrk2, imInOut, imInOut, "sup") vol = mamba.computeVolume(imInOut)
def _initialQuasiDist_(imIn, imOut1, imOut2, grid=mamba.DEFAULT_GRID): """ Computes the initial quasi-distance. For internal use only. The resulting quasi-distance is not lipchitzian (see MM documentation for details). """ maskIm = mamba.imageMb(imIn, 1) imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn, 32) se = mamba.structuringElement(mamba.getDirections(grid), grid) i = 0 mamba.copy(imIn, imWrk1) v2 = mamba.computeVolume(imWrk1) v1 = v2 + 1 imOut1.reset() imOut2.reset() while v1 > v2: i += 1 v1 = v2 mamba.erode(imWrk1, imWrk2, se=se) mamba.sub(imWrk1, imWrk2, imWrk1) _generateMask_(imWrk1, imOut1, maskIm) mamba.convertByMask(maskIm, imWrk3, 0, i) mamba.logic(imOut1, imWrk1, imOut1, "sup") mamba.logic(imOut2, imWrk3, imOut2, "sup") mamba.copy(imWrk2, imWrk1) v2 = mamba.computeVolume(imWrk1)
def largeSquareAlternateFilter(imIn, imOut, start, end, step, openFirst): """ Fast full alternate square filter of image 'imIn'. The initial size is equal to 'start', the final one is bounded by 'end' (this size is not taken into account), the increment of size is 'step'. If 'openFirst' is true, the filter starts with on opening. It starts with a closing otherwise. The result is put in 'imOut'. This operation is efficient if most of the sizes used in the filter are greater than 5. If it is not the case, the 'fullAlternateFilter' should be used instead (with a SQUARE structuring element). """ prev = 0 mamba.copy(imIn, imOut) if openFirst: for i in range(start, end, step): t1 = i + prev t2 = 2 * i mamba.largeSquareErode(imOut, imOut, t1) mamba.largeSquareDilate(imOut, imOut, t2) prev = i mamba.largeSquareErode(imOut, imOut, prev) else: for i in range(start, end, step): t1 = i + prev t2 = 2 * i mamba.largeSquareDilate(imOut, imOut, t1) mamba.largeSquareErode(imOut, imOut, t2) prev = i mamba.largeSquareDilate(imOut, imOut, prev)
def computeDiameter(imIn, dir, scale=(1.0, 1.0), grid=mamba.DEFAULT_GRID): """ Computes the diameter (diametral variation) of binary image 'imIn' in direction 'dir'. 'scale' is a tuple defining the horizontal and vertical scale factors (default is 1.0). Beware, if the input image 'imIn' is not a binary image, the function raises an error. """ if imIn.getDepth() != 1: mamba.raiseExceptionOnError(core.MB_ERR_BAD_DEPTH) if dir == 0: return 0.0 dir = ((dir - 1) % (mamba.gridNeighbors(grid) // 2)) + 1 imWrk = mamba.imageMb(imIn) mamba.copy(imIn, imWrk) mamba.diffNeighbor(imIn, imWrk, 1 << dir, grid=grid) if grid == mamba.HEXAGONAL: l = scale[1] if dir != 2: l = 2 * l * scale[0] / math.sqrt(scale[0] * scale[0] + 4 * scale[1] * scale[1]) else: if dir == 1: l = scale[0] elif dir == 3: l = scale[1] else: l = scale[0] * scale[1] / math.sqrt(scale[0] * scale[0] + scale[1] * scale[1]) l = l * mamba.computeVolume(imWrk) return l
def cellsBuild(imIn, imInOut, grid=mamba.DEFAULT_GRID): """ Geodesic reconstruction of the cells of the partition image 'imIn' which are marked by the image 'imInOut'. The marked cells take the value of their corresponding marker. Note that the background cells (labelled by 0) are also modified if they are marked. The result is stored in 'imInOut'. The images can be 8-bit or 32-bit images. 'grid' can be set to HEXAGONAL or SQUARE. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn, 1) vol = 0 prec_vol = -1 dirs = mamba.getDirections(grid)[1:] while (prec_vol != vol): prec_vol = vol for d in dirs: ed = 1 << d mamba.copy(imIn, imWrk1) mamba.copy(imIn, imWrk2) mamba.supNeighbor(imWrk1, imWrk1, ed, grid=grid) mamba.infNeighbor(imWrk2, imWrk2, ed, grid=grid) mamba.generateSupMask(imWrk2, imWrk1, imWrk3, False) mamba.convertByMask(imWrk3, imWrk1, 0, mamba.computeMaxRange(imIn)[1]) mamba.linearDilate(imInOut, imWrk2, d, 1, grid=grid) mamba.logic(imWrk2, imWrk1, imWrk2, "inf") v = mamba.buildNeighbor(imWrk1, imWrk2, d, grid=grid) mamba.logic(imWrk2, imInOut, imInOut, "sup") vol = mamba.computeVolume(imInOut)
def supOpen(imIn, imOut, n, grid=mamba.DEFAULT_GRID): """ 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 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk1.reset() if grid == mamba.SQUARE: size = int((1.4142 * n + 1) / 2) linearOpen(imIn, imWrk2, 2, size, edge=mamba.EMPTY, grid=grid) mamba.logic(imWrk1, imWrk2, imWrk1, "sup") linearOpen(imIn, imWrk2, 4, size, edge=mamba.EMPTY, grid=grid) mamba.logic(imWrk1, imWrk2, imWrk1, "sup") d = 4 else: d = 6 for i in range(1, d, 2): linearOpen(imIn, imWrk2, i, n, edge=mamba.EMPTY, grid=grid) mamba.logic(imWrk1, imWrk2, imWrk1, "sup") mamba.copy(imWrk1, imOut)
def fullThin(imIn, imOut, dse, edge=mamba.EMPTY): """ Performs a complete thinning of 'imIn' with the successive rotations of 'dse' (until idempotence) and puts the result in 'imOut'. 'imIn' and 'imOut' are binary images. 'edge' is set to EMPTY by default. """ if edge == mamba.EMPTY: imWrk = mamba.imageMb(imIn) mamba.copy(imIn, imOut) v1 = mamba.computeVolume(imOut) v2 = 0 while v1 != v2: v2 = v1 for i in range(mamba.gridNeighbors(dse.getGrid())): hitOrMiss(imOut, imWrk, dse) mamba.diff(imOut, imWrk, imOut) dse = dse.rotate() v1 = mamba.computeVolume(imOut) else: mamba.negate(imIn, imOut) v1 = mamba.computeVolume(imOut) v2 = 0 while v1 != v2: v2 = v1 rotatingThick(imOut, imOut, dse.flip()) v1 = mamba.computeVolume(imOut) mamba.negate(imOut, imOut)
def quasiDistance(imIn, imOut1, imOut2, grid=mamba.DEFAULT_GRID): """ Quasi-distance of image 'imIn'. 'imOut1' contains the residues image and 'imOut2' contains the quasi-distance (associated function). The quasi-distance of a greytone image is made of a patch of distance functions of some almost flat regions in the image. When the image is a simple indicator function of a set, the quasi-distance and the distance function are identical. Depth of 'imOut1' is the same as 'imIn', depth of 'imOut2' is 32. """ imWrk1 = mamba.imageMb(imIn, 32) imWrk2 = mamba.imageMb(imIn, 32) imWrk3 = mamba.imageMb(imIn, 32) maskIm = mamba.imageMb(imIn, 1) se = mamba.structuringElement(mamba.getDirections(grid), grid) _initialQuasiDist_(imIn, imOut1, imOut2, grid=grid) mamba.copy(imOut2, imWrk1) v1 = mamba.computeVolume(imOut2) v2 = v1 + 1 while v2 > v1: v2 = v1 mamba.erode(imWrk1, imWrk2, se=se) mamba.sub(imWrk1, imWrk2, imWrk2) mamba.threshold(imWrk2, maskIm, 2, mamba.computeMaxRange(imWrk2)[1]) mamba.convertByMask(maskIm, imWrk3, 0, mamba.computeMaxRange(imWrk3)[1]) mamba.logic(imWrk2, imWrk3, imWrk2, "inf") mamba.subConst(imWrk2, 1, imWrk3) mamba.logic(imWrk2, imWrk3, imWrk2, "inf") # Patch non saturated subtraction mamba.sub(imWrk1, imWrk2, imWrk1) v1 = mamba.computeVolume(imWrk1) mamba.copy(imWrk1, imOut2)
def isotropicDistance(imIn, imOut, edge=mamba.FILLED): """ Computes the distance function of a set in 'imIn'. This distance function uses dodecagonal erosions and the grid is assumed to be hexagonal. The procedure is quite slow but the result is more aesthetic. This operator also illustrates how to perform successive dodecagonal operations of increasing sizes. """ if imIn.getDepth() != 1: mamba.raiseExceptionOnError(core.MB_ERR_BAD_DEPTH) imOut.reset() oldn = 0 size = 0 imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) mamba.copy(imIn, imWrk1) while mamba.computeVolume(imWrk1) != 0: mamba.add(imOut, imWrk1, imOut) size += 1 n = int(0.4641 * size) n += abs(n % 2 - size % 2) if (n - oldn) == 1: mamba.copy(imWrk1, imWrk2) mamba.erode(imWrk1, imWrk1, 1, se=mamba.HEXAGON, edge=edge) else: mamba.conjugateHexagonalErode(imWrk2, imWrk1, 1, edge=edge) oldn = n
def nonEqualNeighbors(imIn, imOut, nb, grid=mamba.DEFAULT_GRID, edge=mamba.FILLED): """ This operator compares the value of each pixel of image 'imIn' with the value of its neighbors encoded in 'nb'. If all the neighbor values are different, the pixel is unchanged. Otherwise, it takes value 0. This operator works on hexagonal or square 'grid' and 'edge' is set to FILLED by default. This operator works for 8-bit and 32-bit images. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn, 1) imWrk4 = mamba.imageMb(imIn, 1) for d in mamba.getDirections(grid): ed = 1<<d if (nb & ed): mamba.copy(imIn, imWrk1) mamba.copy(imIn, imWrk2) mamba.supNeighbor(imWrk1, imWrk1, ed, grid=grid, edge=edge) mamba.infNeighbor(imWrk2, imWrk2, ed, grid=grid, edge=edge) mamba.generateSupMask(imWrk2, imWrk1, imWrk3, False) mamba.logic(imWrk4, imWrk3, imWrk4, "or") mamba.convertByMask(imWrk4, imWrk1, mamba.computeMaxRange(imIn)[1], 0) mamba.logic(imIn, imWrk1, imOut, "inf")
def cellsHMT(imIn, imOut, dse, edge=mamba.EMPTY): """ A Hit-Or-Miss transform is performed on each cell of the partition 'imIn'. 'dse' is a double structuring element (see thinthick.py module). The result is put in 'imOut'. 'edge' is set to EMPTY by default. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) grid = dse.getGrid() cse0 = dse.getStructuringElement(0) cse1 = dse.getStructuringElement(1) mamba.copy(imIn, imOut) mamba.copy(imIn, imWrk1) equalNeighbors(imWrk1, imWrk2, cse1.getEncodedDirections(withoutZero=True), grid=grid, edge=edge) mamba.logic(imOut, imWrk2, imOut, "inf") nonEqualNeighbors(imWrk1, imWrk2, cse0.getEncodedDirections(withoutZero=True), grid=grid, edge=edge) mamba.logic(imOut, imWrk2, imOut, "inf")
def largeHexagonalDilate(imIn, imOut, size, edge=mamba.EMPTY): """ Dilation by large hexagons using dilations by large segments and the Steiner decomposition property of the hexagon. Edge effects are corrected by dilations with transposed decompositions combined with sup operators. This operator is quite complex to avoid edge effects. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) sizemax = min(imIn.getSize())//2 # If size larger than sizemax, the operation must be iterated to prevent edge effects. n = size mamba.copy(imIn, imOut) while n > 0: s = min(n, sizemax) largeLinearDilate(imOut, imWrk1, 6, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imWrk1, imWrk1, 4, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imOut, imWrk2, 4, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imWrk2, imWrk2, 6, s, grid=mamba.HEXAGONAL, edge=edge) mamba.logic(imWrk1, imWrk2, imWrk1, "sup") largeLinearDilate(imWrk1, imWrk2, 2, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imOut, imWrk1, 1, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imWrk1, imWrk1, 3, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imOut, imOut, 3, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imOut, imOut, 1, s, grid=mamba.HEXAGONAL, edge=edge) mamba.logic(imWrk1, imOut, imWrk1, "sup") largeLinearDilate(imWrk1, imOut, 5, s, grid=mamba.HEXAGONAL, edge=edge) mamba.logic(imOut, imWrk2, imOut, "sup") n = n - s
def largeOctogonalAlternateFilter(imIn, imOut, start, end, step, openFirst): """ Fast full alternate octogonal filter of image 'imIn'. The initial size is equal to 'start', the final one is limited by 'end' (this size is not taken into account), the increment of size is 'step'. If 'openFirst' is true, the filter starts with on opening. It starts with a closing otherwise. The result is put in 'imOut'. """ prev = 0 mamba.copy(imIn, imOut) if openFirst: for i in range(start, end, step): t1 = i + prev t2 = 2 * i mamba.largeOctogonalErode(imOut, imOut, t1) mamba.largeOctogonalDilate(imOut, imOut, t2) prev = i mamba.largeOctogonalErode(imOut, imOut, prev) else: for i in range(start, end, step): t1 = i + prev t2 = 2 * i mamba.largeOctogonalDilate(imOut, imOut, t1) mamba.largeOctogonalErode(imOut, imOut, t2) prev = i mamba.largeOctogonalDilate(imOut, imOut, prev)
def copy3D(imIn, imOut, firstPlaneIn=0, firstPlaneOut=0): """ Copies 3D image 'imIn' into 'imOut'. 'firstPlaneIn' indicates the starting plane inside 'imIn' and 'firstPlaneOut' the starting plane inside 'imOut'. """ nbPlanes = min(len(imOut)-firstPlaneOut, len(imIn)-firstPlaneIn) for i in range(nbPlanes): mamba.copy(imIn[i+firstPlaneIn], imOut[i+firstPlaneOut])
def copy3D(imIn, imOut, firstPlaneIn=0, firstPlaneOut=0): """ Copies 3D image 'imIn' into 'imOut'. 'firstPlaneIn' indicates the starting plane inside 'imIn' and 'firstPlaneOut' the starting plane inside 'imOut'. """ nbPlanes = min(len(imOut) - firstPlaneOut, len(imIn) - firstPlaneIn) for i in range(nbPlanes): mamba.copy(imIn[i + firstPlaneIn], imOut[i + firstPlaneOut])
def generalSegment(imIn, imOut, gain=2.0, offset=1, grid=mamba.DEFAULT_GRID): """ General segmentation algorithm. This algorithm is controlled by two parameters: the 'gain' (identical to the gain used in standard and P segmentation) and a new one, the 'offset'. The 'offset' indicates which level of hierarchy is compared to the current hierarchical image. The 'offset' is relative to the current hierarchical level. If 'offset' is equal to 1, this operator corresponds to the standard segmentation, if 'offset' is equal to 255 (this value stands for the infinity), the operator is equivalent to P algorithm. Image 'imOut' contains all these hierarchies which are embedded. 'imIn' and 'imOut' must be greyscale images. 'imIn' and 'imOut' must be different. This transformation returns the number of hierarchical levels. """ imWrk0 = mamba.imageMb(imIn) imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn) imWrk4 = mamba.imageMb(imIn, 1) imWrk5 = mamba.imageMb(imIn, 1) imWrk6 = mamba.imageMb(imIn, 32) mamba.copy(imIn, imWrk1) mamba.mulRealConst(imIn, gain, imWrk6) mamba.floorSubConst(imWrk6, 1, imWrk6) mamba.threshold(imWrk6, imWrk4, 255, mamba.computeMaxRange(imWrk6)[1]) mamba.copyBytePlane(imWrk6, 0, imWrk0) mamba.convert(imWrk4, imWrk2) mamba.logic(imWrk0, imWrk2, imWrk0, "sup") mamba.logic(imWrk0, imWrk1, imWrk0, "sup") imOut.reset() nbLevels = 0 mamba.threshold(imWrk1, imWrk4, 1, 255) flag = not(mamba.checkEmptiness(imWrk4)) while flag: nbLevels += 1 hierarchy(imWrk1, imWrk4, imWrk2, grid=grid) mamba.add(imOut, imWrk4, imOut) v = max(nbLevels - offset, 0) + 1 mamba.threshold(imOut, imWrk4, v, 255) mamba.valuedWatershed(imWrk2, imWrk3, grid=grid) mamba.threshold(imWrk3, imWrk5, 1, 255) flag = not(mamba.checkEmptiness(imWrk5)) hierarchy(imWrk3, imWrk5, imWrk2, grid=grid) mamba.generateSupMask(imWrk0, imWrk2, imWrk5, strict=False) mamba.logic(imWrk4, imWrk5, imWrk4, "inf") mamba.convertByMask(imWrk4, imWrk3, 0, 255) mamba.logic(imWrk1, imWrk3, imWrk3, "inf") mamba.negate(imWrk4, imWrk4) mamba.label(imWrk4, imWrk6, grid=grid) mamba.watershedSegment(imWrk3, imWrk6, grid=grid) mamba.copyBytePlane(imWrk6, 3, imWrk3) mamba.logic(imWrk1, imWrk2, imWrk1, "sup") mamba.logic(imWrk1, imWrk3, imWrk1, "inf") mamba.threshold(imWrk1, imWrk4, 1, 255) return nbLevels
def generalSegment(imIn, imOut, gain=2.0, offset=1, grid=mamba.DEFAULT_GRID): """ General segmentation algorithm. This algorithm is controlled by two parameters: the 'gain' (identical to the gain used in standard and P segmentation) and a new one, the 'offset'. The 'offset' indicates which level of hierarchy is compared to the current hierarchical image. The 'offset' is relative to the current hierarchical level. If 'offset' is equal to 1, this operator corresponds to the standard segmentation, if 'offset' is equal to 255 (this value stands for the infinity), the operator is equivalent to P algorithm. Image 'imOut' contains all these hierarchies which are embedded. 'imIn' and 'imOut' must be greyscale images. 'imIn' and 'imOut' must be different. This transformation returns the number of hierarchical levels. """ imWrk0 = mamba.imageMb(imIn) imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn) imWrk4 = mamba.imageMb(imIn, 1) imWrk5 = mamba.imageMb(imIn, 1) imWrk6 = mamba.imageMb(imIn, 32) mamba.copy(imIn, imWrk1) mamba.mulRealConst(imIn, gain, imWrk6) mamba.floorSubConst(imWrk6, 1, imWrk6) mamba.threshold(imWrk6, imWrk4, 255, mamba.computeMaxRange(imWrk6)[1]) mamba.copyBytePlane(imWrk6, 0, imWrk0) mamba.convert(imWrk4, imWrk2) mamba.logic(imWrk0, imWrk2, imWrk0, "sup") mamba.logic(imWrk0, imWrk1, imWrk0, "sup") imOut.reset() nbLevels = 0 mamba.threshold(imWrk1, imWrk4, 1, 255) flag = not (mamba.checkEmptiness(imWrk4)) while flag: nbLevels += 1 hierarchy(imWrk1, imWrk4, imWrk2, grid=grid) mamba.add(imOut, imWrk4, imOut) v = max(nbLevels - offset, 0) + 1 mamba.threshold(imOut, imWrk4, v, 255) mamba.valuedWatershed(imWrk2, imWrk3, grid=grid) mamba.threshold(imWrk3, imWrk5, 1, 255) flag = not (mamba.checkEmptiness(imWrk5)) hierarchy(imWrk3, imWrk5, imWrk2, grid=grid) mamba.generateSupMask(imWrk0, imWrk2, imWrk5, strict=False) mamba.logic(imWrk4, imWrk5, imWrk4, "inf") mamba.convertByMask(imWrk4, imWrk3, 0, 255) mamba.logic(imWrk1, imWrk3, imWrk3, "inf") mamba.negate(imWrk4, imWrk4) mamba.label(imWrk4, imWrk6, grid=grid) mamba.watershedSegment(imWrk3, imWrk6, grid=grid) mamba.copyBytePlane(imWrk6, 3, imWrk3) mamba.logic(imWrk1, imWrk2, imWrk1, "sup") mamba.logic(imWrk1, imWrk3, imWrk1, "inf") mamba.threshold(imWrk1, imWrk4, 1, 255) return nbLevels
def buildClose(imIn, imOut, n=1, se=mamba.DEFAULT_SE): """ Performs a closing by dual reconstruction operation on image 'imIn' and puts the result in 'imOut'. 'n' controls the size of the closing. """ imWrk = mamba.imageMb(imIn) mamba.copy(imIn, imWrk) mamba.dilate(imIn, imOut, n, se=se) mamba.dualBuild(imWrk, imOut, grid=se.getGrid())
def buildOpen(imIn, imOut, n=1, se=mamba.DEFAULT_SE): """ Performs an opening by reconstruction operation on image 'imIn' and puts the result in 'imOut'. 'n' controls the size of the opening. """ imWrk = mamba.imageMb(imIn) mamba.copy(imIn, imWrk) mamba.erode(imIn, imOut, n, se=se) mamba.build(imWrk, imOut, grid=se.getGrid())
def largeLinearDilate(imIn, imOut, dir, size, grid=mamba.DEFAULT_GRID, edge=mamba.EMPTY): """ Dilation 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). """ mamba.copy(imIn, imOut) for i in _sizeSplit(size): supFarNeighbor(imOut, imOut, dir, i, grid=grid, edge=edge)
def standardSegment(imIn, imOut, gain=2.0, grid=mamba.DEFAULT_GRID): """ General standard segmentation. This algorithm keeps the contours of the watershed transform which are above or equal to the hierarchical image associated to the next level of hierarchy when the altitude of the contour is multiplied by a 'gain' factor (default is 2.0). This transform also ends by idempotence. All the hierarchical levels of image 'imIn'(which is a valued watershed) are computed. 'imOut' contains all these hierarchies which are embedded, so that hierarchy i is simply obtained by a threshold [i+1, 255] of image 'imOut'. 'imIn' and 'imOut' must be greyscale images. 'imIn' and 'imOut' must be different. This transformation returns the number of hierarchical levels. """ imWrk0 = mamba.imageMb(imIn) imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn) imWrk4 = mamba.imageMb(imIn, 1) imWrk5 = mamba.imageMb(imIn, 1) imWrk6 = mamba.imageMb(imIn, 32) mamba.copy(imIn, imWrk1) mamba.mulRealConst(imIn, gain, imWrk6) mamba.floorSubConst(imWrk6, 1, imWrk6) mamba.threshold(imWrk6, imWrk4, 255, mamba.computeMaxRange(imWrk6)[1]) mamba.copyBytePlane(imWrk6, 0, imWrk0) mamba.convert(imWrk4, imWrk2) mamba.logic(imWrk0, imWrk2, imWrk0, "sup") mamba.logic(imWrk0, imWrk1, imWrk0, "sup") imOut.reset() nbLevels = 0 mamba.threshold(imWrk1, imWrk4, 1, 255) flag = not (mamba.checkEmptiness(imWrk4)) while flag: hierarchy(imWrk1, imWrk4, imWrk2, grid=grid) mamba.add(imOut, imWrk4, imOut) mamba.valuedWatershed(imWrk2, imWrk3, grid=grid) mamba.threshold(imWrk3, imWrk5, 1, 255) flag = not (mamba.checkEmptiness(imWrk5)) hierarchy(imWrk3, imWrk5, imWrk2, grid=grid) mamba.generateSupMask(imWrk0, imWrk2, imWrk5, strict=False) mamba.logic(imWrk4, imWrk5, imWrk4, "inf") mamba.convertByMask(imWrk4, imWrk3, 0, 255) mamba.logic(imWrk1, imWrk3, imWrk3, "inf") mamba.negate(imWrk4, imWrk4) mamba.label(imWrk4, imWrk6, grid=grid) mamba.watershedSegment(imWrk3, imWrk6, grid=grid) mamba.copyBytePlane(imWrk6, 3, imWrk3) mamba.logic(imWrk1, imWrk2, imWrk1, "sup") mamba.logic(imWrk1, imWrk3, imWrk1, "inf") mamba.threshold(imWrk1, imWrk4, 1, 255) nbLevels += 1 return nbLevels
def standardSegment(imIn, imOut, gain=2.0, grid=mamba.DEFAULT_GRID): """ General standard segmentation. This algorithm keeps the contours of the watershed transform which are above or equal to the hierarchical image associated to the next level of hierarchy when the altitude of the contour is multiplied by a 'gain' factor (default is 2.0). This transform also ends by idempotence. All the hierarchical levels of image 'imIn'(which is a valued watershed) are computed. 'imOut' contains all these hierarchies which are embedded, so that hierarchy i is simply obtained by a threshold [i+1, 255] of image 'imOut'. 'imIn' and 'imOut' must be greyscale images. 'imIn' and 'imOut' must be different. This transformation returns the number of hierarchical levels. """ imWrk0 = mamba.imageMb(imIn) imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn) imWrk4 = mamba.imageMb(imIn, 1) imWrk5 = mamba.imageMb(imIn, 1) imWrk6 = mamba.imageMb(imIn, 32) mamba.copy(imIn, imWrk1) mamba.mulRealConst(imIn, gain, imWrk6) mamba.floorSubConst(imWrk6, 1, imWrk6) mamba.threshold(imWrk6, imWrk4, 255, mamba.computeMaxRange(imWrk6)[1]) mamba.copyBytePlane(imWrk6, 0, imWrk0) mamba.convert(imWrk4, imWrk2) mamba.logic(imWrk0, imWrk2, imWrk0, "sup") mamba.logic(imWrk0, imWrk1, imWrk0, "sup") imOut.reset() nbLevels = 0 mamba.threshold(imWrk1, imWrk4, 1, 255) flag = not(mamba.checkEmptiness(imWrk4)) while flag: hierarchy(imWrk1, imWrk4, imWrk2, grid=grid) mamba.add(imOut, imWrk4, imOut) mamba.valuedWatershed(imWrk2, imWrk3, grid=grid) mamba.threshold(imWrk3, imWrk5, 1, 255) flag = not(mamba.checkEmptiness(imWrk5)) hierarchy(imWrk3, imWrk5, imWrk2, grid=grid) mamba.generateSupMask(imWrk0, imWrk2, imWrk5, strict=False) mamba.logic(imWrk4, imWrk5, imWrk4, "inf") mamba.convertByMask(imWrk4, imWrk3, 0, 255) mamba.logic(imWrk1, imWrk3, imWrk3, "inf") mamba.negate(imWrk4, imWrk4) mamba.label(imWrk4, imWrk6, grid=grid) mamba.watershedSegment(imWrk3, imWrk6, grid=grid) mamba.copyBytePlane(imWrk6, 3, imWrk3) mamba.logic(imWrk1, imWrk2, imWrk1, "sup") mamba.logic(imWrk1, imWrk3, imWrk1, "inf") mamba.threshold(imWrk1, imWrk4, 1, 255) nbLevels += 1 return nbLevels
def conjugateHexagonalDilate(imIn, imOut, size, edge=mamba.EMPTY): """ Dilation by a conjugate hexagon (hexagon turned by 30 degrees). Be aware that the size of operation corresponds to twice the size of the regular hexagon: a conjugate hexagon of size 1 is inscribed in a regular hexagon of size 2. """ mamba.copy(imIn, imOut) for i in range(size): dilate(imOut, imOut, 1, se=TRIPOD, edge=edge) dilate(imOut, imOut, 1, se=TRIPOD.transpose(), edge=edge)
def largeHexagonalDilate(imIn, imOut, size, edge=mamba.EMPTY): """ Dilation by large hexagons using dilations by large segments and the Steiner decomposition property of the hexagon. Edge effects are corrected by dilations with transposed decompositions combined with sup operators. This operator is quite complex to avoid edge effects. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) sizemax = min(imIn.getSize()) // 2 # If size larger than sizemax, the operation must be iterated to prevent edge effects. n = size mamba.copy(imIn, imOut) while n > 0: s = min(n, sizemax) largeLinearDilate(imOut, imWrk1, 6, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imWrk1, imWrk1, 4, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imOut, imWrk2, 4, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imWrk2, imWrk2, 6, s, grid=mamba.HEXAGONAL, edge=edge) mamba.logic(imWrk1, imWrk2, imWrk1, "sup") largeLinearDilate(imWrk1, imWrk2, 2, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imOut, imWrk1, 1, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imWrk1, imWrk1, 3, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imOut, imOut, 3, s, grid=mamba.HEXAGONAL, edge=edge) largeLinearDilate(imOut, imOut, 1, s, grid=mamba.HEXAGONAL, edge=edge) mamba.logic(imWrk1, imOut, imWrk1, "sup") largeLinearDilate(imWrk1, imOut, 5, s, grid=mamba.HEXAGONAL, edge=edge) mamba.logic(imOut, imWrk2, imOut, "sup") n = n - s
def fullGeodesicThick(imIn, imMask, imOut, dse): """ Performs a complete geodesic thickening (until idempotence) of image 'imIn' inside mask 'imMask' with all the rotations of the double structuring element 'dse'. The result is put in 'imOut'. """ mamba.copy(imIn, imOut) v1 = mamba.computeVolume(imOut) v2 = 0 while v1 != v2: v2 = v1 rotatingGeodesicThick(imOut, imMask, imOut, dse) v1 = mamba.computeVolume(imOut)
def linearClose(imIn, imOut, dir, n, grid=mamba.DEFAULT_GRID, 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 = mamba.imageMb(imIn) if edge==mamba.EMPTY: mamba.copy(imIn, imWrk) mamba.linearDilate(imIn, imOut, dir, n, grid=grid) mamba.linearErode(imOut, imOut, mamba.transposeDirection(dir, grid=grid), n, edge=edge, grid=grid) if edge==mamba.EMPTY: mamba.logic(imOut, imWrk, imOut, "sup")
def _sparseDiamondErode(imIn, imOut, size, edge=mamba.FILLED): """ Erosion by a large diamond (conjugate square) on square grid. This diamond is not completely filled. It is for internal use only. """ imWrk = mamba.imageMb(imIn) mamba.copy(imIn, imOut) for i in _sizeSplit(size): mamba.copy(imOut, imWrk) infFarNeighbor(imWrk, imOut, 1, i, grid=mamba.SQUARE, edge=edge) infFarNeighbor(imWrk, imOut, 3, i, grid=mamba.SQUARE, edge=edge) infFarNeighbor(imWrk, imOut, 5, i, grid=mamba.SQUARE, edge=edge) infFarNeighbor(imWrk, imOut, 7, i, grid=mamba.SQUARE, edge=edge)
def firstParticle(imIn, imOut, grid=mamba.DEFAULT_GRID): """ Extraction of the first particle (in scanning order) of binary image imIn. The particle is put into image imOut and removed from imIn. If imIn is empty, imOut is also empty. imIn and imOut must be different. """ imWrk = mamba.imageMb(imIn) mamba.compare(imIn, imWrk, imWrk) mamba.build(imIn, imWrk, grid=grid) mamba.diff(imIn, imWrk, imIn) mamba.copy(imWrk, imOut)
def areaLabelling(imIn, imOut): """ Special case of measure labelling where each connected component of the binary image 'imIn' or each cell of the partition 'imIn' is labelled with its area. The label image is stored in the 32-bit image 'imOut'. """ imWrk = mamba.imageMb(imIn, 1) if imIn.getDepth() == 1: mamba.copy(imIn, imWrk) else: imWrk.fill(1) measureLabelling(imIn, imWrk, imOut)
def geodesicSKIZ(imIn, imMask, imOut, grid=mamba.DEFAULT_GRID): """ Geodesic skeleton by zones of influence of binary image 'imIn' inside the geodesic mask 'imMask'. The result is in binary image 'imOut'. """ imWrk1 = mamba.imageMb(imIn, 8) imWrk2 = mamba.imageMb(imIn) mamba.copy(imIn, imWrk2) mamba.build(imMask, imWrk2, grid=grid) mamba.convertByMask(imWrk2, imWrk1, 2, 1) mamba.sub(imWrk1, imIn, imWrk1) markerControlledWatershed(imWrk1, imIn, imWrk1, grid=grid) mamba.threshold(imWrk1, imOut, 0, 0) mamba.logic(imOut, imWrk2, imOut, "inf")
def partitionDilate(imIn, imOut, n=1, grid=mamba.DEFAULT_GRID): """ Graph dilation of the corresponding partition image 'imIn'. The size is given by 'n'. The corresponding partition image of the resulting dilated graph is put in 'imOut'. 'grid' can be set to HEXAGONAL or SQUARE. """ imWrk = mamba.imageMb(imIn) mamba.copy(imIn, imOut) mamba.copy(imIn, imWrk) se = mamba.structuringElement(mamba.getDirections(grid), grid) for i in range(n): mamba.dilate(imOut, imOut, se=se) cellsBuild(imWrk, imOut, grid=grid)