def computeConnectivityNumber(imIn, grid=mamba.DEFAULT_GRID): """ Computes the connectivity number (Euler_Poincare constant) of image 'ImIn'. The result is an integer number. 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) imWrk = mamba.imageMb(imIn) if grid == mamba.HEXAGONAL: dse = mamba.doubleStructuringElement([1, 6], [0], mamba.HEXAGONAL) mamba.hitOrMiss(imIn, imWrk, dse) n = mamba.computeVolume(imWrk) dse = mamba.doubleStructuringElement([1], [0, 2], mamba.HEXAGONAL) mamba.hitOrMiss(imIn, imWrk, dse) n = n - mamba.computeVolume(imWrk) else: dse = mamba.doubleStructuringElement([3, 4, 5], [0], mamba.SQUARE) mamba.hitOrMiss(imIn, imWrk, dse) n = mamba.computeVolume(imWrk) dse = mamba.doubleStructuringElement([4], [0, 3, 5], mamba.SQUARE) mamba.hitOrMiss(imIn, imWrk, dse) n = n - mamba.computeVolume(imWrk) dse = mamba.doubleStructuringElement([3, 5], [0, 4], mamba.SQUARE) mamba.hitOrMiss(imIn, imWrk, dse) n = n + mamba.computeVolume(imWrk) return n
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 _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 blackClip(imIn, imOut, step=0, grid=mamba.DEFAULT_GRID): """ Performs a black skeleton clipping (clipping of a black skeleton image). If 'step' is not defined (or equal to 0), the clipping is performed until idempotence. If 'step' is defined, 'step' black points (if possible) will be removed from each branch of the black skeleton. 'edge' is always set to FILLED. """ imWrk = mamba.imageMb(imIn) mamba.negate(imIn, imOut) if step == 0: v1 = mamba.computeVolume(imOut) v2 = 0 while v1 != v2: v2 = v1 endPoints(imOut, imWrk, grid=grid, edge=mamba.FILLED) mamba.diff(imOut, imWrk, imOut) v1 = mamba.computeVolume(imOut) else: for i in range(step): endPoints(imOut, imWrk, grid=grid, edge=mamba.FILLED) mamba.diff(imOut, imWrk, 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 computeConnectivityNumber(imIn, grid=mamba.DEFAULT_GRID): """ Computes the connectivity number (Euler_Poincare constant) of image 'ImIn'. The result is an integer number. 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) imWrk = mamba.imageMb(imIn) if grid == mamba.HEXAGONAL: dse = mamba.doubleStructuringElement([1,6],[0],mamba.HEXAGONAL) mamba.hitOrMiss(imIn, imWrk, dse) n = mamba.computeVolume(imWrk) dse = mamba.doubleStructuringElement([1],[0,2],mamba.HEXAGONAL) mamba.hitOrMiss(imIn, imWrk, dse) n = n - mamba.computeVolume(imWrk) else: dse = mamba.doubleStructuringElement([3,4,5],[0],mamba.SQUARE) mamba.hitOrMiss(imIn, imWrk, dse) n = mamba.computeVolume(imWrk) dse = mamba.doubleStructuringElement([4],[0,3,5],mamba.SQUARE) mamba.hitOrMiss(imIn, imWrk, dse) n = n - mamba.computeVolume(imWrk) dse = mamba.doubleStructuringElement([3,5],[0,4],mamba.SQUARE) mamba.hitOrMiss(imIn, imWrk, dse) n = n + mamba.computeVolume(imWrk) return n
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 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 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 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 mosaicGradient(imIn, imOut, grid=mamba.DEFAULT_GRID): """ Builds the mosaic-gradient image of 'imIn' and puts the result in 'imOut'. The mosaic-gradient image is built by computing the differences of two mosaic images generated from 'imIn', the first one having its watershed lines valued by the suprema of the adjacent catchment basins values, the second one been valued by the infima. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn) imWrk4 = mamba.imageMb(imIn) imWrk5 = mamba.imageMb(imIn) imWrk6 = mamba.imageMb(imIn, 1) mosaic(imIn, imWrk2, imWrk3, grid=grid) mamba.sub(imWrk2, imWrk3, imWrk1) mamba.logic(imWrk2, imWrk3, imWrk2, "sup") mamba.negate(imWrk2, imWrk2) mamba.threshold(imWrk3, imWrk6, 1, 255) mamba.multiplePoints(imWrk6, imWrk6, grid=grid) mamba.convertByMask(imWrk6, imWrk3, 0, 255) se = mamba.structuringElement(mamba.getDirections(grid), grid) mamba.dilate(imWrk1, imWrk4, se=se) mamba.dilate(imWrk2, imWrk5, se=se) while mamba.computeVolume(imWrk3) != 0: mamba.dilate(imWrk1, imWrk1, 2, se=se) mamba.dilate(imWrk2, imWrk2, 2, se=se) mamba.logic(imWrk1, imWrk3, imWrk1, "inf") mamba.logic(imWrk2, imWrk3, imWrk2, "inf") mamba.logic(imWrk1, imWrk4, imWrk4, "sup") mamba.logic(imWrk2, imWrk5, imWrk5, "sup") mamba.erode(imWrk3, imWrk3, 2, se=se) mamba.negate(imWrk5, imWrk5) mamba.sub(imWrk4, imWrk5, 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 fullThick(imIn, imOut, dse): """ Performs a complete thickening of 'imIn' with the successive rotations of 'dse' (until idempotence) and puts the result in 'imOut'. 'imIn' and 'imOut' are binary images. The edge is always set to EMPTY. """ mamba.copy(imIn, imOut) v1 = mamba.computeVolume(imOut) v2 = 0 while v1 != v2: v2 = v1 rotatingThick(imOut, imOut, dse) v1 = mamba.computeVolume(imOut)
def cellsFullThin(imIn, imOut, dse, edge=mamba.EMPTY): """ A full thinning transform is performed on each cell of the partition 'imIn' until idempotence. 'dse' is a double structuring element. The result is put in 'imOut'. 'edge' is set to EMPTY by default. """ 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())): cellsThin(imOut, imOut, dse, edge=edge) dse = dse.rotate() v1 = mamba.computeVolume(imOut)
def updateim(self): # Updates the display (perform a rendering) depth = self.im_ref().getDepth() volume = 0 if depth == 1: # binary 3D image self.planeLabel.config(text="") im8 = mamba.imageMb(self.W, self.H, 8) self.raw = b"" for im2D in self.im_ref(): mamba.convert(im2D, im8) self.raw += im8.extractRaw() volume += mamba.computeVolume(im2D) elif depth == 32: # 32-bit 3D image if self.master.bplane == 4: self.planeLabel.config(text="Plane : all") im3D_8 = m3D.image3DMb(self.im_ref(), 8) m3D.convert3D(self.im_ref(), im3D_8) self.raw = im3D_8.extractRaw() volume = m3D.computeVolume3D(self.im_ref()) else: self.planeLabel.config(text="Plane : %d" % (self.master.bplane)) im8 = mamba.imageMb(self.W, self.H, 8) self.raw = b"" for im2D in self.im_ref(): mamba.copyBytePlane(im2D, self.master.bplane, im8) self.raw += im8.extractRaw() volume += mamba.computeVolume(im2D) else: # Greyscale image self.planeLabel.config(text="") self.raw = self.im_ref().extractRaw() volume = m3D.computeVolume3D(self.im_ref()) self.setImagePlaneZ() self.setImagePlaneY() self.setImagePlaneX() self.planex.eraseTarget() self.planey.eraseTarget() self.planez.eraseTarget() self.volLabel.config(text="Volume : %d" % (volume)) value = self.im_ref().getPixel((self.x, self.y, self.z)) self.posLabel.config(text="At (%d,%d,%d) = %d" % (self.x, self.y, self.z, value))
def updateim(self): # Updates the display (perform a rendering) depth = self.im_ref().getDepth() volume = 0 if depth==1: # binary 3D image self.planeLabel.config(text="") im8 = mamba.imageMb(self.W, self.H, 8) self.raw = b"" for im2D in self.im_ref(): mamba.convert(im2D, im8) self.raw += im8.extractRaw() volume += mamba.computeVolume(im2D) elif depth==32: # 32-bit 3D image if self.master.bplane==4: self.planeLabel.config(text="Plane : all") im3D_8 = m3D.image3DMb(self.im_ref(), 8) m3D.convert3D(self.im_ref(), im3D_8) self.raw = im3D_8.extractRaw() volume = m3D.computeVolume3D(self.im_ref()) else: self.planeLabel.config(text="Plane : %d" % (self.master.bplane)) im8 = mamba.imageMb(self.W, self.H, 8) self.raw = b"" for im2D in self.im_ref(): mamba.copyBytePlane(im2D, self.master.bplane, im8) self.raw += im8.extractRaw() volume += mamba.computeVolume(im2D) else: # Greyscale image self.planeLabel.config(text="") self.raw = self.im_ref().extractRaw() volume = m3D.computeVolume3D(self.im_ref()) self.setImagePlaneZ() self.setImagePlaneY() self.setImagePlaneX() self.planex.eraseTarget() self.planey.eraseTarget() self.planez.eraseTarget() self.volLabel.config(text="Volume : %d" % (volume)) value = self.im_ref().getPixel((self.x, self.y, self.z)) self.posLabel.config(text="At (%d,%d,%d) = %d" % (self.x,self.y,self.z,value))
def ultimateOpening(imIn, imOut1, imOut2, grid=mamba.DEFAULT_GRID): """ Ultimate opening of image 'imIn'. 'imOut1' contains the ultimate opening whereas 'imOut2' contains the granulometric function. Ultimate opening is obtained by using successive openings by hexagons or squares as primitive functions depending of the grid in use. Depth of 'imOut1' is the same as 'imIn', depth of 'imOut2' is 32. """ maskIm = mamba.imageMb(imIn, 1) imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn, 32) imWrk4 = mamba.imageMb(imIn) se = mamba.structuringElement(mamba.getDirections(grid), grid) i = 0 mamba.copy(imIn, imWrk1) v2 = mamba.computeVolume(imWrk1) mamba.copy(imWrk1, imWrk4) v1 = v2 + 1 imOut1.reset() imOut2.reset() if grid == mamba.HEXAGONAL: dilation = mamba.largeHexagonalDilate else: dilation = mamba.largeSquareDilate while v1 > v2: i += 1 v1 = v2 mamba.erode(imWrk4, imWrk4, se=se) dilation(imWrk4, imWrk2, i) 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") v2 = mamba.computeVolume(imWrk4) mamba.copy(imWrk2, imWrk1)
def ultimateIsotropicOpening(imIn, imOut1, imOut2, step =1, grid=mamba.DEFAULT_GRID): """ Ultimate opening of image 'imIn' with more isotropic structuring elements. Dodecagons are used on hexagonal grid, octogons on square grid. 'imOut1' contains the ultimate opening whereas 'imOut2' contains the granulometric function. 'step' is the increment of the size of the openings. Its default value is 1 but it can be increased to reduce the computation time. Depth of 'imOut1' is the same as 'imIn', depth of 'imOut2' is 32. """ maskIm = mamba.imageMb(imIn, 1) imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn, 32) i = 0 mamba.copy(imIn, imWrk1) v2 = mamba.computeVolume(imWrk1) v1 = v2 + 1 imOut1.reset() imOut2.reset() if grid == mamba.HEXAGONAL: iso_dilation = mamba.largeDodecagonalDilate iso_erosion = mamba.largeDodecagonalErode else: iso_dilation = mamba.largeOctogonalDilate iso_erosion = mamba.largeOctogonalErode while v1 > v2: i += step v1 = v2 iso_erosion(imWrk1, imWrk2, i) v2 = mamba.computeVolume(imWrk2) iso_dilation(imWrk2, imWrk2, i) 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)
def dualbuildNeighbor3D(imMask, imInOut, d, grid=m3D.DEFAULT_GRID3D): """ Dual builds image 'imInout' in direction 'd' according to 'grid' using 'imMask' as a mask (the propagation is performed only in 'd' direction). The function also returns the volume of the image 'imInout' after the build operation. 'grid' value can be any 3D grid. """ (width, height, length) = imInOut.getSize() if length != len(imMask): mamba.raiseExceptionOnError(core.MB_ERR_BAD_SIZE) grid2D = grid.get2DGrid() scan = grid.convertFromDir(d, 0)[0] volume = 0 if scan == 0: for i in range(length): vol = mamba.dualbuildNeighbor(imMask[i], imInOut[i], d, grid2D) volume += vol else: if scan == 1: startPlane, endPlane = 0, length - 1 else: startPlane, endPlane = length - 1, 0 for i in range(startPlane, endPlane, scan): mamba.logic(imInOut[i], imMask[i], imInOut[i], "sup") vol = mamba.computeVolume(imInOut[i]) volume += vol td = grid.getTranDir(d) dh = grid.convertFromDir(td, i + scan)[1] mamba.infNeighbor(imInOut[i], imInOut[i + scan], 1 << dh, grid2D) mamba.logic(imInOut[endPlane], imMask[endPlane], imInOut[endPlane], "sup") vol = mamba.computeVolume(imInOut[endPlane]) volume += vol return volume
def computeVolume3D(imIn): """ Computes the volume of the 3D image 'imIn', i.e. the sum of its pixel values. The computed integer value is returned by the function. 'imIn' can be a 1-bit, 8-bit or 32-bit image. Be aware that because this operator runs on 3D image, the returned value can be very high. """ vol = 0 for im2D in imIn: vol += mamba.computeVolume(im2D) return vol
def dualbuildNeighbor3D(imMask, imInOut, d, grid=m3D.DEFAULT_GRID3D): """ Dual builds image 'imInout' in direction 'd' according to 'grid' using 'imMask' as a mask (the propagation is performed only in 'd' direction). The function also returns the volume of the image 'imInout' after the build operation. 'grid' value can be any 3D grid. """ (width, height, length) = imInOut.getSize() if length!=len(imMask): mamba.raiseExceptionOnError(core.MB_ERR_BAD_SIZE) grid2D = grid.get2DGrid() scan = grid.convertFromDir(d,0)[0] volume = 0 if scan == 0: for i in range(length): vol = mamba.dualbuildNeighbor(imMask[i], imInOut[i], d, grid2D) volume += vol else: if scan == 1: startPlane, endPlane = 0, length - 1 else: startPlane, endPlane = length - 1, 0 for i in range(startPlane, endPlane, scan): mamba.logic(imInOut[i], imMask[i], imInOut[i], "sup") vol = mamba.computeVolume(imInOut[i]) volume += vol td = grid.getTranDir(d) dh = grid.convertFromDir(td,i+scan)[1] mamba.infNeighbor(imInOut[i], imInOut[i+scan], 1<<dh, grid2D) mamba.logic(imInOut[endPlane], imMask[endPlane], imInOut[endPlane], "sup") vol = mamba.computeVolume(imInOut[endPlane]) volume += vol return volume
def ultimateBuildOpening(imIn, imOut1, imOut2, grid=mamba.DEFAULT_GRID): """ Ultimate opening by build of image 'imIn'. 'imOut1' contains the ultimate opening whereas 'imOut2' contains the granulometric function. This ultimate opening is obtained by using successive openings by build. Depth of 'imOut1' is the same as 'imIn', depth of 'imOut2' is 32. """ maskIm = mamba.imageMb(imIn, 1) imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn, 32) imWrk4 = mamba.imageMb(imIn) se = mamba.structuringElement(mamba.getDirections(grid), grid) i = 0 mamba.copy(imIn, imWrk1) v2 = mamba.computeVolume(imWrk1) mamba.copy(imWrk1, imWrk4) v1 = v2 + 1 imOut1.reset() imOut2.reset() while v1 > v2: i += 1 v1 = v2 mamba.erode(imWrk4, imWrk4, se=se) mamba.copy(imWrk4, imWrk2) mamba.hierarBuild(imWrk1, imWrk2, grid=mamba.DEFAULT_GRID) 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") v2 = mamba.computeVolume(imWrk4) mamba.copy(imWrk2, imWrk1)
def skeletonByOpening(imIn, imOut1, imOut2, grid=mamba.DEFAULT_GRID): """ General skeleton by openings working on greytone image 'imIn'. 'imOut1' contains the skeleton function and 'imOut2' contains the associated function. This skeleton corresponds to the centers of maximal cylinders included in the set under the graph of the image 'imIn'. Depth of 'imOut1' is the same as 'imIn', depth of 'imOut2' is 32. The edge is always set to 'FILLED'. """ 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.opening(imWrk1, imWrk2, se=se) mamba.sub(imWrk1, imWrk2, imWrk2) _generateMask_(imWrk2, imOut1, maskIm) mamba.convertByMask(maskIm, imWrk3, 0, i) mamba.logic(imOut1, imWrk2, imOut1, "sup") mamba.logic(imOut2, imWrk3, imOut2, "sup") mamba.erode(imWrk1, imWrk1, se=se) v2 = mamba.computeVolume(imWrk1)
def ultimateErosion(imIn, imOut1, imOut2, grid=mamba.DEFAULT_GRID): """ General ultimate erosion working on greytone image 'imIn'. 'imOut1' contains the ultimate eroded function and 'imOut2' contains the associated function. This ultimate erosion can be applied to greytone images. Depth of 'imOut1' is the same as 'imIn', depth of 'imOut2' is 32. The edge is always set to 'FILLED'. """ 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.build(imWrk1, imWrk2, grid=grid) mamba.sub(imWrk1, imWrk2, imWrk2) _generateMask_(imWrk2, imOut1, maskIm) mamba.convertByMask(maskIm, imWrk3, 0, i) mamba.logic(imOut1, imWrk2, imOut1, "sup") mamba.logic(imOut2, imWrk3, imOut2, "sup") mamba.erode(imWrk1, imWrk1, se=se) v2 = mamba.computeVolume(imWrk1)
def whiteClip(imIn, imOut, step=0, grid=mamba.DEFAULT_GRID, edge=mamba.FILLED): """ Performs a skeleton clipping of 'imIn' (supposed to contain a skeleton image) and puts the result in 'imOut'. If 'step' is not defined (or equal to 0), the clipping is performed until idempotence. If 'step' is defined, 'step' points (if possible) will be removed from each branch of the skeleton. 'edge' is set to FILLED by default. """ imWrk = mamba.imageMb(imIn) mamba.copy(imIn, imOut) if step == 0: v1 = mamba.computeVolume(imOut) v2 = 0 while v1 != v2: v2 = v1 endPoints(imOut, imWrk, grid=grid, edge=edge) mamba.diff(imOut, imWrk, imOut) v1 = mamba.computeVolume(imOut) else: for i in range(step): endPoints(imOut, imWrk, grid=grid, edge=edge) mamba.diff(imOut, imWrk, imOut)
def geodesicDistance(imIn, imMask, imOut, se=mamba.DEFAULT_SE): """ Computes the geodesic distance function of a set in 'imIn'. This distance function uses successive geodesic erosions of 'imIn' performed in the geodesic space defined by 'imMask'. The result is stored in 'imOut'. Be sure to use an image of sufficient depth as output. This geodesic distance is quite slow as it is performed by successive geodesic erosions. """ if imIn.getDepth() != 1: mamba.raiseExceptionOnError(core.MB_ERR_BAD_DEPTH) imOut.reset() imWrk = mamba.imageMb(imIn) mamba.logic(imIn, imMask, imWrk, "inf") while mamba.computeVolume(imWrk) != 0: mamba.add(imOut, imWrk, imOut) lowerGeodesicErode(imWrk, imMask, imWrk, se=se)
def linearUltimateOpen(imIn, imOut, d, grid=mamba.DEFAULT_GRID): """ This operator performs the ultimate directional opening of the binary image 'imIn' in direction 'd' and puts the result in the 32-bit image 'imOut'. The value of 'imOut' at each point of 'imIn' corresponds to the length of the intercept passing through this point. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) size = 0 imOut.reset() mamba.copy(imIn, imWrk1) while mamba.computeVolume(imWrk1) != 0: size += 1 directionalErode(imWrk1, imWrk1, d, 1, grid=grid, edge=mamba.EMPTY) td = (d + mamba.gridNeighbors(grid) - 1) % (mamba.gridNeighbors(grid) * 2) + 1 directionalDilate(imWrk1, imWrk2, td, size, grid=grid) mamba.add(imOut, imWrk2, imOut)
def computeArea(imIn, scale=(1.0, 1.0)): """ Calculates the area of the binary image 'imIn'. 'scale' is a tuple containing the horizontal scale factor (distance between two adjacent horizontal points) and the vertical scale factor (distance between two successive lines) of image 'imIn' (default is 1.0 for both). The result is a float (when default values are used, the result value is identical to the computeVolume operator). Note that, with hexagonal grid, the "scale' default values do not correspond to an isotropic grid (where triangles would be equilateral). 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) a = scale[0] * scale[1] * mamba.computeVolume(imIn) return a
def computeArea(imIn, scale=(1.0, 1.0)): """ Calculates the area of the binary image 'imIn'. 'scale' is a tuple containing the horizontal scale factor (distance between two adjacent horizontal points) and the vertical scale factor (distance between two successive lines) of image 'imIn' (default is 1.0 for both). The result is a float (when default values are used, the result value is identical to the computeVolume operator). Note that, with hexagonal grid, the "scale' default values do not correspond to an isotropic grid (where triangles would be equilateral). 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) a = scale[0]*scale[1]*mamba.computeVolume(imIn) return a
def updateim(self): # Updates the display with the new contents of the mamba image. if self.im_ref() and self.state()=="normal" and not self.frozen: if self.im_ref().getDepth()==32: im = mamba.imageMb(self.im_ref(), 8) if self.bplane==4: mamba.convert(self.im_ref(), im) self.infos[1].set("plane : all") else: mamba.copyBytePlane(self.im_ref(),self.bplane,im) self.infos[1].set("plane : %d" % (self.bplane)) self.pilImage = utils.convertToPILFormat(im.mbIm) else: self.infos[1].set("") self.pilImage = utils.convertToPILFormat(self.im_ref().mbIm) if self.palname: self.pilImage.putpalette(palette.getPalette(self.palname)) volume = mamba.computeVolume(self.im_ref()) self.infos[0].set("volume : "+str(volume)) self.icon = ImageTk.PhotoImage(self.pilImage.resize(self.icon_size, Image.NEAREST)) self.tk.call('wm','iconphoto', self._w, self.icon) self.drawImage()
def updateim(self): # Updates the display with the new contents of the mamba image. if self.im_ref() and self.state() == "normal" and not self.frozen: if self.im_ref().getDepth() == 32: im = mamba.imageMb(self.im_ref(), 8) if self.bplane == 4: mamba.convert(self.im_ref(), im) self.infos[1].set("plane : all") else: mamba.copyBytePlane(self.im_ref(), self.bplane, im) self.infos[1].set("plane : %d" % (self.bplane)) self.pilImage = utils.convertToPILFormat(im.mbIm) else: self.infos[1].set("") self.pilImage = utils.convertToPILFormat(self.im_ref().mbIm) if self.palname: self.pilImage.putpalette(palette.getPalette(self.palname)) volume = mamba.computeVolume(self.im_ref()) self.infos[0].set("volume : " + str(volume)) self.icon = ImageTk.PhotoImage( self.pilImage.resize(self.icon_size, Image.NEAREST)) self.tk.call('wm', 'iconphoto', self._w, self.icon) self.drawImage()
def waterfalls(imIn, imOut, grid=mamba.DEFAULT_GRID): """ Classical waterfall algorithm. All the hierarchical levels of greyscale image 'imIn' (which must be a valued watershed) are computed. 'imOut' contains all these hierarchies which are embedded, so that hierarchy i is simply obtained by a threshold at [i+1, 255]. This transformation returns the number of hierarchical levels. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) imWrk3 = mamba.imageMb(imIn, 1) mamba.copy(imIn, imWrk1) imOut.reset() nbLevels = 0 mamba.threshold(imWrk1, imWrk3, 1, 255) while mamba.computeVolume(imWrk3) != 0: mamba.add(imOut, imWrk3, imOut) hierarchicalLevel(imWrk1, imWrk2, grid=grid) mamba.threshold(imWrk2, imWrk3, 1, 255) mamba.copy(imWrk2, imWrk1) nbLevels += 1 return nbLevels
# Importing mamba import mamba import mambaDisplay im = mamba.imageMb("wheel.png", 1) im1 = mamba.imageMb(im, 1) im2 = mamba.imageMb(im, 1) # Opening of image mamba.opening(im, im1, 3) # Selection of the outside region mamba.negate(im1, im2) mamba.removeEdgeParticles(im2, im1) mamba.diff(im2, im1, im2) # Extracting the wheel teeth mamba.logic(im, im2, im2, "inf") # Cleaning the image mamba.opening(im2, im2) # Counting and marking each tooth mamba.thinD(im2, im1) nb_teeth = mamba.computeVolume(im1) print("Number of teeth: %d" % (nb_teeth)) mamba.dilate(im1, im1, 3, mamba.SQUARE3X3) im1.convert(8) im8 = mamba.imageMb(im, 8) mamba.convert(im, im8) mamba.subConst(im8, 1, im8) mamba.logic(im8, im1, im8, "sup") name = mambaDisplay.tagOneColorPalette(255, (0,0,255)) im8.save('wheel_teeth.png', palette=mambaDisplay.getPalette(name))
# Importing mamba import mamba import mambaDisplay im = mamba.imageMb("wheel.png", 1) im1 = mamba.imageMb(im, 1) im2 = mamba.imageMb(im, 1) # Opening of image mamba.opening(im, im1, 3) # Selection of the outside region mamba.negate(im1, im2) mamba.removeEdgeParticles(im2, im1) mamba.diff(im2, im1, im2) # Extracting the wheel teeth mamba.logic(im, im2, im2, "inf") # Cleaning the image mamba.opening(im2, im2) # Counting and marking each tooth mamba.thinD(im2, im1) nb_teeth = mamba.computeVolume(im1) print("Number of teeth: %d" % (nb_teeth)) mamba.dilate(im1, im1, 3, mamba.SQUARE3X3) im1.convert(8) im8 = mamba.imageMb(im, 8) mamba.convert(im, im8) mamba.subConst(im8, 1, im8) mamba.logic(im8, im1, im8, "sup") name = mambaDisplay.tagOneColorPalette(255, (0, 0, 255)) im8.save('wheel_teeth.png', palette=mambaDisplay.getPalette(name))