#@File[] (label="List of images", style="file") listpath #@ int (label="delta (relative difference between threshold levels)", min=0, value=1) delta #@ int (label="Minimum region area", min=0, value=500) minarea #@ int (label="Maximum region area", min=0, value=2000) maxarea #@ float (label="Maximum variation of stability for the region", min=0, value=0.5) maxvar #@ String (label = "Identify bright/dark regions", choices={"Both (Default)", "Bright", "Dark"}, value = "Both (Default)") method #@ Boolean (Label = "Add regions to ROI manager", value=true) as_roi #@ Boolean (Label = "Generate mask", value=true) make_mask ''' MSER : Maximally Stable Extremal Region Detector This detector performs successive thresholding with different thresholds : from bright to dark AND dark to bright by default (but one can optionnally choose only one direction too) The Bright to Dark option rather identify bright regions and vice-versa For each threshold it performs a connected component analysis Then it compare the evolution of a connected region over the level of thresholds : the more variation in the size of the region over the threshold levels levels, the less stable is the region. Finally the script retruns a set of stable regions that fits in the criterium of area and stability defined by the user It retruns this region as a cloud of point, as well as a bounding box for the region. In this macro, the convexHull is returned as a ROI stored into the roi manager A nice use can be to combine with the mask of the object using "AND" arithmetic so that we only retain the MSER region on the object ! TO DO : to prevent inclusion of regions within others, just paint all of them (directly in opencv for better performance), the ones inside others are just overwritten/covered by the largest (like in the Knime worklow) Will also fix the cases for which the convexHull do not work However it will cut regions that might overlap Current limitations : all images must have the same size in this list ''' from org.bytedeco.javacpp.opencv_features2d import MSER from org.bytedeco.javacpp.opencv_core import RectVector, PointVectorVector #from org.bytedeco.javacpp.opencv_imgproc import convexHull, drawContours from ij import IJ, ImagePlus, ImageStack from ij.gui import Roi, PolygonRoi from ij.process import ByteProcessor from ij.plugin import LutLoader from ij.plugin.frame import RoiManager from ImageConverter import ImProcToMat from ijopencv.opencv import PointVectorPointRoiConverter from java.lang.System import getProperty # to get the path to a LUT import os if as_roi: # Initialise ROI manager RM = RoiManager() rm = RM.getRoiManager() # Define defaut paramters for detector - according to doc those are used only for color images so lets use the default only min_diversity = 1 max_evolution = 10 area_threshold = 1.01 min_margin = 0.003 edge_blur_size = 5 # Initialise detector detector = MSER.create(delta,minarea,maxarea,maxvar,min_diversity,max_evolution,area_threshold,min_margin,edge_blur_size) # Set unilateral threshold if selected if method != "Both (Default)": print "Set to unidirectionnal thresholding" detector.setPass2Only(True) # Initialise image stacks if as_roi: StackImage = ImageStack() if make_mask: StackMask = ImageStack() # Perform detection on list of images for ImagePath in listpath: # Open image Image = IJ.openImage(ImagePath.getPath()) # Get ImageProcessor from ImagePlus ImProc = Image.getProcessor() if make_mask: # initialise a mask of same size than the image Width, Height = Image.width, Image.height Mask = ByteProcessor(Width, Height) if as_roi: # Append image to stack N_Image = StackImage.getSize() if N_Image: # already some images StackImage.addSlice(ImProc) else:# no images yet StackImage = Image.getImageStack() N_Image+=1 # we use N to set the ROI position in the stack # Prepare the image if method == "Dark": # invert the image before doing the unidirectionnal detection print "Invert image before unidirectionnal thresholding" ImProc = ImProc.duplicate() # duplicate prevent in place change of the initial image ImProc.invert() # Convert image to 8-bit (scaling) opencv matrix ImCV = ImProcToMat(ImProc, Bit=8) # Initialise result vectors Regions = PointVectorVector() # Regions contains n cloud of points (one/region). Each cloud of points is a PointVector, hence Regions is a Vector of PointVector BBox = RectVector() # detect MSER regions detector.detectRegions(ImCV, Regions, BBox) # Initialise ROI converter ROIconverter = PointVectorPointRoiConverter() # Report the number of detected BBoxes (rather report it in a result table ?) N_Box = BBox.size() message = "Found {} stable regions".format(N_Box) print message IJ.log(message) # Loop over the regions color = 1 # initialise color to fill regions for i in range(N_Box): # HULL # not done in opencv because the input of the function should be a Mat while we have a PointVector # Recover the Point cloud as a PointVector PointVec = Regions.get(i) # Test to draw contour #drawContours(ImCV, PointVec, -1, 255, -1, 0, Mat(),0,Point(0,0)) # Convert to a IJ PointROI PointRoi = ROIconverter.convert(PointVec, float) # Do a convex Hull to prevent hollow shapes and reduce the number of points. Hull = PointRoi.getConvexHull() # Returned object is a Java Polygon (not a ROI) # Fix when Hull do not work if not Hull: # Create a line connecting the points (allow to fit spline) LinePolygon = PointRoi.getContainedFloatPoints() # Convert this line back to a ROI (to be able to apply the spline) LineRoi = PolygonRoi(LinePolygon, 2) LineRoi.fitSpline() # Apply then the convexHull on this spline (directly on the Hull = LineRoi.getConvexHull() # Convert Polygon object back to a ROI (in both cases) Hull_Roi = PolygonRoi(Hull, 2) # Add Hull to ROI manager # Images were already appendedto the stack if as_roi: Hull_Roi.setPosition(N_Image) rm.add(ImagePlus(), Hull_Roi, rm.getCount()+1) # workaround to add ROI with a position see https://forum.image.sc/t/roi-setposition-cant-be-set-to-a-position-greater-than-the-active-image-frame-numbers/10943/11 ''' if showBox: # Can be achieved in Fiji by to bounding box # Bounding Boxes box = BBox.get(i) BoxRoi = Roi(box.x(), box.y(), box.width(), box.height()) # Add to ROI manager BoxRoi.setPosition(N_Image) rm.add(ImagePlus(), BoxRoi, rm.getCount()+1) ''' if make_mask: # Set fill color Mask.setColor(color) # Set current Roi on the image and fill Mask.fill(Hull_Roi) # Update color for next ROI color+=1 if color==256: color=1 # reset color counter to stay in a 8-bit range # Once we finish looping over the regions # append the mask to the MaskStack if make_mask: # Check stack size N_Mask = StackMask.getSize() # create a stack of proper size if empty if N_Mask==0 : StackMask = ImageStack(Width, Height) # Append image to stack StackMask.addSlice(Mask) # Once looping over images is done if make_mask: # Convert StackMask to ImagePlus StackMask_imp = ImagePlus("Mask", StackMask) # Set LUT PathLut = os.path.join(getProperty('fiji.dir'),'luts','glasbey_on_dark.lut') Lut = LutLoader.openLut(PathLut) StackMask_imp.setLut(Lut) # Show stack of mask StackMask_imp.show() if as_roi : # Show stack of images StackImage_imp = ImagePlus("Images", StackImage) StackImage_imp.show() # Show ROI rm.runCommand("Show All with labels")