コード例 #1
0
def hist_equalization(img, device, debug=None):
    """Histogram equalization is a method to normalize the distribution of intensity values. If the image has low
       contrast it will make it easier to threshold.

    Inputs:
    img    = input image
    device = device number. Used to count steps in the pipeline
    debug  = None, print, or plot. Print = save to file, Plot = print to screen.

    Returns:
    device = device number
    img_eh = normalized image

    :param img: numpy array
    :param device: int
    :param debug: str
    :return device: int
    :return img_eh: numpy array
    """

    if len(np.shape(img)) == 3:
        fatal_error("Input image must be gray")

    img_eh = cv2.equalizeHist(img)
    device += 1
    if debug == 'print':
        print_image(img_eh, str(device) + '_hist_equal_img.png')
    elif debug == 'plot':
        plot_image(img_eh, cmap='gray')

    return device, img_eh
コード例 #2
0
def closing(gray_img, kernel=None):
    """Wrapper for scikit-image closing functions. Opening can remove small dark spots (i.e. pepper).

    Inputs:
    gray_img = input image (grayscale or binary)
    kernel   = optional neighborhood, expressed as an array of 1s and 0s. If None, use cross-shaped structuring element.

    :param gray_img: ndarray
    :param kernel = ndarray
    :return filtered_img: ndarray
    """

    params.device += 1

    # Make sure the image is binary/grayscale
    if len(np.shape(gray_img)) != 2:
        fatal_error("Input image must be grayscale or binary")

    # If image is binary use the faster method
    if len(np.unique(gray_img)) == 2:
        bool_img = morphology.binary_closing(image=gray_img, selem=kernel)
        filtered_img = np.copy(bool_img.astype(np.uint8) * 255)
    # Otherwise use method appropriate for grayscale images
    else:
        filtered_img = morphology.closing(gray_img, kernel)

    if params.debug == 'print':
        print_image(
            filtered_img,
            os.path.join(params.debug_outdir,
                         str(params.device) + '_opening' + '.png'))
    elif params.debug == 'plot':
        plot_image(filtered_img, cmap='gray')

    return filtered_img
コード例 #3
0
def erode(gray_img, kernel, i):
    """Perform morphological 'erosion' filtering. Keeps pixel in center of the kernel if conditions set in kernel are
       true, otherwise removes pixel.

    Inputs:
    gray_img = Grayscale (usually binary) image data
    kernel   = Kernel size (int). A k x k kernel will be built. Must be greater than 1 to have an effect.
    i        = interations, i.e. number of consecutive filtering passes

    Returns:
    er_img = eroded image

    :param gray_img: numpy.ndarray
    :param kernel: int
    :param i: int
    :return er_img: numpy.ndarray
    """

    kernel1 = int(kernel)
    kernel2 = np.ones((kernel1, kernel1), np.uint8)
    er_img = cv2.erode(src=gray_img, kernel=kernel2, iterations=i)
    params.device += 1
    if params.debug == 'print':
        print_image(
            er_img,
            os.path.join(
                params.debug_outdir,
                str(params.device) + '_er_image_' + 'itr_' + str(i) + '.png'))
    elif params.debug == 'plot':
        plot_image(er_img, cmap='gray')
    return er_img
コード例 #4
0
def distance_transform(bin_img, distance_type, mask_size):
    """Creates an image where for each object pixel, a number is assigned that corresponds to the distance to the
    nearest background pixel.

    Inputs:
    img             = Binary image data
    distance_type   = Type of distance. It can be CV_DIST_L1, CV_DIST_L2 , or CV_DIST_C which are 1, 2 and 3,
                      respectively.
    mask_size       = Size of the distance transform mask. It can be 3, 5, or CV_DIST_MASK_PRECISE (the latter option
                      is only supported by the first function). In case of the CV_DIST_L1 or CV_DIST_C distance type,
                      the parameter is forced to 3 because a 3 by 3 mask gives the same result as 5 by 5 or any larger
                      aperture.

    Returns:
    norm_image      = grayscale distance-transformed image normalized between [0, 1]

    :param bin_img: numpy.ndarray
    :param distance_type: int
    :param mask_size: int
    :return norm_image: numpy.ndarray
    """

    params.device += 1
    dist = cv2.distanceTransform(src=bin_img, distanceType=distance_type, maskSize=mask_size)
    norm_image = cv2.normalize(src=dist, dst=dist, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32F)

    if params.debug == 'print':
        print_image(norm_image, os.path.join(params.debug, str(params.device) + '_distance_transform.png'))
    elif params.debug == 'plot':
        plot_image(norm_image, cmap='gray')

    return norm_image
コード例 #5
0
ファイル: resize.py プロジェクト: danforthcenter/plantcv
def resize(img, resize_x, resize_y):
    """Resize image.

    Inputs:
    img      = RGB or grayscale image data to resize
    resize_x = scaling factor
    resize_y = scaling factor

    Returns:
    reimg    = resized image

    :param img: numpy.ndarray
    :param resize_x: int
    :param resize_y: int
    :return reimg: numpy.ndarray
    """

    params.device += 1

    if resize_x <= 0 and resize_y <= 0:
        fatal_error("Resize values both cannot be 0 or negative values!")

    reimg = cv2.resize(img, (0, 0), fx=resize_x, fy=resize_y)

    if params.debug == 'print':
        print_image(reimg, os.path.join(params.debug_outdir, str(params.device) + "_resize1.png"))
    elif params.debug == 'plot':
        plot_image(reimg)

    return reimg
コード例 #6
0
def skeletonize(mask):
    """Reduces binary objects to 1 pixel wide representations (skeleton)

    Inputs:
    mask       = Binary image data

    Returns:
    skeleton   = skeleton image

    :param mask: numpy.ndarray
    :return skeleton: numpy.ndarray
    """

    # Convert mask to boolean image, rather than 0 and 255 for skimage to use it
    skeleton = skmorph.skeletonize(mask.astype(bool))

    skeleton = skeleton.astype(np.uint8) * 255

    # Auto-increment device
    params.device += 1

    if params.debug == 'print':
        print_image(
            skeleton,
            os.path.join(params.debug_outdir,
                         str(params.device) + '_skeleton.png'))
    elif params.debug == 'plot':
        plot_image(skeleton, cmap='gray')

    return skeleton
コード例 #7
0
def background_subtraction(background_image, foreground_image):
    """Creates a binary image from a background subtraction of the foreground using cv2.BackgroundSubtractorMOG().
    The binary image returned is a mask that should contain mostly foreground pixels.
    The background image should be the same background as the foreground image except not containing the object
    of interest.

    Images must be of the same size and type.
    If not, larger image will be taken and downsampled to smaller image size.
    If they are of different types, an error will occur.

    Inputs:
    background_image       = img object, RGB or binary/grayscale/single-channel
    foreground_image       = img object, RGB or binary/grayscale/single-channel

    Returns:
    fgmask                 = background subtracted foreground image (mask)

    :param background_image: numpy.ndarray
    :param foreground_image: numpy.ndarray
    :return fgmask: numpy.ndarray
    """

    params.device += 1
    # Copying images to make sure not alter originals
    bg_img = np.copy(background_image)
    fg_img = np.copy(foreground_image)
    # Checking if images need to be resized or error raised
    if bg_img.shape != fg_img.shape:
        # If both images are not 3 channel or single channel then raise error.
        if len(bg_img.shape) != len(fg_img.shape):
            fatal_error(
                "Images must both be single-channel/grayscale/binary or RGB")
        # Forcibly resizing largest image to smallest image
        print("WARNING: Images are not of same size.\nResizing")
        if bg_img.shape > fg_img.shape:
            width, height = fg_img.shape[1], fg_img.shape[0]
            bg_img = cv2.resize(bg_img, (width, height),
                                interpolation=cv2.INTER_AREA)
        else:
            width, height = bg_img.shape[1], bg_img.shape[0]
            fg_img = cv2.resize(fg_img, (width, height),
                                interpolation=cv2.INTER_AREA)

    bgsub = cv2.createBackgroundSubtractorMOG2()
    # Applying the background image to the background subtractor first.
    # Anything added after is subtracted from the previous iterations.
    _ = bgsub.apply(bg_img)
    # Applying the foreground image to the background subtractor (therefore removing the background)
    fgmask = bgsub.apply(fg_img)

    # Debug options
    if params.debug == "print":
        print_image(
            fgmask,
            os.path.join(params.debug_outdir,
                         str(params.device) + "_background_subtraction.png"))
    elif params.debug == "plot":
        plot_image(fgmask, cmap="gray")

    return fgmask
コード例 #8
0
ファイル: flip.py プロジェクト: danforthcenter/plantcv
def flip(img, direction):
    """Flip image.

    Inputs:
    img       = RGB or grayscale image data
    direction = "horizontal" or "vertical"

    Returns:
    vh_img    = flipped image

    :param img: numpy.ndarray
    :param direction: str
    :return vh_img: numpy.ndarray
    """
    params.device += 1
    if direction.upper() == "VERTICAL":
        vh_img = cv2.flip(img, 1)
    elif direction.upper() == "HORIZONTAL":
        vh_img = cv2.flip(img, 0)
    else:
        fatal_error(str(direction) + " is not a valid direction, must be horizontal or vertical")

    if params.debug == 'print':
        print_image(vh_img, os.path.join(params.debug_outdir, str(params.device) + "_flipped.png"))
    elif params.debug == 'plot':
        if len(np.shape(vh_img)) == 3:
            plot_image(vh_img)
        else:
            plot_image(vh_img, cmap='gray')

    return vh_img
コード例 #9
0
ファイル: readimage.py プロジェクト: stevenhwu/plantcv
def readimage(filename, debug=None):
    """Read image from file.

    Inputs:
    filename = name of image file
    debug    = None, print, or plot. Print = save to file, Plot = print to screen.

    Returns:
    img      = image object as numpy array
    path     = path to image file
    img_name = name of image file

    :param filename: str
    :param debug: str
    :return img: numpy array
    :return path: str
    :return img_name: str
    """

    img = cv2.imread(filename)

    if img is None:
        fatal_error("Failed to open " + filename)

    # Split path from filename
    path, img_name = os.path.split(filename)

    if debug == "print":
        print_image(img, "input_image.png")
    elif debug == "plot":
        plot_image(img)

    return img, path, img_name
コード例 #10
0
def average_all_img(directory, outdir):
    allfiles = os.listdir(directory)

    path = str(directory)

    allpaths = []

    for files in allfiles:
        p = path + str(files)
        allpaths.append(p)

    img, path, filename = pcv.readimage(allpaths[0])
    n = len(allpaths)

    if len(np.shape(img)) == 3:
        ix, iy, iz = np.shape(img)
        arr = np.zeros((ix, iy, iz), np.float)
    else:
        ix, iy = np.shape(img)
        arr = np.zeros((ix, iy), np.float)

    # Build up average pixel intensities, casting each image as an array of floats
    for i, paths in enumerate(allpaths):
        img, path, filename = pcv.readimage(allpaths[i])
        imarr = np.array(img, dtype=np.float)
        arr = arr + imarr / n

    # Round values in array and cast as 8-bit integer
    arr = np.array(np.round(arr), dtype=np.uint8)

    pcv.print_image(arr, (str(outdir) + "average_" + str(allfiles[0])))
コード例 #11
0
ファイル: opening.py プロジェクト: danforthcenter/plantcv
def opening(gray_img, kernel=None):
    """Wrapper for scikit-image opening functions. Opening can remove small bright spots (i.e. salt).

    Inputs:
    gray_img = input image (grayscale or binary)
    kernel   = optional neighborhood, expressed as an array of 1s and 0s. If None, use cross-shaped structuring element.

    :param gray_img: ndarray
    :param kernel = ndarray
    :return filtered_img: ndarray
    """

    params.device += 1

    # Make sure the image is binary/grayscale
    if len(np.shape(gray_img)) != 2:
        fatal_error("Input image must be grayscale or binary")

    # If image is binary use the faster method
    if len(np.unique(gray_img)) == 2:
        bool_img = morphology.binary_opening(gray_img, kernel)
        filtered_img = np.copy(bool_img.astype(np.uint8) * 255)
    # Otherwise use method appropriate for grayscale images
    else:
        filtered_img = morphology.opening(gray_img, kernel)

    if params.debug == 'print':
        print_image(filtered_img, os.path.join(params.debug_outdir, str(params.device) + '_opening' + '.png'))
    elif params.debug == 'plot':
        plot_image(filtered_img, cmap='gray')

    return filtered_img
コード例 #12
0
def scharr_filter(gray_img, dx, dy, scale):
    """This is a filtering method used to identify and highlight gradient edges/features using the 1st derivative.
       Typically used to identify gradients along the x-axis (dx = 1, dy = 0) and y-axis (dx = 0, dy = 1) independently.
       Performance is quite similar to Sobel filter. Used to detect edges / changes in pixel intensity. ddepth = -1
       specifies that the dimensions of output image will be the same as the input image.

    Inputs:
    gray_img = Grayscale image data
    dx       = derivative of x to analyze (1-3)
    dy       = derivative of x to analyze (1-3)
    scale    = scaling factor applied (multiplied) to computed Scharr values (scale = 1 is unscaled)

    Returns:
    sr_img   = Scharr filtered image

    :param gray_img: numpy.ndarray
    :param dx: int
    :param dy: int
    :param scale: int
    :return sr_img: numpy.ndarray
    """

    sr_img = cv2.Scharr(src=gray_img, ddepth=-1, dx=dx, dy=dy, scale=scale)
    params.device += 1
    if params.debug == 'print':
        name = os.path.join(params.debug_outdir, str(params.device))
        name += '_sr_img_dx' + str(dx) + '_dy' + str(dy) + '_scale' + str(scale) + '.png'
        print_image(sr_img, name)
    elif params.debug == 'plot':
        plot_image(sr_img, cmap='gray')
    return sr_img
コード例 #13
0
ファイル: erode.py プロジェクト: stevenhwu/plantcv
def erode(img, kernel, i, device, debug=None):
    """Perform morphological 'erosion' filtering. Keeps pixel in center of the kernel if conditions set in kernel are
       true, otherwise removes pixel.

    Inputs:
    img    = input image
    kernel = filtering window, you'll need to make your own using as such:
             kernal = np.zeros((x,y), dtype=np.uint8), then fill the kernal with appropriate values
    i      = interations, i.e. number of consecutive filtering passes
    device = device number. Used to count steps in the pipeline
    debug  = None, print, or plot. Print = save to file, Plot = print to screen.

    Returns:
    device = device number
    er_img = eroded image

    :param img: numpy array
    :param kernel: numpy array
    :param i: int
    :param device: int
    :param debug: str
    :return device: int
    :return er_img: numpy array
    """

    kernel1 = int(kernel)
    kernel2 = np.ones((kernel1, kernel1), np.uint8)
    er_img = cv2.erode(src=img, kernel=kernel2, iterations=i)
    device += 1
    if debug == 'print':
        print_image(er_img,
                    str(device) + '_er_image_' + 'itr_' + str(i) + '.png')
    elif debug == 'plot':
        plot_image(er_img, cmap='gray')
    return device, er_img
コード例 #14
0
def find_objects(img, mask):
    """Find all objects and color them blue.

    Inputs:
    img       = RGB or grayscale image data for plotting
    mask      = Binary mask used for contour detection


    Returns:
    objects   = list of contours
    hierarchy = contour hierarchy list

    :param img: numpy.ndarray
    :param mask: numpy.ndarray
    :return objects: list
    :return hierarchy: numpy.ndarray
    """

    params.device += 1
    mask1 = np.copy(mask)
    ori_img = np.copy(img)
    # If the reference image is grayscale convert it to color
    if len(np.shape(ori_img)) == 2:
        ori_img = cv2.cvtColor(ori_img, cv2.COLOR_GRAY2BGR)
    objects, hierarchy = cv2.findContours(mask1, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:]
    for i, cnt in enumerate(objects):
        cv2.drawContours(ori_img, objects, i, (255, 102, 255), -1, lineType=8, hierarchy=hierarchy)
    if params.debug == 'print':
        print_image(ori_img, os.path.join(params.debug_outdir, str(params.device) + '_id_objects.png'))
    elif params.debug == 'plot':
        plot_image(ori_img)

    return objects, hierarchy
コード例 #15
0
def gaussian_blur(img, ksize, sigma_x=0, sigma_y=None):
    """Applies a Gaussian blur filter.

    Inputs:
    # img     = RGB or grayscale image data
    # ksize   = Tuple of kernel dimensions, e.g. (5, 5)
    # sigmax  = standard deviation in X direction; if 0, calculated from kernel size
    # sigmay  = standard deviation in Y direction; if sigmaY is None, sigmaY is taken to equal sigmaX

    Returns:
    img_gblur = blurred image

    :param img: numpy.ndarray
    :param ksize: tuple
    :param sigmax: int
    :param sigmay: str or int
    :return img_gblur: numpy.ndarray
    """

    img_gblur = cv2.GaussianBlur(img, ksize, sigma_x, sigma_y)

    params.device += 1
    if params.debug == 'print':
        print_image(img_gblur, os.path.join(params.debug_outdir, str(params.device) + '_gaussian_blur.png'))
    elif params.debug == 'plot':
        if len(np.shape(img_gblur)) == 3:
            plot_image(img_gblur)
        else:
            plot_image(img_gblur, cmap='gray')

    return img_gblur
コード例 #16
0
def average_all_img(directory, outdir):
    allfiles = os.listdir(directory)
    
    path = str(directory)
    
    allpaths = []
    
    for files in allfiles:
        p = path + str(files)
        allpaths.append(p)
    
    img, path, filename = pcv.readimage(allpaths[0])
    n = len(allpaths)

    if len(np.shape(img)) == 3:
        ix, iy, iz = np.shape(img)
        arr = np.zeros((ix, iy, iz), np.float)
    else:
        ix, iy = np.shape(img)
        arr = np.zeros((ix, iy), np.float)

    # Build up average pixel intensities, casting each image as an array of floats
    for i, paths in enumerate(allpaths):
        img, path, filename = pcv.readimage(allpaths[i])
        imarr = np.array(img, dtype=np.float)
        arr = arr + imarr / n

    # Round values in array and cast as 8-bit integer
    arr = np.array(np.round(arr), dtype=np.uint8)

    pcv.print_image(arr, (str(outdir)+"average_"+str(allfiles[0])))
コード例 #17
0
def watershed_segmentation(rgb_img, mask, distance=10):
    """Uses the watershed algorithm to detect boundary of objects. Needs a marker file which specifies area which is
       object (white), background (grey), unknown area (black).

    Inputs:
    rgb_img             = image to perform watershed on needs to be 3D (i.e. np.shape = x,y,z not np.shape = x,y)
    mask                = binary image, single channel, object in white and background black
    distance            = min_distance of local maximum

    Returns:
    analysis_images     = list of output images

    :param rgb_img: numpy.ndarray
    :param mask: numpy.ndarray
    :param distance: int
    :return analysis_images: list
    """
    params.device += 1

    # Store debug mode
    debug = params.debug
    params.debug = None

    dist_transform = cv2.distanceTransformWithLabels(mask, cv2.DIST_L2, maskSize=0)[0]

    localMax = peak_local_max(dist_transform, indices=False, min_distance=distance, labels=mask)

    markers = ndi.label(localMax, structure=np.ones((3, 3)))[0]
    dist_transform1 = -dist_transform
    labels = watershed(dist_transform1, markers, mask=mask)

    img1 = np.copy(rgb_img)

    for x in np.unique(labels):
        rand_color = color_palette(len(np.unique(labels)))
        img1[labels == x] = rand_color[x]

    img2 = apply_mask(img1, mask, 'black')

    joined = np.concatenate((img2, rgb_img), axis=1)

    estimated_object_count = len(np.unique(markers)) - 1

    # Reset debug mode
    params.debug = debug
    if params.debug == 'print':
        print_image(dist_transform, os.path.join(params.debug_outdir, str(params.device) + '_watershed_dist_img.png'))
        print_image(joined, os.path.join(params.debug_outdir, str(params.device) + '_watershed_img.png'))
    elif params.debug == 'plot':
        plot_image(dist_transform, cmap='gray')
        plot_image(joined)

    outputs.add_observation(variable='estimated_object_count', trait='estimated object count',
                            method='plantcv.plantcv.watershed', scale='none', datatype=int,
                            value=estimated_object_count, label='none')

    # Store images
    outputs.images.append([dist_transform, joined])

    return joined
def gaussian_blur(img, ksize, sigma_x=0, sigma_y=None):
    """Applies a Gaussian blur filter.

    Inputs:
    # img     = RGB or grayscale image data
    # ksize   = Tuple of kernel dimensions, e.g. (5, 5)
    # sigmax  = standard deviation in X direction; if 0, calculated from kernel size
    # sigmay  = standard deviation in Y direction; if sigmaY is None, sigmaY is taken to equal sigmaX

    Returns:
    img_gblur = blurred image

    :param img: numpy.ndarray
    :param ksize: tuple
    :param sigma_x: int
    :param sigma_y: str or int
    :return img_gblur: numpy.ndarray
    """

    img_gblur = cv2.GaussianBlur(img, ksize, sigma_x, sigma_y)

    params.device += 1
    if params.debug == 'print':
        print_image(
            img_gblur,
            os.path.join(params.debug_outdir,
                         str(params.device) + '_gaussian_blur.png'))
    elif params.debug == 'plot':
        if len(np.shape(img_gblur)) == 3:
            plot_image(img_gblur)
        else:
            plot_image(img_gblur, cmap='gray')

    return img_gblur
コード例 #19
0
ファイル: image_add.py プロジェクト: stevenhwu/plantcv
def image_add(img1, img2, device, debug=None):
    """This is a function used to add images. The numpy addition function '+' is used. This is a modulo operation
       rather than the cv2.add fxn which is a saturation operation. ddepth = -1 specifies that the dimensions of output
       image will be the same as the input image.

    Inputs:
    img1      = input image
    img2      = second input image
    device    = device number. Used to count steps in the pipeline
    debug     = None, print, or plot. Print = save to file, Plot = print to screen.

    Returns:
    device    = device number
    added_img = summed images

    :param img1: numpy array
    :param img2: numpy array
    :param device: int
    :param debug: str
    :return device: int
    :return added_img: numpy array
    """

    added_img = img1 + img2
    device += 1
    if debug == 'print':
        print_image(added_img, str(device) + '_added' + '.png')
    elif debug == 'plot':
        plot_image(added_img, cmap='gray')
    return device, added_img
コード例 #20
0
def scharr_filter(img, dx, dy, scale):
    """This is a filtering method used to identify and highlight gradient edges/features using the 1st derivative.
       Typically used to identify gradients along the x-axis (dx = 1, dy = 0) and y-axis (dx = 0, dy = 1) independently.
       Performance is quite similar to Sobel filter. Used to detect edges / changes in pixel intensity. ddepth = -1
       specifies that the dimensions of output image will be the same as the input image.

    Inputs:
    gray_img = Grayscale image data
    dx       = derivative of x to analyze (1-3)
    dy       = derivative of x to analyze (1-3)
    scale    = scaling factor applied (multiplied) to computed Scharr values (scale = 1 is unscaled)

    Returns:
    sr_img   = Scharr filtered image

    :param img: numpy.ndarray
    :param dx: int
    :param dy: int
    :param scale: int
    :return sr_img: numpy.ndarray
    """

    sr_img = cv2.Scharr(src=img, ddepth=-1, dx=dx, dy=dy, scale=scale)
    params.device += 1
    if params.debug == 'print':
        name = os.path.join(params.debug_outdir, str(params.device))
        name += '_sr_img_dx' + str(dx) + '_dy' + str(dy) + '_scale' + str(scale) + '.png'
        print_image(sr_img, name)
    elif params.debug == 'plot':
        plot_image(sr_img, cmap='gray')
    return sr_img
コード例 #21
0
def sobel_filter(gray_img, dx, dy, ksize):
    """This is a filtering method used to identify and highlight gradient edges/features using the 1st derivative.
       Typically used to identify gradients along the x-axis (dx = 1, dy = 0) and y-axis (dx = 0, dy = 1) independently.
       Performance is quite similar to Scharr filter. Used to detect edges / changes in pixel intensity. ddepth = -1
       specifies that the dimensions of output image will be the same as the input image.

    Inputs:
    gray_img = Grayscale image data
    dx       = derivative of x to analyze
    dy       = derivative of x to analyze
    ksize        = specifies the size of the kernel (must be an odd integer: 1,3,5, ... , 31)

    Returns:
    sb_img   = Sobel filtered image

    :param gray_img: numpy.ndarray
    :param dx: int
    :param dy: int
    :param ksize: int
    :param scale: int
    :return sb_img: numpy.ndarray
    """
    params.device += 1
    sb_img = cv2.Sobel(src=gray_img, ddepth=-1, dx=dx, dy=dy, ksize=ksize)

    if params.debug == 'print':
        name = os.path.join(params.debug_outdir,
                            str(params.device) + '_sb_img_dx' + str(dx) + '_dy' + str(dy) + '_kernel' + str(ksize) + '.png')
        print_image(sb_img, name)
    elif params.debug == 'plot':
        plot_image(sb_img, cmap='gray')
    return sb_img
コード例 #22
0
def image_subtract(gray_img1, gray_img2):
    """This is a function used to subtract values of one gray-scale image array from another gray-scale image array. The
    resulting gray-scale image array has a minimum element value of zero. That is all negative values resulting from the
    subtraction are forced to zero.

    Inputs:
    gray_img1   = Grayscale image data from which gray_img2 will be subtracted
    gray_img2   = Grayscale image data which will be subtracted from gray_img1

    Returns:
    new_img = subtracted image

    :param gray_img1: numpy.ndarray
    :param gray_img2: numpy.ndarray
    :return new_img: numpy.ndarray
    """

    params.device += 1  # increment device

    # check inputs for gray-scale
    if len(np.shape(gray_img1)) != 2 or len(np.shape(gray_img2)) != 2:
        fatal_error("Input image is not gray-scale")

    new_img = gray_img1.astype(np.float64) - gray_img2.astype(np.float64)  # subtract values
    new_img[np.where(new_img < 0)] = 0  # force negative array values to zero
    new_img = new_img.astype(np.uint8)  # typecast image to 8-bit image
    # print-plot handling
    if params.debug == 'print':
        print_image(new_img, os.path.join(params.debug_outdir, str(params.device) + "_subtraction.png"))
    elif params.debug == 'plot':
        plot_image(new_img, cmap='gray')
    return new_img  # return
コード例 #23
0
ファイル: skeletonize.py プロジェクト: danforthcenter/plantcv
def skeletonize(mask):
    """Reduces binary objects to 1 pixel wide representations (skeleton)

    Inputs:
    mask       = Binary image data

    Returns:
    skeleton   = skeleton image

    :param mask: numpy.ndarray
    :return skeleton: numpy.ndarray
    """
    # Store debug
    debug = params.debug
    params.debug = None

    # Convert mask to boolean image, rather than 0 and 255 for skimage to use it
    skeleton = skmorph.skeletonize(mask.astype(bool))

    skeleton = skeleton.astype(np.uint8) * 255

    # Reset debug mode
    params.debug = debug
    # Auto-increment device
    params.device += 1

    if params.debug == 'print':
        print_image(skeleton, os.path.join(params.debug_outdir, str(params.device) + '_skeleton.png'))
    elif params.debug == 'plot':
        plot_image(skeleton, cmap='gray')

    return skeleton
def hist_equalization(gray_img):
    """Histogram equalization is a method to normalize the distribution of intensity values. If the image has low
       contrast it will make it easier to threshold.

    Inputs:
    gray_img    = Grayscale image data

    Returns:
    img_eh = normalized image

    :param gray_img: numpy.ndarray
    :return img_eh: numpy.ndarray
    """

    if len(np.shape(gray_img)) == 3:
        fatal_error("Input image must be gray")

    img_eh = cv2.equalizeHist(gray_img)
    params.device += 1
    if params.debug == 'print':
        print_image(img_eh, os.path.join(params.debug_outdir, str(params.device) + '_hist_equal_img.png'))
    elif params.debug == 'plot':
        plot_image(img_eh, cmap='gray')

    return img_eh
コード例 #25
0
def median_blur(gray_img, ksize):
    """Applies a median blur filter (applies median value to central pixel within a kernel size).

    Inputs:
    gray_img  = Grayscale image data
    ksize = kernel size => integer or tuple, ksize x ksize box if integer, (n, m) size box if tuple

    Returns:
    img_mblur = blurred image


    :param gray_img: numpy.ndarray
    :param ksize: int or tuple
    :return img_mblur: numpy.ndarray
    """

    # Make sure ksize is valid
    if type(ksize) is not int and type(ksize) is not tuple:
        fatal_error("Invalid ksize, must be integer or tuple")

    img_mblur = median_filter(gray_img, size=ksize)
    params.device += 1
    if params.debug == 'print':
        print_image(
            img_mblur,
            os.path.join(
                params.debug_outdir,
                str(params.device) + '_median_blur' + str(ksize) + '.png'))
    elif params.debug == 'plot':
        plot_image(img_mblur, cmap='gray')
    return img_mblur
コード例 #26
0
ファイル: logical_and.py プロジェクト: stevenhwu/plantcv
def logical_and(img1, img2, device, debug=None):
    """Join two images using the bitwise AND operator.

    Inputs:
    img1   = image object1, grayscale
    img2   = image object2, grayscale
    device = device number. Used to count steps in the pipeline
    debug  = None, print, or plot. Print = save to file, Plot = print to screen.

    Returns:
    device = device number
    merged = joined image

    :param img1: numpy array
    :param img2: numpy array
    :param device: int
    :param debug: str
    :return device: int
    :return merged: numpy array
    """

    device += 1
    merged = cv2.bitwise_and(img1, img2)
    if debug == 'print':
        print_image(merged, (str(device) + '_and_joined.png'))
    elif debug == 'plot':
        plot_image(merged, cmap='gray')
    return device, merged
コード例 #27
0
def invert(img, device, debug=None):
    """Inverts grayscale images.

    Inputs:
    img     = image object, grayscale
    device  = device number. Used to count steps in the pipeline
    debug   = None, print, or plot. Print = save to file, Plot = print to screen.

    Returns:
    device  = device number
    img_inv = inverted image

    :param img: numpy array
    :param device: int
    :param debug: str
    :return device: int
    :return img_inv: numpy array
    """

    device += 1
    img_inv = cv2.bitwise_not(img)
    if debug == 'print':
        print_image(img_inv, (str(device) + '_invert.png'))
    elif debug == 'plot':
        plot_image(img_inv, cmap='gray')
    return device, img_inv
コード例 #28
0
def sobel_filter(gray_img, dx, dy, ksize):
    """This is a filtering method used to identify and highlight gradient edges/features using the 1st derivative.
       Typically used to identify gradients along the x-axis (dx = 1, dy = 0) and y-axis (dx = 0, dy = 1) independently.
       Performance is quite similar to Scharr filter. Used to detect edges / changes in pixel intensity. ddepth = -1
       specifies that the dimensions of output image will be the same as the input image.

    Inputs:
    gray_img = Grayscale image data
    dx       = derivative of x to analyze
    dy       = derivative of x to analyze
    ksize        = specifies the size of the kernel (must be an odd integer: 1,3,5, ... , 31)

    Returns:
    sb_img   = Sobel filtered image

    :param gray_img: numpy.ndarray
    :param dx: int
    :param dy: int
    :param ksize: int
    :return sb_img: numpy.ndarray
    """
    params.device += 1
    sb_img = cv2.Sobel(src=gray_img, ddepth=-1, dx=dx, dy=dy, ksize=ksize)

    if params.debug == 'print':
        fname = str(params.device) + '_sb_img_dx' + str(dx) + '_dy' + str(dy) + '_kernel' + str(ksize) + '.png'
        name = os.path.join(params.debug_outdir, fname)
        print_image(sb_img, name)
    elif params.debug == 'plot':
        plot_image(sb_img, cmap='gray')
    return sb_img
コード例 #29
0
def median_blur(img, ksize, device, debug=None):
    """Applies a median blur filter (applies median value to central pixel within a kernel size ksize x ksize).

    Inputs:
    # img     = img object
    # ksize   = kernel size => ksize x ksize box
    # device  = device number. Used to count steps in the pipeline
    # debug   = None, print, or plot. Print = save to file, Plot = print to screen.

    Returns:
    device    = device number
    img_mblur = blurred image

    :param img: numpy array
    :param ksize: int
    :param device: int
    :param debug: str
    :return device: int
    :return img_mblur: numpy array
    """

    img_mblur = cv2.medianBlur(img, ksize)
    device += 1
    if debug == 'print':
        print_image(img_mblur,
                    (str(device) + '_median_blur' + str(ksize) + '.png'))
    elif debug == 'plot':
        plot_image(img_mblur, cmap='gray')
    return device, img_mblur
def image_add(gray_img1, gray_img2):
    """This is a function used to add images. The numpy addition function '+' is used. This is a modulo operation
       rather than the cv2.add fxn which is a saturation operation. ddepth = -1 specifies that the dimensions of output
       image will be the same as the input image.

    Inputs:
    gray_img1      = Grayscale image data to be added to image 2
    gray_img2      = Grayscale image data to be added to image 1

    Returns:
    added_img      = summed images

    :param gray_img1: numpy.ndarray
    :param gray_img2: numpy.ndarray
    :return added_img: numpy.ndarray
    """

    added_img = gray_img1 + gray_img2
    params.device += 1
    if params.debug == 'print':
        print_image(
            added_img,
            os.path.join(params.debug_outdir,
                         str(params.device) + '_added' + '.png'))
    elif params.debug == 'plot':
        plot_image(added_img, cmap='gray')
    return added_img
コード例 #31
0
ファイル: roi2mask.py プロジェクト: sarahmathew/plantcv
def roi2mask(img, contour):
    """Create a binary mask from an ROI contour
    Inputs:
    img                  = RGB or grayscale image data
    contour              = An ROI set of points (contour)

    Returns:
    mask   = Binary mask

    :param roi_contour: numpy.ndarray
    :param contour: list
    :return mask: numpy.ndarray
    """
    params.device += 1

    # create a blank image of same size
    shape_info = np.shape(img)
    bnk = np.zeros((shape_info[0], shape_info[1]), dtype=np.uint8)

    mask = cv2.drawContours(bnk, contour, 0, 255, -1)

    if params.debug == 'print':
        print_image(mask, os.path.join(params.debug_outdir, str(params.device) + '_roi_mask.png'))
    elif params.debug == 'plot':
        plot_image(mask, cmap="gray")

    return mask
コード例 #32
0
ファイル: median_blur.py プロジェクト: danforthcenter/plantcv
def median_blur(gray_img, ksize):
    """Applies a median blur filter (applies median value to central pixel within a kernel size).

    Inputs:
    gray_img  = Grayscale image data
    ksize = kernel size => integer or tuple, ksize x ksize box if integer, (n, m) size box if tuple

    Returns:
    img_mblur = blurred image


    :param gray_img: numpy.ndarray
    :param ksize: int or tuple
    :return img_mblur: numpy.ndarray
    """

    # Make sure ksize is valid
    if type(ksize) is not int and type(ksize) is not tuple:
        fatal_error("Invalid ksize, must be integer or tuple")

    img_mblur = median_filter(gray_img, size=ksize)
    params.device += 1
    if params.debug == 'print':
        print_image(img_mblur, os.path.join(params.debug_outdir,
                                            str(params.device) + '_median_blur' + str(ksize) + '.png'))
    elif params.debug == 'plot':
        plot_image(img_mblur, cmap='gray')
    return img_mblur
コード例 #33
0
def resize(img, resize_x, resize_y):
    """Resize image.

    Inputs:
    img      = RGB or grayscale image data to resize
    resize_x = scaling factor
    resize_y = scaling factor

    Returns:
    reimg    = resized image

    :param img: numpy.ndarray
    :param resize_x: int
    :param resize_y: int
    :return reimg: numpy.ndarray
    """

    params.device += 1

    if resize_x <= 0 and resize_y <= 0:
        fatal_error("Resize values both cannot be 0 or negative values!")

    reimg = cv2.resize(img, (0, 0), fx=resize_x, fy=resize_y)

    if params.debug == 'print':
        print_image(
            reimg,
            os.path.join(params.debug_outdir,
                         str(params.device) + "_resize1.png"))
    elif params.debug == 'plot':
        plot_image(reimg)

    return reimg
コード例 #34
0
ファイル: image_add.py プロジェクト: danforthcenter/plantcv
def image_add(gray_img1, gray_img2):

    """This is a function used to add images. The numpy addition function '+' is used. This is a modulo operation
       rather than the cv2.add fxn which is a saturation operation. ddepth = -1 specifies that the dimensions of output
       image will be the same as the input image.

    Inputs:
    gray_img1      = Grayscale image data to be added to image 2
    gray_img2      = Grayscale image data to be added to image 1

    Returns:
    added_img      = summed images

    :param gray_img1: numpy.ndarray
    :param gray_img2: numpy.ndarray
    :return added_img: numpy.ndarray
    """

    added_img = gray_img1 + gray_img2
    params.device += 1
    if params.debug == 'print':
        print_image(added_img, os.path.join(params.debug_outdir, str(params.device) + '_added' + '.png'))
    elif params.debug == 'plot':
        plot_image(added_img, cmap='gray')
    return added_img
コード例 #35
0
ファイル: rescale.py プロジェクト: sarahmathew/plantcv
def rescale(gray_img, min_value=0, max_value=255):
    """Rescale image.

        Inputs:
        gray_img  = Grayscale image data
        min_value = (optional) new minimum value for range of interest. default = 0
        max_value = (optional) new maximum value for range of interest. default = 255

        Returns:
        rescaled_img = rescaled image

        :param gray_img: numpy.ndarray
        :param min_value: int
        :param max_value: int
        :return c: numpy.ndarray
        """
    if len(np.shape(gray_img)) != 2:
        fatal_error("Image is not grayscale")

    rescaled_img = np.interp(gray_img, (gray_img.min(), gray_img.max()),
                             (min_value, max_value))
    rescaled_img = (rescaled_img).astype('uint8')

    # Autoincrement the device counter
    params.device += 1

    if params.debug == 'print':
        print_image(
            rescaled_img,
            os.path.join(params.debug_outdir,
                         str(params.device) + "_rescaled.png"))
    elif params.debug == 'plot':
        plot_image(rescaled_img, cmap='gray')

    return rescaled_img
コード例 #36
0
def laplace_filter(gray_img, ksize, scale):
    """This is a filtering method used to identify and highlight fine edges based on the 2nd derivative. A very
       sensetive method to highlight edges but will also amplify background noise. ddepth = -1 specifies that the
       dimensions of output image will be the same as the input image.

    Inputs:
    gray_img    = Grayscale image data
    ksize       = apertures size used to calculate 2nd derivative filter, specifies the size of the kernel
                  (must be an odd integer: 1,3,5...)
    scale       = scaling factor applied (multiplied) to computed Laplacian values (scale = 1 is unscaled)

    Returns:
    lp_filtered = laplacian filtered image

    :param gray_img: numpy.ndarray
    :param kernel: int
    :param scale: int
    :return lp_filtered: numpy.ndarray
    """

    lp_filtered = cv2.Laplacian(src=gray_img, ddepth=-1, ksize=ksize, scale=scale)
    params.device += 1
    if params.debug == 'print':
        print_image(lp_filtered,
                    os.path.join(params.debug_outdir,
                                 str(params.device) + '_lp_out_k' + str(ksize) + '_scale' + str(scale) + '.png'))
    elif params.debug == 'plot':
        plot_image(lp_filtered, cmap='gray')
    return lp_filtered
コード例 #37
0
ファイル: image_subtract.py プロジェクト: raonarendra/plantcv
def image_subtract(img1, img2, device, debug=None):
    """This is a function used to subtract one image from another image (img1 - img2). The numpy subtraction function
       '-' is used. This is a modulo operation rather than the cv2.subtract fxn which is a saturation operation.
       ddepth = -1 specifies that the dimensions of output image will be the same as the input image.

    Inputs:
    img1      = input image
    img2      = input image used to subtract from img1
    device    = device number. Used to count steps in the pipeline
    debug     = None, print, or plot. Print = save to file, Plot = print to screen.

    Returns:
    device    = device number
    subed_img = subtracted image

    :param img1: numpy array
    :param img2: numpy array
    :param device: int
    :param debug: str
    :return device: int
    :return subed_img: numpy array
    """

    subed_img = img1 - img2
    device += 1
    if debug == 'print':
        print_image(subed_img, str(device) + '_subtracted' + '.png')
    elif debug == 'plot':
        plot_image(subed_img, cmap='gray')
    return device, subed_img
コード例 #38
0
ファイル: watershed.py プロジェクト: danforthcenter/plantcv
def watershed_segmentation(rgb_img, mask, distance=10):
    """Uses the watershed algorithm to detect boundary of objects. Needs a marker file which specifies area which is
       object (white), background (grey), unknown area (black).

    Inputs:
    rgb_img             = image to perform watershed on needs to be 3D (i.e. np.shape = x,y,z not np.shape = x,y)
    mask                = binary image, single channel, object in white and background black
    distance            = min_distance of local maximum

    Returns:
    analysis_images     = list of output images

    :param rgb_img: numpy.ndarray
    :param mask: numpy.ndarray
    :param distance: int
    :return analysis_images: list
    """
    params.device += 1
    # # Will be depricating opencv version 2
    # if cv2.__version__[0] == '2':
    #     dist_transform = cv2.distanceTransform(mask, cv2.cv.CV_DIST_L2, maskSize=0)
    # else:
    dist_transform = cv2.distanceTransformWithLabels(mask, cv2.DIST_L2, maskSize=0)[0]

    localMax = peak_local_max(dist_transform, indices=False, min_distance=distance, labels=mask)

    markers = ndi.label(localMax, structure=np.ones((3, 3)))[0]
    dist_transform1 = -dist_transform
    labels = watershed(dist_transform1, markers, mask=mask)

    img1 = np.copy(rgb_img)

    for x in np.unique(labels):
        rand_color = color_palette(len(np.unique(labels)))
        img1[labels == x] = rand_color[x]

    img2 = apply_mask(img1, mask, 'black')

    joined = np.concatenate((img2, rgb_img), axis=1)

    estimated_object_count = len(np.unique(markers)) - 1

    analysis_image = []
    analysis_image.append(joined)

    if params.debug == 'print':
        print_image(dist_transform, os.path.join(params.debug_outdir, str(params.device) + '_watershed_dist_img.png'))
        print_image(joined, os.path.join(params.debug_outdir, str(params.device) + '_watershed_img.png'))
    elif params.debug == 'plot':
        plot_image(dist_transform, cmap='gray')
        plot_image(joined)

    outputs.add_observation(variable='estimated_object_count', trait='estimated object count',
                            method='plantcv.plantcv.watershed', scale='none', datatype=int,
                            value=estimated_object_count, label='none')

    # Store images
    outputs.images.append(analysis_image)

    return analysis_image
コード例 #39
0
def stdev_filter(img, ksize, borders='nearest'):
    """Creates a binary image from a grayscale image using skimage texture calculation for thresholding.
    This function is quite slow.

    Inputs:
    gray_img       = Grayscale image data
    ksize          = Kernel size for texture measure calculation
    borders        = How the array borders are handled, either 'reflect',
                     'constant', 'nearest', 'mirror', or 'wrap'

    Returns:
    output         = Standard deviation values image

    :param gray_img: numpy.ndarray
    :param ksize: int
    :param borders: str
    :return output: numpy.ndarray
    """

    # Make an array the same size as the original image
    output = np.zeros(img.shape, dtype=img.dtype)

    # Apply the texture function over the whole image
    generic_filter(img, np.std, size=ksize, output=output, mode=borders)

    if params.debug == "print":
        # If debug is print, save the image to a file
        print_image(output, os.path.join(params.debug_outdir, str(params.device) + "_variance.png"))
    elif params.debug == "plot":
        # If debug is plot, print to the plotting device
        plot_image(output)

    return output
コード例 #40
0
def object_composition(img, contours, hierarchy):
    """Groups objects into a single object, usually done after object filtering.

    Inputs:
    img       = RGB or grayscale image data for plotting
    contours  = Contour list
    hierarchy = Contour hierarchy NumPy array

    Returns:
    group    = grouped contours list
    mask     = image mask

    :param img: numpy.ndarray
    :param contours: list
    :param hierarchy: numpy.ndarray
    :return group: list
    :return mask: numpy.ndarray
    """

    params.device += 1
    ori_img = np.copy(img)
    # If the reference image is grayscale convert it to color
    if len(np.shape(ori_img)) == 2:
        ori_img = cv2.cvtColor(ori_img, cv2.COLOR_GRAY2BGR)

    stack = np.zeros((len(contours), 1))
    r, g, b = cv2.split(ori_img)
    mask = np.zeros(g.shape, dtype=np.uint8)

    for c, cnt in enumerate(contours):
        # if hierarchy[0][c][3] == -1:
        if hierarchy[0][c][2] == -1 and hierarchy[0][c][3] > -1:
            stack[c] = 0
        else:
            stack[c] = 1

    ids = np.where(stack == 1)[0]
    if len(ids) > 0:
        group = np.vstack(contours[i] for i in ids)
        cv2.drawContours(mask, contours, -1, 255, -1, hierarchy=hierarchy)

        if params.debug is not None:
            cv2.drawContours(ori_img, group, -1, (255, 0, 0), 4)
            for cnt in contours:
                cv2.drawContours(ori_img, cnt, -1, (255, 0, 0), 4)
            if params.debug == 'print':
                print_image(
                    ori_img,
                    os.path.join(params.debug_outdir,
                                 str(params.device) + '_objcomp.png'))
                print_image(
                    ori_img,
                    os.path.join(params.debug_outdir,
                                 str(params.device) + '_objcomp_mask.png'))
            elif params.debug == 'plot':
                plot_image(ori_img)
        return group, mask
    else:
        print("Warning: Invalid contour.")
        return None, None
コード例 #41
0
ファイル: erode.py プロジェクト: danforthcenter/plantcv
def erode(gray_img, ksize, i):
    """Perform morphological 'erosion' filtering. Keeps pixel in center of the kernel if conditions set in kernel are
       true, otherwise removes pixel.

    Inputs:
    gray_img = Grayscale (usually binary) image data
    ksize   = Kernel size (int). A ksize x ksize kernel will be built. Must be greater than 1 to have an effect.
    i        = interations, i.e. number of consecutive filtering passes

    Returns:
    er_img = eroded image

    :param gray_img: numpy.ndarray
    :param ksize: int
    :param i: int
    :return er_img: numpy.ndarray
    """

    if ksize <= 1:
        raise ValueError('ksize needs to be greater than 1 for the function to have an effect')

    kernel1 = int(ksize)
    kernel2 = np.ones((kernel1, kernel1), np.uint8)
    er_img = cv2.erode(src=gray_img, kernel=kernel2, iterations=i)
    params.device += 1
    if params.debug == 'print':
        print_image(er_img, os.path.join(params.debug_outdir,
                                         str(params.device) + '_er_image' + str(ksize) + '_itr_' + str(i) + '.png'))
    elif params.debug == 'plot':
        plot_image(er_img, cmap='gray')
    return er_img
コード例 #42
0
def dilate(gray_img, ksize, i):
    """Performs morphological 'dilation' filtering. Adds pixel to center of kernel if conditions set in kernel are true.

    Inputs:
    gray_img = Grayscale (usually binary) image data
    ksize   = Kernel size (int). A k x k kernel will be built. Must be greater than 1 to have an effect.
    i        = iterations, i.e. number of consecutive filtering passes

    Returns:
    dil_img = dilated image

    :param gray_img: numpy.ndarray
    :param ksize: int
    :param i: int
    :return dil_img: numpy.ndarray
    """

    if ksize <= 1:
        raise ValueError('ksize needs to be greater than 1 for the function to have an effect')

    kernel1 = int(ksize)
    kernel2 = np.ones((kernel1, kernel1), np.uint8)
    dil_img = cv2.dilate(src=gray_img, kernel=kernel2, iterations=i)
    params.device += 1
    if params.debug == 'print':
        print_image(dil_img, os.path.join(params.debug_outdir, str(params.device) + '_dil_image' + str(ksize) + '_itr' + str(i) + '.png'))
    elif params.debug == 'plot':
        plot_image(dil_img, cmap='gray')
    return dil_img
コード例 #43
0
ファイル: laplace_filter.py プロジェクト: sarahmathew/plantcv
def laplace_filter(gray_img, ksize, scale):
    """This is a filtering method used to identify and highlight fine edges based on the 2nd derivative. A very
       sensetive method to highlight edges but will also amplify background noise. ddepth = -1 specifies that the
       dimensions of output image will be the same as the input image.

    Inputs:
    gray_img    = Grayscale image data
    ksize       = apertures size used to calculate 2nd derivative filter, specifies the size of the kernel
                  (must be an odd integer: 1,3,5...)
    scale       = scaling factor applied (multiplied) to computed Laplacian values (scale = 1 is unscaled)

    Returns:
    lp_filtered = laplacian filtered image

    :param gray_img: numpy.ndarray
    :param kernel: int
    :param scale: int
    :return lp_filtered: numpy.ndarray
    """

    lp_filtered = cv2.Laplacian(src=gray_img, ddepth=-1, ksize=ksize, scale=scale)
    params.device += 1
    if params.debug == 'print':
        print_image(lp_filtered, os.path.join(params.debug_outdir,
                                 str(params.device) + '_lp_out_k' + str(ksize) + '_scale' + str(scale) + '.png'))
    elif params.debug == 'plot':
        plot_image(lp_filtered, cmap='gray')
    return lp_filtered
コード例 #44
0
def logical_xor(bin_img1, bin_img2):
    """Join two images using the bitwise XOR operator.

    Inputs:
    bin_img1   = Binary image data to be compared to bin_img2
    bin_img2   = Binary image data to be compared to bin_img1

    Returns:
    merged     = joined binary image

    :param bin_img1: numpy.ndarray
    :param bin_img2: numpy.ndarray
    :return merged: numpy.ndarray
    """

    params.device += 1
    merged = cv2.bitwise_xor(bin_img1, bin_img2)
    if params.debug == 'print':
        print_image(
            merged,
            os.path.join(params.debug_outdir,
                         str(params.device) + '_xor_joined.png'))
    elif params.debug == 'plot':
        plot_image(merged, cmap='gray')
    return merged
コード例 #45
0
ファイル: rotate.py プロジェクト: yangkunqi/plantcv
def rotate(img, rotation_deg, crop):
    """Rotate image, sometimes it is necessary to rotate image, especially when clustering for
       multiple plants is needed.

    Inputs:
    img          = RGB or grayscale image data
    rotation_deg = rotation angle in degrees, can be a negative number,
                   positive values move counter clockwise.
    crop         = either true or false, if true, dimensions of rotated image will be same as original image.

    Returns:
    rotated_img  = rotated image

    :param img: numpy.ndarray
    :param rotation_deg: double
    :param crop: bool
    :return rotated_img: numpy.ndarray
    """

    if len(np.shape(img)) == 3:
        iy, ix, iz = np.shape(img)
    else:
        iy, ix = np.shape(img)

    m = cv2.getRotationMatrix2D((ix / 2, iy / 2), rotation_deg, 1)

    cos = np.abs(m[0, 0])
    sin = np.abs(m[0, 1])

    if not crop:
        # compute the new bounding dimensions of the image
        nw = int((iy * sin) + (ix * cos))
        nh = int((iy * cos) + (ix * sin))

        # adjust the rotation matrix to take into account translation
        m[0, 2] += (nw / 2) - (ix / 2)
        m[1, 2] += (nh / 2) - (iy / 2)

        rotated_img = cv2.warpAffine(img, m, (nw, nh))
    else:
        rotated_img = cv2.warpAffine(img, m, (ix, iy))

    params.device += 1

    if params.debug == 'print':
        print_image(
            rotated_img,
            os.path.join(
                params.debug_outdir,
                str(params.device) + '_' + str(rotation_deg) +
                '_rotated_img.png'))

    elif params.debug == 'plot':
        if len(np.shape(img)) == 3:
            plot_image(rotated_img)
        else:
            plot_image(rotated_img, cmap='gray')

    return rotated_img
コード例 #46
0
ファイル: segment_id.py プロジェクト: nateellis/plantcv
def segment_id(skel_img, objects, hierarchies, mask=None):
    """ Plot segment ID's

            Inputs:
            skel_img      = Skeletonized image
            objects       = List of contours
            hierarchy     = Contour hierarchy NumPy array
            mask          = (Optional) binary mask for debugging. If provided, debug image will be overlaid on the mask.

            Returns:
            segmented_img = Segmented image
            labeled_img   = Labeled image

            :param skel_img: numpy.ndarray
            :param objects: list
            :param hierarchies: numpy.ndarray
            :param mask: numpy.ndarray
            :return segmented_img: numpy.ndarray
            :return labeled_img: numpy.ndarray
            """
    label_coord_x = []
    label_coord_y = []

    if mask is None:
        segmented_img = skel_img.copy()
    else:
        segmented_img = mask.copy()

    segmented_img = cv2.cvtColor(segmented_img, cv2.COLOR_GRAY2RGB)

    # Color each segment a different color
    rand_color = color_palette(len(objects))

    # Plot all segment contours
    for i, cnt in enumerate(objects):
        cv2.drawContours(segmented_img, objects, i, rand_color[i], params.line_thickness, lineType=8,
                         hierarchy=hierarchies)
        # Store coordinates for labels
        label_coord_x.append(objects[i][0][0][0])
        label_coord_y.append(objects[i][0][0][1])

    labeled_img = segmented_img.copy()

    for i, cnt in enumerate(objects):
        # Label slope lines
        w = label_coord_x[i]
        h = label_coord_y[i]
        text = "ID:{}".format(i)
        cv2.putText(img=labeled_img, text=text, org=(w, h), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                    fontScale=.65, color=rand_color[i], thickness=2)
    # Auto-increment device
    params.device += 1

    if params.debug == 'print':
        print_image(labeled_img, os.path.join(params.debug_outdir, str(params.device) + '_segmented_ids.png'))
    elif params.debug == 'plot':
        plot_image(labeled_img)

    return segmented_img, labeled_img
コード例 #47
0
ファイル: prune.py プロジェクト: danforthcenter/plantcv
def prune(skel_img, size):
    """
    The pruning algorithm was inspired by Jean-Patrick Pommier: https://gist.github.com/jeanpat/5712699
    Iteratively remove endpoints (tips) from a skeletonized image. "Prunes" barbs off a skeleton.

    Inputs:
    skel_img    = Skeletonized image
    size        = Size to get pruned off each branch

    Returns:
    pruned_img  = Pruned image

    :param skel_img: numpy.ndarray
    :param size: int
    :return pruned_img: numpy.ndarray

    """
    # Store debug
    debug = params.debug
    params.debug = None

    pruned_img = skel_img.copy()

    # Check to see if the skeleton has multiple objects
    objects, _ = find_objects(pruned_img, pruned_img)
    if not len(objects) == 1:
        print("Warning: Multiple objects detected! Pruning will further separate the difference pieces.")

    # Iteratively remove endpoints (tips) from a skeleton
    for i in range(0, size):
        endpoints = find_tips(pruned_img)
        pruned_img = image_subtract(pruned_img, endpoints)

    # Make debugging image
    pruned_plot = np.zeros(skel_img.shape[:2], np.uint8)
    pruned_plot = cv2.cvtColor(pruned_plot, cv2.COLOR_GRAY2RGB)
    skel_obj, skel_hierarchy = find_objects(skel_img, skel_img)
    pruned_obj, pruned_hierarchy = find_objects(pruned_img, pruned_img)
    cv2.drawContours(pruned_plot, skel_obj, -1, (0, 0, 255), params.line_thickness,
                     lineType=8, hierarchy=skel_hierarchy)
    cv2.drawContours(pruned_plot, pruned_obj, -1, (255, 255, 255), params.line_thickness,
                     lineType=8, hierarchy=pruned_hierarchy)

    # Reset debug mode
    params.debug = debug

    params.device += 1

    if params.debug == 'print':
        print_image(pruned_img, os.path.join(params.debug_outdir, str(params.device) + '_pruned.png'))
        print_image(pruned_plot, os.path.join(params.debug_outdir, str(params.device) + '_pruned_debug.png'))

    elif params.debug == 'plot':
        plot_image(pruned_img, cmap='gray')
        plot_image(pruned_plot)

    return pruned_img
コード例 #48
0
def segment_skeleton(skel_img, mask=None):
    """ Segment a skeleton image into pieces

        Inputs:
        skel_img      = Skeletonized image
        mask          = (Optional) binary mask for debugging. If provided, debug image will be overlaid on the mask.

        Returns:
        segmented_img = Segmented debugging image
        objects       = list of contours
        hierarchy     = contour hierarchy list

        :param skel_img: numpy.ndarray
        :param mask: numpy.ndarray
        :return segmented_img: numpy.ndarray
        :return segment_objects: list
        "return segment_hierarchies: numpy.ndarray
        """

    # Store debug
    debug = params.debug
    params.debug = None

    # Find branch points
    bp = find_branch_pts(skel_img)
    bp = dilate(bp, 3, 1)

    # Subtract from the skeleton so that leaves are no longer connected
    segments = image_subtract(skel_img, bp)

    # Gather contours of leaves
    segment_objects, _ = find_objects(segments, segments)

    # Color each segment a different color
    rand_color = color_palette(len(segment_objects))

    if mask is None:
        segmented_img = skel_img.copy()
    else:
        segmented_img = mask.copy()

    segmented_img = cv2.cvtColor(segmented_img, cv2.COLOR_GRAY2RGB)
    for i, cnt in enumerate(segment_objects):
        cv2.drawContours(segmented_img, segment_objects, i, rand_color[i], params.line_thickness, lineType=8)

    # Reset debug mode
    params.debug = debug
    # Auto-increment device
    params.device += 1

    if params.debug == 'print':
        print_image(segmented_img, os.path.join(params.debug_outdir, str(params.device) + '_segmented.png'))
    elif params.debug == 'plot':
        plot_image(segmented_img)

    return segmented_img, segment_objects
コード例 #49
0
ファイル: segment_id.py プロジェクト: danforthcenter/plantcv
def segment_id(skel_img, objects, mask=None):
    """ Plot segment ID's

            Inputs:
            skel_img      = Skeletonized image
            objects       = List of contours
            mask          = (Optional) binary mask for debugging. If provided, debug image will be overlaid on the mask.

            Returns:
            segmented_img = Segmented image
            labeled_img   = Labeled image

            :param skel_img: numpy.ndarray
            :param objects: list
            :param mask: numpy.ndarray
            :return segmented_img: numpy.ndarray
            :return labeled_img: numpy.ndarray
            """
    label_coord_x = []
    label_coord_y = []

    if mask is None:
        segmented_img = skel_img.copy()
    else:
        segmented_img = mask.copy()

    segmented_img = cv2.cvtColor(segmented_img, cv2.COLOR_GRAY2RGB)

    # Color each segment a different color
    rand_color = color_palette(len(objects))

    # Plot all segment contours
    for i, cnt in enumerate(objects):
        cv2.drawContours(segmented_img, objects, i, rand_color[i], params.line_thickness, lineType=8)
        # Store coordinates for labels
        label_coord_x.append(objects[i][0][0][0])
        label_coord_y.append(objects[i][0][0][1])

    labeled_img = segmented_img.copy()

    for i, cnt in enumerate(objects):
        # Label slope lines
        w = label_coord_x[i]
        h = label_coord_y[i]
        text = "ID:{}".format(i)
        cv2.putText(img=labeled_img, text=text, org=(w, h), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                    fontScale=params.text_size, color=rand_color[i], thickness=params.text_thickness)
    # Auto-increment device
    params.device += 1

    if params.debug == 'print':
        print_image(labeled_img, os.path.join(params.debug_outdir, str(params.device) + '_segmented_ids.png'))
    elif params.debug == 'plot':
        plot_image(labeled_img)

    return segmented_img, labeled_img
コード例 #50
0
def colorize_masks(masks, colors):
    """Plot masks with different colors
    Inputs:
        masks    = list of masks to colorize
        colors   = list of colors (either keys from the color_dict or a list of custom tuples)

        :param masks: list
        :param colors: list
        :return colored_img: ndarray
        """

    # Users must enter the exact same number of colors as classes they'd like to color
    num_classes = len(masks)
    num_colors = len(colors)
    if not num_classes == num_colors:
        fatal_error("The number of colors provided doesn't match the number of class masks provided.")

    # Check to make sure user provided at least one mask and color
    if len(colors) == 0 or len(masks) == 0:
        fatal_error("At least one class mask and color must be provided.")

    # Dictionary of colors and the BGR values, based on some of the colors listed here:
    # https://en.wikipedia.org/wiki/X11_color_names
    color_dict = {'white': (255,255,255), 'black': (0,0,0), 'aqua': (0,255,255), 'blue': (255,0,0), 'blue violet':
    (228,44,138), 'brown': (41,41,168), 'chartreuse': (0,255,128), 'dark blue': (140,0,0), 'gray': (169,169,169),
    'yellow': (0, 255, 255), 'turquoise': (210,210,64), 'red': (0, 0, 255), 'purple': (241,33,161), 'orange red':
    (0,69, 255), 'orange': (0,166,255), 'lime': (0, 255, 0), 'lime green': (52,205,52), 'fuchsia': (255,0,255),
    'crimson': (61,20,220), 'beige': (197,220,246), 'chocolate': (31,105,210), 'coral': (79,128,255), 'dark green':
    (0,100,0), 'dark orange': (0,140,255), 'green yellow': (46,255,174), 'light blue': (230,218,174), 'tomato':
    (72,100,255), 'slate gray': (143,128,113), 'gold': (0,215,255), 'goldenrod': (33,166,218), 'light green':
    (143,238,143), 'sea green': (77,141,46), 'dark red': (0,0,141), 'pink': (204,192,255), 'dark yellow': (0,205,255),
    'green': (0,255,0)}

    ix, iy = np.shape(masks[0])
    colored_img = np.zeros((ix,iy,3), dtype=np.uint8)
    # Assign pixels to the selected color

    for i in range(0, len(masks)):
        mask = np.copy(masks[i])
        mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
        if isinstance(colors[i], tuple):
            mask[masks[i] > 0] = colors[i]
        elif isinstance(colors[i], str):
            mask[masks[i] > 0] = color_dict[colors[i]]
        else:
            fatal_error("All elements of the 'colors' list must be either str or tuple")
        colored_img = colored_img + mask


    if params.debug == 'print':
        print_image(colored_img, os.path.join(params.debug_outdir, str(params.device) + '_classes_plot.png'))
    elif params.debug == 'plot':
        plot_image(colored_img)

    return colored_img
コード例 #51
0
def object_composition(img, contours, hierarchy):
    """Groups objects into a single object, usually done after object filtering.

    Inputs:
    img       = RGB or grayscale image data for plotting
    contours  = Contour list
    hierarchy = Contour hierarchy NumPy array

    Returns:
    group    = grouped contours list
    mask     = image mask

    :param img: numpy.ndarray
    :param contours: list
    :param hierarchy: numpy.ndarray
    :return group: list
    :return mask: numpy.ndarray
    """

    params.device += 1
    ori_img = np.copy(img)
    # If the reference image is grayscale convert it to color
    if len(np.shape(ori_img)) == 2:
        ori_img = cv2.cvtColor(ori_img, cv2.COLOR_GRAY2BGR)

    stack = np.zeros((len(contours), 1))
    r, g, b = cv2.split(ori_img)
    mask = np.zeros(g.shape, dtype=np.uint8)

    for c, cnt in enumerate(contours):
        # if hierarchy[0][c][3] == -1:
        if hierarchy[0][c][2] == -1 and hierarchy[0][c][3] > -1:
            stack[c] = 0
        else:
            stack[c] = 1

    ids = np.where(stack == 1)[0]
    if len(ids) > 0:
        group = np.vstack(contours[i] for i in ids)
        cv2.drawContours(mask, contours, -1, 255, -1, hierarchy=hierarchy)

        if params.debug is not None:
            cv2.drawContours(ori_img, group, -1, (255, 0, 0), params.line_thickness)
            for cnt in contours:
                cv2.drawContours(ori_img, cnt, -1, (255, 0, 0), params.line_thickness)
            if params.debug == 'print':
                print_image(ori_img, os.path.join(params.debug_outdir, str(params.device) + '_objcomp.png'))
                print_image(ori_img, os.path.join(params.debug_outdir, str(params.device) + '_objcomp_mask.png'))
            elif params.debug == 'plot':
                plot_image(ori_img)
        return group, mask
    else:
        print("Warning: Invalid contour.")
        return None, None
コード例 #52
0
def _call_adaptive_threshold(gray_img, max_value, adaptive_method, threshold_method, method_name):
    # Threshold the image
    bin_img = cv2.adaptiveThreshold(gray_img, max_value, adaptive_method, threshold_method, 11, 2)

    # Print or plot the binary image if debug is on
    if params.debug == 'print':
        print_image(bin_img, os.path.join(params.debug_outdir,
                                          str(params.device) + method_name + '.png'))
    elif params.debug == 'plot':
        plot_image(bin_img, cmap='gray')

    return bin_img
コード例 #53
0
ファイル: rotate.py プロジェクト: danforthcenter/plantcv
def rotate(img, rotation_deg, crop):
    """Rotate image, sometimes it is necessary to rotate image, especially when clustering for
       multiple plants is needed.

    Inputs:
    img          = RGB or grayscale image data
    rotation_deg = rotation angle in degrees, can be a negative number,
                   positive values move counter clockwise.
    crop         = either true or false, if true, dimensions of rotated image will be same as original image.

    Returns:
    rotated_img  = rotated image

    :param img: numpy.ndarray
    :param rotation_deg: double
    :param crop: bool
    :return rotated_img: numpy.ndarray
    """
    params.device += 1

    if len(np.shape(img)) == 3:
        iy, ix, iz = np.shape(img)
    else:
        iy, ix = np.shape(img)

    m = cv2.getRotationMatrix2D((ix / 2, iy / 2), rotation_deg, 1)

    cos = np.abs(m[0, 0])
    sin = np.abs(m[0, 1])

    if not crop:
        # compute the new bounding dimensions of the image
        nw = int((iy * sin) + (ix * cos))
        nh = int((iy * cos) + (ix * sin))

        # adjust the rotation matrix to take into account translation
        m[0, 2] += (nw / 2) - (ix / 2)
        m[1, 2] += (nh / 2) - (iy / 2)

        rotated_img = cv2.warpAffine(img, m, (nw, nh))
    else:
        rotated_img = cv2.warpAffine(img, m, (ix, iy))

    if params.debug == 'print':
        print_image(rotated_img, os.path.join(params.debug_outdir, str(params.device) + str(rotation_deg) + '_rotated_img.png'))

    elif params.debug == 'plot':
        if len(np.shape(img)) == 3:
            plot_image(rotated_img)
        else:
            plot_image(rotated_img, cmap='gray')

    return rotated_img
コード例 #54
0
def segment_path_length(segmented_img, objects):
    """ Use segments to calculate geodesic distance per segment

        Inputs:
        segmented_img = Segmented image to plot lengths on
        objects       = List of contours

        Returns:
        labeled_img        = Segmented debugging image with lengths labeled

        :param segmented_img: numpy.ndarray
        :param objects: list
        :return labeled_img: numpy.ndarray

        """

    label_coord_x = []
    label_coord_y = []
    segment_lengths = []
    labeled_img = segmented_img.copy()

    for i, cnt in enumerate(objects):
        # Calculate geodesic distance, divide by two since cv2 seems to be taking the perimeter of the contour
        segment_lengths.append(cv2.arcLength(objects[i], False) / 2)
        # Store coordinates for labels
        label_coord_x.append(objects[i][0][0][0])
        label_coord_y.append(objects[i][0][0][1])

    segment_ids = []

    # Put labels of length
    for c, value in enumerate(segment_lengths):
        text = "{:.2f}".format(value)
        w = label_coord_x[c]
        h = label_coord_y[c]
        cv2.putText(img=labeled_img, text=text, org=(w, h), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                    fontScale=params.text_size, color=(150, 150, 150), thickness=params.text_thickness)
        segment_label = "ID" + str(c)
        segment_ids.append(c)

    outputs.add_observation(variable='segment_path_length', trait='segment path length',
                            method='plantcv.plantcv.morphology.segment_path_length', scale='pixels', datatype=list,
                            value=segment_lengths, label=segment_ids)

    # Auto-increment device
    params.device += 1

    if params.debug == 'print':
        print_image(labeled_img, os.path.join(params.debug_outdir, str(params.device) + '_segment_path_lengths.png'))
    elif params.debug == 'plot':
        plot_image(labeled_img)

    return labeled_img
コード例 #55
0
ファイル: readimage.py プロジェクト: danforthcenter/plantcv
def readimage(filename, mode="native"):
    """Read image from file.

    Inputs:
    filename = name of image file
    mode     = mode of imread ("native", "rgb", "rgba", "gray")

    Returns:
    img      = image object as numpy array
    path     = path to image file
    img_name = name of image file

    :param filename: str
    :param mode: str
    :return img: numpy.ndarray
    :return path: str
    :return img_name: str
    """
    if mode.upper() == "GRAY" or mode.upper() == "GREY":
        img = cv2.imread(filename, 0)
    elif mode.upper() == "RGB":
        img = cv2.imread(filename)
    elif mode.upper() == "RGBA":
        img = cv2.imread(filename, -1)
    else:
        img = cv2.imread(filename, -1)

    # Default to drop alpha channel if user doesn't specify 'rgba'
    if len(np.shape(img))==3 and np.shape(img)[2] == 4 and mode.upper() == "NATIVE":
        img = cv2.imread(filename)

    if img is None:
        fatal_error("Failed to open " + filename)

    # Split path from filename
    path, img_name = os.path.split(filename)

    if params.debug == "print":
        print_image(img, os.path.join(params.debug_outdir, "input_image.png"))
    elif params.debug == "plot":
        plot_image(img)

    return img, path, img_name
コード例 #56
0
ファイル: roi_methods.py プロジェクト: danforthcenter/plantcv
def _draw_roi(img, roi_contour):
    """Draw an ROI

    :param img: numpy.ndarray
    :param roi_contour: list
    """
    # Make a copy of the reference image
    ref_img = np.copy(img)
    # If the reference image is grayscale convert it to color
    if len(np.shape(ref_img)) == 2:
        ref_img = cv2.cvtColor(ref_img, cv2.COLOR_GRAY2BGR)
    # Draw the contour on the reference image
    cv2.drawContours(ref_img, roi_contour, -1, (255, 0, 0), params.line_thickness)
    if params.debug == "print":
        # If debug is print, save the image to a file
        print_image(ref_img, os.path.join(params.debug_outdir, str(params.device) + "_roi.png"))
    elif params.debug == "plot":
        # If debug is plot, print to the plotting device
        plot_image(ref_img)
コード例 #57
0
ファイル: rgb2gray.py プロジェクト: danforthcenter/plantcv
def rgb2gray(rgb_img):
    """Convert image from RGB colorspace to Gray.

    Inputs:
    rgb_img    = RGB image data

    Returns:
    gray   = grayscale image

    :param rgb_img: numpy.ndarray
    :return gray: numpy.ndarray
    """

    gray = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY)
    params.device += 1
    if params.debug == 'print':
        print_image(gray, os.path.join(params.debug_outdir, str(params.device) + '_gray.png'))
    elif params.debug == 'plot':
        plot_image(gray, cmap='gray')
    return gray
コード例 #58
0
ファイル: logical_xor.py プロジェクト: danforthcenter/plantcv
def logical_xor(bin_img1, bin_img2):
    """Join two images using the bitwise XOR operator.

    Inputs:
    bin_img1   = Binary image data to be compared to bin_img2
    bin_img2   = Binary image data to be compared to bin_img1

    Returns:
    merged     = joined binary image

    :param bin_img1: numpy.ndarray
    :param bin_img2: numpy.ndarray
    :return merged: numpy.ndarray
    """

    params.device += 1
    merged = cv2.bitwise_xor(bin_img1, bin_img2)
    if params.debug == 'print':
        print_image(merged, os.path.join(params.debug_outdir, str(params.device) + '_xor_joined.png'))
    elif params.debug == 'plot':
        plot_image(merged, cmap='gray')
    return merged
コード例 #59
0
def rgb2gray_lab(rgb_img, channel):
    """Convert image from RGB colorspace to LAB colorspace. Returns the specified subchannel as a gray image.

    Inputs:
    rgb_img   = RGB image data
    channel   = color subchannel (l = lightness, a = green-magenta, b = blue-yellow)

    Returns:
    l | a | b = grayscale image from one LAB color channel

    :param rgb_img: numpy.ndarray
    :param channel: str
    :return channel: numpy.ndarray
    """
    # Auto-increment the device counter
    params.device += 1
    # The allowable channel inputs are l, a or b
    names = {"l": "lightness", "a": "green-magenta", "b": "blue-yellow"}
    channel = channel.lower()
    if channel not in names:
        fatal_error("Channel " + str(channel) + " is not l, a or b!")

    # Convert the input BGR image to LAB colorspace
    lab = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2LAB)
    # Split LAB channels
    l, a, b = cv2.split(lab)
    # Create a channel dictionaries for lookups by a channel name index
    channels = {"l": l, "a": a, "b": b}

    if params.debug == "print":
        print_image(channels[channel], os.path.join(params.debug_outdir,
                                                    str(params.device) + "_lab_" + names[channel] + ".png"))
    elif params.debug == "plot":
        plot_image(channels[channel], cmap="gray")

    return channels[channel]
コード例 #60
0
ファイル: fill.py プロジェクト: danforthcenter/plantcv
def fill(bin_img, size):
    """Identifies objects and fills objects that are less than size.

    Inputs:
    bin_img      = Binary image data
    size         = minimum object area size in pixels (integer)


    Returns:
    filtered_img = image with objects filled

    :param bin_img: numpy.ndarray
    :param size: int
    :return filtered_img: numpy.ndarray
    """
    params.device += 1

    # Make sure the image is binary
    if len(np.shape(bin_img)) != 2 or len(np.unique(bin_img)) != 2:
        fatal_error("Image is not binary")

    # Cast binary image to boolean
    bool_img = bin_img.astype(bool)

    # Find and fill contours
    bool_img = remove_small_objects(bool_img, size)

    # Cast boolean image to binary and make a copy of the binary image for returning
    filtered_img = np.copy(bool_img.astype(np.uint8) * 255)

    if params.debug == 'print':
        print_image(filtered_img, os.path.join(params.debug_outdir, str(params.device) + '_fill' + str(size) + '.png'))
    elif params.debug == 'plot':
        plot_image(filtered_img, cmap='gray')

    return filtered_img