1

I have a single-frame greyscale image. I would like to modify the image so that all pixels of a certain value becomes colored, say red. Obviously I need to convert the image into a 3-frame image. I use the package EBImage for convenience, but a simple code using only the base package would be great.

mat = round(matrix(runif(100), ncol = 5), digits = 1)
mat
      [,1] [,2] [,3] [,4] [,5]
 [1,]  0.1  0.2  0.6  0.1  0.8
 [2,]  0.1  0.3  0.4  0.5  0.6
 [3,]  0.6  0.3  0.8  0.8  0.5
 [4,]  0.5  0.9  0.7  0.3  0.2
 [5,]  0.7  0.7  0.9  0.3  0.2
 [6,]  0.6  1.0  0.4  0.7  0.3
 [7,]  0.7  0.6  0.5  0.8  0.5
 [8,]  0.4  0.8  0.2  0.2  0.1
 [9,]  0.2  0.1  0.4  0.9  0.6
[10,]  0.7  0.3  0.2  0.3  0.8
[11,]  0.8  1.0  0.4  0.8  0.3
[12,]  0.8  1.0  0.5  0.8  0.7
[13,]  0.6  0.5  0.9  0.9  0.1
[14,]  1.0  0.5  0.6  0.0  0.3
[15,]  0.1  0.5  0.6  0.6  0.2
[16,]  0.2  0.8  0.2  0.5  0.2
[17,]  0.9  0.9  0.4  0.7  0.1
[18,]  0.3  0.7  0.9  0.7  0.4
[19,]  1.0  0.0  0.5  0.0  1.0
[20,]  0.2  0.3  0.9  0.3  0.6

matgray = 255*mat
mat_rgb = channel(matgray, 'rgb') ## using EBImage package
dim(mat_rgb)
[1] 20  5  3
mat_red = channel(mat_rgb, 'asred')

# To generate and save gray image from gray matrix
par(mar = c(0,0,0,0))
png(filename = paste("Image.png"), res=1)
image(mat_red, axes = FALSE)
dev.off()

When I go on to save the image, it says Missing frame index for an image stack, assuming 'i = 1', and the image indeed does not show any red... Thank you for any help!

Gabriel123
  • 426
  • 5
  • 11

1 Answers1

1

You got it almost right. channel(mat, 'rgb'), or its alias function toRGB(mat), promotes a single greyscale frame to an RGB image by duplicating the pixel intensities over the red, green, and blue color channels resulting in an 3D array. By default EBImage operates on image data in the [0:1] range, so normally you wouldn't multiply your matrix by 255. You can use display to visualize the data.

library(EBImage)
set.seed(0) # set random number generator state for reproducibility

mat <- round(matrix(runif(100), ncol = 5), digits = 1)
mat_rgb <- channel(mat, 'rgb') #or: toRGB(mat)
mat_rgb

## Image 
##   colorMode    : Color 
##   storage.mode : double 
##   dim          : 20 5 3 
##   frames.total : 3 
##   frames.render: 1 
## 
## imageData(object)[1:5,1:5,1]
##      [,1] [,2] [,3] [,4] [,5]
## [1,]  0.9  0.8  0.4  0.4  1.0
## [2,]  0.3  0.9  0.8  0.9  0.4
## [3,]  0.4  0.2  0.6  0.3  0.7
## [4,]  0.6  0.7  0.8  0.5  0.4
## [5,]  0.9  0.1  0.6  0.3  0.3

display(mat_rgb)

enter image description here

But even though the colorMode of the image is Color the result still shows up in shades of gray because the color intensities for each pixel are identical across the 3 channels. channel(mat_rgb, 'asred') extracts only the red component which was generated from the greyscale original by copying its values. Therefore you will end up having the same frame as you started with.

mat_red <- channel(mat_rgb, 'asred')
mat_red

## Image 
##   colorMode    : Grayscale 
##   storage.mode : double 
##   dim          : 20 5 
##   frames.total : 1 
##   frames.render: 1 
## 
## imageData(object)[1:5,1:5]
##      [,1] [,2] [,3] [,4] [,5]
## [1,]  0.9  0.8  0.4  0.4  1.0
## [2,]  0.3  0.9  0.8  0.9  0.4
## [3,]  0.4  0.2  0.6  0.3  0.7
## [4,]  0.6  0.7  0.8  0.5  0.4
## [5,]  0.9  0.1  0.6  0.3  0.3

identical(imageData(mat_red), mat) #or: identical(as.array(mat_red), mat)

## [1] TRUE

You can color all pixels red by using the grayscale matrix as the red channel in rgbImage.

mat_red <- rgbImage(red = mat)

display(mat_red)

enter image description here

In order to use a fixed color for highlighting only pixels meeting certain criteria while leaving the rest of the image in greyscale you need to provide the original intensity matrix as the red, green and blue channels to rgbImage but with the pixels of interest altered such that they encode the desired color. For example, to paint all pixels below a threshold of 0.2 red you would set them to 1 (max color value) in the red channel and to 0 across the other two.

pts <- which(mat<0.2)

mat_r <- mat_g <- mat_b <- mat
mat_r[pts] <- 1
mat_g[pts] <- 0
mat_b[pts] <- 0

display( rgbImage(mat_r, mat_g, mat_b) )

enter image description here

Values corresponding to other colors can be easily obtained from col2rgb.

col2rgb("orange")/255

##           [,1]
## red   1.0000000
## green 0.6470588
## blue  0.0000000
aoles
  • 1,525
  • 10
  • 17
  • Thank you oales, that looks great. but I'm not sure that this is quite what I'm trying to do. What you propose colors all pixels in red while just keeping the light-dark contrast. What I want is to keep most pixels in the grey scale and change all pixels whose greyscale value is say 0 to red. The result would be a greyscale image with few red pixels floating in it – Gabriel123 Aug 16 '17 at 16:01
  • Thanks for the clarification, I've updated my answer accordingly. – aoles Aug 16 '17 at 23:44