示例#1
0
文件: tests.py 项目: stiphyMT/plantcv
def test_plantcv_naive_bayes_classifier():
    img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
    device, mask = pcv.naive_bayes_classifier(img=img, pdf_file=os.path.join(TEST_DATA, TEST_PDFS),
                                              device=0, debug=None)

    # Assert that the output image has the dimensions of the input image
    if all([i == j] for i, j in zip(np.shape(mask), TEST_GRAY_DIM)):
        # Assert that the image is binary
        if all([i == j] for i, j in zip(np.unique(mask), [0, 255])):
            assert 1
        else:
            assert 0
    else:
        assert 0
示例#2
0
def test_plantcv_naive_bayes_classifier():
    img = cv2.imread(os.path.join(TEST_DATA, TEST_INPUT_COLOR))
    device, mask = pcv.naive_bayes_classifier(img=img,
                                              pdf_file=os.path.join(
                                                  TEST_DATA, TEST_PDFS),
                                              device=0,
                                              debug=None)

    # Assert that the output image has the dimensions of the input image
    if all([i == j] for i, j in zip(np.shape(mask), TEST_GRAY_DIM)):
        # Assert that the image is binary
        if all([i == j] for i, j in zip(np.unique(mask), [0, 255])):
            assert 1
        else:
            assert 0
    else:
        assert 0
def naive_bayes():

    if request.method == 'POST':
        imagefile = request.files['image']
        pdffile = request.files['pdf']

        i = os.path.join(app.config['UPLOAD_FOLDER'], imagefile.filename)
        imagefile.save(i)

        p = os.path.join(app.config['UPLOAD_FOLDER'], pdffile.filename)
        pdffile.save(p)

        print("Files uploaded successfully")

    img, path, filename = pcv.readimage(i)

    #Creating the mask from the base image and the model
    device, mask = pcv.naive_bayes_classifier(img,
                                              pdf_file=pdffile.filename,
                                              device=0,
                                              debug="print")

    #Applying the mask to the colour image
    device, masked_image = pcv.apply_mask(img,
                                          mask['plant'],
                                          'white',
                                          device,
                                          debug="print")

    #Converting the image from a Numpy array to a Base64 string to allow the website to render it properly
    im = Image.fromarray(masked_image.astype("uint8"))
    b, g, r = im.split()
    im = Image.merge("RGB", (r, g, b))
    rawBytes = io.BytesIO()
    im.save(rawBytes, "PNG")
    rawBytes.seek(0)
    outimage = base64.b64encode(rawBytes.read())

    #Returning the template and image
    return render_template("result.html", image=outimage)
def main():
    # Get options
    args = options()

    # Read image
    img, path, filename = pcv.readimage(args.image)
    # roi = cv2.imread(args.roi)

    # Pipeline step
    device = 0

    device, mask = pcv.naive_bayes_classifier(img, "naive_bayes.pdf.txt",
                                              device, args.debug)

    mask1 = np.uint8(mask)

    mask_copy = np.copy(mask1)

    # Fill small objects
    device, soil_fill = pcv.fill(mask1, mask_copy, 200, device, args.debug)

    # Median Filter
    device, soil_mblur = pcv.median_blur(soil_fill, 5, device, args.debug)
    device, soil_cnt = pcv.median_blur(soil_fill, 5, device, args.debug)

    # Apply mask (for vis images, mask_color=white)
    device, masked2 = pcv.apply_mask(img, soil_cnt, 'white', device,
                                     args.debug)

    # Identify objects
    device, id_objects, obj_hierarchy = pcv.find_objects(
        masked2, soil_cnt, device, args.debug)

    # Define ROI
    device, roi1, roi_hierarchy = pcv.define_roi(img, 'rectangle', device,
                                                 None, 'default', args.debug,
                                                 True, 0, 0, 0, -925)

    # Decide which objects to keep
    device, roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects(
        img, 'partial', roi1, roi_hierarchy, id_objects, obj_hierarchy, device,
        args.debug)

    # Object combine kept objects
    device, obj, mask = pcv.object_composition(img, roi_objects, hierarchy3,
                                               device, args.debug)

    # ############## Analysis ################
    # output mask
    device, maskpath, mask_images = pcv.output_mask(device, img, mask,
                                                    filename, args.outdir,
                                                    True, args.debug)

    # Find shape properties, output shape image (optional)
    device, shape_header, shape_data, shape_img = pcv.analyze_object(
        img, args.image, obj, mask, device, args.debug)

    # Shape properties relative to user boundary line (optional)
    device, boundary_header, boundary_data, boundary_img1 = pcv.analyze_bound(
        img, args.image, obj, mask, 900, device)

    # Determine color properties: Histograms, Color Slices and Pseudocolored Images,
    # output color analyzed images (optional)
    device, color_header, color_data, color_img = pcv.analyze_color(
        img, args.image, mask, 256, device, args.debug, None, 'v', 'img', 300)

    result = open(args.result, "a")
    result.write('\t'.join(map(str, shape_header)))
    result.write("\n")
    result.write('\t'.join(map(str, shape_data)))
    result.write("\n")
    for row in mask_images:
        result.write('\t'.join(map(str, row)))
        result.write("\n")
    result.write('\t'.join(map(str, color_header)))
    result.write("\n")
    result.write('\t'.join(map(str, color_data)))
    result.write("\n")
    result.write('\t'.join(map(str, boundary_header)))
    result.write("\n")
    result.write('\t'.join(map(str, boundary_data)))
    result.write("\n")
    result.close()
def get_feature(img):
    print("step one")
    """
    Step one: Background forground substraction 
    """
    # Get options
    args = options()
    debug = args.debug
    # Read image
    filename = args.result
    # img, path, filename = pcv.readimage(args.image)
    # Pipeline step
    device = 0
    device, resize_img = pcv.resize(img, 0.4, 0.4, device, debug)
    # Classify the pixels as plant or background
    device, mask_img = pcv.naive_bayes_classifier(
        resize_img,
        pdf_file=
        "/home/matthijs/PycharmProjects/SMR1/src/vision/ML_background/Trained_models/model_3/naive_bayes_pdfs.txt",
        device=0,
        debug='print')

    # Median Filter
    device, blur = pcv.median_blur(mask_img.get('plant'), 5, device, debug)
    print("step two")
    """
    Step one: Identifiy the objects, extract and filter the objects
    """

    # Identify objects
    device, id_objects, obj_hierarchy = pcv.find_objects(resize_img,
                                                         blur,
                                                         device,
                                                         debug=None)

    # Define ROI
    device, roi1, roi_hierarchy = pcv.define_roi(resize_img,
                                                 'rectangle',
                                                 device,
                                                 roi=True,
                                                 roi_input='default',
                                                 debug=True,
                                                 adjust=True,
                                                 x_adj=50,
                                                 y_adj=10,
                                                 w_adj=-100,
                                                 h_adj=0)
    # Decide which objects to keep
    device, roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects(
        resize_img, 'cutto', roi1, roi_hierarchy, id_objects, obj_hierarchy,
        device, debug)
    # print(roi_objects[0])
    # cv2.drawContours(resize_img, [roi_objects[0]], 0, (0, 255, 0), 3)
    # cv2.imshow("img",resize_img)
    # cv2.waitKey(0)
    area_oud = 0
    i = 0
    index = 0
    object_list = []
    # a = np.array([[hierarchy3[0][0]]])
    hierarchy = []
    for cnt in roi_objects:
        area = cv2.contourArea(cnt)
        M = cv2.moments(cnt)
        if M["m10"] or M["m01"]:
            cX = int(M["m10"] / M["m00"])
            cY = int(M["m01"] / M["m00"])
            # check if the location of the contour is between the constrains
            if cX > 200 and cX < 500 and cY > 25 and cY < 400:
                # cv2.circle(resize_img, (cX, cY), 5, (255, 0, 255), thickness=1, lineType=1, shift=0)
                # check if the size of the contour is bigger than 250
                if area > 450:
                    obj = np.vstack(roi_objects)
                    object_list.append(roi_objects[i])
                    hierarchy.append(hierarchy3[0][i])
                    print(i)
        i = i + 1
    a = np.array([hierarchy])
    # a = [[[-1,-1,-1,-1][-1,-1,-1,-1][-1,-1,-1,-1]]]
    # Object combine kept objects
    # device, obj, mask_2 = pcv.object_composition(resize_img, object_list, a, device, debug)

    mask_contours = np.zeros(resize_img.shape, np.uint8)
    cv2.drawContours(mask_contours, object_list, -1, (255, 255, 255), -1)
    gray_image = cv2.cvtColor(mask_contours, cv2.COLOR_BGR2GRAY)
    ret, mask_contours = cv2.threshold(gray_image, 127, 255, cv2.THRESH_BINARY)

    # Identify objects
    device, id_objects, obj_hierarchy = pcv.find_objects(resize_img,
                                                         mask_contours,
                                                         device,
                                                         debug=None)
    # Decide which objects to keep
    device, roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects(
        resize_img,
        'cutto',
        roi1,
        roi_hierarchy,
        id_objects,
        obj_hierarchy,
        device,
        debug=None)
    # Object combine kept objects
    device, obj, mask = pcv.object_composition(resize_img,
                                               roi_objects,
                                               hierarchy3,
                                               device,
                                               debug=None)
    ############### Analysis ################
    masked = mask.copy()

    outfile = False
    if args.writeimg == True:
        outfile = args.outdir + "/" + filename

    print("step three")
    """
    Step three: Calculate all the features
    """
    # Find shape properties, output shape image (optional)
    device, shape_header, shape_data, shape_img = pcv.analyze_object(
        resize_img, args.image, obj, mask, device, debug, filename="/file")
    print(shape_img)
    # Shape properties relative to user boundary line (optional)
    device, boundary_header, boundary_data, boundary_img1 = pcv.analyze_bound(
        resize_img, args.image, obj, mask, 1680, device)

    # Determine color properties: Histograms, Color Slices and Pseudocolored Images, output color analyzed images (optional)
    device, color_header, color_data, color_img = pcv.analyze_color(
        resize_img, args.image, kept_mask, 256, device, debug, 'all', 'v',
        'img', 300)
    maks_watershed = mask.copy()
    kernel = np.zeros((5, 5), dtype=np.uint8)
    device, mask_watershed, = pcv.erode(maks_watershed, 5, 1, device, debug)

    device, watershed_header, watershed_data, analysis_images = pcv.watershed_segmentation(
        device, resize_img, mask, 50, './examples', debug)
    device, list_of_acute_points = pcv.acute_vertex(obj, 30, 60, 10,
                                                    resize_img, device, debug)

    device, top, bottom, center_v = pcv.x_axis_pseudolandmarks(
        obj, mask, resize_img, device, debug)

    device, left, right, center_h = pcv.y_axis_pseudolandmarks(
        obj, mask, resize_img, device, debug)

    device, points_rescaled, centroid_rescaled, bottomline_rescaled = pcv.scale_features(
        obj, mask, list_of_acute_points, 225, device, debug)

    # Identify acute vertices (tip points) of an object
    # Results in set of point values that may indicate tip points
    device, vert_ave_c, hori_ave_c, euc_ave_c, ang_ave_c, vert_ave_b, hori_ave_b, euc_ave_b, ang_ave_b = pcv.landmark_reference_pt_dist(
        points_rescaled, centroid_rescaled, bottomline_rescaled, device, debug)

    landmark_header = [
        'HEADER_LANDMARK', 'tip_points', 'tip_points_r', 'centroid_r',
        'baseline_r', 'tip_number', 'vert_ave_c', 'hori_ave_c', 'euc_ave_c',
        'ang_ave_c', 'vert_ave_b', 'hori_ave_b', 'euc_ave_b', 'ang_ave_b',
        'left_lmk', 'right_lmk', 'center_h_lmk', 'left_lmk_r', 'right_lmk_r',
        'center_h_lmk_r', 'top_lmk', 'bottom_lmk', 'center_v_lmk', 'top_lmk_r',
        'bottom_lmk_r', 'center_v_lmk_r'
    ]
    landmark_data = [
        'LANDMARK_DATA', 0, 0, 0, 0,
        len(list_of_acute_points), vert_ave_c, hori_ave_c, euc_ave_c,
        ang_ave_c, vert_ave_b, hori_ave_b, euc_ave_b, ang_ave_b, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0
    ]
    shape_data_train = list(shape_data)
    shape_data_train.pop(0)
    shape_data_train.pop(10)
    watershed_data_train = list(watershed_data)
    watershed_data_train.pop(0)
    landmark_data_train = [
        len(list_of_acute_points), vert_ave_c, hori_ave_c, euc_ave_c,
        ang_ave_c, vert_ave_b, hori_ave_b, euc_ave_b, ang_ave_b
    ]
    X = shape_data_train + watershed_data_train + landmark_data_train
    print("len X", len(X))
    print(X)
    # Write shape and color data to results fil
    result = open(args.result, "a")
    result.write('\t'.join(map(str, shape_header)))
    result.write("\n")
    result.write('\t'.join(map(str, shape_data)))
    result.write("\n")
    result.write('\t'.join(map(str, watershed_header)))
    result.write("\n")
    result.write('\t'.join(map(str, watershed_data)))
    result.write("\n")
    result.write('\t'.join(map(str, landmark_header)))
    result.write("\n")
    result.write('\t'.join(map(str, landmark_data)))
    result.write("\n")
    for row in shape_img:
        result.write('\t'.join(map(str, row)))
        result.write("\n")
    result.write('\t'.join(map(str, color_header)))
    result.write("\n")
    result.write('\t'.join(map(str, color_data)))
    result.write("\n")
    for row in color_img:
        result.write('\t'.join(map(str, row)))
        result.write("\n")
    result.close()
    print("done")
    print(shape_img)
    return X, shape_img, masked
示例#6
0
def get_height(img):
    # print("step one")
    """
    Step one: Background forground substraction
    """
    # Get options
    args = options()
    debug = args.debug
    # Read image
    filename = args.result
    # img, path, filename = pcv.readimage(args.image)
    # Pipeline step
    device = 0
    device, resize_img = pcv.resize(img, 0.4, 0.4, device, debug)
    # Classify the pixels as plant or background
    device, mask_img = pcv.naive_bayes_classifier(
        resize_img,
        pdf_file=
        "./../ML_background/Trained_models/model_6/naive_bayes_pdfs.txt",
        device=0,
        debug='print')

    # Median Filter
    device, blur = pcv.median_blur(mask_img.get('plant'), 5, device, debug)
    # print("step two")
    """
    Step one: Identifiy the objects, extract and filter the objects
    """

    # Identify objects
    device, id_objects, obj_hierarchy = pcv.find_objects(resize_img,
                                                         blur,
                                                         device,
                                                         debug=None)

    # Define ROI
    device, roi1, roi_hierarchy = pcv.define_roi(resize_img,
                                                 'rectangle',
                                                 device,
                                                 roi=True,
                                                 roi_input='default',
                                                 debug=None,
                                                 adjust=True,
                                                 x_adj=50,
                                                 y_adj=10,
                                                 w_adj=0,
                                                 h_adj=0)
    # Decide which objects to keep
    device, roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects(
        resize_img, 'cutto', roi1, roi_hierarchy, id_objects, obj_hierarchy,
        device, debug)
    # print(roi_objects[0])
    # cv2.drawContours(resize_img, [roi_objects[0]], 0, (0, 255, 0), 3)
    # cv2.imshow("img",resize_img)
    # cv2.waitKey(0)
    area_oud = 0
    i = 0
    index = 0
    object_list = []
    # a = np.array([[hierarchy3[0][0]]])
    hierarchy = []
    for cnt in roi_objects:
        area = cv2.contourArea(cnt)
        M = cv2.moments(cnt)
        if M["m10"] or M["m01"]:
            cX = int(M["m10"] / M["m00"])
            cY = int(M["m01"] / M["m00"])
            # check if the location of the contour is between the constrains
            # cv2.circle(resize_img, (cX, cY), 5, (255, 0, 255), thickness=1, lineType=1, shift=0)
            # check if the size of the contour is bigger than 250
            if area > 200:
                obj = np.vstack(roi_objects)
                object_list.append(roi_objects[i])
                hierarchy.append(hierarchy3[0][i])
                # print(i)
        i = i + 1
    a = np.array([hierarchy])
    # a = [[[-1,-1,-1,-1][-1,-1,-1,-1][-1,-1,-1,-1]]]
    # Object combine kept objects
    # device, obj, mask_2 = pcv.object_composition(resize_img, object_list, a, device, debug)

    mask_contours = np.zeros(resize_img.shape, np.uint8)
    cv2.drawContours(mask_contours, object_list, -1, (255, 255, 255), -1)
    gray_image = cv2.cvtColor(mask_contours, cv2.COLOR_BGR2GRAY)
    ret, mask_contours = cv2.threshold(gray_image, 127, 255, cv2.THRESH_BINARY)

    # Identify objects
    device, id_objects, obj_hierarchy = pcv.find_objects(resize_img,
                                                         mask_contours,
                                                         device,
                                                         debug=None)
    # Decide which objects to keep
    device, roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects(
        resize_img,
        'cutto',
        roi1,
        roi_hierarchy,
        id_objects,
        obj_hierarchy,
        device,
        debug=None)
    # Object combine kept objects
    device, obj, mask = pcv.object_composition(resize_img,
                                               roi_objects,
                                               hierarchy3,
                                               device,
                                               debug=None)
    ############### Analysis ################
    try:
        if len(obj) > 0:
            # Find shape properties, output shape image (optional)
            device, shape_header, shape_data, shape_img = pcv.analyze_object(
                resize_img, args.image, obj, mask, device, debug)
            # cv2.waitKey(10000)
            return shape_data[6]
        else:
            return -1
    except:
        return -1
示例#7
0
import glob

import cv2
import plantcv as pcv

cv_img = []
for img in glob.glob("Data_set/Foto's/*.jpg"):
    n = cv2.imread(img)
    device = 0
    device, n = pcv.resize(n, 0.2, 0.2, device)
    # Classify the pixels as plant or background
    device, mask = pcv.naive_bayes_classifier(n, pdf_file="Trained_models/model_2/naive_bayes_pdfs.txt", device=0,
                                              debug=None)
    cv2.imshow("mask", mask.get('plant'))
    cv2.waitKey(2000)
def main():
    # Parse command-line options
    args = options()

    device = 0

    # Open output file
    out = open(args.outfile, "w")

    # Open the image file
    img, path, fname = pcv.readimage(filename=args.image, debug=args.debug)
    # Classify healthy and unhealthy plant pixels
    device, masks = pcv.naive_bayes_classifier(img=img,
                                               pdf_file=args.pdfs,
                                               device=device)

    # Use the identified blue mesh area to build a mask for the pot area
    # First errode the blue mesh region to remove background
    device, mesh_errode = pcv.erode(img=masks["Background_Blue"],
                                    kernel=9,
                                    i=3,
                                    device=device,
                                    debug=args.debug)
    # Define a region of interest for blue mesh contours
    device, pot_roi, pot_hierarchy = pcv.define_roi(img=img,
                                                    shape='rectangle',
                                                    device=device,
                                                    roi=None,
                                                    roi_input='default',
                                                    debug=args.debug,
                                                    adjust=True,
                                                    x_adj=0,
                                                    y_adj=500,
                                                    w_adj=0,
                                                    h_adj=-650)
    # Find blue mesh contours
    device, mesh_objects, mesh_hierarchy = pcv.find_objects(img=img,
                                                            mask=mesh_errode,
                                                            device=device,
                                                            debug=args.debug)
    # Keep blue mesh contours in the region of interest
    device, kept_mesh_objs, kept_mesh_hierarchy, kept_mask_mesh, _ = pcv.roi_objects(
        img=img,
        roi_type='partial',
        roi_contour=pot_roi,
        roi_hierarchy=pot_hierarchy,
        object_contour=mesh_objects,
        obj_hierarchy=mesh_hierarchy,
        device=device,
        debug=args.debug)
    # Flatten the blue mesh contours into a single object
    device, mesh_flattened, mesh_mask = pcv.object_composition(
        img=img,
        contours=kept_mesh_objs,
        hierarchy=kept_mesh_hierarchy,
        device=device,
        debug=args.debug)
    # Initialize a pot mask
    pot_mask = np.zeros(np.shape(masks["Background_Blue"]), dtype=np.uint8)
    # Find the minimum bounding rectangle for the blue mesh region
    rect = cv2.minAreaRect(mesh_flattened)
    # Create a contour for the minimum bounding box
    box = cv2.boxPoints(rect)
    box = np.int0(box)
    # Create a mask from the bounding box contour
    cv2.drawContours(pot_mask, [box], 0, (255), -1)
    # If the bounding box area is too small then the plant has likely occluded too much of the pot for us to use this
    # as a marker for the pot area
    if np.sum(pot_mask) / 255 < 2900000:
        print(np.sum(pot_mask) / 255)
        # Create a new pot mask
        pot_mask = np.zeros(np.shape(masks["Background_Blue"]), dtype=np.uint8)
        # Set the mask area to the ROI area
        box = np.array([[0, 500], [0, 2806], [2304, 2806], [2304, 500]])
        cv2.drawContours(pot_mask, [box], 0, (255), -1)
    # Dialate the blue mesh area to include the ridge of the pot
    device, pot_mask_dilated = pcv.dilate(img=pot_mask,
                                          kernel=3,
                                          i=60,
                                          device=device,
                                          debug=args.debug)
    # Mask the healthy mask
    device, healthy_masked = pcv.apply_mask(img=cv2.merge(
        [masks["Healthy"], masks["Healthy"], masks["Healthy"]]),
                                            mask=pot_mask_dilated,
                                            mask_color="black",
                                            device=device,
                                            debug=args.debug)
    # Mask the unhealthy mask
    device, unhealthy_masked = pcv.apply_mask(img=cv2.merge(
        [masks["Unhealthy"], masks["Unhealthy"], masks["Unhealthy"]]),
                                              mask=pot_mask_dilated,
                                              mask_color="black",
                                              device=device,
                                              debug=args.debug)
    # Convert the masks back to binary
    healthy_masked, _, _ = cv2.split(healthy_masked)
    unhealthy_masked, _, _ = cv2.split(unhealthy_masked)

    # Fill small objects
    device, fill_image_healthy = pcv.fill(img=np.copy(healthy_masked),
                                          mask=np.copy(healthy_masked),
                                          size=300,
                                          device=device,
                                          debug=args.debug)
    device, fill_image_unhealthy = pcv.fill(img=np.copy(unhealthy_masked),
                                            mask=np.copy(unhealthy_masked),
                                            size=1000,
                                            device=device,
                                            debug=args.debug)
    # Define a region of interest
    device, roi1, roi_hierarchy = pcv.define_roi(img=img,
                                                 shape='rectangle',
                                                 device=device,
                                                 roi=None,
                                                 roi_input='default',
                                                 debug=args.debug,
                                                 adjust=True,
                                                 x_adj=450,
                                                 y_adj=1000,
                                                 w_adj=-400,
                                                 h_adj=-1000)
    # Filter objects that overlap the ROI
    device, id_objects, obj_hierarchy_healthy = pcv.find_objects(
        img=img, mask=fill_image_healthy, device=device, debug=args.debug)
    device, _, _, kept_mask_healthy, _ = pcv.roi_objects(
        img=img,
        roi_type='partial',
        roi_contour=roi1,
        roi_hierarchy=roi_hierarchy,
        object_contour=id_objects,
        obj_hierarchy=obj_hierarchy_healthy,
        device=device,
        debug=args.debug)
    device, id_objects, obj_hierarchy_unhealthy = pcv.find_objects(
        img=img, mask=fill_image_unhealthy, device=device, debug=args.debug)
    device, _, _, kept_mask_unhealthy, _ = pcv.roi_objects(
        img=img,
        roi_type='partial',
        roi_contour=roi1,
        roi_hierarchy=roi_hierarchy,
        object_contour=id_objects,
        obj_hierarchy=obj_hierarchy_unhealthy,
        device=device,
        debug=args.debug)
    # Combine the healthy and unhealthy mask
    device, mask = pcv.logical_or(img1=kept_mask_healthy,
                                  img2=kept_mask_unhealthy,
                                  device=device,
                                  debug=args.debug)

    # Output a healthy/unhealthy image
    classified_img = cv2.merge([
        np.zeros(np.shape(mask), dtype=np.uint8), kept_mask_healthy,
        kept_mask_unhealthy
    ])
    pcv.print_image(img=classified_img,
                    filename=os.path.join(
                        args.outdir,
                        os.path.basename(args.image)[:-4] + ".classified.png"))

    # Output a healthy/unhealthy image overlaid on the original image
    overlayed = cv2.addWeighted(src1=np.copy(classified_img),
                                alpha=0.5,
                                src2=np.copy(img),
                                beta=0.5,
                                gamma=0)
    pcv.print_image(img=overlayed,
                    filename=os.path.join(
                        args.outdir,
                        os.path.basename(args.image)[:-4] + ".overlaid.png"))

    # Extract hue values from the image
    device, h = pcv.rgb2gray_hsv(img=img,
                                 channel="h",
                                 device=device,
                                 debug=args.debug)

    # Extract the plant hue values
    plant_hues = h[np.where(mask == 255)]

    # Initialize hue histogram
    hue_hist = {}
    for i in range(0, 180):
        hue_hist[i] = 0

    # Store all hue values
    hue_values = []

    # Populate histogram
    total_px = len(plant_hues)
    for hue in plant_hues:
        hue_hist[hue] += 1
        hue_values.append(hue)

    # Parse the filename
    genotype, treatment, replicate, timepoint = os.path.basename(
        args.image)[:-4].split("_")
    replicate = replicate.replace("#", "")
    if timepoint[-3:] == "dbi":
        timepoint = -1
    else:
        timepoint = timepoint.replace("dpi", "")

    # Output results
    for i in range(0, 180):
        out.write("\t".join(
            map(str, [
                genotype, treatment, timepoint, replicate, total_px, i,
                hue_hist[i]
            ])) + "\n")
    out.close()

    # Calculate basic statistics
    healthy_sum = int(np.sum(kept_mask_healthy))
    unhealthy_sum = int(np.sum(kept_mask_unhealthy))
    healthy_total_ratio = healthy_sum / float(healthy_sum + unhealthy_sum)
    unhealthy_total_ratio = unhealthy_sum / float(healthy_sum + unhealthy_sum)
    stats = open(args.outfile[:-4] + ".stats.txt", "w")
    stats.write("%s, %f, %f, %f, %f" %
                (os.path.basename(args.image), healthy_sum, unhealthy_sum,
                 healthy_total_ratio, unhealthy_total_ratio) + '\n')
    stats.close()

    # Fit a 3-component Gaussian Mixture Model
    gmm = mixture.GaussianMixture(n_components=3,
                                  covariance_type="full",
                                  tol=0.001)
    gmm.fit(np.expand_dims(hue_values, 1))
    gmm3 = open(args.outfile[:-4] + ".gmm3.txt", "w")
    gmm3.write("%s, %f, %f, %f, %f, %f, %f, %f, %f, %f" %
               (os.path.basename(args.image), gmm.means_.ravel()[0],
                gmm.means_.ravel()[1], gmm.means_.ravel()[2],
                np.sqrt(gmm.covariances_.ravel()[0]),
                np.sqrt(gmm.covariances_.ravel()[1]),
                np.sqrt(gmm.covariances_.ravel()[2]), gmm.weights_.ravel()[0],
                gmm.weights_.ravel()[1], gmm.weights_.ravel()[2]) + '\n')
    gmm3.close()

    # Fit a 2-component Gaussian Mixture Model
    gmm = mixture.GaussianMixture(n_components=2,
                                  covariance_type="full",
                                  tol=0.001)
    gmm.fit(np.expand_dims(hue_values, 1))
    gmm2 = open(args.outfile[:-4] + ".gmm2.txt", "w")
    gmm2.write("%s, %f, %f, %f, %f, %f, %f" %
               (os.path.basename(args.image), gmm.means_.ravel()[0],
                gmm.means_.ravel()[1], np.sqrt(gmm.covariances_.ravel()[0]),
                np.sqrt(gmm.covariances_.ravel()[1]), gmm.weights_.ravel()[0],
                gmm.weights_.ravel()[1]) + '\n')
    gmm2.close()

    # Fit a 1-component Gaussian Mixture Model
    gmm = mixture.GaussianMixture(n_components=1,
                                  covariance_type="full",
                                  tol=0.001)
    gmm.fit(np.expand_dims(hue_values, 1))
    gmm1 = open(args.outfile[:-4] + ".gmm1.txt", "w")
    gmm1.write(
        "%s, %f, %f, %f" %
        (os.path.basename(args.image), gmm.means_.ravel()[0],
         np.sqrt(gmm.covariances_.ravel()[0]), gmm.weights_.ravel()[0]) + '\n')
    gmm1.close()