FLIR visualisation of the thermal IR band

//VERSION=3 

// Set Min and Max values for display and number of steps in the palette
const minValue = 10
const maxValue = 45
const numSteps = 200

// Setup palette
const palette_colours = palette(numSteps, minValue, maxValue)
const viz = new ColorRampVisualizer(palette_colours);


function setup() {
  return {
    input: ["B10", "BQA", "dataMask"],
    output: {
      bands: 4
    }
}
}

function evaluatePixel(samples) {
  let val = samples.B10 - 273;
  let clouds = isCloud(samples)
  
  return [...viz.process(val), (samples.dataMask * (clouds ? 0 : 1))];
}

function isCloud(sample) {
  const BQA = decodeL8C2Qa(sample.BQA);
  let cloudPresence = false
  if (BQA.cloud == 1 || BQA.cloudShadow == 1 || BQA.cirrus == 1 || BQA.dilatedCloud == 1) {
    cloudPresence = true 
  }
  return cloudPresence
}

function componentToHex(c) {
    var hex = c.toString(16);
    return hex.length == 1 ? "0" + hex : hex;
  }
  
  function rgbToHex(r, g, b) {
    return "0x" + componentToHex(r) + componentToHex(g) + componentToHex(b);
  }

function palette(colour_length, min_value, max_value){
    let colourPairs = []
    let values = Array.from({length: colour_length}, (_, i) => min_value + (max_value - min_value) * i / (colour_length - 1));
    for (var idx = 0; idx < colour_length; idx++){
        var x = idx * 1.0/colour_length;
        
        // Convert RGB to hex
        let coulours = rgbToHex(Math.round(255*Math.sqrt(x)), 
                        Math.round(255*Math.pow(x,3)), 
                        Math.round(255*(Math.sin(2 * Math.PI * x)>=0?
                                      Math.sin(2 * Math.PI * x) :
                                      0 )))
        
        //Make pairs of colours
        colourPairs.push([values[idx], coulours.toString()])
       
    }
    return colourPairs
}
//VERSION=3 

// Set Min and Max values for display and number of steps in the palette
const minValue = 10
const maxValue = 45
const numSteps = 200

// Setup palette
const palette_colours = palette(numSteps, minValue, maxValue)
const viz = new ColorRampVisualizer(palette_colours);


function setup() {
  return {
    input: ["B10", "BQA", "dataMask"],
    output: [
      { id: "default", bands: 4 },
      { id: "eobrowserStats", bands: 2 },
      { id: "dataMask", bands: 1 },
    ],
  };
}

function evaluatePixel(samples) {
  let val = samples.B10 - 273;
  let clouds = isCloud(samples)
  
  return {
    default: [...viz.process(val), (samples.dataMask * (clouds ? 0 : 1))],
    eobrowserStats: [val, clouds ? 0 : 1],
    dataMask: [samples.dataMask],
  };
}

function isCloud(sample) {
  const BQA = decodeL8C2Qa(sample.BQA);
  let cloudPresence = false
  if (BQA.cloud == 1 || BQA.cloudShadow == 1 || BQA.cirrus == 1 || BQA.dilatedCloud == 1) {
    cloudPresence = true 
  }
  return cloudPresence
}

function componentToHex(c) {
    var hex = c.toString(16);
    return hex.length == 1 ? "0" + hex : hex;
  }
  
  function rgbToHex(r, g, b) {
    return "0x" + componentToHex(r) + componentToHex(g) + componentToHex(b);
  }

function palette(colour_length, min_value, max_value){
    let colourPairs = []
    let values = Array.from({length: colour_length}, (_, i) => min_value + (max_value - min_value) * i / (colour_length - 1));
    for (var idx = 0; idx < colour_length; idx++){
        var x = idx * 1.0/colour_length;
        
        // Convert RGB to hex
        let coulours = rgbToHex(Math.round(255*Math.sqrt(x)), 
                        Math.round(255*Math.pow(x,3)), 
                        Math.round(255*(Math.sin(2 * Math.PI * x)>=0?
                                      Math.sin(2 * Math.PI * x) :
                                      0 )))
        
        //Make pairs of colours
        colourPairs.push([values[idx], coulours.toString()])
       
    }
    return colourPairs
}
//VERSION=3 

function setup() {
  return {
    input: ["B10"],
    output: {
      bands: 1,
      sampleType: "FLOAT32"
    }
  };
}

function evaluatePixel(samples) {
  // Convert to Celsius
  return [samples.B10 - 273];
}

Evaluate and Visualize

General description

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.”

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:

  • minValue: The minimum value of the thermal band mapped to the black color.
  • maxValue: The maximum value of the thermal band mapped to the white color.
  • numSteps: The number of increments in the color palette.

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).

Description of representative images

Thermal data over Morocco. Acquired on 15.07.2024.

Morocco thermal image

References

[1] StackOverflow, Thermal imaging palette. Accessed on 19th September 2024.