Visualizing Wildfires and Burn Scars with the Sentinel Hub EO Browser V2

Wildfire visualization
Some months ago I posted a script on this blog which could be used to visualize wildfires in Sentinel-2 images using the really excellent Sentinel Hub EO Browser (since you are somehow reading this article I suspect you know the EO Browser already, but if not, read about it here and try it out, it is free for non-commercial use!).
In the past months I saw it used by quite a few people and was even more amazed that people contacted me asking questions about it or giving general feedback.

So I decided to write a second version of the script, taking in some of the ideas and feedback.The result is the script you can find here, which is quite a bit longer and more complex than the first one, and will hopefully give you even better results. It can still do everything the first script could do, but I improved the visualization of the hot spots, which are looking a bit smoother now, as well as the preset band combinations. It also allows for a lot more fine-tuning of the resulting image and is a lot more flexible in general. I put more attention on burn scars this time, so some of the new band combinations are meant to especially highlight those.
As an added feature, I included a function to automatically highlight burn scars in images, which is especially interesting if you want to visualize the extent of burn scars in a natural color image, or if you are using this highlighted burn scars with a black backdrop as a modifying layer in your GIS application to create a map.
As with most automatic classifications, the results are not always perfect. They vary between really good and totally unusable, it really depends on the scene, the type of vegetation burning and the atmospheric conditions. If you are trying to automatically highlight the burn scars near a still ongoing fire for example, the smoke can be a problem.
That all being said, the script allows a lot of different settings, and with a little manual fine-tuning you can usually generate quite appealing images in a very short time, one of the really great advantages of the EO Browser. Change a setting, and more or less immediately see the result.
The Script
// Wildfire and burn scar visualization in Sentinel-2 images V2.0.0 // Twitter: Pierre Markuse (@pierre_markuse) // CC BY 4.0 International - https://creativecommons.org/licenses/by/4.0/ function a(a, b) {return a + b}; function stretch(val, min, max) {return (val - min) / (max - min);} function satEnh(rgbArr) { var avg = rgbArr.reduce((a, b) => a + b, 0) / rgbArr.length; return rgbArr.map(a => avg * (1 - saturation) + a * saturation); } function highlightBurnscar(val, oLow, oHigh, deSat, darken) { if ((B12 + B11 > 0.05) && (val > 0)) { if (((B8A - B12) / (B8A + B12)) > oLow) { saturation = saturation - deSat; stretchMax = stretchMax + darken; } else { if (((B8A - B12) / (B8A + B12)) <= oHigh) { noFire[0] = noFire[0] + 0.2 * val; noFire[1] = noFire[1] + 0.05 * val; } else { noFire[0] = noFire[0] + 0.15 * val; noFire[1] = noFire[1] + 0.15 * val; } } } } function indexMap(ind, lVal, mVal, hVal, cont, dir, pal) { var col1=GREEN;var col2=YELLOW;var col3=RED; if (pal == 1) {col1=CBL;col2=CBM;col3=CBH;} if (pal == 2) {col1=OWNL;col2=OWNM;col3=OWNH;} var lValCol = col1; var mValCol = col2;var hValCol = col3; if (dir == 1){ lValCol = col3;hValCol = col1; } if (cont == 0) { if (ind <= lVal) return lValCol; if ((ind > lVal) && (ind < hVal)) return mValCol; if (ind >= hVal) return hValCol; } else { return colorBlend(ind, [lVal, mVal,hVal], [lValCol,mValCol,hValCol]); } } function blend(bArr1, bArr2, opa1, opa2) { return bArr1.map(function(num, index) { return (num / 100 * opa1 + bArr2[index] / 100 * opa2); }); } function applyEnh(bArr) { highlightBurnscar(burnscarHighlight, burnscarThresholdLow, burnscarThresholdHigh, burnscarDesaturateBackdrop, burnscarDarkenBackdrop); return satEnh([stretch(bArr[0], stretchMin, stretchMax), stretch(bArr[1], stretchMin, stretchMax), stretch(bArr[2], stretchMin, stretchMax)]); } var BLACK = [0.0, 0.0, 0.0]; var RED = [0.9, 0.1, 0.1]; var YELLOW = [0.9, 0.9, 0.1]; var GREEN = [0.0, 0.6, 0.0]; var CBL = [0/255, 80/255, 0/255]; var CBM = [120/255, 120/255, 230/255]; var CBH = [70/255, 195/255, 255/255]; var OWNL = [0.0, 0.0, 0.0]; var OWNM = [0.0, 0.0, 0.0]; var OWNH = [0.0, 0.0, 0.0]; // Visualization style of the different fire zones var Fire1OVL = [stretch((2.1 * B04 + 0.5 * B12), 0.01, 0.99) + 1.1, stretch((2.2 * B03 + 0.5 * B08), 0.01, 0.99), stretch(2.1 * B02, 0.01, 0.99)]; var Fire2OVL = [stretch((2.1 * B04 + 0.5 * B12), 0.01, 0.99) + 1.1, stretch((2.2 * B03 + 0.5 * B08), 0.01, 0.99) + 0.25, stretch(2.1 * B02, 0.01, 0.99)]; var Fire3OVL = [stretch((2.1 * B04 + 0.5 * B12), 0.01, 0.99) + 1.1, stretch((2.2 * B03 + 0.5 * B08), 0.01, 0.99) + 0.5, stretch(2.1 * B02, 0.01, 0.99)]; // Band combinations (To get quicker processing you should comment out all those you are not using in the Settings further down) var NaturalColors = [2.9 * B04, 3.1 * B03, 3.0 * B02]; // var EnhancedNaturalColors = [2.8 * B04 + 0.1 * B05, 2.8 * B03 + 0.15 * B08, 2.8 * B02]; // var NaturalNIRSWIRMix = [2.1 * B04 + 0.5 * B12, 2.2 * B03 + 0.5 * B08, 3.0 * B02]; // var NIRSWIRColors1 = [2.6 * B12, 1.9 * B08, 2.7 * B02]; var NIRSWIRColors2 = [2.4 * B12, 1.7 * B8A, 2.2 * B05]; // var NIRSWIRColors3 = [0.5 * (B12 + B11) / 4 / B07, 0.8 * B8A, 1 * B07]; // var NIRSWIRColors4 = [2.0 * B12, 1.1 * B11, 1.6 * B08]; // var FalseColor = [B08 * 2, B04 * 2, B03 * 2]; // var NatFalseColor = [B12 * 2.6, B11 * 2, B04 * 2.7]; // var Vegetation = [B11 * 2.4, B8A * 2, B04 * 2.9]; // var PanBand = [B08, B08, B08]; // var NBR8A12 = indexMap((B8A - B12) / (B8A + B12), -0.8, -0.4, 0.0, 1, 1, 1); // var NDVI = indexMap((B08 - B04) / (B08 + B04), -0.4, -0.2, 0.0, 1, 1, 1); // Settings // Fire (hot spot) visualization var fire1 = Fire1OVL; var fire2 = Fire2OVL; var fire3 = Fire3OVL; // Used band combinations and mixing var layer1 = NIRSWIRColors2; var layer2 = NaturalColors; var layer1Amount = 0; var layer2Amount = 100; // Influence contrast and saturation var stretchMin = 0.00; var stretchMax = 1.00; var saturation = 1.00; // Fire sensitivity (Default = 1.00), higher values increase fire (hot spot) detection and false positives var fireSensitivity = 1.00; // Burn scar visualization var burnscarHighlight = 0.00; var burnscarThresholdLow = -0.25; var burnscarThresholdHigh = -0.38; var burnscarDesaturateBackdrop = 0.25; var burnscarDarkenBackdrop = 0.25; // Manually influence RGB output var manualCorrection = [0.00, 0.00, 0.00]; // Image generation and output noFire = blend(layer1, layer2, layer1Amount, layer2Amount); finalRGB = applyEnh(noFire).map(function(num, index) { return num + manualCorrection[index];}); return (a(B12, B11) > (1.0 / fireSensitivity)) ? (a(B12, B11) > (2.0 / fireSensitivity)) ? fire3 : (a(B12, B11) > (1.5 / fireSensitivity)) ? fire2 : fire1 : finalRGB;
Copy and paste the script or right-click and download it here. You can also find it – and other custom scripts – in this custom script repository.
A few explanations
The part of the script you will be using most are the settings used in the variables, here is a very brief explanation of what those variables will do and how you can use them to alter the resulting image. Don’t think about it too much, just give it a try, change some settings and see the results. You might also have noticed that I commented out most band combinations. In order to get a quicker processing in the EO Browser you should only have the combinations active you are using for the image you are processing, it will save their resources and your time.

var fire1 = Fire1OVL; var fire2 = Fire2OVL; var fire3 = Fire3OVL;
I divided the hot spots (which usually align quite well with active fires and zones with lots of residual heat) into three zones. The inner part of the hot spot is fire3, followed by fire2 and then fire1 at the edge of the hot spot.Those three variables define the way the script will visualize hot spots. Usually you can go with the default as shown here, this way the hot spots will appear in red at the edge, followed by orange, and then yellow at the center, creating a more or less natural, fire-like appearance. For some visualizations (for example for better readability in maps) you could set those variables to “RED” (or any other solid color), mimicking the hot spot visualization in MODIS fire products.
var layer1 = NIRSWIRColors2; var layer2 = NaturalColors; var layer1Amount = 100; var layer2Amount = 0;
This block of variables defines the overall visualization used. While the first version of the script just had one static background you can now define two different layers and blend them. The layer1Amount and layer2Amount variables define the visibility of the layer. This can also be used to lighten up a dark image (by setting the amount to a value over 100) or to subtract one visualization from the other (by setting one of them to a negative value).
In the script you will find some predefined band combinations, but instead of using one of those you can also define your own (e.g. “var layer1 = [B04, B03, B02]”; for a basic natural color image or “var layer1 = [B08, B08 * 1.1, B08];” for a green tinted monochrome image).
Here a brief description of the predefined options:
- NaturalColors = A simple natural color band combination
- EnhancedNaturalColors = Natural colors with some NIR mixed in, especially to get better greens for vegetation
- NaturalNIRSWIRMix = A mix of natural and NIR/SWIR bands, usually quite pleasing visually and showing burn scars
- NIRSWIRColors1 = A classic NIR/SWIR combination, very green vegetation, very red burn scars
- NIRSWIRColors2 = Similar to the first one, colors are a bit more subtle with better smoke penetration
- NIRSWIRColors3 = Very red and punchy combination, give it a try.
- NIRSWIRColors4 = Good atmospheric penetration, somewhat visible burn scars and nicely highlighted hot spots
- FalseColor = Classic NIR false color image
- NatFalseColor = NIR false color image with a natural-color-like appearance
- Vegetation= Useful to show healthy vegetation
- PanBand = Monochrome B08 image
- NBR8A12 = NBR based on B8A and B12
- NDVI = simple NDVI based on B08 and B04
For the NBR8A12 and NDVI you can also define thresholds and whether they will be shown as discrete colors or as color gradient, color order, and palette used. To change those things you have to edit these lines here:
var NBR8A12 = indexMap((B8A - B12) / (B8A + B12), -0.8, -0.4, 0.0, 1, 1, 1); var NDVI = indexMap((B08 - B04) / (B08 + B04), -0.4, -0.2, 0.0, 1, 1, 1);
The first three values after the index are the thresholds used. The next value can either be a “0” for discrete colors, or a “1” for a color gradient. The medium threshold is only used with gradients, with discrete colors everything equal or less than the low threshold will be colored as the low value color (e.g. green), everything between low and high threshold will be colored as medium value color (e.g. yellow) and everything equal and bigger than the high threshold will be colored as high value color (e.g. red). The next value defines whether you want to reverse the color order “1” or not “0”. The last number defines the palette to be used, a “0” yields a classic green-yellow-red palette, a “1” yields a palette that also works for color-blind people, a “2” uses your own palette, the colors of which you can define in the variables OWNL, OWNM, and OWNH.
In the future I also hope to include the automatic generation of dNBR images using the EO Browser’s temporal abilities. Read more on that in the “Possible ideas for the next version” section further down.
NBR and dNBR can be used to quickly estimate burn severity, however, their values can depend on the scene and type of vegetation. To conclusively determine burn severity, field assessments will be necessary. Read more on what NBR and dNBR are here.
var stretchMin = 0.01; var stretchMax = 0.90; var saturation = 1.10;
With the stretchMin and stretchMax variables you can adjust the lightness of the resulting image, play around to get a nice result.
Saturation can be used to make the resulting image more vivid or, if set to 0, to get a monochrome version of your used band combination. The, in Miha Kadunc’s words, “very crude method of saturation enhancement” was taken from his “Color Correction with JavaScript” script[1], you should take a look at it here and follow him on Twitter, and as you might see yourself, the “crude method” yields a nice result.
var fireSensitivity = 1.00;
The fireSensitivity variable defines the sensitivity of the hot spot detection, increase it to show more hot spots (with more and more false positives). Usually “1.00” works quite well, if you don’t want hot spots highlighted in your image set it to 0 to deactivate this function completely.
var burnscarHighlight = 0.00; var burnscarThresholdLow = -0.25; var burnscarThresholdHigh = -0.38; var burnscarDesaturateBackdrop = 0.00; var burnscarDarkenBackdrop = 0.00;
Set burnscarHighlight to a value >0 (bigger means brighter) to highlight burn scars. The burn scars will be highlighted in two zones, to distinguish between more and less affected areas, use the low and high thresholds to adjust for your scene. The highlighting used here is based on the NBR.
burnscarDesaturateBackdrop and burnscarDarkenBackdrop can be used to desaturate and darken those parts of the images, that aren’t burn scars. Useful to make burn scars stand out even more and focus attention.
var manualCorrection = [0.00, 0.00, 0.00];
More or less a “cheat” variable. You can use it to adjust the RGB balance in the final image after all processing is done. Positive values will be added to the image, negative values subtracted. Can be useful to get a more natural looking image.
Examples
Take a look at these example images, produced with this script. I’m pretty sure you can come up with even more different styles if you play around a little bit.
Natural color images with highlighted hot spots, showing areas with possible active fires and residual heat. Very basic, but nice to look at and usually a nice choice for outreach to the general public as it shows the hot spots in fire-like colors. However, this can be somewhat misleading if media outlets fail to communicate the fact that we are not seeing the actual fires but IR emissions, which sadly they usually do.
And a classic false color view of it. A little better in showing the burn scar, and usually also well received by the public.
Different NIR/SWIR views, you can’t get much better when it comes to show the difference between burn scar and healthy vegetation.
Here we see the automatic burn scar highlighting on a darkened and desaturated natural-color backdrop.
Or with a black backdrop so you can use it as a modifying layer in a GIS application.
NBR image using discrete colors.
NBR image using a color gradient.
NBR image using a colorblind-friendly gradient.
Volcanoes are another possibility to use the script. See this nice visualization of lava flows on Mount Mayon in the Philippines.
More!For even more (and full-size) examples you can head over to my wildfire album on Flickr with hundreds of images, a lot of which have been processed using this script.Possible ideas for the next version
There are already some ideas for the next version of the script. I would like to include the temporal abilities of the EO Browser, this way it would be possible to create dNBR images, giving a better idea of how strong an area was affected than a simple NBR image and also about subsequent changes in the vegetation cover and its recovery over time (In case you need that functionality already, a custom script for solely that function is available here). It might also become possible to create images highlighting all fires in a given area over a given time period of Sentinel-2 images.
I would also like to include some more indices in the next version of the script, and streamline the ways to visualize them. Technically it is possible to visualize different indices with this script already, but I would like to make it easier for the user of the script.
Feedback / Questions
If you have some kind of feedback or questions regarding this script, or ideas how it could be made better, feel free to share. Comment on this post or contact me via email or on Twitter. I would also very much like to see images you create with the script, so in case you do, let me know where I can find them or tag me in your images on Twitter. You might also want to follow Sentinel Hub on Twitter for news about the EO Browser.
In case you have general questions about the EO Browser you should read this FAQ and register at the Sentinel Hub Forum here to ask questions or maybe contribute your own scripts for the EO Browser.
Should you find settings and combinations that work particularly well, maybe consider to post an example image along with the settings used to the Sentinel Hub Forum, so other users can benefit from it as well.
Acknowledgements
I would like to thank the team at Sentinel Hub for answering all the questions I came up with in record time and their continued improvement of the service. I would also like to thank all of those who gave me ideas and feedback to earlier versions of this script.
I should also mention the team of Copernicus EU that in its continuous endeavor to inform and educate is managing to get more and more people interested in, and informed about, the abilities and applications of remote sensing and the European Union Copernicus Programme for Earth Observation. Its data is what made all of this possible.
References
1. ^ Kadunc M. (2017), Color Correction with JavaScript, Sentinel Hub Blog. https://medium.com/sentinel-hub/color-correction-with-javascript-d721e12a919.
Images if not otherwise stated: Contains modified Copernicus Sentinel data [2017, 2018], processed by Pierre Markuse, CC BY 4.0 license
Hi Pierre,
Really cool tool! I was just wondering what settings you used to achieve your image here: https://twitter.com/Pierre_Markuse/status/1089818349977198592?s=20
I’m having trouble achieving the all yellow colour for the fires, only able to get the very red blots.
Thanks!
Hey Sean,
if you keep
var fireSensitivity = 1.00;
you should actually get those yellow-orange hot spots.
Hello, Pierre!
Well! This is interesting.
I am trying run this script in google earth engine code editor, but report me SyntaxError: Assigning to rvalue (9:29) on console panel.
Please, can you help me solve this issue.
Hi Hercilo, the script is meant to be used in the Sentinel Hub EO Browser, try it there and it will work 🙂
Hello Pierre.
Is it possible to make these lights in the image, but make them less bright regardless of the image itself? Something like a transparency or “alpha” (r, g, b, a) setting.
Thanks a lot!
Hello Pierre.
I am trying to integrate your code in python where I have following evalscript for True color.
evalscript_true_color = “””
//VERSION=3
function setup() {
return {
input: [{ bands: [“B02”, “B03”, “B04”] }],
output: { bands: 3 } };
}
function evaluatePixel(sample) {
return [sample.B04, sample.B03, sample.B02];
}
“””
ASK : How do I integrate your script in evalscript V3 ?
Thanks a lot!
evalscript is working now.
Can you please share how did you manage to integrate it to an EVALSCRIPT V3?
Thanks
This visualization looks great!
Thanks Pierre, this is an awesome script and introduction to EO Browser!
I find I’m getting a lot of false positives with agricultural land when trying to visualise the hotspots – but I think that is just the nature of the area i’m looking at (Perth Australia after a week of hot temperatures), and the fire front itself must have died down by the time the image was captured.
Thanks again!
Hello Pierre,
Firstly, thank you to share your work on this topic. I would like to ask you a question about manipulated data.
If I am not misunderstanding, the values of pixels from the different bands in your script are expected to belong to [0,1] interval.
Nevertheless, with S2 products which I have recovered via Copernicus Open Access Hub, pixels of band images are coded over 15bits and have values as 8860 for instance which does not match with this section related to EOBrowser and Sentinel Hub (https://docs.sentinel-hub.com/api/latest/data/sentinel-2-l1c/#units).
May you please enlighten me on these issues? Are you considering reflectance values as indicated in above mentioned sentinel hub doc section in your script? Do you know the defaults settings of Sentinel Hub which provide those type of data for S2-L1C products, namely data with pixel values ranging either in 0-0.4 or 0-4000?
Thanks in advance for your help.
Best regards,