Skip to content

Commit 999afca

Browse files
authored
Add FLIR visualisation for Landsat-8 (#329)
* Add EOB script for FLIR vis * Raw thermal band in celsius * Add link in Landsat page * Add readme * Add visualisation script * Add example image
1 parent d733222 commit 999afca

File tree

6 files changed

+199
-0
lines changed

6 files changed

+199
-0
lines changed

landsat-8/landsat-8.md

+1
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@ The Landsat program is the longest running enterprise for acquisition of satelli
4040
- [Clouds Segmentation](/landsat-8/clouds_segmentation)
4141
- [Pansharpened true color](/landsat-8/true-color-pansharpened)
4242
- [Thermal visualization](/landsat-8/thermal)
43+
- [Thermal FLIR visualization](/landsat-8/thermal-iron)
4344
- [Band quality assessment band visualization](/landsat-8/bqa)

landsat-8/thermal-iron/README.md

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
title: FLIR visualisation of the thermal IR band
3+
parent: Landsat-8
4+
grand_parent: Landsat
5+
layout: script
6+
permalink: /landsat-8/thermal-iron/
7+
nav_exclude: true
8+
scripts:
9+
- - Visualization
10+
- script.js
11+
- - EO Browser
12+
- eob.js
13+
- - Raw Values
14+
- raw.js
15+
examples:
16+
- zoom: '11'
17+
lat: '35.18167'
18+
lng: '-5.4657'
19+
datasetId: AWS_LOTL2
20+
fromTime: '2024-07-15T00:00:00.000Z'
21+
toTime: '2024-07-15T23:59:59.999Z'
22+
platform:
23+
- EOB
24+
evalscripturl: https://custom-scripts.sentinel-hub.com/custom-scripts/landsat-8/thermal-iron/eob.js
25+
---
26+
## General description
27+
28+
Since the inception of thermal imaging, infrared cameras have commonly employed a distinctive color palette, ranging from black to blue, magenta, orange, yellow, and culminating in bright white. This palette is frequently referred to as “Iron” or “Ironbow.”
29+
30+
This script allows the visualisation of the thermal IR band of Landsat 8 (Band 10, centered on 10.895 µm) in a FLIR-like palette that was inspired by a StackOverflow post [1]. The script allows the adjustment of 3 parameters:
31+
32+
- **minValue**: The minimum value of the thermal band mapped to the black color.
33+
- **maxValue**: The maximum value of the thermal band mapped to the white color.
34+
- **numSteps**: The number of increments in the color palette.
35+
36+
The script returns the thermal band values in degrees Celsius, converted from Kelvin, for an easier interpretation. Furthermore, the script masks out clouds and cloud shadows using the QA band (BQA).
37+
38+
## Description of representative images
39+
40+
Thermal data over Morocco. Acquired on 15.07.2024.
41+
42+
![Morocco thermal image](fig/fig1.png)
43+
44+
45+
## References
46+
[1] StackOverflow, [Thermal imaging palette](https://stackoverflow.com/questions/28495390/thermal-imaging-palette). Accessed on 19th September 2024.

landsat-8/thermal-iron/eob.js

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//VERSION=3
2+
3+
// Set Min and Max values for display and number of steps in the palette
4+
const minValue = 10
5+
const maxValue = 45
6+
const numSteps = 200
7+
8+
// Setup palette
9+
const palette_colours = palette(numSteps, minValue, maxValue)
10+
const viz = new ColorRampVisualizer(palette_colours);
11+
12+
13+
function setup() {
14+
return {
15+
input: ["B10", "BQA", "dataMask"],
16+
output: [
17+
{ id: "default", bands: 4 },
18+
{ id: "eobrowserStats", bands: 2 },
19+
{ id: "dataMask", bands: 1 },
20+
],
21+
};
22+
}
23+
24+
function evaluatePixel(samples) {
25+
let val = samples.B10 - 273;
26+
let clouds = isCloud(samples)
27+
28+
return {
29+
default: [...viz.process(val), (samples.dataMask * (clouds ? 0 : 1))],
30+
eobrowserStats: [val, clouds ? 0 : 1],
31+
dataMask: [samples.dataMask],
32+
};
33+
}
34+
35+
function isCloud(sample) {
36+
const BQA = decodeL8C2Qa(sample.BQA);
37+
let cloudPresence = false
38+
if (BQA.cloud == 1 || BQA.cloudShadow == 1 || BQA.cirrus == 1 || BQA.dilatedCloud == 1) {
39+
cloudPresence = true
40+
}
41+
return cloudPresence
42+
}
43+
44+
function componentToHex(c) {
45+
var hex = c.toString(16);
46+
return hex.length == 1 ? "0" + hex : hex;
47+
}
48+
49+
function rgbToHex(r, g, b) {
50+
return "0x" + componentToHex(r) + componentToHex(g) + componentToHex(b);
51+
}
52+
53+
function palette(colour_length, min_value, max_value){
54+
let colourPairs = []
55+
let values = Array.from({length: colour_length}, (_, i) => min_value + (max_value - min_value) * i / (colour_length - 1));
56+
for (var idx = 0; idx < colour_length; idx++){
57+
var x = idx * 1.0/colour_length;
58+
59+
// Convert RGB to hex
60+
let coulours = rgbToHex(Math.round(255*Math.sqrt(x)),
61+
Math.round(255*Math.pow(x,3)),
62+
Math.round(255*(Math.sin(2 * Math.PI * x)>=0?
63+
Math.sin(2 * Math.PI * x) :
64+
0 )))
65+
66+
//Make pairs of colours
67+
colourPairs.push([values[idx], coulours.toString()])
68+
69+
}
70+
return colourPairs
71+
}

landsat-8/thermal-iron/fig/fig1.png

348 KB
Loading

landsat-8/thermal-iron/raw.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//VERSION=3
2+
3+
function setup() {
4+
return {
5+
input: ["B10"],
6+
output: {
7+
bands: 1,
8+
sampleType: "FLOAT32"
9+
}
10+
};
11+
}
12+
13+
function evaluatePixel(samples) {
14+
// Convert to Celsius
15+
return [samples.B10 - 273];
16+
}

landsat-8/thermal-iron/script.js

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//VERSION=3
2+
3+
// Set Min and Max values for display and number of steps in the palette
4+
const minValue = 10
5+
const maxValue = 45
6+
const numSteps = 200
7+
8+
// Setup palette
9+
const palette_colours = palette(numSteps, minValue, maxValue)
10+
const viz = new ColorRampVisualizer(palette_colours);
11+
12+
13+
function setup() {
14+
return {
15+
input: ["B10", "BQA", "dataMask"],
16+
output: {
17+
bands: 4
18+
}
19+
}
20+
}
21+
22+
function evaluatePixel(samples) {
23+
let val = samples.B10 - 273;
24+
let clouds = isCloud(samples)
25+
26+
return [...viz.process(val), (samples.dataMask * (clouds ? 0 : 1))];
27+
}
28+
29+
function isCloud(sample) {
30+
const BQA = decodeL8C2Qa(sample.BQA);
31+
let cloudPresence = false
32+
if (BQA.cloud == 1 || BQA.cloudShadow == 1 || BQA.cirrus == 1 || BQA.dilatedCloud == 1) {
33+
cloudPresence = true
34+
}
35+
return cloudPresence
36+
}
37+
38+
function componentToHex(c) {
39+
var hex = c.toString(16);
40+
return hex.length == 1 ? "0" + hex : hex;
41+
}
42+
43+
function rgbToHex(r, g, b) {
44+
return "0x" + componentToHex(r) + componentToHex(g) + componentToHex(b);
45+
}
46+
47+
function palette(colour_length, min_value, max_value){
48+
let colourPairs = []
49+
let values = Array.from({length: colour_length}, (_, i) => min_value + (max_value - min_value) * i / (colour_length - 1));
50+
for (var idx = 0; idx < colour_length; idx++){
51+
var x = idx * 1.0/colour_length;
52+
53+
// Convert RGB to hex
54+
let coulours = rgbToHex(Math.round(255*Math.sqrt(x)),
55+
Math.round(255*Math.pow(x,3)),
56+
Math.round(255*(Math.sin(2 * Math.PI * x)>=0?
57+
Math.sin(2 * Math.PI * x) :
58+
0 )))
59+
60+
//Make pairs of colours
61+
colourPairs.push([values[idx], coulours.toString()])
62+
63+
}
64+
return colourPairs
65+
}

0 commit comments

Comments
 (0)