Since its commissioning on July 25th 2019, GEO-KOMPSAT-2A (GK-2A) has been downlinking Full Disk images every 10 minutes over LRIT and HRIT. Unfortunately the LRIT downlink only transmits a single Infrared channel called IR105 (10.4μm) due to throughput constraints. This means false colour imagery cannot be created using data from the LRIT downlink alone.
However, interesting imagery can still be created from this single channel using Infrared Colour Enhancement. This process replaces a range of grayscale values in Infrared imagery with a colour gradient. Metrologists use this to show the severity of weather systems in thermal Infrared imagery.
Typhoon Krosa near Japan (13th August 2019)
In typical thermal Infrared imagery, luminosity increases as temperature decreases, meaning the strongest (coldest) weather systems appear the brightest. As can be seen in the above unenhanced image of Typhoon Krosa, most of the detail is washed out in the coldest parts of the system. A considerable amount of detail is revealed after colour enhancement of the brighter regions.
Enhanced animation of Typhoon Krosa (13th August 2019)
> pip3 install pillow numpy
Basic usage of
enhance-ir.py requires only an input image path. After processing, an output image will be saved in the same folder as the input image with "
_ENHANCED" appended to the file name.
> python3 enhance-ir.py in.jpg
Muiltiple images can be processed at once by setting the input path to a folder rather than an individual file. The script will scan the input folder for files that match
IMG_*.jpg (ignoring file names with "
_ENHANCED") and process them into individual output images.
> python3 enhance-ir.py images
-h shows a help message describing each of the arguments.
--hot specifies the hotter (darker) threshold of the colour enhancement in Kelvin (275K by default).
--cold specifies the colder (brighter) threshold of the colour enhancement in Kelvin (230K by default).
-s (simple) option disables drawing of the LUT and text at the bottom of the image.
-t will output only the enhanced parts of an image on a transparent PNG.
-o will overwrite any images in the output folder with the same file name as the output image.
usage: enhance-ir.py [-h] [--hot HOT] [--cold COLD] [-s] [-o] [-t] INPUT
If you find a bug in this script please submit a new issue in the xrit-rx GitHub repository.
The calibration table is sent with every every thermal infrared image downlinked by GK-2A. This table describes the relationship between pixel brightness and temperature in Kelvin.
NAME:=IR105 UNIT:=KELVIN 0:=330.05254 1:=319.99371 2:=309.08976 3:=297.08050 4:=283.54929 5:=267.75568 6:=248.14399 7:=220.17763
The 8 equally spaced values in the table provide very a coarse conversion between pixel brightness and temperature. This table is interpolated for a smoother conversion and to match the total number of possible pixel brightness values in the input image. In the case of 8-bit GK-2A LRIT imagery, there are a total of 256 possible pixel brightness values.
# Setup interpolation points xp =  for i in range(len(cal)): m = 256 / len(cal) xp.append((i+1) * m) # Interpolate conversion table for i in range(256): k = round(np.interp(i, xp, cal), 3) # Append interpolated temperature value kelvin.append(k)
This interpolated table allows selective enhancement of brighter or darker parts of the input image by specifying a temperature range in Kelvin, then using the table to convert that temperature to a pixel brightness value.
The Colour Look-up Table (CLUT) defines the colours used to enhance the input image. In this case it is an 8-bit RGB gradient with 256 colours starting at blue and ending at red.
Colour Look-up Table (CLUT)
This CLUT is scaled to match the size of the temperature range specified by the user, which is 45K by default (275K to 230K). The above calibration table is used to find the nearest pixel brightness value to the temperatures specified by the user. Then a scale factor (
scale) is calculated from the indexes of those pixel brightness values (
coldI). Finally a new scaled CLUT (
sclut) is generated by sampling the full size CLUT at regular intervals spaced out by multiples of the scale factor.
# Find nearest Kelvin CLUT bounds indicies hotI = get_nearest(float(args.hot), kelvin) coldI = get_nearest(float(args.cold), kelvin) # Get scale factor for CLUT scale = (len(clut) / (coldI - hotI)) # Scale CLUT sclut =  for i in range(coldI - hotI): idx = round(i * scale) if idx > 255: idx = 255 sclut.append(clut[idx])
Next, an 8-bit grayscale LUT is generated as a base for the scaled CLUT to be overlayed onto.
# Generate base 8-bit grayscale LUT for i in range(256): lut.append((i, i, i))
Grayscale Look-up Table
Then the scaled CLUT is overlayed on the grayscale LUT. The lower bound of the CLUT is cross-faded with the grayscale LUT to create a smoother transition to enhanced areas in the output image.
# Insert CLUT into LUT for i in range(len(sclut)): cf = 10 if i <= cf: # Crossfade LUTs r = int(sclut[i] * (i / cf)) + int(lut[hotI + i] * ((cf - i) / cf)) g = int(sclut[i] * (i / cf)) + int(lut[hotI + i] * ((cf - i) / cf)) b = int(sclut[i] * (i / cf)) + int(lut[hotI + i] * ((cf - i) / cf)) lut[hotI + i] = (r, g, b) else: lut[hotI + i] = sclut[i]
This produces the final LUT which will be applied to the input image.
Final combined look-up table
The final LUT is separated into red, green and blue channels before being applied to the input image. It is far easier to deal with the channels individually rather than as an array of
(r, g, b) Tuples.
Each channel of the final LUT is then applied to the input image which creates three enhanced channels. These channels are now reassembled into the final RGB image.
# Create empty NumPy arrays for each channel nplutR = np.zeros(len(lut), dtype=np.uint8) nplutG = np.zeros(len(lut), dtype=np.uint8) nplutB = np.zeros(len(lut), dtype=np.uint8) # Convert LUT channels into separate NumPy arrays for i, c in enumerate(lut): nplutR[i] = c nplutG[i] = c nplutB[i] = c # Get grayscale values from input image gray = np.array(input) # Apply each channel of LUT to grayscale image enhR = nplutR[gray] enhG = nplutG[gray] enhB = nplutB[gray] # Convert enhanced arrays to images iR = Image.fromarray(enhR) iG = Image.fromarray(enhG) iB = Image.fromarray(enhB) # Combine enhanced channels into an RGB image i = Image.merge("RGB", (iR, iG, iB))
-s option is not set, the final LUT and temperature values will also be drawn at the bottom of the output image.
Final output image