How do I create a 16-bit grayscale image from my array dataset

Question:

I want to convert a height map from NASA database into an image file. There is already a bit about this on the net and that helped me to read the file into an array and it looks like this:

data = [(113.0, 39.0, 1242), (113.00027777777778, 39.0, 1231), (113.00055555555555, 39.0, 1232), (113.00083333333333, 39.0, 1239), (113.00111111111111, 39.0, 1244), ...]

So I have an array with all the data according to the data pattern

data[width][height][tupel]
tupel = [longitude, latitude, height] 

the width and height are both 3601 long.

print (data[1800][1800])

returns the tupel: (113.5, 39.5, 2032)

and that’s fine. It is exactly the center of the dataset which goes from longitude 39 to 40 and from latitude 113 to 114. I don’t think I will need longitude and latitude because I know that the data set is 3601 x 3601 in size. The valuable information is in the last value, the height. In my example here the 2032.

My question now is: How do i get the data set data = [rows, columns, [longitude, latitude, height] ] to a 16 bit grayscale image. As mentioned, longitude and latitude are not relevant. Do I have to first make the dataset something like data = [rows, columns, heights] so filter out longitude and latitude? before I can further process the image file?

And how exactly do I create a 16-bit grayscale image file in png from this?

Asked By: Spiri

||

Answers:

We may convert the data to NumPy array, and use few NumPy data operation:

  • Convert data to NumPy array
    array shape is supposed to be 3601*3601 rows by 3 columns:

     arr = np.array(data)
    
  • Keep only the altitude (height) data:

     alt = arr[:, 2]
    
  • Reshape the long column to 3601 x 3601:

     alt = alt.reshape(height, width)
    
  • Clip alt to range [0, 65535], round and convert to np.uint16:

     alt = alt.clip(0, np.iinfo(np.uint16).max).round().astype(np.uint16)
    

Here is a code sample with 4×4 image instead of 3601×3601:

import numpy as np
import cv2

width, height = 4, 4  # Test 4x4 image instead of 3601x3601

data = [(113.0, 39.0, 1242), (113.00027777777778, 39.0, 1231), (113.00055555555555, 39.0, 1232), (113.00083333333333, 39.0, 1239), (113.00111111111111, 39.0, 1244), 
        (1113.0, 139.0, 11242), (1113.00027777777778, 139.0, 11231), (1113.00055555555555, 139.0, 11232), (1113.00083333333333, 139.0, 1239), (1113.00111111111111, 139.0, 11244),
        (2113.0, 239.0, 21242), (2113.00027777777778, 239.0, 21231), (2113.00055555555555, 239.0, 21232), (2113.00083333333333, 239.0, 21239), (2113.00111111111111, 39.0, 1244), (2113.00111111111111, 239.0, 21244)]

arr = np.array(data)  # arr shape is 16 rows by 3 columns.

alt = arr[:, 2]  # Keep only the altitude (height) data (16 rows).

assert alt.size == width*height  # Make sure the number of elements is correct.

alt = alt.reshape(height, width)  # Reshape alt to 4x4.

alt = alt.clip(0, np.iinfo(np.uint16).max).round().astype(np.uint16)  # Clip alt to range [0, 65535], round and convert to uint16

cv2.imwrite('alt.png', alt)  # Save alt as 16-bit grayscale image file in PNG format.

Note:
The above code is based on your description of the dataset.
The solution may require minor adjustments to work with the actual dataset.

Answered By: Rotem