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 hierarchicalLevel(imIn, imOut, grid=mamba.DEFAULT_GRID): """ Computes the next hierarchical level of image 'imIn' in the waterfalls transformation and puts the result in 'imOut'. This operation makes sure that the next hierarchical level is embedded in the previous one. 'imIn' must be a valued watershed image. """ imWrk0 = mamba.imageMb(imIn) imWrk1 = mamba.imageMb(imIn, 1) imWrk2 = mamba.imageMb(imIn, 1) imWrk3 = mamba.imageMb(imIn, 1) imWrk4 = mamba.imageMb(imIn, 32) mamba.threshold(imIn, imWrk1, 0, 0) mamba.negate(imWrk1, imWrk2) hierarchy(imIn, imWrk2, imWrk0, grid=grid) mamba.minima(imWrk0, imWrk2, grid=grid) mamba.label(imWrk2, imWrk4, grid=grid) mamba.watershedSegment(imWrk0, imWrk4, grid=grid) mamba.copyBytePlane(imWrk4, 3, imWrk0) mamba.threshold(imWrk0, imWrk2, 0, 0) mamba.diff(imWrk1, imWrk2, imWrk3) mamba.build(imWrk1, imWrk3) se = mamba.structuringElement(mamba.getDirections(grid), grid) mamba.dilate(imWrk3, imWrk1, 1, se) mamba.diff(imWrk2, imWrk1, imWrk1) mamba.logic(imWrk1, imWrk3, imWrk1, "sup") mamba.convertByMask(imWrk1, imWrk0, 255, 0) mamba.logic(imIn, imWrk0, imOut, "inf")
def hierarchicalLevel(imIn, imOut, grid=mamba.DEFAULT_GRID): """ Computes the next hierarchical level of image 'imIn' in the waterfalls transformation and puts the result in 'imOut'. This operation makes sure that the next hierarchical level is embedded in the previous one. 'imIn' must be a valued watershed image. """ imWrk0 = mamba.imageMb(imIn) imWrk1 = mamba.imageMb(imIn, 1) imWrk2 = mamba.imageMb(imIn, 1) imWrk3 = mamba.imageMb(imIn, 1) imWrk4 = mamba.imageMb(imIn, 32) mamba.threshold(imIn,imWrk1, 0, 0) mamba.negate(imWrk1, imWrk2) hierarchy(imIn, imWrk2, imWrk0, grid=grid) mamba.minima(imWrk0, imWrk2, grid=grid) mamba.label(imWrk2, imWrk4, grid=grid) mamba.watershedSegment(imWrk0, imWrk4, grid=grid) mamba.copyBytePlane(imWrk4, 3, imWrk0) mamba.threshold(imWrk0, imWrk2, 0, 0) mamba.diff(imWrk1, imWrk2, imWrk3) mamba.build(imWrk1, imWrk3) se = mamba.structuringElement(mamba.getDirections(grid), grid) mamba.dilate(imWrk3, imWrk1, 1, se) mamba.diff(imWrk2, imWrk1, imWrk1) mamba.logic(imWrk1, imWrk3, imWrk1, "sup") mamba.convertByMask(imWrk1, imWrk0, 255, 0) mamba.logic(imIn, imWrk0, imOut, "inf")
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 maxPartialBuild(imIn, imMask, imOut, grid=mamba.DEFAULT_GRID): """ Performs the partial reconstruction of 'imIn' with its maxima which are contained in the binary mask 'imMask'. The result is put in 'imOut'. 'imIn' and 'imOut' must be different and greyscale images. """ imWrk = mamba.imageMb(imIn, 1) maxima(imIn, imWrk, 1, grid=grid) mamba.logic(imMask, imWrk, imWrk, "inf") mamba.convertByMask(imWrk, imOut, 0, mamba.computeMaxRange(imIn)[1]) mamba.logic(imIn, imOut, imOut, "inf") mamba.build(imIn, imOut)
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 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 highMaxima(imIn, imOut, h, grid=mamba.DEFAULT_GRID): """ Computes the maxima of the reconstruction of image 'imIn' by imIn - h and puts the result in 'imOut'. Grid used by the build operation can be specified by 'grid'. Only works with 8-bit or 32-bit images as input. 'imOut' must be binary. """ imWrk = mamba.imageMb(imIn) if imIn.getDepth() == 8: mamba.subConst(imIn, h, imWrk) mamba.hierarBuild(imIn, imWrk, grid=grid) else: mamba.floorSubConst(imIn, h, imWrk) mamba.build(imIn, imWrk, grid=grid) maxima(imWrk, imOut, 1, grid=grid)
def maxDynamics(imIn, imOut, h, grid=mamba.DEFAULT_GRID): """ Extracts the maxima of 'imIn' with a dynamics higher or equal to 'h' and puts the result in 'imOut'. Grid used by the dual build operation can be specified by 'grid'. Only works with 8-bit or 32-bit images as input. 'imOut' must be binary. """ imWrk = mamba.imageMb(imIn) if imIn.getDepth() == 8: mamba.subConst(imIn, h, imWrk) mamba.hierarBuild(imIn, imWrk, grid=grid) mamba.sub(imIn, imWrk, imWrk) else: mamba.floorSubConst(imIn, h, imWrk) mamba.build(imIn, imWrk, grid=grid) mamba.floorSub(imIn, imWrk, imWrk) mamba.threshold(imWrk, imOut, h, mamba.computeMaxRange(imIn)[1])
def maxima(imIn, imOut, h=1, grid=mamba.DEFAULT_GRID): """ Computes the maxima of 'imIn' using a build operation and puts the result in 'imOut'. When 'h' is equal to 1 (default value), the operator provides the maxima of 'imIn'. Grid used by the build operation can be specified by 'grid'. Only works with with 8-bit or 32-bit images as input. 'imOut' must be binary. """ imWrk = mamba.imageMb(imIn) if imIn.getDepth() == 8: mamba.subConst(imIn, h, imWrk) mamba.hierarBuild(imIn, imWrk, grid=grid) mamba.sub(imIn, imWrk, imWrk) else: mamba.floorSubConst(imIn, h, imWrk) mamba.build(imIn, imWrk, grid=grid) mamba.floorSub(imIn, imWrk, imWrk) mamba.threshold(imWrk, imOut, 1, mamba.computeMaxRange(imIn)[1])
def simpleLevelling(imIn, imMask, imOut, grid=mamba.DEFAULT_GRID): """ Performs a simple levelling of image 'imIn' controlled by image 'imMask' and puts the result in 'imOut'. This operation is composed of two geodesic reconstructions. This filter tends to level regions in the image of homogeneous grey values. """ imWrk1 = mamba.imageMb(imIn) imWrk2 = mamba.imageMb(imIn) mask_im = mamba.imageMb(imIn, 1) mamba.logic(imIn, imMask, imWrk1, "inf") mamba.build(imIn, imWrk1, grid=grid) mamba.logic(imIn, imMask, imWrk2, "sup") mamba.dualBuild(imIn, imWrk2, grid=grid) mamba.generateSupMask(imIn, imMask, mask_im, False) mamba.convertByMask(mask_im, imOut, 0, mamba.computeMaxRange(imIn)[1]) mamba.logic(imOut, imWrk1, imWrk1, "inf") mamba.negate(imOut, imOut) mamba.logic(imOut, imWrk2, imOut, "inf") mamba.logic(imWrk1, imOut, imOut, "sup")
def strongLevelling(imIn, imOut, n, eroFirst, grid=mamba.DEFAULT_GRID): """ Strong levelling of 'imIn', result in 'imOut'. 'n' defines the size of the erosion and dilation of 'imIn' in the operation. If 'eroFirst' is true, the operation starts with an erosion, it starts with a dilation otherwise. This filter is stronger (more efficient) that simpleLevelling. However, the order of the initial operations (erosion and dilation) matters. """ imWrk = mamba.imageMb(imIn) se = mamba.structuringElement(mamba.getDirections(grid), grid) if eroFirst: mamba.erode(imIn, imWrk, n, se=se) mamba.build(imIn, imWrk, grid=grid) mamba.dilate(imIn, imOut, n, se=se) mamba.dualBuild(imWrk, imOut, grid=grid) else: mamba.dilate(imIn, imWrk, n, se=se) mamba.dualBuild(imIn, imWrk, grid=grid) mamba.erode(imIn, imOut, n, se=se) mamba.build(imWrk, imOut, grid=grid)
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 feretDiameterLabelling(imIn, imOut, direc): """ The Feret diameter of each connected component of the binary image or the partition image 'imIn' is computed and its value labels the corresponding component. The labelled image is stored in the 32-bit image 'imOut'. If 'direc' is "vertical", the vertical Feret diameter is computed. If it is set to "horizontal", the corresponding diameter is used. """ imWrk1 = mamba.imageMb(imIn, 1) imWrk2 = mamba.imageMb(imIn, 32) imWrk3 = mamba.imageMb(imIn, 32) imWrk4 = mamba.imageMb(imIn, 32) imWrk1.fill(1) if direc == "horizontal": dir = 7 elif direc == "vertical": dir = 1 else: mamba.raiseExceptionOnError(core.MB_ERR_BAD_DIRECTION) # The above statement generates an error ('direc' is not horizontal or # vertical. # An horizontal or vertical distance function is generated. mamba.linearErode(imWrk1, imWrk1, dir, grid=mamba.SQUARE, edge=mamba.EMPTY) mamba.computeDistance(imWrk1, imOut, grid=mamba.SQUARE, edge=mamba.FILLED) mamba.addConst(imOut, 1, imOut) if imIn.getDepth() == 1: # Each particle is valued with the distance. mamba.convertByMask(imIn, imWrk2, 0, mamba.computeMaxRange(imWrk3)[1]) mamba.logic(imOut, imWrk2, imWrk3, "inf") # The valued image is preserved. mamba.copy(imWrk3, imWrk4) # Each component is labelled by the maximal coordinate. mamba.build(imWrk2, imWrk3) # Using the dual reconstruction, we label the particles with the # minimal ccordinate. mamba.negate(imWrk2, imWrk2) mamba.logic(imWrk2, imWrk4, imWrk4, "sup") mamba.dualBuild(imWrk2, imWrk4) # We subtract 1 because the selected coordinate must be outside the particle. mamba.subConst(imWrk4, 1, imWrk4) mamba.negate(imWrk2, imWrk2) mamba.logic(imWrk2, imWrk4, imWrk4, "inf") # Then, the subtraction gives the Feret diameter. mamba.sub(imWrk3, imWrk4, imOut) else: mamba.copy(imOut, imWrk3) if imIn.getDepth() == 32: mamba.copy(imIn, imWrk2) else: mamba.convert(imIn, imWrk2) # Using the cells builds (direct and dual to label the cells with the maximum # and minimum distance. mamba.cellsBuild(imWrk2, imWrk3) mamba.cellsBuild(imWrk2, imWrk3) mamba.negate(imOut, imOut) mamba.cellsBuild(imWrk2, imOut) mamba.negate(imOut, imOut) # Subtracting 1... mamba.subConst(imOut, 1, imOut) # ... and getting the final result. mamba.sub(imWrk3, imOut, imOut)
def _watershed_using_quasi_distance(self): """ 疑似ユークリッド距離(Quasi Distance) に基づく Watershed 領域分割 Returns ------- numpy.ndarray 領域分割線の画像 """ from mamba import ( imageMb, gradient, add, negate, quasiDistance, copyBytePlane, subConst, build, maxima, label, watershedSegment, logic, mix, ) from utils.convert import mamba2np, np2mamba # Channel Split if self.src_img.ndim == 3: b, g, r = [np2mamba(self.src_img[:, :, i]) for i in range(3)] elif self.src_img.ndim == 2: b, g, r = [np2mamba(self.src_img)] * 3 # We will perform a thick gradient on each color channel (contours in original # picture are more or less fuzzy) and we add all these gradients gradient = imageMb(r) tmp_1 = imageMb(r) gradient.reset() gradient(r, tmp_1, 2) add(tmp_1, gradient, gradient) gradient(g, tmp_1, 2) add(tmp_1, gradient, gradient) gradient(b, tmp_1, 2) add(tmp_1, gradient, gradient) # Then we invert the gradient image and we compute its quasi-distance quasi_dist = imageMb(gradient, 32) negate(gradient, gradient) quasiDistance(gradient, tmp_1, quasi_dist) if self.is_logging: self.logger.logging_img(tmp_1, "quasi_dist_gradient") self.logger.logging_img(quasi_dist, "quasi_dist") # The maxima of the quasi-distance are extracted and filtered (too close maxima, # less than 6 pixels apart, are merged) tmp_2 = imageMb(r) marker = imageMb(gradient, 1) copyBytePlane(quasi_dist, 0, tmp_1) subConst(tmp_1, 3, tmp_2) build(tmp_1, tmp_2) maxima(tmp_2, marker) # The marker-controlled watershed of the gradient is performed watershed = imageMb(gradient) label(marker, quasi_dist) negate(gradient, gradient) watershedSegment(gradient, quasi_dist) copyBytePlane(quasi_dist, 3, watershed) # The segmented binary and color image are stored logic(r, watershed, r, "sup") logic(g, watershed, g, "sup") logic(b, watershed, b, "sup") segmented_image = mix(r, g, b) if self.is_logging: self.logger.logging_img(segmented_image, "segmented_image") watershed = mamba2np(watershed) return watershed
def reconstruction(npIm, npMask): mbIm = convertNumpy2Mamba(npIm) mbMask = convertNumpy2Mamba(npMask) mamba.build(mbMask, mbIm, mamba.SQUARE) height, width = npIm.shape[-2:] return convertMamba2Numpy(mbIm)[:height, :width].astype(npIm.dtype)