{"id":28383394,"url":"https://github.com/dronemapper-io/cropanalysis","last_synced_at":"2025-06-25T14:31:55.915Z","repository":{"id":47023649,"uuid":"219025787","full_name":"dronemapper-io/CropAnalysis","owner":"dronemapper-io","description":"A jupyter notebook with crop analysis algorithms utilizing digital elevation models and multi-spectral imagery (R-G-B-NIR-Rededge-Thermal)","archived":false,"fork":false,"pushed_at":"2019-11-07T22:44:55.000Z","size":13626,"stargazers_count":40,"open_issues_count":2,"forks_count":15,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-06-06T05:34:21.962Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Jupyter Notebook","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dronemapper-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-11-01T16:48:24.000Z","updated_at":"2025-03-22T21:53:20.000Z","dependencies_parsed_at":"2022-08-26T09:41:08.842Z","dependency_job_id":null,"html_url":"https://github.com/dronemapper-io/CropAnalysis","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dronemapper-io/CropAnalysis","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dronemapper-io%2FCropAnalysis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dronemapper-io%2FCropAnalysis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dronemapper-io%2FCropAnalysis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dronemapper-io%2FCropAnalysis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dronemapper-io","download_url":"https://codeload.github.com/dronemapper-io/CropAnalysis/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dronemapper-io%2FCropAnalysis/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261891764,"owners_count":23225794,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-30T05:13:16.596Z","updated_at":"2025-06-25T14:31:55.906Z","avatar_url":"https://github.com/dronemapper-io.png","language":"Jupyter Notebook","funding_links":[],"categories":[],"sub_categories":[],"readme":"## DroneMapper Crop Analysis\nA jupyter notebook with crop analysis algorithms utilizing digital elevation models, dtm and multi-spectral imagery (R-G-B-NIR-Rededge-Thermal) from a [MicaSense Altum](https://micasense.com) sensor processed with [DroneMapper Remote Expert](https://dronemapper.com).\n\nDue to limitations on git file sizes, you will need to download the GeoTIFF data for this project from the following url: [https://dronemapper.com/software/DroneMapper_CropAnalysis_Data.zip](https://dronemapper.com/software/DroneMapper_CropAnalysis_Data.zip). Once that has been completed, extract the TIF files into the notebook data directory matching the structure below.\n\n### Included Data\n* data/DrnMppr-DEM-AOI.tif - 32bit georeferenced digital elevation model\n* data/DrnMppr-ORT-AOI.tif - 16bit georeferenced orthomosaic (Red-Green-Blue-NIR-Rededge-Thermal)\n* data/DrnMppr-DTM-AOI.tif - 32bit georeferenced dtm\n* data/plant_count.shp - plant count AOI\n* data/plots_1.shp - plot 1 AOI\n* data/plots_2.shp - plot 2 AOI\n\n### Algorithms\n* plot volume/biomass\n* plot canopy height\n* plot ndvi zonal statistics\n* plot thermals\n* plant count\n\n## Notes\nThese basic algorithms are intended to get you started and interested in multi-spectral processing and analysis.\n\nThe orthomosaic, digital elevation model, and dtm were clipped to an AOI using [GlobalMapper](https://bluemarblegeo.com). The shapefile plots were also generated using GlobalMapper grid tool. We highly recommend GlobalMapper for GIS work!\n\nWe cloned the MicaSense [imageprocessing](https://github.com/dronemapper-io/imageprocessing) repository and created the `Batch Processing DroneMapper.ipynb` notebook which allows you to quickly align and stack a Altum or RedEdge dataset creating the correct TIF files with EXIF/GPS metadata preserved. These stacked TIF files are then directly loaded into DroneMapper Remote Expert for processing.\n\nThis notebook assumes the user has basic knowledge of setting up their python environment, importing libraries and working inside jupyter.\n\n## Do More!\nImplement additional algorithms like NDRE or alternative methods for plant counts. Submit a pull request!\n\n\n```python\n%load_ext autoreload\n%autoreload 2\n```\n\n### Load Digital Elevation Model and Orthomosaic\n\n\n```python\nimport numpy as np\nimport rasterio\nfrom matplotlib import pyplot as plt\nimport matplotlib as mpl\n\nimport earthpy as et\nimport earthpy.plot as ep\nimport earthpy.spatial as es\n\n# ensure libspatialindex-dev is installed via apt-get or yum\n\n%matplotlib inline\n\ndem = rasterio.open('data/DrnMppr-DEM-AOI.tif')\northo = rasterio.open('data/DrnMppr-ORT-AOI.tif')\n\ndem_arr = dem.read()\northo_arr = ortho.read()\n\n# mask elevation \u003c= 0\nelevation = dem_arr[0]\nelevation[elevation \u003c= 0] = np.nan\n\n# rededge mask \u003c= 0\nmasked_re = np.ma.masked_where(ortho_arr[4] \u003c= 0, ortho_arr[4])\n\n# generate hillshade\nhillshade = es.hillshade(elevation, altitude=30, azimuth=210)\n\nfig, ax = plt.subplots(1, 4, figsize=(20, 20))\n\n# plot\nep.plot_rgb(ortho_arr, ax=ax[0], rgb=[0, 1, 2], title=\"Red Green Blue\", stretch=True)\nep.plot_rgb(ortho_arr, ax=ax[1], rgb=[3, 1, 2], title=\"NIR Green Blue\", stretch=True)\nep.plot_bands(masked_re, ax=ax[2], scale=False, cmap=\"terrain\", title=\"RedEdge\")\nep.plot_bands(elevation, ax=ax[3], scale=False, cmap=\"terrain\", title=\"Digital Elevation Model\")\nax[3].imshow(hillshade, cmap=\"Greys\", alpha=0.5)\nplt.show()\n\n```\n\n\n![png](doc/output_3_0.png)\n\n\n### Load Plot 1 AOI and Generate NDVI\n\n\n```python\nimport geopandas as gpd\nfrom rasterio.plot import plotting_extent\n\nnp.seterr(divide='ignore', invalid='ignore')\n\nfig, ax = plt.subplots(figsize=(20, 20))\nplot_extent = plotting_extent(dem_arr[0], dem.transform)\n\n# generate ndvi\nndvi = es.normalized_diff(ortho_arr[3], ortho_arr[0])\nep.plot_bands(ndvi, \n              ax=ax, \n              cmap=\"RdYlGn\",\n              title=\"NDVI \u0026 Plots\", \n              scale=False, \n              vmin=-1, \n              vmax=1, \n              extent=plot_extent)\n\nplots = gpd.read_file('data/plots_1.shp')\nplots.plot(ax=ax,\n           color='None', \n           edgecolor='black', \n           linewidth=1)\n\n# show plot names\nplots.apply(lambda x: ax.annotate(s=x.NAME, xy=x.geometry.centroid.coords[0], ha='center'), axis=1);\nplt.show()\n```\n\n\n![png](doc/output_5_0.png)\n\n\n### Generate NDVI Zonal Statistics For Each Plot\n\n\n```python\nimport rasterstats as rs\nfrom shapely.geometry import Polygon\nfrom IPython.display import display\n\n# compute zonal statistics on each plot\nplot_zs = rs.zonal_stats(plots, \n                         ndvi, \n                         nodata=0, \n                         affine=dem.transform, \n                         geojson_out=True, \n                         copy_properties=True, \n                         stats=\"count min mean max median std\")\n\n# build dataframe and display first 5 records\nplot_df = gpd.GeoDataFrame.from_features(plot_zs)\ndisplay(plot_df.head())\nplot_df.to_csv('output/aoi1_plot_mean_ndvi.csv')\n\nfig, ax = plt.subplots(figsize=(20, 20))\n\n# plot ndvi\nep.plot_bands(ndvi, \n              ax=ax, \n              cmap=\"RdYlGn\",\n              title=\"NDVI \u0026 Plot Mean Values\", \n              scale=False, \n              vmin=-1, \n              vmax=1, \n              extent=plot_extent)\n\n# overlay the mean ndvi value color for each plot and all pixels inside plot\nplot_df.plot('mean',\n             ax=ax, \n             cmap='RdYlGn', \n             edgecolor='black', \n             linewidth=1,\n             vmin=-1,\n             vmax=1)\n\n# show plot mean values\nplot_df.apply(lambda x: ax.annotate(s='{:.2}'.format(x['mean']), xy=x.geometry.centroid.coords[0], ha='center'), axis=1);\nplt.show()\n```\n\n\n\u003cdiv\u003e\n\u003ctable border=\"1\" class=\"dataframe\"\u003e\n  \u003cthead\u003e\n    \u003ctr style=\"text-align: right;\"\u003e\n      \u003cth\u003e\u003c/th\u003e\n      \u003cth\u003egeometry\u003c/th\u003e\n      \u003cth\u003eLAYER\u003c/th\u003e\n      \u003cth\u003eMAP_NAME\u003c/th\u003e\n      \u003cth\u003eNAME\u003c/th\u003e\n      \u003cth\u003emin\u003c/th\u003e\n      \u003cth\u003emax\u003c/th\u003e\n      \u003cth\u003emean\u003c/th\u003e\n      \u003cth\u003ecount\u003c/th\u003e\n      \u003cth\u003estd\u003c/th\u003e\n      \u003cth\u003emedian\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003cth\u003e0\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289583.708 5130289.226 0.000, 2895...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features\u003c/td\u003e\n      \u003ctd\u003e2\u003c/td\u003e\n      \u003ctd\u003e0.188461\u003c/td\u003e\n      \u003ctd\u003e0.873092\u003c/td\u003e\n      \u003ctd\u003e0.438559\u003c/td\u003e\n      \u003ctd\u003e4444\u003c/td\u003e\n      \u003ctd\u003e0.078921\u003c/td\u003e\n      \u003ctd\u003e0.436500\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e1\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289588.705 5130289.052 0.000, 2895...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features\u003c/td\u003e\n      \u003ctd\u003e3\u003c/td\u003e\n      \u003ctd\u003e0.193214\u003c/td\u003e\n      \u003ctd\u003e0.887971\u003c/td\u003e\n      \u003ctd\u003e0.445282\u003c/td\u003e\n      \u003ctd\u003e4440\u003c/td\u003e\n      \u003ctd\u003e0.091090\u003c/td\u003e\n      \u003ctd\u003e0.425304\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e2\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289593.702 5130288.877 0.000, 2895...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features\u003c/td\u003e\n      \u003ctd\u003e4\u003c/td\u003e\n      \u003ctd\u003e0.232222\u003c/td\u003e\n      \u003ctd\u003e0.890147\u003c/td\u003e\n      \u003ctd\u003e0.552864\u003c/td\u003e\n      \u003ctd\u003e4440\u003c/td\u003e\n      \u003ctd\u003e0.112440\u003c/td\u003e\n      \u003ctd\u003e0.519746\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e3\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289598.699 5130288.703 0.000, 2896...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features\u003c/td\u003e\n      \u003ctd\u003e5\u003c/td\u003e\n      \u003ctd\u003e0.090825\u003c/td\u003e\n      \u003ctd\u003e0.865083\u003c/td\u003e\n      \u003ctd\u003e0.530295\u003c/td\u003e\n      \u003ctd\u003e4444\u003c/td\u003e\n      \u003ctd\u003e0.110570\u003c/td\u003e\n      \u003ctd\u003e0.515392\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e4\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289603.696 5130288.528 0.000, 2896...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features\u003c/td\u003e\n      \u003ctd\u003e6\u003c/td\u003e\n      \u003ctd\u003e0.104697\u003c/td\u003e\n      \u003ctd\u003e0.922450\u003c/td\u003e\n      \u003ctd\u003e0.536660\u003c/td\u003e\n      \u003ctd\u003e4442\u003c/td\u003e\n      \u003ctd\u003e0.132731\u003c/td\u003e\n      \u003ctd\u003e0.495813\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003c/div\u003e\n\n\n\n![png](doc/output_7_1.png)\n\n\nYou can view the Plot 1 AOI mean plot NDVI values: [aoi1_plot_mean_ndvi.csv](output/aoi1_plot_mean_ndvi.csv)\n### Load Plot 2 AOI \u0026 Compute DEM Canopy Mean Height For Each Plot\n\n\n```python\nplots = gpd.read_file('data/plots_2.shp')\nplt.rcParams.update({'font.size': 8})\n\n# compute zonal statistics on each plot\nplot_zs = rs.zonal_stats(plots, \n                         elevation, \n                         nodata=0, \n                         affine=dem.transform, \n                         geojson_out=True, \n                         copy_properties=True, \n                         stats=\"count min mean max median std\")\n\n# build dataframe and display first 5 records\nplot_df = gpd.GeoDataFrame.from_features(plot_zs)\ndisplay(plot_df.head())\nplot_df.to_csv('output/aoi2_plot_mean_height.csv')\n\nfig, ax = plt.subplots(figsize=(20, 20))\n\n# plot dem\nep.plot_bands(elevation, \n              ax=ax, \n              cmap=\"terrain\",\n              title=\"DEM \u0026 Plot Canopy Mean Height\", \n              scale=False, \n              extent=plot_extent)\n\n# overlay the mean dem value color for each plot and all pixels inside plot\nplot_df.plot('mean',\n             ax=ax, \n             cmap='terrain', \n             edgecolor='black', \n             linewidth=1)\n\n# show plot mean values\nplot_df.apply(lambda x: ax.annotate(s='{0:0.1f}'.format(x['mean']), xy=x.geometry.centroid.coords[0], ha='center'), axis=1);\nplt.show()\n```\n\n\n\u003cdiv\u003e\n\u003ctable border=\"1\" class=\"dataframe\"\u003e\n  \u003cthead\u003e\n    \u003ctr style=\"text-align: right;\"\u003e\n      \u003cth\u003e\u003c/th\u003e\n      \u003cth\u003egeometry\u003c/th\u003e\n      \u003cth\u003eLAYER\u003c/th\u003e\n      \u003cth\u003eMAP_NAME\u003c/th\u003e\n      \u003cth\u003eNAME\u003c/th\u003e\n      \u003cth\u003emin\u003c/th\u003e\n      \u003cth\u003emax\u003c/th\u003e\n      \u003cth\u003emean\u003c/th\u003e\n      \u003cth\u003ecount\u003c/th\u003e\n      \u003cth\u003estd\u003c/th\u003e\n      \u003cth\u003emedian\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003cth\u003e0\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289707.875 5130279.812 1182.502, 2...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features - Coverage/Quad\u003c/td\u003e\n      \u003ctd\u003e1\u003c/td\u003e\n      \u003ctd\u003e360.129120\u003c/td\u003e\n      \u003ctd\u003e363.381683\u003c/td\u003e\n      \u003ctd\u003e361.141294\u003c/td\u003e\n      \u003ctd\u003e3318\u003c/td\u003e\n      \u003ctd\u003e1.091593\u003c/td\u003e\n      \u003ctd\u003e360.441467\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e1\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289712.189 5130279.586 1190.569, 2...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features - Coverage/Quad\u003c/td\u003e\n      \u003ctd\u003e2\u003c/td\u003e\n      \u003ctd\u003e360.131866\u003c/td\u003e\n      \u003ctd\u003e363.382446\u003c/td\u003e\n      \u003ctd\u003e361.710297\u003c/td\u003e\n      \u003ctd\u003e3316\u003c/td\u003e\n      \u003ctd\u003e1.215926\u003c/td\u003e\n      \u003ctd\u003e361.927017\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e2\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289716.503 5130279.360 1183.212, 2...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features - Coverage/Quad\u003c/td\u003e\n      \u003ctd\u003e3\u003c/td\u003e\n      \u003ctd\u003e360.117279\u003c/td\u003e\n      \u003ctd\u003e363.384766\u003c/td\u003e\n      \u003ctd\u003e361.138592\u003c/td\u003e\n      \u003ctd\u003e3310\u003c/td\u003e\n      \u003ctd\u003e1.122890\u003c/td\u003e\n      \u003ctd\u003e360.425781\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e3\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289720.817 5130279.134 1182.668, 2...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features - Coverage/Quad\u003c/td\u003e\n      \u003ctd\u003e4\u003c/td\u003e\n      \u003ctd\u003e360.110443\u003c/td\u003e\n      \u003ctd\u003e363.387207\u003c/td\u003e\n      \u003ctd\u003e361.915436\u003c/td\u003e\n      \u003ctd\u003e3322\u003c/td\u003e\n      \u003ctd\u003e1.258644\u003c/td\u003e\n      \u003ctd\u003e362.585251\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e4\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289725.131 5130278.908 1182.782, 2...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features - Coverage/Quad\u003c/td\u003e\n      \u003ctd\u003e5\u003c/td\u003e\n      \u003ctd\u003e360.006683\u003c/td\u003e\n      \u003ctd\u003e363.377991\u003c/td\u003e\n      \u003ctd\u003e361.558501\u003c/td\u003e\n      \u003ctd\u003e3320\u003c/td\u003e\n      \u003ctd\u003e1.305164\u003c/td\u003e\n      \u003ctd\u003e360.546524\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003c/div\u003e\n\n\n\n![png](doc/output_9_1.png)\n\n\nYou can view the Plot 2 AOI mean plot height values: [aoi2_plot_mean_height.csv](output/aoi2_plot_mean_height.csv)\n### Compute Thermal Mean For Each Plot\nThe thermal band (6) in the processed orthomosaic shows stitching artifacts which could likely be improved using more accurate pre-processing alignment and de-distortion algorithms. You can find more information about these functions in the MicaSense imageprocessing github repository. See notes at the top of this notebook.\n\n\n```python\n# thermal mask \u003c= 0\nmasked_thermal = np.ma.masked_where(ortho_arr[5] \u003c= 0, ortho_arr[5])\n\n# compute zonal statistics on each plot\nplot_zs = rs.zonal_stats(plots, \n                         masked_thermal, \n                         nodata=0, \n                         affine=dem.transform, \n                         geojson_out=True, \n                         copy_properties=True, \n                         stats=\"count min mean max median std\")\n\n# build dataframe and display first 5 records\nplot_df = gpd.GeoDataFrame.from_features(plot_zs)\ndisplay(plot_df.head())\nplot_df.to_csv('output/aoi2_plot_mean_thermal.csv')\n\nfig, ax = plt.subplots(1, 2, figsize=(20, 20))\n\n# plot thermal\nep.plot_bands(masked_thermal,\n              ax=ax[0], \n              scale=False, \n              cmap=\"gist_gray\", \n              title=\"Thermal\",\n              extent=plot_extent)\n\nplots.plot(ax=ax[0], color='None', edgecolor='white', linewidth=1)\n\n# display thermal plot\nplot_df.plot('mean',\n             ax=ax[1], \n             cmap='inferno', \n             edgecolor='black',\n             linewidth=1,\n             legend=True)\n\n\n# show plot mean values\nplot_df.apply(lambda x: ax[1].annotate(s='{0:0.1f}'.format(x['mean']), xy=x.geometry.centroid.coords[0], ha='center'), axis=1);\nplt.show()\n```\n\n\n\u003cdiv\u003e\n\u003ctable border=\"1\" class=\"dataframe\"\u003e\n  \u003cthead\u003e\n    \u003ctr style=\"text-align: right;\"\u003e\n      \u003cth\u003e\u003c/th\u003e\n      \u003cth\u003egeometry\u003c/th\u003e\n      \u003cth\u003eLAYER\u003c/th\u003e\n      \u003cth\u003eMAP_NAME\u003c/th\u003e\n      \u003cth\u003eNAME\u003c/th\u003e\n      \u003cth\u003emin\u003c/th\u003e\n      \u003cth\u003emax\u003c/th\u003e\n      \u003cth\u003emean\u003c/th\u003e\n      \u003cth\u003ecount\u003c/th\u003e\n      \u003cth\u003estd\u003c/th\u003e\n      \u003cth\u003emedian\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003cth\u003e0\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289707.875 5130279.812 1182.502, 2...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features - Coverage/Quad\u003c/td\u003e\n      \u003ctd\u003e1\u003c/td\u003e\n      \u003ctd\u003e30008.0\u003c/td\u003e\n      \u003ctd\u003e30431.0\u003c/td\u003e\n      \u003ctd\u003e30196.628692\u003c/td\u003e\n      \u003ctd\u003e3318\u003c/td\u003e\n      \u003ctd\u003e120.982058\u003c/td\u003e\n      \u003ctd\u003e30193.0\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e1\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289712.189 5130279.586 1190.569, 2...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features - Coverage/Quad\u003c/td\u003e\n      \u003ctd\u003e2\u003c/td\u003e\n      \u003ctd\u003e30068.0\u003c/td\u003e\n      \u003ctd\u003e30560.0\u003c/td\u003e\n      \u003ctd\u003e30333.192702\u003c/td\u003e\n      \u003ctd\u003e3316\u003c/td\u003e\n      \u003ctd\u003e123.856093\u003c/td\u003e\n      \u003ctd\u003e30332.0\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e2\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289716.503 5130279.360 1183.212, 2...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features - Coverage/Quad\u003c/td\u003e\n      \u003ctd\u003e3\u003c/td\u003e\n      \u003ctd\u003e29792.0\u003c/td\u003e\n      \u003ctd\u003e30645.0\u003c/td\u003e\n      \u003ctd\u003e30266.030211\u003c/td\u003e\n      \u003ctd\u003e3310\u003c/td\u003e\n      \u003ctd\u003e170.831207\u003c/td\u003e\n      \u003ctd\u003e30275.5\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e3\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289720.817 5130279.134 1182.668, 2...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features - Coverage/Quad\u003c/td\u003e\n      \u003ctd\u003e4\u003c/td\u003e\n      \u003ctd\u003e29790.0\u003c/td\u003e\n      \u003ctd\u003e30700.0\u003c/td\u003e\n      \u003ctd\u003e30386.137267\u003c/td\u003e\n      \u003ctd\u003e3322\u003c/td\u003e\n      \u003ctd\u003e201.266919\u003c/td\u003e\n      \u003ctd\u003e30391.0\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e4\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289725.131 5130278.908 1182.782, 2...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features - Coverage/Quad\u003c/td\u003e\n      \u003ctd\u003e5\u003c/td\u003e\n      \u003ctd\u003e29618.0\u003c/td\u003e\n      \u003ctd\u003e30691.0\u003c/td\u003e\n      \u003ctd\u003e30209.904518\u003c/td\u003e\n      \u003ctd\u003e3320\u003c/td\u003e\n      \u003ctd\u003e292.299392\u003c/td\u003e\n      \u003ctd\u003e30262.0\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003c/div\u003e\n\n\n\n![png](doc/output_11_1.png)\n\n\nYou can view the Plot 2 AOI mean plot thermal values: [aoi2_plot_mean_thermal.csv](output/aoi2_plot_mean_thermal.csv)\n### Load Plot 1 AOI \u0026 Compute Volume/Biomass For Each Plot\n\n\n```python\n# we will use the masked dtm and masked elevation data\nplots = gpd.read_file('data/plots_1.shp')\n\ndtm = rasterio.open('data/DrnMppr-DTM-AOI.tif')\ndtm_arr = dtm.read()\n\n# mask dtm \u003c= 0\nelevation_dtm = dtm_arr[0]\nelevation_dtm[elevation_dtm \u003c= 0] = np.nan\n\n# create canopy model\ncanopy_model = (elevation - elevation_dtm)\n\n# compute zonal statistics on each plot\nplot_zs = rs.zonal_stats(plots, \n                         canopy_model, \n                         nodata=0, \n                         affine=dem.transform, \n                         geojson_out=True, \n                         copy_properties=True, \n                         stats=\"sum count min mean max\")\n\n# get pixel size\ntransform = dtm.transform\npixel_size_x = transform[0]\npixel_size_y = -transform[4]\n\n# calculate volume\ndef volume(pixel_count, pixel_sum):\n    return (pixel_sum / pixel_count * (pixel_size_x * pixel_size_y) * pixel_count)\n\n# build dataframe and display first 5 records\nplot_df = gpd.GeoDataFrame.from_features(plot_zs)\n\n# add columns to dataframe\nplot_df['volume_m3'] = plot_df.apply(lambda x: volume(x['count'], x['sum']), axis=1)\nplot_df['area_m2'] = plot_df.apply(lambda x: x.geometry.area, axis=1)\n\ndisplay(plot_df.head())\nplot_df.to_csv('output/aoi1_plot_volume.csv')\n\nfig, ax = plt.subplots(figsize=(20, 20))\n\n# plot canopy model\nep.plot_bands(canopy_model, \n              ax=ax, \n              scale=False, \n              cmap=\"RdYlGn_r\", \n              title=\"Canopy Model\", \n              extent=plot_extent,\n              vmin=0,\n              vmax=5)\n\n# display volume plot\nplot_df.plot('volume_m3',\n             ax=ax, \n             cmap='hot', \n             edgecolor='black',\n             linewidth=1,\n             legend=True)\n\n# show plot names\nplot_df.apply(lambda x: ax.annotate(s='{0:0.1f}'.format(x['volume_m3']), xy=x.geometry.centroid.coords[0], ha='center'), axis=1);\nplt.show()\n```\n\n\n\u003cdiv\u003e\n\u003ctable border=\"1\" class=\"dataframe\"\u003e\n  \u003cthead\u003e\n    \u003ctr style=\"text-align: right;\"\u003e\n      \u003cth\u003e\u003c/th\u003e\n      \u003cth\u003egeometry\u003c/th\u003e\n      \u003cth\u003eLAYER\u003c/th\u003e\n      \u003cth\u003eMAP_NAME\u003c/th\u003e\n      \u003cth\u003eNAME\u003c/th\u003e\n      \u003cth\u003emin\u003c/th\u003e\n      \u003cth\u003emax\u003c/th\u003e\n      \u003cth\u003emean\u003c/th\u003e\n      \u003cth\u003ecount\u003c/th\u003e\n      \u003cth\u003esum\u003c/th\u003e\n      \u003cth\u003evolume_m3\u003c/th\u003e\n      \u003cth\u003earea_m2\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003cth\u003e0\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289583.708 5130289.226 0.000, 2895...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features\u003c/td\u003e\n      \u003ctd\u003e2\u003c/td\u003e\n      \u003ctd\u003e-0.153473\u003c/td\u003e\n      \u003ctd\u003e3.525543\u003c/td\u003e\n      \u003ctd\u003e1.532524\u003c/td\u003e\n      \u003ctd\u003e4444\u003c/td\u003e\n      \u003ctd\u003e6810.538483\u003c/td\u003e\n      \u003ctd\u003e76.650368\u003c/td\u003e\n      \u003ctd\u003e50.0\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e1\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289588.705 5130289.052 0.000, 2895...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features\u003c/td\u003e\n      \u003ctd\u003e3\u003c/td\u003e\n      \u003ctd\u003e-0.094482\u003c/td\u003e\n      \u003ctd\u003e3.575226\u003c/td\u003e\n      \u003ctd\u003e1.646664\u003c/td\u003e\n      \u003ctd\u003e4440\u003c/td\u003e\n      \u003ctd\u003e7311.188690\u003c/td\u003e\n      \u003ctd\u003e82.285021\u003c/td\u003e\n      \u003ctd\u003e50.0\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e2\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289593.702 5130288.877 0.000, 2895...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features\u003c/td\u003e\n      \u003ctd\u003e4\u003c/td\u003e\n      \u003ctd\u003e-0.070648\u003c/td\u003e\n      \u003ctd\u003e3.992493\u003c/td\u003e\n      \u003ctd\u003e1.884596\u003c/td\u003e\n      \u003ctd\u003e4440\u003c/td\u003e\n      \u003ctd\u003e8367.608276\u003c/td\u003e\n      \u003ctd\u003e94.174676\u003c/td\u003e\n      \u003ctd\u003e50.0\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e3\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289598.699 5130288.703 0.000, 2896...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features\u003c/td\u003e\n      \u003ctd\u003e5\u003c/td\u003e\n      \u003ctd\u003e0.032928\u003c/td\u003e\n      \u003ctd\u003e4.575989\u003c/td\u003e\n      \u003ctd\u003e2.969443\u003c/td\u003e\n      \u003ctd\u003e4444\u003c/td\u003e\n      \u003ctd\u003e13196.202637\u003c/td\u003e\n      \u003ctd\u003e148.518915\u003c/td\u003e\n      \u003ctd\u003e50.0\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e4\u003c/th\u003e\n      \u003ctd\u003ePOLYGON Z ((289603.696 5130288.528 0.000, 2896...\u003c/td\u003e\n      \u003ctd\u003eCoverage/Quad\u003c/td\u003e\n      \u003ctd\u003eUser Created Features\u003c/td\u003e\n      \u003ctd\u003e6\u003c/td\u003e\n      \u003ctd\u003e0.074188\u003c/td\u003e\n      \u003ctd\u003e5.105408\u003c/td\u003e\n      \u003ctd\u003e3.155879\u003c/td\u003e\n      \u003ctd\u003e4442\u003c/td\u003e\n      \u003ctd\u003e14018.412506\u003c/td\u003e\n      \u003ctd\u003e157.772617\u003c/td\u003e\n      \u003ctd\u003e50.0\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003c/div\u003e\n\n\n\n![png](doc/output_13_1.png)\n\n\nYou can view the Plot 1 AOI plot volume/biomass values: [aoi1_plot_volume.csv](output/aoi1_plot_volume.csv)\n### Load Plant Count AOI \u0026 Count Plants\n\n\n```python\nimport pandas as pd\nimport cv2\n\n# we will use the masked dtm and masked elevation data\nplot = gpd.read_file('data/plant_count.shp')\n\n# mask the dtm and dem to the plot extent\ndtm_clip, dtm_transform = rasterio.mask.mask(dtm, plot.geometry, crop=True)\ndem_clip, dem_transform = rasterio.mask.mask(dem, plot.geometry, crop=True)\nrgb_clip, ort_transform = rasterio.mask.mask(ortho, plot.geometry, crop=True)\n\n# filter elevations \u003c= 0\ndtm_clip[dtm_clip \u003c= 0] = np.nan\ndem_clip[dem_clip \u003c= 0] = np.nan\n\n# filter plant model ground pixels\nplant_model = (dem_clip - dtm_clip)\nplant_model[plant_model \u003c= 0.20] = np.nan\n\n# generate binary image\nbinary_image = 255 * (plant_model[0] \u003e 0)\nbinary_image_int = cv2.bitwise_not(binary_image.astype(np.uint8))\n\nfig, ax = plt.subplots(1, 2, figsize=(20, 20))\n\n# plot plant model\nep.plot_bands(plant_model[0], \n              ax=ax[0], \n              scale=False, \n              cmap=\"terrain\", \n              title=\"Plant Model\", \n              extent=plot_extent)\n\n# plot binary image\nep.plot_bands(binary_image_int, \n              ax=ax[1], \n              scale=False, \n              cmap=\"binary\", \n              title=\"Binary Plant Mask\", \n              extent=plot_extent)\nplt.show()\n\nfig, ax = plt.subplots(figsize=(20,20))\n\n# setup basic blob detector\nparams = cv2.SimpleBlobDetector_Params()\nparams.minDistBetweenBlobs = 1\nparams.filterByColor = False\nparams.blobColor = 255\nparams.filterByArea = True\nparams.minArea = 5; \nparams.maxArea = 5000; \nparams.filterByCircularity = False\nparams.filterByConvexity = False\nparams.filterByInertia = True\nparams.minInertiaRatio = 0.01\nparams.maxInertiaRatio = 1\ndetector = cv2.SimpleBlobDetector_create(params)\n\n# build new rgb image\nrgb = np.stack((rgb_clip[0], rgb_clip[1], rgb_clip[2]), -1)\nrgb = es.bytescale(rgb, high=255, low=0)\n\n# resize binary_image_int to match rgb\nbinary_image_int = cv2.resize(binary_image_int, dsize=(rgb.shape[1], rgb.shape[0]), interpolation=cv2.INTER_CUBIC)\n\n# detect\nkeypoints = detector.detect(binary_image_int)\n\nprint('Plant count: {}'.format(len(keypoints)))\n\n# plot plant count\nplants = cv2.drawKeypoints(rgb, keypoints, np.array([]), (0, 255, 0), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)\nplt.imshow(plants)\nplt.show()\n\n# show full values in dataframe display\npd.set_option('display.float_format', lambda x: '%.6f' % x)\nplant_coordinates = {}\n\n# extract plant geo positions\nfor i, keypoint in enumerate(keypoints):\n    plant_center = ortho.xy(keypoint.pt[0], keypoint.pt[1])\n    plant_coordinates[i] = [plant_center[0], plant_center[1]]\n    \nplant_df = pd.DataFrame.from_dict(plant_coordinates, orient='index', columns=['UTMX', 'UTMY'])\ndisplay(plant_df.head())\nplant_df.to_csv('output/plant_count.csv')\n```\n\n\n![png](doc/output_15_0.png)\n\n\n    Plant count: 310\n\n\n\n![png](doc/output_15_2.png)\n\n\n\n\u003cdiv\u003e\n\u003ctable border=\"1\" class=\"dataframe\"\u003e\n  \u003cthead\u003e\n    \u003ctr style=\"text-align: right;\"\u003e\n      \u003cth\u003e\u003c/th\u003e\n      \u003cth\u003eUTMX\u003c/th\u003e\n      \u003cth\u003eUTMY\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003cth\u003e0\u003c/th\u003e\n      \u003ctd\u003e289622.411171\u003c/td\u003e\n      \u003ctd\u003e5130240.899705\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e1\u003c/th\u003e\n      \u003ctd\u003e289621.849705\u003c/td\u003e\n      \u003ctd\u003e5130244.325966\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e2\u003c/th\u003e\n      \u003ctd\u003e289622.101569\u003c/td\u003e\n      \u003ctd\u003e5130248.858655\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e3\u003c/th\u003e\n      \u003ctd\u003e289621.865086\u003c/td\u003e\n      \u003ctd\u003e5130253.863387\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth\u003e4\u003c/th\u003e\n      \u003ctd\u003e289621.436280\u003c/td\u003e\n      \u003ctd\u003e5130258.158493\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003c/div\u003e\n\n\nYou can view the plant counts: [plant_count.csv](output/plant_count.csv)\n### Thanks! Keep an eye out for future notebooks and algorithms! [DroneMapper.com](https://dronemapper.com)\n\n\n```python\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdronemapper-io%2Fcropanalysis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdronemapper-io%2Fcropanalysis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdronemapper-io%2Fcropanalysis/lists"}