1

I'm using a machine learning model to automatically recognize (a specific type of) vegetation in RGB imagery. The output of this model is a binary NumPy 2d array, with '1' meaning vegetation and '0' meaning no vegetation. The output is basically a mask. For writing this array into a .TIF file, I use the Python rasterio library (see code below).

import rasterio as rs

[...]

tile_dest = rs.open(tile_path)

img = Image.open(tile_path)
img = np.array(img)

inference = inference_function(img)

inference_fname = output_folder_path / tile

with rs.Env():
    profile = tile_dest.profile

    profile.update(
        nodata=0,
        count=1)

    # Storing .tif image in original CRS
    with rs.open(inference_fname, 'w', **profile) as dst:
        dst.write(inference.astype(rs.uint8), 1)

This all works perfectly fine; I can add the .TIF file as a raster layer in QGIS. My problem, however, is that the raster layer has 2 values: 1 and 2. As you can see in the code above, the 0 value has already been assigned as 'nodata'. I do not want to have two values, because the resulting raster layer should be a mask of the vegetation find in the RGB imagery. The resulting mask, added as a raster layer in QGIS and overlaying the original RGB image, can be seen in the image at the end of this post.

I do not want to have two separate values (1 - black and 2 - white). Instead, I want to have one single value, representing the vegetation mask.

I think that I get the two values due to compression, but from what I've read in the rasterio documentation, the only types of compression available for .TIF files are LZW and JPEG which both could create 'new' values during compression. How do I avoid altering the data range during the writing of a .TIF file with rasterio?

EDIT: I've also tried to change the type of compression, but both JPEG and LZW produce the same result. Setting the NBITS parameter to 0 also didn't help.

Resulting vegetation mask overlaying the original RGB image in QGIS

8
  • 1
    Which type of compression is it using? You'll want LZW over JPEG here. LZW is lossless, so it will preserve your values exactly, while JPEG is lossy. TIFFs support various other types of compression as well, which are listed here – mikewatt May 10 at 18:08
  • Thanks for your response @mikewatt! I tried using LZW as well (by updating the profile), but the result is the same (= value range of 1-2).. – Sytze May 11 at 7:29
  • can you check the values in inference by adding print(np.unique(inference)) ? – Pierrick Rambaud May 11 at 13:54
  • Thanks for the suggestion! I do, however, already check the values of the Tensor (I'm using PyTorch to get the inferences) and those are in the 0-1 range. – Sytze May 11 at 14:38
  • ok thanks for the feedback, so then there is a problem with your image, because on what you show us there are clearly 3 values (0 masked by your GIS software) and 1-2 on the 2 spots of vegetation. Assuming that the white pixels should be black I think the problem is comming from the type change. What is the type of inference ? – Pierrick Rambaud May 12 at 8:39
0

So based on the information you gave us, I think there is a problem during the casting of your inference numpy array from float to int. I suggest you to use the following code that will clip the pixels with value 2. I also took the liberty to improve Python code structure:

import rasterio as rio
import numpy as np
from PIL import Image # I assumed you were using PIL for your image read

# if you can only use PIL image for your function use this
with Image.open(tile_path) as f:
    img = np.array(f)
    inference = inference_function(img)

# if you just need an array I suggest you do everything with rasterio
# build the image and get the initial profile
with rio.open(tile_path) as f:
    img = f.read()
    inference = inference_function(img) # to comment if you prefer using PIL
    
    profile = f.profile
    
# write the new image file with an updated profil 
inference_f = output_folder_path/tile # again i'm assuming it's a pathlib path
profile.update(nodata=0, count=1)
with rio.open(inference_f, 'w', **profile) as dst:

    # create the array to be written
    print(np.unique(inference)) # to make sure that only 0 and 1 exist in your array
    inference = inference.astype(np.uint8)
    print(np.unique(inference)) # to check that the problem is effectively coming from recasting

    # clip the array to the values you want to use 
    inference = np.clip(inference, 0, 1)

    dst.write(inference, 1)
3
  • Thanks for your help! In your comments, you suggest doing everything with rasterio. However, you use the PIL library to open the image and try to call the read function; should this be the rasterio library? – Sytze May 14 at 9:56
  • Other than that, it worked, thank you! – Sytze May 14 at 10:06
  • If it works without commenting any lines then skip the PIL part (it's recomputed with rasterio after the second with) – Pierrick Rambaud May 14 at 10:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.