rr-rasterr-spbit-depth

Convert 32-bit float raster to 16-bit in R


I have ~100 different 4-band satellite imagery .tif rasters that have floating-point 32 bit depth. I need to convert these to 16-bit unsigned in R while scaling pixel values (not just trashing high values), but I have no idea where to begin for even a single raster, let alone the whole batch. Any help would be much appreciated!

Edit with more info: I searched the raster package documentation for bit, pixel, and depth keywords without much luck. Judging by min/max pixel values information found under dataType(), I want to go from FLT4S (32-bit floating point) to INT2U (16 bit). I tried setting datatype with writeRaster() as shown in the example there, but the output was just a black and white image rather than normal satellite imagery.

image
class      : RasterStack 
dimensions : 4300, 8909, 38308700, 4  (nrow, ncol, ncell, nlayers)
resolution : 3, 3  (x, y)
extent     : 691032, 717759, 57492, 70392  (xmin, xmax, ymin, ymax)
crs        : +proj=utm +zone=17 +datum=WGS84 +units=m +no_defs +ellps=WGS84 +towgs84=0,0,0 
names      :        image.1,        image.2,        image.3,        image.4 
min values : 7.244503e-02, 8.278998e-02, 2.286164e-05, 8.571137e-02 
max values :    0.5347134,    0.3522218,    0.4896736,    0.7308348 

dataType(image) #[1] "FLT4S" "FLT4S" "FLT4S" "FLT4S"
image2 = writeRaster(image, 'new.tif', datatype='INT2U', overwrite=TRUE, format="GTiff")
dataType(image2) #[1] "INT2U"

image2
class      : RasterBrick 
dimensions : 4300, 8909, 38308700, 4  (nrow, ncol, ncell, nlayers)
resolution : 3, 3  (x, y)
extent     : 691032, 717759, 57492, 70392  (xmin, xmax, ymin, ymax)
crs        : +proj=utm +zone=17 +datum=WGS84 +units=m +no_defs +ellps=WGS84 +towgs84=0,0,0 
source     : new.tif 
names      : new.1, new.2, new.3, new.4 
min values :     0,     0,     0,     0 
max values :     1,     0,     0,     1 

Solution

  • In your example you have real values between 0 and 1. By writing to an integer type, these are all truncated to 0. If you want to write to INT2U (unsigned byte) you could first scale the values to be between 0 and 255.

    Example data

    library(raster)
    b <- brick(system.file("external/rlogo.grd", package="raster"))
    image <- clamp(b/255, 0, 0.6)
    #class      : RasterBrick 
    #dimensions : 77, 101, 7777, 3  (nrow, ncol, ncell, nlayers)
    #resolution : 1, 1  (x, y)
    #extent     : 0, 101, 0, 77  (xmin, xmax, ymin, ymax)
    #crs        : +proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs 
    #source     : memory
    #names      : red, green, blue 
    #min values :   0,     0,    0 
    #max values : 0.6,   0.6,  0.6 
    

    What you do (truncation)

    fname <- paste0(tempfile(), ".tif")      
    x <- writeRaster(image, fname, datatype='INT2U', overwrite=TRUE)
    x
    #min values : 0, 0, 0 
    #max values : 1, 1, 1 
    

    But note the difference when you use rounding

    fname <- paste0(tempfile(), ".tif")      
    y <- round(image)
    z <- writeRaster(y, fname, datatype='INT2U', overwrite=TRUE)
    
    s <- stack(x[[1]], z[[1]]) 
    plot(s)
    

    Now with some scaling

    maxv <- 65535
    r <- round(image * maxv)
    fname <- paste0(tempfile(), ".tif")
    s <- writeRaster(r, fname, datatype='INT2U', overwrite=TRUE)
    
    #s
    #min values :     0,     0,     0 
    #max values : 39321, 39321, 39321 
    

    With your data you would get max values of

    round(maxv * c(0.5347134, 0.3522218, 0.4896736, 0.7308348 ))
    #[1] 35042 23083 32091 47895
    

    You can also choose to set the max values of all layers to maxv, to keep more variation (but make the values no longer comparable with other data) --- more relevant if you were using a smaller range such as 0-255.

    ss <- round(maxv * image / maxValue(image))
    #names      :   red, green,  blue 
    #min values :     0,     0,     0 
    #max values : 65535, 65535, 65535 
    

    the above works because the lowest values are zero; if you have negative values you would do

    ss <- image - minValue(image)
    ss <- round(maxv * ss / maxValue(ss))
    

    In other cases you might want to use clamp. So you need to decide how to scale

    What I showed is linear scaling. There are other ways. For example, there is the is also the scale method, to improve the statistical distribution of numbers. That may be relevant; but it depends on what your goals are.

    Note. You do not say why you do this, and that is OK. But if it is to save hard disk space, you can use compression instead (see ?writeRaster)