diff --git a/book/labs/lab3c.ipynb b/book/labs/lab3c.ipynb
index 49572b8..5228d3c 100644
--- a/book/labs/lab3c.ipynb
+++ b/book/labs/lab3c.ipynb
@@ -34,7 +34,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": 1,
"id": "4cfc231a",
"metadata": {},
"outputs": [
@@ -54,7 +54,7 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": 2,
"id": "500bd678",
"metadata": {},
"outputs": [
@@ -1024,20 +1024,72 @@
"6. Perform accuracy assessment.\n",
"7. Export the classified image.\n",
"\n",
- "In this section, you will learn how to perform supervised classification using the CART algorithm. You can easily adapt the code to other supervised classification algorithms, such as SVM and Random Forest. We will use labeled training data from the [USGS National Land Cover Database (NLCD)](https://developers.google.com/earth-engine/datasets/catalog/USGS_NLCD_RELEASES_2019_REL_NLCD) dataset and train a CART classifier using the training data. The trained classifier will then be applied to the Landsat-9 image to generate a classified image.\n",
+ "In this section, you will learn how to perform supervised classification using the CART algorithm. You can easily adapt the code to other supervised classification algorithms, such as Support Vector Machine (SVM) and Random Forest. We will use labeled training data from the [USGS National Land Cover Database (NLCD)](https://developers.google.com/earth-engine/datasets/catalog/USGS_NLCD_RELEASES_2019_REL_NLCD) dataset and train a CART classifier using the training data. The trained classifier will then be applied to the Landsat-9 image to generate a classified image.\n",
"\n",
"First, filter the [Landsat 8 image collection](https://developers.google.com/earth-engine/datasets/catalog/LANDSAT_LC08_C02_T1_L2) to select a cloud-free image acquired in 2019 for your region of interest:"
]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 6,
"id": "7744225e",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "7ba534c560ad4a35bb729e3fbbc16810",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Map(center=[29.999999999999993, -90], controls=(WidgetControl(options=['position', 'transparent_bg'], widget=S…"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
+ "# This initializes an interactive map object using the geemap library\n",
"m = geemap.Map()\n",
- "point = ee.Geometry.Point([-122.4439, 37.7538])\n",
+ "# This creates a point geometry object in Earth Engine, representing a location at coordinates\n",
+ "point = ee.Geometry.Point([-90, 30])\n",
"\n",
"image = (\n",
" ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')\n",
@@ -1049,7 +1101,7 @@
")\n",
"\n",
"image = image.multiply(0.0000275).add(-0.2).set(image.toDictionary())\n",
- "vis_params = {'min': 0, 'max': 0.3, 'bands': ['SR_B5', 'SR_B4', 'SR_B3']}\n",
+ "vis_params = {'min': 0, 'max': 0.3, 'bands': ['SR_B4', 'SR_B3', 'SR_B2']}\n",
"\n",
"m.center_object(point, 8)\n",
"m.add_layer(image, vis_params, \"Landsat-8\")\n",
@@ -1066,10 +1118,60 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 7,
"id": "3d1b024c",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "dac9c7ea32bd4a99a679ce18cb6d8e1b",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Tree(nodes=(Node(name='Image (7 bands)', nodes=(Node(icon='file', name='type: Image'), Node(name='bands: List…"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"geemap.get_info(image)"
]
@@ -1084,10 +1186,55 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 8,
"id": "82abc9ca",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "'2019-03-06'"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"image.get('DATE_ACQUIRED').getInfo()"
]
@@ -1102,10 +1249,55 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 9,
"id": "bb65c0d2",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0.02"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"image.get('CLOUD_COVER').getInfo()"
]
@@ -1120,12 +1312,65 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 10,
"id": "aa203ea5",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "7ba534c560ad4a35bb729e3fbbc16810",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Map(bottom=27329.0, center=[30.043602744036605, -90.0277576052237], controls=(WidgetControl(options=['position…"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
+ "# This line loads the NLCD 2019 dataset from the USGS (United States Geological Survey) as an Earth Engine Image object. \n",
+ "# The ee.Image function creates an image object from the specified data source, in this case, the 2019 NLCD.\n",
"nlcd = ee.Image('USGS/NLCD_RELEASES/2019_REL/NLCD/2019')\n",
+ "# This selects the land cover classification band from the NLCD image.\n",
"landcover = nlcd.select('landcover').clip(image.geometry())\n",
"m.add_layer(landcover, {}, 'NLCD Landcover')\n",
"m"
@@ -1141,17 +1386,55 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 11,
"id": "b7b4abf8",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
+ "# his code samples points from the landcover image within a specified region. \n",
"points = landcover.sample(\n",
" **{\n",
" 'region': image.geometry(),\n",
" 'scale': 30,\n",
" 'numPixels': 5000,\n",
+ " # seed: A seed value for randomization, ensuring reproducibility of the sample. A seed of 0 is used.\n",
" 'seed': 0,\n",
+ " # When set to True, the sampled points are returned as geometries, meaning they include geographic coordinates for each sampled pixel.\n",
" 'geometries': True,\n",
" }\n",
")\n",
@@ -1169,10 +1452,52 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 14,
"id": "369a8a6f",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "5000\n"
+ ]
+ }
+ ],
"source": [
"print(points.size().getInfo())"
]
@@ -1189,15 +1514,60 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 15,
"id": "9c7e8798",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
+ "# This defines a list of band names that correspond to different spectral bands from the satellite image.\n",
"bands = ['SR_B1', 'SR_B2', 'SR_B3', 'SR_B4', 'SR_B5', 'SR_B6', 'SR_B7']\n",
"label = 'landcover'\n",
+ "# This part of the code creates a new set of features by sampling the selected bands from the image at the locations defined in the points collection. \n",
+ "\n",
+ "#.sampleRegions(): This function samples the pixel values from the image at specific geographical locations. \n",
+ "# These locations are defined by the points collection that was created earlier. \n",
"features = image.select(bands).sampleRegions(\n",
- " **{'collection': points, 'properties': [label], 'scale': 30}\n",
+ " **{'collection': points, \n",
+ " # 'properties': [label]: This ensures that the land cover property ('landcover') is included in the sampled data. \n",
+ " # It adds this label as an attribute to each feature, so the feature collection has both reflectance and the landcover labels.\n",
+ " 'properties': [label], \n",
+ " 'scale': 30\n",
+ " }\n",
")"
]
},
@@ -1211,10 +1581,156 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 16,
"id": "50ddd0e4",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " SR_B1 | \n",
+ " SR_B2 | \n",
+ " SR_B3 | \n",
+ " SR_B4 | \n",
+ " SR_B5 | \n",
+ " SR_B6 | \n",
+ " SR_B7 | \n",
+ " landcover | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " 0.014445 | \n",
+ " 0.016343 | \n",
+ " 0.028470 | \n",
+ " 0.024867 | \n",
+ " 0.192370 | \n",
+ " 0.110365 | \n",
+ " 0.055227 | \n",
+ " 42 | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " 0.023657 | \n",
+ " 0.029433 | \n",
+ " 0.050003 | \n",
+ " 0.038205 | \n",
+ " 0.228697 | \n",
+ " 0.108825 | \n",
+ " 0.062295 | \n",
+ " 95 | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " 0.021320 | \n",
+ " 0.027507 | \n",
+ " 0.057235 | \n",
+ " 0.044255 | \n",
+ " 0.185495 | \n",
+ " 0.134235 | \n",
+ " 0.087072 | \n",
+ " 90 | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " 0.009632 | \n",
+ " 0.015627 | \n",
+ " 0.037105 | \n",
+ " 0.037600 | \n",
+ " 0.172268 | \n",
+ " 0.101923 | \n",
+ " 0.057922 | \n",
+ " 81 | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " 0.015022 | \n",
+ " 0.032073 | \n",
+ " 0.064330 | \n",
+ " 0.072607 | \n",
+ " 0.026600 | \n",
+ " 0.007433 | \n",
+ " 0.008065 | \n",
+ " 11 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " SR_B1 SR_B2 SR_B3 SR_B4 SR_B5 SR_B6 SR_B7 \\\n",
+ "0 0.014445 0.016343 0.028470 0.024867 0.192370 0.110365 0.055227 \n",
+ "1 0.023657 0.029433 0.050003 0.038205 0.228697 0.108825 0.062295 \n",
+ "2 0.021320 0.027507 0.057235 0.044255 0.185495 0.134235 0.087072 \n",
+ "3 0.009632 0.015627 0.037105 0.037600 0.172268 0.101923 0.057922 \n",
+ "4 0.015022 0.032073 0.064330 0.072607 0.026600 0.007433 0.008065 \n",
+ "\n",
+ " landcover \n",
+ "0 42 \n",
+ "1 95 \n",
+ "2 90 \n",
+ "3 81 \n",
+ "4 11 "
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"geemap.ee_to_df(features.limit(5))"
]
@@ -1224,49 +1740,151 @@
"id": "76ca5d84",
"metadata": {},
"source": [
- "The training dataset is ready. You can now train a classifier using the training data. The following code initializes a [CART classifier](https://developers.google.com/earth-engine/apidocs/ee-classifier-smilecart) and trains it using the training data:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "bb844524",
- "metadata": {},
- "outputs": [],
- "source": [
- "params = {\n",
- "\n",
- " 'features': features,\n",
- " 'classProperty': label,\n",
- " 'inputProperties': bands,\n",
+ "The training dataset is ready. You can now train a classifier using the training data. The following code initializes a [CART classifier](https://developers.google.com/earth-engine/apidocs/ee-classifier-smilecart) and trains it using the training data:\n",
"\n",
- "}\n",
- "classifier = ee.Classifier.smileCart(maxNodes=None).train(**params)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "4e488e8d",
- "metadata": {},
- "source": [
- "The `features` parameter specifies the training data. The `classProperty` parameter specifies the property name of the training data that contains the class labels. The `inputProperties` parameter specifies the property names of the training data that contain the predictor values.\n",
+ "Summary of the Code:\n",
+ "You define a set of parameters (params) that include the training data (features), the property to be predicted (label), and the input features (bands).\n",
+ "You then create a CART decision tree classifier using Earth Engine’s smileCart algorithm, and train it using the specified parameters.\n",
+ "The classifier will learn to map the input spectral bands (from satellite imagery) to the land cover classes based on the provided training data.\n",
"\n",
- "All Earth Engine classifiers have a `train()` function to train the classifier using the training data. The CART classifier has a `maxNodes` parameter to specify the maximum number of nodes in the tree. The default value is `None`, which means that the tree will be grown until all leaves are pure or until all leaves contain less than 5 training samples.\n",
+ "The **params syntax unpacks the dictionary so that the features, classProperty, and inputProperties are passed directly as arguments to the training function.\n",
"\n",
- "Since the classifier has been trained, you can now apply it to the Landsat image to generate a classified image. Make sure you use the same spectral bands as the training data. The following code applies the trained classifier to the selected Landsat image and adds the classified image with a random color palette to the map:"
+ "features provides the training data,\n",
+ "classProperty defines the class to be predicted, and\n",
+ "inputProperties are the features used for prediction."
]
},
{
"cell_type": "code",
- "execution_count": null,
- "id": "d3dfa244",
+ "execution_count": 18,
+ "id": "bb844524",
"metadata": {},
- "outputs": [],
- "source": [
- "classified = image.select(bands).classify(classifier).rename('landcover')\n",
- "m.add_layer(classified.randomVisualizer(), {}, 'Classified')\n",
- "m"
- ]
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Definine a dictionary params that contains the necessary inputs for training the classifier.\n",
+ "params = {\n",
+ "\n",
+ " 'features': features,\n",
+ " # This represents the class or category that you want the classifier to predict\n",
+ " 'classProperty': label,\n",
+ " # This specifies the input features (predictor variables) that the classifier will use to make predictions.\n",
+ " 'inputProperties': bands,\n",
+ "\n",
+ "}\n",
+ "\n",
+ "# This argument specifies the maximum number of nodes (branches) in the decision tree. \n",
+ "# When set to None, the classifier is allowed to grow the tree fully, meaning there is no constraint on the size of the tree unless other stopping criteria are met.\n",
+ "classifier = ee.Classifier.smileCart(maxNodes=None).train(**params)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4e488e8d",
+ "metadata": {},
+ "source": [
+ "The `features` parameter specifies the training data. The `classProperty` parameter specifies the property name of the training data that contains the class labels. The `inputProperties` parameter specifies the property names of the training data that contain the predictor values.\n",
+ "\n",
+ "All Earth Engine classifiers have a `train()` function to train the classifier using the training data. The CART classifier has a `maxNodes` parameter to specify the maximum number of nodes in the tree. The default value is `None`, which means that the tree will be grown until all leaves are pure or until all leaves contain less than 5 training samples.\n",
+ "\n",
+ "Since the classifier has been trained, you can now apply it to the Landsat image to generate a classified image. Make sure you use the same spectral bands as the training data. The following code applies the trained classifier to the selected Landsat image and adds the classified image with a random color palette to the map:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "d3dfa244",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "7ba534c560ad4a35bb729e3fbbc16810",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Map(bottom=216735.0, center=[29.92458888321647, -90.84697723388672], controls=(WidgetControl(options=['positio…"
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "classified = image.select(bands).classify(classifier).rename('landcover')\n",
+ "m.add_layer(classified.randomVisualizer(), {}, 'Classified')\n",
+ "m"
+ ]
},
{
"cell_type": "markdown",
@@ -1278,10 +1896,60 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 20,
"id": "8a6720d5",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "5ea6ec1128d84e09a56dcd770d82d03f",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Tree(nodes=(Node(name='Image (3 bands)', nodes=(Node(icon='file', name='type: Image'), Node(icon='file', name…"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"geemap.get_info(nlcd)"
]
@@ -1296,10 +1964,45 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 21,
"id": "8011f6a0",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
"class_values = nlcd.get('landcover_class_values')\n",
"class_palette = nlcd.get('landcover_class_palette')\n",
@@ -1319,12 +2022,62 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 24,
"id": "d4b93e1e",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "7ba534c560ad4a35bb729e3fbbc16810",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Map(bottom=54446.0, center=[29.835878945929952, -89.62783813476564], controls=(WidgetControl(options=['positio…"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "m.add_layer(classified, {}, 'Land cover')\n",
+ "m.add_layer(classified, {}, 'Land cover') # Empty dictionary {} is where visualization parameters (such as color mapping, opacity, etc.) would normally be specified.\n",
"m.add_legend(title=\"Land cover type\", builtin_legend='NLCD')\n",
"m"
]
@@ -1341,10 +2094,59 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 26,
"id": "b6a6ba03",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "5f097bf592c94fcf81977a036d9f562c",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "supervised.tif: | | 0.00/65.4M (raw) [ 0.0%] in 00:00 (eta: ?)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
"geemap.download_ee_image(\n",
" landcover,\n",
@@ -1368,13 +2170,63 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 27,
"id": "f63fe03a",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "f277f321a6344ce09f04b0dba675bee1",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Map(center=[29.999999999999993, -90], controls=(WidgetControl(options=['position', 'transparent_bg'], widget=S…"
+ ]
+ },
+ "execution_count": 27,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"m = geemap.Map()\n",
- "point = ee.Geometry.Point([-122.4439, 37.7538])\n",
+ "point = ee.Geometry.Point([-90.0, 30.0])\n",
"\n",
"img = (\n",
" ee.ImageCollection('COPERNICUS/S2_SR')\n",
@@ -1402,283 +2254,232 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 28,
"id": "420d632a",
"metadata": {},
- "outputs": [],
- "source": [
- "lc = ee.Image('ESA/WorldCover/v100/2020')\n",
- "classValues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100]\n",
- "remapValues = ee.List.sequence(0, 10)\n",
- "label = 'lc'\n",
- "lc = lc.remap(classValues, remapValues).rename(label).toByte()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "b30dc93a",
- "metadata": {},
- "source": [
- "Next, add the ESA land cover as a band of the Sentinel-2 reflectance image and sample 100 pixels at a 10m scale from each land cover class within the region of interest:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "2e3a1c42",
- "metadata": {},
- "outputs": [],
- "source": [
- "sample = img.addBands(lc).stratifiedSample(**{\n",
- " 'numPoints': 100,\n",
- " 'classBand': label,\n",
- " 'region': img.geometry(),\n",
- " 'scale': 10,\n",
- " 'geometries': True\n",
- "})"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "191430b1",
- "metadata": {},
- "source": [
- "Add a random value field to the sample and use it to approximately split 80% of the features into a training set and 20% into a validation set:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "af21fdcc",
- "metadata": {},
- "outputs": [],
- "source": [
- "sample = sample.randomColumn()\n",
- "trainingSample = sample.filter('random <= 0.8')\n",
- "validationSample = sample.filter('random > 0.8')"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "cd5b8262",
- "metadata": {},
- "source": [
- "With the training data ready, we can train a random forest classifier using the training data.\n",
- "\n",
- "The following code trains a random forest classifier with 10 trees:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "75243563",
- "metadata": {},
- "outputs": [],
- "source": [
- "trainedClassifier = ee.Classifier.smileRandomForest(numberOfTrees=10).train(**{\n",
- " 'features': trainingSample,\n",
- " 'classProperty': label,\n",
- " 'inputProperties': img.bandNames()\n",
- "})"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "c1cb7ffb",
- "metadata": {},
- "source": [
- "To get information about the trained classifier:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "af64d3ad",
- "metadata": {},
- "outputs": [],
- "source": [
- "print('Results of trained classifier', trainedClassifier.explain().getInfo())"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "bd5e8acc",
- "metadata": {},
- "source": [
- "To get a confusion matrix and overall accuracy for the training sample:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "e3e4e434",
- "metadata": {},
- "outputs": [],
- "source": [
- "trainAccuracy = trainedClassifier.confusionMatrix()\n",
- "trainAccuracy.getInfo()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "03ba6671",
- "metadata": {},
- "source": [
- "```text\n",
- "[[81, 0, 0, 0, 1, 0, 0, 0, 0],\n",
- " [0, 83, 1, 0, 0, 0, 0, 0, 0],\n",
- " [1, 1, 73, 2, 0, 0, 0, 0, 0],\n",
- " [0, 0, 1, 77, 0, 0, 0, 0, 0],\n",
- " [0, 0, 0, 1, 81, 1, 0, 0, 0],\n",
- " [0, 1, 2, 3, 2, 70, 0, 0, 2],\n",
- " [0, 0, 0, 0, 0, 0, 0, 0, 0],\n",
- " [0, 0, 0, 0, 0, 0, 0, 71, 0],\n",
- " [1, 0, 0, 1, 0, 4, 0, 0, 71]]\n",
- "```\n",
- "\n",
- "The horizontal axis of the confusion matrix corresponds to the input classes, and the vertical axis corresponds to the output classes. The rows and columns start at class 0 and increase sequentially up to the maximum class value, so some rows or columns might be empty if the input classes aren't 0-based or sequential. That's the reason why we remapped the ESA land cover class values to a 0-based sequential series earlier. Note that your confusion matrix may look slightly different from the one shown above as the training data is randomly sampled.\n",
- "\n",
- "The overall accuracy essentially tells us what proportion of al the reference sites was mapped correctly. The overall accuracy is usually expressed as a percent, with 100% accuracy being a perfect classification where all reference sites were classified correctly."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "4eba21ed",
- "metadata": {},
- "outputs": [],
- "source": [
- "trainAccuracy.accuracy().getInfo()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "015601ea",
- "metadata": {},
- "source": [
- "The Kappa Coefficient is generated from a statistical test to evaluate the accuracy of a classification. Kappa essentially evaluates how well the classification performed as compared to just randomly assigning values, i.e., did the classification do better than random? The Kappa Coefficient can range from -1 to 1. A value of 0 indicated that the classification is no better than a random classification. A negative number indicates the classification is significantly worse than random. A value close to 1 indicates that the classification is significantly better than random."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "3501e191",
- "metadata": {},
- "outputs": [],
- "source": [
- "trainAccuracy.kappa().getInfo()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "a16d8a3d",
- "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
- "To get a confusion matrix and overall accuracy for the validation sample:"
+ "lc = ee.Image('ESA/WorldCover/v100/2020')\n",
+ "classValues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100]\n",
+ "remapValues = ee.List.sequence(0, 10)\n",
+ "label = 'lc'\n",
+ "lc = lc.remap(classValues, remapValues).rename(label).toByte()"
]
},
{
- "cell_type": "code",
- "execution_count": null,
- "id": "9460b3dd",
+ "cell_type": "markdown",
+ "id": "b30dc93a",
"metadata": {},
- "outputs": [],
"source": [
- "validationSample = validationSample.classify(trainedClassifier)\n",
- "validationAccuracy = validationSample.errorMatrix(label, 'classification')\n",
- "validationAccuracy.getInfo()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "9c692acc",
- "metadata": {},
- "source": [
- "```text\n",
- "[[13, 1, 3, 0, 1, 0, 0, 0, 0],\n",
- " [0, 11, 2, 0, 1, 0, 0, 0, 2],\n",
- " [1, 0, 12, 7, 1, 2, 0, 0, 0],\n",
- " [0, 3, 6, 9, 3, 1, 0, 0, 0],\n",
- " [2, 0, 3, 0, 10, 2, 0, 0, 0],\n",
- " [1, 1, 1, 3, 7, 6, 0, 0, 1],\n",
- " [0, 0, 0, 0, 0, 0, 0, 0, 0],\n",
- " [0, 0, 0, 0, 0, 0, 0, 29, 0],\n",
- " [2, 2, 2, 0, 2, 2, 0, 0, 13]]\n",
- "```\n",
- "\n",
- "To compute the overall accuracy for the validation sample:"
+ "Next, add the ESA land cover as a band of the Sentinel-2 reflectance image and sample 100 pixels at a 10m scale from each land cover class within the region of interest:"
]
},
{
"cell_type": "code",
- "execution_count": null,
- "id": "b95c085e",
+ "execution_count": 29,
+ "id": "2e3a1c42",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
- "validationAccuracy.accuracy().getInfo()"
+ "sample = img.addBands(lc).stratifiedSample(**{\n",
+ " 'numPoints': 100,\n",
+ " 'classBand': label,\n",
+ " 'region': img.geometry(),\n",
+ " 'scale': 10,\n",
+ " 'geometries': True\n",
+ "})"
]
},
{
"cell_type": "markdown",
- "id": "35553000",
+ "id": "191430b1",
"metadata": {},
"source": [
- "Producer's Accuracy, also known as the Sensitivity or Recall, is a measure of how well a classifier correctly identifies positive instances. It is the ratio of true positive classifications to the total number of actual positive instances. This metric is used to evaluate the performance of a classifier when the focus is on minimizing false negatives (i.e., instances that are actually positive but are classified as negative)."
+ "Add a random value field to the sample and use it to approximately split 80% of the features into a training set and 20% into a validation set:"
]
},
{
"cell_type": "code",
- "execution_count": null,
- "id": "714c5020",
+ "execution_count": 30,
+ "id": "af21fdcc",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
- "validationAccuracy.producersAccuracy().getInfo()"
+ "sample = sample.randomColumn()\n",
+ "trainingSample = sample.filter('random <= 0.8')\n",
+ "validationSample = sample.filter('random > 0.8')"
]
},
{
"cell_type": "markdown",
- "id": "ae161d14",
+ "id": "cd5b8262",
"metadata": {},
"source": [
- "On the other hand, Consumer's Accuracy, also known as Precision, is a measure of how well a classifier correctly identifies negative instances. It is the ratio of true negative classifications to the total number of actual negative instances. This metric is used to evaluate the performance of a classifier when the focus is on minimizing false positives (i.e., instances that are actually negative but are classified as positive)."
+ "With the training data ready, we can train a random forest classifier using the training data.\n",
+ "\n",
+ "The following code trains a random forest classifier with 10 trees:"
]
},
{
"cell_type": "code",
- "execution_count": null,
- "id": "92cdbc2b",
+ "execution_count": 31,
+ "id": "75243563",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
- "validationAccuracy.consumersAccuracy().getInfo()"
+ "trainedClassifier = ee.Classifier.smileRandomForest(numberOfTrees=10).train(**{\n",
+ " 'features': trainingSample,\n",
+ " 'classProperty': label,\n",
+ " 'inputProperties': img.bandNames()\n",
+ "})"
]
},
{
"cell_type": "markdown",
- "id": "e970bad6",
- "metadata": {},
- "source": [
- "The confusion matrices can be saved as a csv file:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "b75dd521",
+ "id": "c1cb7ffb",
"metadata": {},
- "outputs": [],
"source": [
- "import csv\n",
- "\n",
- "with open(\"training.csv\", \"w\", newline=\"\") as f:\n",
- " writer = csv.writer(f)\n",
- " writer.writerows(trainAccuracy.getInfo())\n",
- "\n",
- "with open(\"validation.csv\", \"w\", newline=\"\") as f:\n",
- " writer = csv.writer(f)\n",
- " writer.writerows(validationAccuracy.getInfo())"
+ "To get information about the trained classifier:"
]
},
{
@@ -1691,10 +2492,45 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 39,
"id": "15129c08",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
"imgClassified = img.classify(trainedClassifier)"
]
@@ -1709,10 +2545,60 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 40,
"id": "8ba15931",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "f277f321a6344ce09f04b0dba675bee1",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Map(bottom=54523.0, center=[29.651589558640676, -89.94060259796309], controls=(WidgetControl(options=['positio…"
+ ]
+ },
+ "execution_count": 40,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"classVis = {\n",
" 'min': 0,\n",
@@ -1722,9 +2608,9 @@
"}\n",
"m.add_layer(lc, classVis, 'ESA Land Cover', False)\n",
"m.add_layer(imgClassified, classVis, 'Classified')\n",
- "m.add_layer(trainingSample, {'color': 'black'}, 'Training sample')\n",
- "m.add_layer(validationSample, {'color': 'white'}, 'Validation sample')\n",
- "m.add_legend(title='Land Cover Type', builtin_legend='ESA_WorldCover')\n",
+ "#m.add_layer(trainingSample, {'color': 'black'}, 'Training sample')\n",
+ "#m.add_layer(validationSample, {'color': 'white'}, 'Validation sample')\n",
+ "#m.add_legend(title='Land Cover Type', builtin_legend='ESA_WorldCover')\n",
"m.center_object(img)\n",
"m"
]
diff --git a/book/labs/supervised.tif b/book/labs/supervised.tif
new file mode 100644
index 0000000..30e6f48
Binary files /dev/null and b/book/labs/supervised.tif differ