{"cells":[{"metadata":{"trusted":true},"cell_type":"code","source":"# Global Wheat Head Challenge\n# Evan J. Williams evanwconsl@gmail.com Princeton, NJ\n# July 27, 2020\n#\n# Hamming Window Algorithm\n# This code using a Hamming window (frequency calculation) to determine the most probable\n# Areas for a Wheat Head flower.\n#\n# NOTE: I did not have enough time to properly train the Hamming window so I have\n#       returned a partially operational test set with a confidence of 1 as default.\n#\n#\n# 1. Install prerequisites: opencv-python, pandas, numpy, Tk, Tcl\n#\n# TRAINING\n# 2. Set Paths of Images\n# 3. Set Path of Train.csv\n# 4. Run Code; check results.\n#\n# TEST\n# 1. Set Paths of Images\n# 2. Run Code\n# 3. Find images with bounding boxes and test_results.csv in output directory\n#\n# Code inspired by https://github.com/Crop-Phenomics-Group/Leaf-GP\n# https://plantmethods.biomedcentral.com/articles/10.1186/s13007-017-0266-3","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#!pip install opencv-python","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#!pip install pandas","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from io import StringIO","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import cv2 as cv","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import os\nimport io","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"%matplotlib inline\nfrom pathlib import Path\nimport pandas as pd\nimport seaborn as sns\nimport numpy as np\nimport matplotlib.pylab as plt\n\nHOME = Path('.').resolve()  # Adapt this to your system\nprint(HOME)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Source code is adapted from the Leaf-GP.\n#\n###################################################################\n#                      Document History                           #\n#                                                                 #\n#    Author: Dr Ji Zhou (EI/JIC)                                  #         \n#               <ji.zhou@tgac.ac.uk>                              # \n#            Dr Christopher Applegate (The Zhou lab, EI)          #\n#               <Christopher.Applegate@earlham.ac.uk>             #\n#                                                                 #\n#    Date: May 2017, Version: 1.18 on TGAC internal Github        #\n#    Changes: 1) Hanlde wheat images series                       #\n#             3) profiled code for distribution                   #\n#             4) optimsed for paralle computing libraries         #\n#                                                                 #\n###################################################################\n\n#STEP 1: Import libraries \n%matplotlib inline\n\n# Essential libraries \nimport numpy as np\nimport scipy as sp\n\n# Computer vision libraries \nfrom scipy import ndimage\nfrom skimage.color import rgb2gray\nfrom skimage import io \n# All other skimage functions will be listed with the code fragments below\nimport cv2\nfrom matplotlib import pyplot as plt # Plotting and generating figures\n\n# Other libraries\nimport math # Feature measures \nimport os # Access the file system\nimport gc # Garbage collection \nimport csv # For results output\nimport sys # for operating systems \ngc.enable()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"OS_Delimiter = '/' ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"##STEP 2.2: Reassemble the directory of the selected image \nimport glob # Find all the pathnames matching a specified pattern\nfullname = '../input/global-wheat-detection/test/'\n# Get the full path of the image\nfullPath = fullname.split(\"/\", -1) \n# The following can handle different platforms\nprint('The selected image: ', fullname)\nprint('The full path: ', fullname)\n\nworkingRootFolder = fullname\n# Locate the image directoriy \nprint(\"The working directory: \",workingRootFolder)\n\nworkDirFolders = workingRootFolder","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Get the full path of the .CSV file\n#@csvPath = csvname.split(\"/\", -1) \n# The following can handle different platforms\n#Platform_Delimiter = PlatformDelimiter()\n#csvPathRef = Platform_Delimiter.join(csvPath[: -1]) + Platform_Delimiter\n#print('The selected file: ', csvname)\n#print('The full path: ', csvPathRef)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Load the data\n#train_df = pd.read_csv(csvname)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Function_1 \n# Return the current date and time based on the OS\nimport time\nfrom datetime import datetime, timedelta\n\ndef LocalTimeStamp(): \n    \"\"\"Detect curret data and time\"\"\"\n    # Current OS date and time \n    currentDate = time.localtime(time.time())\n    return currentDate","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"ourTime = LocalTimeStamp()\nprint(ourTime)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"##STEP 2.4: Set up a result folder to contain the processing results  \n# Sort different image datasets included in the GUI system \n\n# Get the pre-processing date \ntimeStamp = LocalTimeStamp()\ncurYear = timeStamp[0]\ncurMonth = timeStamp[1]\ncurDay = timeStamp[2]\n\n# Assemble a result folder for processed results\nResult_Folder =  'Processed_%d' %curYear + '-%d' %curMonth + '-%d' %curDay\nResult_Directory = workingRootFolder + Result_Folder\n\n# Folder for processed results \nprint('Result folder: ', Result_Directory)","execution_count":null,"outputs":[]},{"metadata":{"scrolled":true,"trusted":true},"cell_type":"code","source":"#STEP 4: Start to loop through the images   \n# The following libraries are used for image processing\nfrom skimage import color\nfrom skimage import filters\nfrom skimage import img_as_float, img_as_ubyte\nfrom skimage import feature\nfrom skimage import exposure\nfrom skimage.transform import rescale\nfrom skimage.morphology import skeletonize\nfrom skimage import measure\nfrom skimage.measure import label, find_contours\nfrom skimage.measure import regionprops\nfrom skimage.morphology import dilation, erosion, remove_small_objects\nfrom skimage.morphology import disk, remove_small_holes\nfrom skimage.morphology import convex_hull_image\nimport matplotlib.patches as mpatches\nfrom skimage.draw import circle\n\n\n##STEP 4.1: Set up a result folder to contain the processing results  \n# pattern match jpg and png files, make sure images are .jpg, .jpeg, or .png\nimageTypes = ('*.jpg', '*.JPG', '*.jpeg', '*.JPEG', '*.png', '*.PNG') \n\nImgDirectory = workingRootFolder\n#print(os.listdir(ImgDirectory))\nImageFiles = []\n# Only select jpg related images \nfor imgType in imageTypes:\n    ImageFiles.extend(glob.glob(ImgDirectory + imgType))    \n# Sort the image files based on their create dates \nImageFiles.sort(key=str.lower) # changed from os.path.getmtime","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"number_of_images = len(ImageFiles)\nprint(number_of_images)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import cv2 as cv","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def freqmean(crop_img,sample_size):\n    \"\"\"\n    function returns a new square based on the variation of color in the image\n    requires the input image grid and the sample size\n    \"\"\"\n    theShape = crop_img.shape\n    rows = theShape[0]\n    cols = theShape[1]\n    rowcount = sample_size\n    colcount = sample_size\n    blank_cropped = np.zeros((rows,cols,3), np.uint8)\n    if rows < sample_size or cols < sample_size:\n        return blank_cropped\n    channels = cv.mean(crop_img)\n    distance = [0,0,0]\n    normalized_channels = [0,0,0]\n    override = 0\n    for row in range(0,rowcount):\n        crop_img_row = crop_img[row]\n        for col in range(0,colcount):\n            crop_img_item = crop_img_row[col]\n            for bgri in range(0,3):\n                color_frequency = abs(crop_img_item[bgri]-channels[bgri])\n                distance[bgri] = distance[bgri] + color_frequency\n                # Normalize Distance\n                override = 0\n                distance[bgri] = np.log(distance[bgri] / channels[bgri])\n                if override == 1:\n                    new_value = 255\n                elif override == 2:\n                    new_value = 0\n                else:\n                    if distance[bgri] <= 0.5:\n                        #new_value = channels[bgri]\n                        new_value = 0\n                        override = 2\n                    else:\n                        if distance[bgri] >= 1.1:\n                            new_value = 255\n                            override = 1\n                            #new_value = channels[bgri] * distance[bgri]\n                        else:\n                            new_value = channels[bgri] \n                            if new_value > 100:\n                                override = 1\n                normalized_channels[bgri] = new_value\n    for row in range(0,4):\n        for col in range(0,4):\n            for bgri in range(0,3):\n                try:\n                    if override == 1:\n                        blank_cropped[row][col][bgri] = 255\n                    elif override == 2:\n                        blank_cropped[row][col][bgri] = 0\n                    else:\n                        blank_cropped[row][col][bgri] = 0\n                        #blank_cropped[row][col][bgri] = normalized_channels[bgri]\n                except:\n                    blank_cropped[row][col][bgri] = 0\n    return blank_cropped","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def getIntensity(crop_img):\n    \"\"\"\n    function returns the intensity of the image.\n    \"\"\"\n    channels = cv2.mean(crop_img)\n    red = channels[2]\n    green = channels[1]\n    blue = channels[0]\n    total_intensity = red + green + blue / 3\n    return total_intensity","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def sparsemean(crop_img,sample_size):\n    \"\"\"\n    function returns a new square based on the variation of color in the image\n    requires the input image grid and the sample size\n    sample size is repeated throughout grid.\n    \"\"\"\n    theShape = crop_img.shape\n    rows = theShape[0]\n    cols = theShape[1]\n    rowcount = sample_size\n    colcount = sample_size\n    blank_cropped = np.zeros((rows,cols,3), np.uint8)\n    if rows < sample_size or cols < sample_size:\n        return blank_cropped\n    channels = cv2.mean(crop_img)\n    distance = [0,0,0]\n    normalized_channels = [0,0,0]\n    override = 0\n    number_of_samples_rows = int(rows/rowcount)\n    for row_sample_no in range(0,number_of_samples_rows):\n        start_row_sample = row_sample_no * rowcount\n        end_row_sample = row_sample_no * (rowcount + 1) - 1\n        for row in range(start_row_sample,end_row_sample):\n            crop_img_row = crop_img[row]\n            number_of_samples_cols = int(cols/colcount)\n            for col_sample_no in range(0,number_of_samples_cols):\n                start_col_sample = col_sample_no * colcount\n                end_col_sample = col_sample_no * (colcount + 1) - 1\n                for col in range(start_col_sample,end_col_sample):\n                    crop_img_item = crop_img_row[col]\n                    for bgri in range(0,3):\n                        color_frequency = abs(crop_img_item[bgri]-channels[bgri])\n                        distance[bgri] = distance[bgri] + color_frequency\n                        # Normalize Distance\n                        override = 0\n                        distance[bgri] = np.log(distance[bgri] / channels[bgri])\n                        if override == 1:\n                            new_value = 255\n                        elif override == 2:\n                            new_value = 0\n                        else:\n                            if distance[bgri] <= 0.5:\n                                #new_value = channels[bgri]\n                                new_value = 0\n                                override = 2\n                            else:\n                                if distance[bgri] >= 1.1:\n                                    new_value = 255\n                                    override = 1\n                                    #new_value = channels[bgri] * distance[bgri]\n                                else:\n                                    new_value = channels[bgri] \n                                    if new_value > 100:\n                                        override = 1\n                        normalized_channels[bgri] = new_value\n    for row in range(0,4):\n        for col in range(0,4):\n            for bgri in range(0,3):\n                try:\n                    if override == 1:\n                        blank_cropped[row][col][bgri] = 255\n                    elif override == 2:\n                        blank_cropped[row][col][bgri] = 0\n                    else:\n                        blank_cropped[row][col][bgri] = 0\n                        #blank_cropped[row][col][bgri] = normalized_channels[bgri]\n                except:\n                    blank_cropped[row][col][bgri] = 0\n    return blank_cropped","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def returnAvgIntensity(img_input,grid_size):\n    \"\"\"\n    Function returns the average intensity of all the grids\n    \"\"\"\n    start_point_x = 0\n    start_point_y = 0\n    end_point_x = start_point_x + grid_size\n    end_point_y = start_point_y + grid_size\n    start_point = (start_point_x,start_point_y)\n    end_point = (end_point_x,end_point_y)\n    width, height, depth = img_input.shape\n    x_range = range(0,width,end_point_x)\n    y_range = range(0,height,end_point_y)\n    blank_image = np.zeros((width,height,3), np.uint8)\n    blocks = []\n    last_square_was_white = 0\n    count_number_of_grids = 0\n    runningTotal = 0\n    for x in x_range:\n        start_point_x = x\n        if x + grid_size > width:\n            end_point_x = 1024\n            w = width - x\n        else:\n            end_point_x = start_point_x + grid_size\n            w = grid_size\n        for y in y_range:\n            start_point_y = y\n            if y + 10 > height:\n                end_point_y = height\n                h = height - y\n            else:\n                end_point_y = end_point_y + grid_size\n                h = grid_size\n            start_point = (start_point_x,start_point_y)\n            end_point = (end_point_x,end_point_y)\n            crop_img = img_input[y:y+h, x:x+w]\n            #blank = freqmean(crop_img,sample_size)\n            runningTotal += getIntensity(crop_img)\n            count_number_of_grids += 1\n    totalIntensity = runningTotal / count_number_of_grids\n    return totalIntensity","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def clearLowIntensity(img_input,grid_size):\n    \"\"\"\n    Function to sample the image with a sample window applied over a grid.\n    Returns a resampled image result in black and white.\n    \"\"\"\n    start_point_x = 0\n    start_point_y = 0\n    end_point_x = start_point_x + grid_size\n    end_point_y = start_point_y + grid_size\n    start_point = (start_point_x,start_point_y)\n    end_point = (end_point_x,end_point_y)\n    width, height, depth = img_input.shape\n    x_range = range(0,width,end_point_x)\n    y_range = range(0,height,end_point_y)\n    blank_image = np.zeros((width,height,3), np.uint8)\n    blocks = []\n    last_square_was_white = 0\n    avgIntensity = returnAvgIntensity(img_input,grid_size)\n    for x in x_range:\n        start_point_x = x\n        if x + grid_size > width:\n            end_point_x = 1024\n            w = width - x\n        else:\n            end_point_x = start_point_x + grid_size\n            w = grid_size\n        for y in y_range:\n            start_point_y = y\n            if y + 10 > height:\n                end_point_y = height\n                h = height - y\n            else:\n                end_point_y = end_point_y + grid_size\n                h = grid_size\n            start_point = (start_point_x,start_point_y)\n            end_point = (end_point_x,end_point_y)\n            crop_img = img_input[y:y+h, x:x+w]\n            #blank = freqmean(crop_img,sample_size)\n            #blank = sparsemean(crop_img,sample_size)\n            intensity = getIntensity(crop_img)\n            if intensity < avgIntensity:\n                red = 0\n                green = 0\n                blue = 0\n                cv2.rectangle(blank_image,(start_point_x,start_point_y),(end_point_x,end_point_y),(red,green,blue),-1)\n            else:\n                blank_image[y:y+h,x:x+w] = crop_img\n    return blank_image","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def highlightWheatHeads(img_input,grid_size,sample_size):\n    \"\"\"\n    Function to sample the image with a sample window applied over a grid.\n    Returns a resampled image result in black and white.\n    \"\"\"\n    start_point_x = 0\n    start_point_y = 0\n    end_point_x = start_point_x + grid_size\n    end_point_y = start_point_y + grid_size\n    start_point = (start_point_x,start_point_y)\n    end_point = (end_point_x,end_point_y)\n    width, height, depth = img_input.shape\n    x_range = range(0,width,end_point_x)\n    y_range = range(0,height,end_point_y)\n    blank_image = np.zeros((width,height,3), np.uint8)\n    blocks = []\n    last_square_was_white = 0\n    for x in x_range:\n        start_point_x = x\n        if x + grid_size > width:\n            end_point_x = 1024\n            w = width - x\n        else:\n            end_point_x = start_point_x + grid_size\n            w = grid_size\n        for y in y_range:\n            start_point_y = y\n            if y + 10 > height:\n                end_point_y = height\n                h = height - y\n            else:\n                end_point_y = end_point_y + grid_size\n                h = grid_size\n            start_point = (start_point_x,start_point_y)\n            end_point = (end_point_x,end_point_y)\n            crop_img = img_input[y:y+h, x:x+w]\n            blank = freqmean(crop_img,sample_size)\n            #blank = sparsemean(crop_img,sample_size)\n            #\n            channels = cv.mean(blank)\n            red = channels[0]\n            green = channels[1]\n            blue = channels[2]\n            cv.rectangle(blank_image,(start_point_x,start_point_y),(end_point_x,end_point_y),(red,green,blue),-1)\n    return blank_image","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def findBoundingBoxes(result_img3,img_tmp,ImageName,image_file_name,forCVSOutput):\n    # https://gist.github.com/bigsnarfdude/d811e31ee17495f82f10db12651ae82d\n\n    import cv2 as cv\n    #import numpy as np\n\n    # read and scale down image\n    # wget https://bigsnarf.files.wordpress.com/2017/05/hammer.png #black and white\n    # wget https://i1.wp.com/images.hgmsites.net/hug/2011-volvo-s60_100323431_h.jpg\n    #img = cv2.pyrDown(cv2.imread('2011-volvo-s60_100323431_h.jpg', cv2.IMREAD_UNCHANGED))\n\n    # threshold image\n    ret, threshed_img = cv.threshold(cv.cvtColor(result_img3, cv.COLOR_BGR2GRAY),\n                    10, 255, cv.THRESH_BINARY)\n    # find contours and get the external one\n\n    contours, hier = cv.findContours(threshed_img, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)\n\n    #image, contours, hier = cv2.findContours(threshed_img, cv2.RETR_TREE,\n    #                cv2.CHAIN_APPROX_SIMPLE)\n\n    # with each contour, draw boundingRect in green\n    # a minAreaRect in red and\n    # a minEnclosingCircle in blue\n    boundingRectangles = []\n    totalBoxes = 0\n    for c in contours:\n        # get the bounding rect\n        x, y, w, h = cv.boundingRect(c)\n        if w>50 and h>50 and abs(w-h)<100:\n            totalBoxes += 1\n            rectangle = (x,y,w,h)\n            boundingRectangles.append(rectangle)\n            # draw a green rectangle to visualize the bounding rect\n            cv.rectangle(img_tmp, (x, y), (x+w, y+h), (266, 0, 0), 2)\n            # Write the coordinates of this rectangle to the .CSV file.\n            # We don't know the confidence so we put 1.0.\n            predictionString = \"1.0 \" + str(x) + \" \" + str(y) + \" \" + str(w) + \" \" + str(h)\n            theDataLine = [image_file_name,predictionString]\n            # Close the file object as the format of the csv is finished\n            # Create the pandas DataFrame \n            forCVSOutput.append(theDataLine)\n            \n    #print(totalBoxes)\n    #cv.drawContours(result_img3, contours, -1, (255, 255, 0), 1)\n\n    #cv2.imshow(\"contours\", result_img3)\n\n    #cv2.imshow(\"contours\", img)\n    return totalBoxes","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def paintTrainingBoxes(img_tmp,image_training_set):\n    trainset = pd.DataFrame(image_training_set)\n    boxes = trainset.loc[:,'bbox']\n    for boxstr in boxes:\n        boxstrlen = len(boxstr)\n        boxstr = boxstr[1:boxstrlen-1]\n        items = boxstr.split(',')\n        x=items[0]\n        y=items[1]\n        w=items[2]\n        h=items[3]\n        x=x.strip()\n        y=y.strip()\n        w=w.strip()\n        h=h.strip()\n        x=int(float(x))\n        y=int(float(y))\n        w=int(float(w))\n        h=int(float(h))\n        cv.rectangle(img_tmp, (x, y), (x+w, y+h), (0,255, 0), 2)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Loop through the entire set of images\n# And create resulting images setored in the Processed Directory\n#for i in range(0,number_of_images):\n#for i in range(0,number_of_images):\nResult_Directory = \"/kaggle/working/\"\nforCVSOutput = []\nfor i in range(0,number_of_images):\n    tmp_IMG_File = ImageFiles[i]\n    Image_FullName = tmp_IMG_File\n    # Other parts of an image name    \n    ImageName_Length = len(Image_FullName)\n    ImageDirectory = tmp_IMG_File[:(ImageName_Length * -1)]\n    ImageName = Image_FullName[:-4]\n    # Retrieve the Image Training Set\n    #image_training_set = train_df[train_df.image_id == ImageName]\n    # Buffer the image file to the memory\n    img_tmp = cv.imread(tmp_IMG_File)\n    #img = cv2.imread(tmp_IMG_File)\n    Resize_Ratio = 1.0/(img_tmp.shape[0]/1024.0) # dynamically transfer the original resolution \n    image_resized = img_as_ubyte(rescale(img_tmp.copy(), Resize_Ratio)) \n    # Apply the Hamming window to highlight the wheat heads.\n    #result_img4 = clearLowIntensity(img_tmp,10)\n    result_img3 = highlightWheatHeads(img_tmp,10,4)\n    image_file_name = os.path.basename(tmp_IMG_File)\n    image_id = image_file_name[:-4]\n    findBoundingBoxes(result_img3,img_tmp,ImageName,image_id,forCVSOutput)\n    #paintTrainingBoxes(img_tmp,image_training_set)\n    #fig, ax1 = plt.subplots(ncols=1, nrows=1, figsize=(30, 20))\n    #ax1.set_xlabel('Length', fontsize=16)\n    #ax1.set_ylabel('Width', fontsize=16)\n    #ax1.set_title('Resized Image', fontsize=16)\n    #x1.imshow(img_tmp)\n    figname = Result_Directory + image_file_name\n    plt.imsave(figname,img_tmp)\n    # Create the pandas DataFrame \nresults = pd.DataFrame(forCVSOutput, columns = ['image_id', 'PredictionString']) \nresults.to_csv(\"/kaggle/working/submission.csv\",index=False)","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}