1

Problem:

I want to convert a Hue, Saturation, Value (HSV) raster to the RGB color space. I have already successfully converted from RGB to HSV, but I'm having trouble with the reverse.

I'm using the following code, where stack is a raster stack object.

final_rgb <- overlay(brick(stack), fun=hsv2rgb)

I get the error:

"Error in .overlayList(x, fun = fun, filename = filename, forcefun = forcefun,  : 
  cannot use this formula, probably because it is not vectorized"

Solutions I've tried so far:

(1) The solution from this post, that is, a custom function (hsv2rbgna()) that drops potential NA values. I still get the same behavior.

(2) I've tried using a hsv2rgb() function from the package ColorMod. I get the error shown.

rgb <- colormod::hsv2rgb(stack)

"Error in dimnames(x) <- list(n) : 'dimnames' applied to non-array"

(3) And a hsv2rgb() function from the package ColorPalette:

rgb <- ColorPalette::hsv2rgb(stack[[1]], stack[[2]], stack[[3]])

"Error in rgbVector + m : non-numeric argument to binary operator"

(4) Convert raster to a matrix. I previously removed NA values from my raster, so I'm not sure what the non-finite values mentioned in the error message could be.

stack_matrix <- raster::as.matrix(stack)
final_rgb <- hsv2rgb(stack_matrix )

"Error in hsv(h = x["h"], s = x["s"], v = x["v"]) : inputs must be finite"

(5) The documentation for hsv2rgb() says that the input matrix should contain (h, s, v) rows, rather than columns, so I tried transposing the matrix, with the function t(). I get the same error as (4).

2 Answers 2

2

There are built in R functions (via grDevices) for doing these colorspace transformations. You were on the right track, the issue was in how you were trying to write a function around a stack (multi-band) raster object. First, I would highly recommend migrating to the terra package (replacement for raster by same developer).

Create a random raster in RGB space.

library(terra)

nr = 100; nc = 100
( rrgb <- do.call(c, replicate(3, terra::rast( 
    floor(256 * matrix(stats::runif(nr*nc), nr, nc) 
  )))) )
plotRGB(rrgb)

Now, convert RGB to an HSV space. Note that the terra::app function is the same as raster::overlay. The grDevices::rgb2hsv does the transformation but, there is not a function for going the other way so, we will have to write one.

( rhsv <- app(rrgb, \(x) {
  as.numeric(rgb2hsv(matrix(c(x[1], x[2], x[3]), nrow=3, ncol=1) ))
}) )

This lets us get to your original question of HSV to RGB. Here is a function that performs the HSV to RGB transformation. The hsv function converts the HSV values to a hexadecimal string then col2rgb converts hexadecimal to RGB. We can then apply this function to the HSV raster. Although, note that there is an assumption that the bands are ordered as (1) H, (2) S, (3) V. There is NA checking and if there are any missing values NA's are returned for the RGB values.

hsv2rgb <- function(x, alpha = FALSE) {
  if(any(is.na(x))) {
    return(rep(NA,3)) 
  } else {
    return(grDevices::col2rgb(grDevices::hsv(x[1], x[2], 
                             x[3]), alpha = alpha))
  }
}

( hsv.trans <- app(rhsv, \(x) {
  as.numeric(hsv2rgb(matrix(c(x[1], x[2], x[3]), nrow=3, ncol=1) ))
}) )
0

As shown in terra issue #1060, using the builtin colorize function is the quickest and easiest way of doing this. The important, and undocumented step, is to use set.RGB in between the two colorize steps.

# Load a raster
library(terra)
r <- rast(system.file("ex/logo.tif", package="terra"))
autoplot(r)

# convert to hsv (or hsl or hsi)
r_hsv <- colorize(r, to = "hsv")

# modify hsv raster as needed
r_hsv$hue <- r_hsv$hue * 0.5

# IMPORTANT: reset the colorspace to rgb
set.RGB(r_hsv, 1:3, "hsv")

# Convert back to rgb
r_modified <- colorize(r_hsv, to = "rgb")

autoplot(r_modified)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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