Skip to content

Commit 33323e6

Browse files
committed
Review text and add next steps section
1 parent eec3f44 commit 33323e6

File tree

1 file changed

+57
-23
lines changed

1 file changed

+57
-23
lines changed

tutorials/site-monitoring-hls.ipynb

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,16 @@
2121
"- Adjust Parameters and Explore Trends \n",
2222
"- Export Results \n",
2323
"- What's Next \n",
24+
"</details>\n",
2425
"\n",
2526
"Monitoring vegetation over time is critical for understanding land health, agricultural productivity, and environmental change. In this notebook, we’ll use Harmonized Landsat and Sentinel-2 (HLS) data from Microsoft’s Planetary Computer to analyze vegetation patterns through time. This intermediate-level notebook focuses on calculating the Normalized Difference Vegetation Index (NDVI) and interpreting changes across a selected site. You’ll build on foundational skills to explore multi-temporal satellite imagery and generate actionable insights from remote sensing data.\n",
2627
"\n",
27-
"➡️ **Next notebooks in this series:** \n",
28-
"- Route Monitoring for Extreme Weather Events (Advanced)\n",
28+
"➡️ **Next notebook in this series:** \n",
29+
"- [Monitoring for Extreme Weather Events](/tutorials/climate-risk.ipynb) (Advanced)\n",
30+
"\n",
31+
"⬅️ **Previous notebook in this series:**\n",
32+
"- [Site Monitoring Foundations](/tutorials/site-monitoring-foundatons.ipynb) (Beginner)\n",
33+
"\n",
2934
"\n",
3035
"## Learning Objectives\n",
3136
"\n",
@@ -49,7 +54,9 @@
4954
"- **AOI (Area of Interest):** A polygon that defines the region you want to monitor.\n",
5055
"- **Time Series:** A sequence of imagery observations collected over time for the same location.\n",
5156
"- **STAC (SpatioTemporal Asset Catalog):** A specification for searching and accessing geospatial data.\n",
52-
"- **Planetary Computer:** A platform hosting open geospatial datasets with a powerful STAC API."
57+
"- **Planetary Computer:** A platform hosting open geospatial datasets with a powerful STAC API.\n",
58+
"\n",
59+
"Want to explore the catalog? Check out the [Planetary Computer](https://planetarycomputer.microsoft.com/)."
5360
]
5461
},
5562
{
@@ -249,7 +256,7 @@
249256
"id": "d7c3dc55-9ffa-4aab-8581-48e3bc46cc42",
250257
"metadata": {},
251258
"source": [
252-
"Use `pystac-client` to search the Planetary Computer catalog for STAC items from the HLS collections for the 2024 growing season (May-October)."
259+
"We will use `pystac-client` to search the Planetary Computer catalog for STAC items from the HLS collections for the 2024 growing season (May-October)."
253260
]
254261
},
255262
{
@@ -286,7 +293,7 @@
286293
"id": "879a05e7-9445-44ca-af88-806f9e0568a2",
287294
"metadata": {},
288295
"source": [
289-
"The STAC search returns all of the items that intersect the provided bounding box and match the other search criteria."
296+
"The STAC search returns all items intersecting the provided bounding box and matching the other search criteria."
290297
]
291298
},
292299
{
@@ -501,7 +508,7 @@
501508
"metadata": {},
502509
"source": [
503510
"### odc-stac\n",
504-
"[odc-stac](https://github.com/opendatacube/odc-stac) is a tool for creating an xarray datacube from a STAC ItemCollection. This is a really useful tool because (if the STAC metadata are well structured) you don't have to think about file paths or projections among other details. `odc-stac` will create a lazily-evaluated datacube that represents a mosaic of STAC items. It will read actual bytes from the assets when you call the `.compute()` method on an `xarray` object.\n",
511+
"[odc-stac](https://github.com/opendatacube/odc-stac) is a tool for creating an xarray datacube from a STAC ItemCollection. This is a handy tool because (if the STAC metadata is well structured) you don't have to think about file paths or projections among other details. `odc-stac` will create a lazily-evaluated datacube that represents a mosaic of STAC items. When you call the `.compute()` method on an `xarray` object, it will read actual bytes from the assets.\n",
505512
"\n",
506513
"You can provide some configuration details for a datacube in the form of a dictionary like this:"
507514
]
@@ -571,9 +578,9 @@
571578
"id": "2db7db7a-6c3d-4237-a3bb-3cdfe1ba5b7d",
572579
"metadata": {},
573580
"source": [
574-
"You can just pass the STAC ItemCollection straight to odc.stac.load but if you want to limit your analysis to a particular area of interest, you can provide a bounding box in the form of the `x` and `y` coordinate tuples. This is particularly useful because often your area of interest will not cover the entirety of the STAC item footprint and therefore you xarray can selectively read chunks of the assets. You can also specify the output CRS and resolution for the array.\n",
581+
"You can pass the STAC ItemCollection straight to odc.stac.load, but if you want to limit your analysis to a particular area of interest, you can provide a bounding box in the form of the `x` and `y` coordinate tuples. This is particularly useful because often your area of interest will not cover the entirety of the STAC item footprint, and therefore, you can selectively read chunks of the assets. You can also specify the output CRS and resolution for the array.\n",
575582
"\n",
576-
"`odc.stac.load` will create a 4 dimensional datacube: band (variables in the Dataset), time, x, and y."
583+
"`odc.stac.load` will create a 4-dimensional data cube: band (variables in the Dataset), time, x, and y."
577584
]
578585
},
579586
{
@@ -1736,7 +1743,7 @@
17361743
"id": "6bb65a09-be7e-4f00-85c3-c84cd43467cc",
17371744
"metadata": {},
17381745
"source": [
1739-
"The rest of the notebook is going to involve a lot of datacubes. The easiest way to describe those datacubes is by rendering time series GIFs, so here are some functions to render GIFs in the notebook."
1746+
"The rest of the notebook will involve many datacubes. The easiest way to describe those datacubes is by rendering time series GIFs, so here are some functions to render GIFs in the notebook."
17401747
]
17411748
},
17421749
{
@@ -1834,7 +1841,7 @@
18341841
"id": "bb680700-57fb-4ff6-a9f4-74f83bc8d3d3",
18351842
"metadata": {},
18361843
"source": [
1837-
"Here is a view of the images collected during the month of July:"
1844+
"Here is a view of the images collected during July:"
18381845
]
18391846
},
18401847
{
@@ -1909,9 +1916,9 @@
19091916
"id": "5b36c0f6-b4be-4be8-99c7-0c8830e5345b",
19101917
"metadata": {},
19111918
"source": [
1912-
"There are a lot of clouds! That could be a problem for our site-monitoring application, but since we have such a rich time series from the combined Landsat and Sentinel-2 dataset we can still work with it as long as we know how to mask out the clouds and cloud shadows.\n",
1919+
"There are a lot of clouds! That could be a problem for our site-monitoring application, but since we have such a rich time series from the combined Landsat and Sentinel-2 dataset, we can still work with it as long as we know how to mask out the clouds and cloud shadows.\n",
19131920
"\n",
1914-
"The Fmask band contains the information we need to identify pixels that we want to exclude from downstream analysis. We need the integer representation for values where any of bits 1, 2, and 3 are `1`."
1921+
"The Fmask band contains the information we need to identify pixels we want to exclude from downstream analysis. We need the integer representation for values where any of bits 1, 2, and 3 are `1`."
19151922
]
19161923
},
19171924
{
@@ -1999,7 +2006,7 @@
19992006
"id": "42de16eb-9606-4975-8197-6af7825de6f0",
20002007
"metadata": {},
20012008
"source": [
2002-
"We can use the valid pixel mask to identify dates where the images are at least 50% valid for our area of interest, then exclude dates that are not."
2009+
"We can use the valid pixel mask to identify dates when the images are at least 50% valid for our area of interest and exclude those that are not."
20032010
]
20042011
},
20052012
{
@@ -2022,7 +2029,7 @@
20222029
"id": "ad6a3db2-ce1e-4bf6-a5c5-e0a2cad23166",
20232030
"metadata": {},
20242031
"source": [
2025-
"`cloud_free_stack` is a filtered version of the full imagery stack where mostly cloudy images have been excluded and invalid pixels have been masked out. We are calling the `comptute()` method to load the data into memory since we are going to use it for several subsequent operations, but you could skip that in real applications."
2032+
"`cloud_free_stack` is a filtered version of the full imagery stack, where mostly cloudy images have been excluded and invalid pixels masked out. We are calling the `compute()` method to load the data into memory since we will use it for several subsequent operations, but you could skip that in real applications."
20262033
]
20272034
},
20282035
{
@@ -2768,7 +2775,7 @@
27682775
"metadata": {},
27692776
"source": [
27702777
"### Land cover data\n",
2771-
"Our crop monitoring analysis will be much simpler if we can use an existing dataset to identify pixels in croplands. The Planetary Computer catalog contains a 10 meter resolution annual land cover dataset for 2017-2023. We can load create a datacube from this dataset that can be easily combined with our satellite imagery datacube."
2778+
"Our crop monitoring analysis would be much simpler if we used an existing dataset to identify pixels in croplands. The Planetary Computer catalog contains a 10-meter resolution annual land cover dataset for 2017-2023. We can create a data cube from this dataset that can be easily combined with our satellite imagery data cube."
27722779
]
27732780
},
27742781
{
@@ -3801,7 +3808,7 @@
38013808
"id": "1d8899c8-f5d8-487d-8e40-b4ef7417ee00",
38023809
"metadata": {},
38033810
"source": [
3804-
"The land cover data are stored as integers, this cell contains the integer/label mapping."
3811+
"The land cover data are stored as integers; this cell contains the integer/label mapping."
38053812
]
38063813
},
38073814
{
@@ -3865,7 +3872,7 @@
38653872
"source": [
38663873
"### NDVI\n",
38673874
"\n",
3868-
"Normalized Difference Vegetation Index (NDVI) is a good proxy for vegetation health so we will use it for our crop monitoring operations. The next cell creates a 3D NDVI array with invalid pixel observations masked out."
3875+
"Normalized Difference Vegetation Index (NDVI) is a good proxy for vegetation health, so we will use it for our crop monitoring operations. The next cell creates a 3D NDVI array with invalid pixel observations masked out."
38693876
]
38703877
},
38713878
{
@@ -3923,7 +3930,7 @@
39233930
"id": "f3c6ec00-2801-48e4-85c7-094c6e5d0db1",
39243931
"metadata": {},
39253932
"source": [
3926-
"To identify anomalies, we are going to compare individual pixel image-over-image NDVI changes to the average image-over-image change for the entire crop land cover class. The following cell computes a smoothed NDVI trend for the entire time series of our imagery stack."
3933+
"To identify anomalies, we will compare individual pixel image-over-image NDVI changes to the average image-over-image change for the entire crop land cover class. The following cell computes a smoothed NDVI trend for the whole time series of our imagery stack."
39273934
]
39283935
},
39293936
{
@@ -3982,7 +3989,7 @@
39823989
"id": "547cd29e-4e22-482c-9fa8-196d3e9f5b62",
39833990
"metadata": {},
39843991
"source": [
3985-
"We will be evaluating the image-over-image change to identify pixels where the change is highly negative when most other crop pixels are showing an increase in NDVI."
3992+
"We will evaluate the image-over-image change to identify pixels where the change is highly negative when most other crop pixels show an increase in NDVI."
39863993
]
39873994
},
39883995
{
@@ -4035,7 +4042,7 @@
40354042
"source": [
40364043
"### Anomaly detection\n",
40374044
"\n",
4038-
"The difference between a pixels's image-over-image change and the expected value (average) for the entire crop land cover class at a given point in time represents the \"deviation from expectation\". Pixels with highly negative values may have experienced an unexpected decline in vegetation health."
4045+
"The difference between a pixel's image-over-image change and the expected value (average) for the entire cropland cover class at a given point in time represents the \"deviation from expectation.” Pixels with highly negative values may have experienced an unexpected decline in vegetation health."
40394046
]
40404047
},
40414048
{
@@ -4103,7 +4110,7 @@
41034110
"id": "8e536dbf-f344-4157-9368-6095b354389a",
41044111
"metadata": {},
41054112
"source": [
4106-
"We are trying to find areas that showed a **decrease** in NDVI when similar pixels were **increasing**.\n",
4113+
"We are trying to find areas that showed **decreased** NDVI when similar pixels **increased**.\n",
41074114
"\n",
41084115
"Now define a threshold for classifying a pixel as having an anomalous decrease in NDVI. Pixels where the difference between its change in NDVI and the expected value is at least as negative as the threshold will be classified as anomalies."
41094116
]
@@ -4180,7 +4187,7 @@
41804187
"id": "a32c9202-a257-4c30-ba82-277e144b2b09",
41814188
"metadata": {},
41824189
"source": [
4183-
"To capture the actual date of the first anomalous event in the time series use the `idxmax` method. This will calculate the time coordinate values where each pixel had a marked anomaly (`True` in the `crop_anomalies` array)."
4190+
"To capture the actual date of the first anomalous event in the time series, use the `idxmax` method. This will calculate the time coordinate values where each pixel had a marked anomaly (`True` in the `crop_anomalies` array)."
41844191
]
41854192
},
41864193
{
@@ -4200,7 +4207,7 @@
42004207
"id": "bb590790-b05a-4a98-bfed-2d021f37491e",
42014208
"metadata": {},
42024209
"source": [
4203-
"For plotting, we want to express the anomaly date as an numeric value like \"number of days from beginning of time series\"."
4210+
"For plotting, we want to express the anomaly date as a numeric value like \"number of days from the beginning of the time series\"."
42044211
]
42054212
},
42064213
{
@@ -4647,6 +4654,33 @@
46474654
"\n",
46484655
"df_out"
46494656
]
4657+
},
4658+
{
4659+
"cell_type": "markdown",
4660+
"id": "4939629d",
4661+
"metadata": {},
4662+
"source": [
4663+
"### ✅ Next Steps \n",
4664+
"This notebook explored time-series vegetation monitoring using HLS (Harmonized Landsat and Sentinel-2) imagery and NDVI to track changes in vegetation health over time for a defined location.\n",
4665+
"\n",
4666+
"To continue learning about site monitoring workflows, check out the next notebook:\n",
4667+
"\n",
4668+
"👉 (climate-risk.ipynb)[tutorial/climate-risk.ipynb] \n",
4669+
"This advanced notebook focuses on extreme weather conditions that could lead to wildfire events. It explores using additional indices, temporal filtering, and geospatial joins to infrastructure data.\n",
4670+
"\n",
4671+
"#### 💡 Other ideas to try: \n",
4672+
"- Compare NDVI trends across multiple years to analyze drought or regrowth \n",
4673+
"- Combine NDVI with precipitation or temperature datasets for deeper insights \n",
4674+
"- Use zonal statistics to summarize NDVI by land parcel or administrative boundary \n",
4675+
"- Export aggregated time series to CSV or GeoParquet for downstream analysis\n",
4676+
"\n",
4677+
"### 📎 Supporting Materials \n",
4678+
"- HLS STAC collection on Planetary Computer \n",
4679+
"- NDVI overview – USGS \n",
4680+
"- STAC specification \n",
4681+
"- Xarray documentation \n",
4682+
"- Planetary Computer Explorer "
4683+
]
46504684
}
46514685
],
46524686
"metadata": {

0 commit comments

Comments
 (0)