Visualize LandTrendr Outputs¶
Example of how to visualize LandTrendr outputs using the Python visualization tools
Takes pre-exported LT stack output and provides a visualization of loss and gain years, duration, and magnitude
Copyright 2025 Ian Housman
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
import os,sys
sys.path.append(os.getcwd())
#Module imports
try:
import geeViz.getImagesLib as gil
except:
!python -m pip install geeViz
import geeViz.getImagesLib as gil
import geeViz.changeDetectionLib as cdl
ee = gil.ee
#Set up to mapper objects to use
#Can use the default one first
Map = gil.Map
print('done')
Initializing GEE
Successfully initialized
done
Set up parameters¶
We will first set up several parameters to help us visualize LandTrendr outputs
# Define user parameters:
# Specify which years to look at
# Available years are 1984-2021
startYear = 1984
endYear = 2024
# Which property stores which band/index LandTrendr was run across
bandPropertyName = "band"
# Specify which bands to run across
# Set to None to run all available bands
# Available bands include: ['NBR', 'NDMI', 'NDSI', 'NDVI', 'blue', 'brightness', 'green', 'greenness', 'nir', 'red', 'swir1', 'swir2', 'tcAngleBG', 'wetness']
bandNames = None
# Specify if output is an array image or not
arrayMode = True
Visualize and Inspect LandTrendr Annual Fitted Time Series¶
First, we’ll convert the raw array image outputs into collection of fitted, magnitude, slope, duration, etc values for each year
We will visualize the standard deviation of the tasseled cap brightness, greenness, and wetness values on the map, but all annual fitted values will be available for querying when you double-click the map
Note: layer drawing and querying can be slow since all LandTrendr post-processing computation is being performed in GEE on-th-fly.
Map.clearMap()
# Bring in LCMS LandTrendr outputs (see other examples that include LCMS final data)
lt = ee.ImageCollection("projects/lcms-tcc-shared/assets/CONUS/Base-Learners/LandTrendr-Collection")
print(
"Available bands/indices:",
lt.aggregate_histogram(bandPropertyName).keys().getInfo(),
)
lt_props = lt.first().toDictionary().getInfo()
print(lt_props)
# Convert stacked outputs into collection of fitted, magnitude, slope, duration, etc values for each year
# Divide by 10000 (0.0001) so values are back to original values (0-1 or -1-1)
lt_fit = cdl.batchSimpleLTFit(
lt,
startYear,
endYear,
None,
bandPropertyName,
arrayMode,
lt_props["maxSegments"],
0.0001,
)
# Vizualize image collection for charting
# We will visualize the standard deviation of the tasseled cap brightness, greenness, and wetness values on the map, but all annual fitted values will be available for querying when you double-click the map
Map.addLayer(lt_fit.select(['.*_LT_fitted']), {"reducer":ee.Reducer.stdDev(),"bands":"brightness_LT_fitted,greenness_LT_fitted,wetness_LT_fitted","min": 0,'max':0.2}, "LandTrendr Fitted")
Map.setQueryDateFormat("YYYY")
Map.turnOnInspector()
Map.setCenter(-110,41,6)
Map.view()
Available bands/indices: ['NBR', 'NDMI', 'NDSI', 'NDVI', 'blue', 'brightness', 'green', 'greenness', 'nir', 'red', 'swir1', 'swir2', 'tcAngleBG', 'wetness']
{'band': 'NBR', 'bestModelProportion': 0.75, 'endJulian_firstPeriod': 244, 'endJulian_secondPeriod': 244, 'endYear': 2024, 'maxSegments': 9, 'minObservationsNeeded': 6, 'pvalThreshold': 0.05, 'recoveryThreshold': 0.25, 'spikeThreshold': 0.9, 'startJulian_firstPeriod': 151, 'startJulian_secondPeriod': 182, 'startYear': 1984, 'vertexCountOvershoot': 3}
Adding layer: LandTrendr Fitted
Setting default query date format to: YYYY
Starting webmap
Using default refresh token for geeView
Local web server at: http://localhost:8001/geeView/ already serving.
cwd c:\RCR\geeVizBuilder\geeViz\examples
geeView URL: http://localhost:8001/geeView/?projectID=rcr-gee&accessToken=ya29.a0AeXRPp6FTRMwsLoSXTvE2W6FPZeW5pm3VYQb2QyWtw1iDSTcAGeoaL8j1wK_D8_xRfLtM2YU_UG5Cgu98IkLbBJNle0VYnMZZUpexUtm23sybgb4iza4y1ICEvIAMX7iUD5oX4xK4hJMaNxZa11nLkBC9mKEH1AWiXWo8dYM-6_cb6dAjsOIaCgYKAfkSARASFQHGX2MiJrjWJ3Aeba_r02LT59FhNQ0187&accessTokenCreationTime=1743467494181
LandTrendr Change Detection¶
Since LandTrendr divides the annual time series into linear segments, it is easy to use the output to perform a basic change detection
This example will show you how to threshold the change in NBR segments and show the year, magnitude, and duration from the corresponding segment with the largest change
Take note how the different change types have different durations.
Note: layer drawing and querying can be slow since all LandTrendr post-processing computation is being performed in GEE on-th-fly.
####################################################################################################
#Clear the map in case it has been populated with layers/commands earlier
Map.clearMap()
# How many significant loss and/or gain segments to include
# Do not make less than 1
# If you only want the first loss and/or gain, choose 1
# Generally any past 2 are noise
howManyToPull = 1
# Parameters to identify suitable LANDTRENDR segments
# Thresholds to identify loss in vegetation
# Any segment that has a change magnitude or slope less than both of these thresholds is omitted
lossMagThresh = -0.15
lossSlopeThresh = -0.1
# Thresholds to identify gain in vegetation
# Any segment that has a change magnitude or slope greater than both of these thresholds is omitted
gainMagThresh = 0.1
gainSlopeThresh = 0.1
slowLossDurationThresh = 3
# Choose from: 'newest','oldest','largest','smallest','steepest','mostGradual','shortest','longest'
chooseWhichLoss = 'largest'
chooseWhichGain = 'largest'
# Choose band or index
# NBR, NDMI, and NDVI tend to work best
if bandNames == None:
bandNames = ["NBR"]
# Number of years of duration to separate between slow and fast loss (>= this number will be called slow loss)
slowLossDurationThresh = 3
# Which segment to show change from
# Choose from: 'newest','oldest','largest','smallest','steepest','mostGradual','shortest','longest'
chooseWhichLoss = "largest"
chooseWhichGain = "largest"
howManyToPull = 1
# Iterate across each band to look for areas of change
for bandName in bandNames:
# Do basic change detection with raw LT output
ltt = lt.filter(ee.Filter.eq(bandPropertyName, bandName)).mosaic()
ltt = cdl.multLT(ltt, cdl.changeDirDict[bandName] * 0.0001)
lossGainDict = cdl.convertToLossGain(
ltt,
format="arrayLandTrendr",
lossMagThresh=lossMagThresh,
lossSlopeThresh=lossSlopeThresh,
gainMagThresh=gainMagThresh,
gainSlopeThresh=gainSlopeThresh,
slowLossDurationThresh=slowLossDurationThresh,
chooseWhichLoss=chooseWhichLoss,
chooseWhichGain=chooseWhichGain,
howManyToPull=howManyToPull,
)
lossGainStack = cdl.LTLossGainExportPrep(lossGainDict, indexName=bandName, multBy=1)
cdl.addLossGainToMap(
lossGainStack,
startYear,
endYear,
lossMagThresh - 0.7,
lossMagThresh,
gainMagThresh,
gainMagThresh + 0.7,
)
####################################################################################################
####################################################################################################
# View map
Map.setQueryDateFormat("YYYY")
Map.turnOnInspector()
Map.setCenter(-110,41,6)
Map.view()
Converting LandTrendr from array output to Gain & Loss
Adding layer: LandTrendr NBR Loss Year
Adding layer: LandTrendr NBR Loss Magnitude
Adding layer: LandTrendr NBR Loss Duration
Adding layer: LandTrendr NBR Gain Year
Adding layer: LandTrendr NBR Gain Magnitude
Adding layer: LandTrendr NBR Gain Duration
Setting default query date format to: YYYY
Starting webmap
Using default refresh token for geeView
Local web server at: http://localhost:8001/geeView/ already serving.
cwd c:\RCR\geeVizBuilder\geeViz\examples
geeView URL: http://localhost:8001/geeView/?projectID=rcr-gee&accessToken=ya29.a0AeXRPp4hisZDncz0qy9jnQjGXMaZB6JP58aU9Ki-wabDCaMhzHSLR_nImlV7cUbFEGyVmC4rWHdCzqN9pdId7Dzv95VSKgTmDVXJHhfg0iQQ---ExFE_m1ZthvzVqw0h75LUlE3unOSVfL4SCqL0SBlW8HXXin0A3ifFOe8CAYR9XJd-MH9faCgYKAdMSARASFQHGX2Mi5Y1z5lTbb4QJ3pj-3EXWhw0187&accessTokenCreationTime=1743468092725