Is there a way to combine 4+ channels in a single image? (Images.jl)

Input: 4-5 intensity channels, represented as 2D arrays with elements in [0, 1].
Problem: combine and display them in a single image, giving each channel a specific color, e.g. yellow, red, green, blue for 4 channels, using Images.jl

EDIT: maybe this cannot be achieved with Images.jl and I should use a plotting package instead?

One option might be to use the greyscale trick (i forget the name, staggered brightness per channel) and then a colourmap

Thank you @y4lu, could you please detail a bit your answer? Indeed, it seems that I’m looking after colormaps, one for each channel, and then need to combine them together, but I have no idea how to do that (I did my homework and extensively searched through docs and on the web)

see Grayscale - Wikipedia

I thought a black-to-white to rainbow type colour filter at first, but you could probably just put the greyscales back into the colour channels

So you have intens[:,:, channel] or intens[channel, :,:] or something similar
And then greyscale1[:,:] = intens[:,:, channel1] .* 0.7 .+ intens[:,:, channel2] .* 0.3 for an ~ 2:1 channel mix

1 Like

First and foremost, if the 4-5 images you’re trying to combine are not discretized, then keep in mind that you’ll get a many-to-one mapping: we humans have only 3 cone photoreceptors, so intensity & color live in a 3-dimensional space. If you have 4-5 continuous-valued arrays you have a 4-5 dimensional space at each pixel, and as a consequence the perceived colors will be ambiguous. (If each channel’s data is either 0 or 1 and not values in between, then of course you can encode a large number of discrete colors, so that case would not be ambiguous.)

That said, Julia & JuliaImages makes it easy to do basically anything you want here. You may have noticed colorview in the docs:

julia> using Images

julia> a, b, c = rand(5, 5), rand(5, 5), rand(5, 5)
([0.602573 0.0741727 … 0.355561 0.119715; 0.451787 0.563087 … 0.538494 0.564459; … ; 0.967716 0.247382 … 0.377908 0.964664; 0.679923 0.209195 … 0.122144 0.504967], [0.587022 0.945633 … 0.588516 0.207504; 0.508459 0.478767 … 0.290102 0.910498; … ; 0.0567286 0.129025 … 0.721583 0.401006; 0.454971 0.536882 … 0.701881 0.400132], [0.295955 0.402741 … 0.332491 0.712356; 0.165172 0.595745 … 0.711917 0.804955; … ; 0.785742 0.197636 … 0.188005 0.626392; 0.970145 0.637129 … 0.65131 0.42688])

julia> cv = colorview(RGB, a, b, c)
5×5 mappedarray(RGB{Float64}, ImageCore.extractchannels, ::Array{Float64,2}, ::Array{Float64,2}, ::Array{Float64,2}) with eltype RGB{Float64}:
 RGB{Float64}(0.602573,0.587022,0.295955)     RGB{Float64}(0.0741727,0.945633,0.402741)   RGB{Float64}(0.546985,0.992298,0.674326)  RGB{Float64}(0.355561,0.588516,0.332491)  RGB{Float64}(0.119715,0.207504,0.712356)
 RGB{Float64}(0.451787,0.508459,0.165172)     RGB{Float64}(0.563087,0.478767,0.595745)    RGB{Float64}(0.482917,0.58218,0.769182)   RGB{Float64}(0.538494,0.290102,0.711917)  RGB{Float64}(0.564459,0.910498,0.804955)
 RGB{Float64}(0.478996,0.0149608,0.00117074)  RGB{Float64}(0.00735151,0.587855,0.807404)  RGB{Float64}(0.753689,0.661367,0.19432)   RGB{Float64}(0.751488,0.621405,0.41503)   RGB{Float64}(0.608803,0.279269,0.791344)
 RGB{Float64}(0.967716,0.0567286,0.785742)    RGB{Float64}(0.247382,0.129025,0.197636)    RGB{Float64}(0.866418,0.915348,0.683901)  RGB{Float64}(0.377908,0.721583,0.188005)  RGB{Float64}(0.964664,0.401006,0.626392)
 RGB{Float64}(0.679923,0.454971,0.970145)     RGB{Float64}(0.209195,0.536882,0.637129)    RGB{Float64}(0.604198,0.927303,0.133829)  RGB{Float64}(0.122144,0.701881,0.65131)   RGB{Float64}(0.504967,0.400132,0.42688) 

Notice that the type of cv is a mappedarray. So you can easily do this:

julia> d, e = rand(5, 5), rand(5, 5)   # make more color channels
([0.378119 0.825132 … 0.93718 0.337192; 0.399923 0.272639 … 0.36042 0.357065; … ; 0.88771 0.140719 … 0.879098 0.884272; 0.141043 0.0173446 … 0.575349 0.630429], [0.231239 0.863472 … 0.750323 0.37466; 0.626438 0.654218 … 0.891385 0.0098046; … ; 0.0813098 0.177314 … 0.163829 0.965542; 0.697322 0.0950159 … 0.658177 0.419483])

julia> rd, gr, bl = RGB(1,0,0), RGB(0,1,0), RGB(0,0,1)
(RGB{N0f8}(1.0,0.0,0.0), RGB{N0f8}(0.0,1.0,0.0), RGB{N0f8}(0.0,0.0,1.0))

julia> using MappedArrays

julia> cv5 = mappedarray((u,v,w,x,y)->(u+w)*rd + y*gr + (v+x)*bl, a, b, c, d, e)
5×5 mappedarray(getfield(Main, Symbol("##13#14"))(), ::Array{Float64,2}, ::Array{Float64,2}, ::Array{Float64,2}, ::Array{Float64,2}, ::Array{Float64,2}) with eltype RGB{Float64}:
 RGB{Float64}(0.898528,0.231239,0.965142)  RGB{Float64}(0.476914,0.863472,1.77076)    RGB{Float64}(1.22131,0.458332,1.16993)    RGB{Float64}(0.688053,0.750323,1.5257)   RGB{Float64}(0.832072,0.37466,0.544695)
 RGB{Float64}(0.616959,0.626438,0.908382)  RGB{Float64}(1.15883,0.654218,0.751406)    RGB{Float64}(1.2521,0.0795215,1.2919)     RGB{Float64}(1.25041,0.891385,0.650522)  RGB{Float64}(1.36941,0.0098046,1.26756)
 RGB{Float64}(0.480166,0.567315,0.934295)  RGB{Float64}(0.814755,0.266489,1.45927)    RGB{Float64}(0.948009,0.81312,0.688448)   RGB{Float64}(1.16652,0.819015,1.60996)   RGB{Float64}(1.40015,0.11565,0.683988) 
 RGB{Float64}(1.75346,0.0813098,0.944439)  RGB{Float64}(0.445018,0.177314,0.269744)   RGB{Float64}(1.55032,0.857035,1.23982)    RGB{Float64}(0.565913,0.163829,1.60068)  RGB{Float64}(1.59106,0.965542,1.28528) 
 RGB{Float64}(1.65007,0.697322,0.596014)   RGB{Float64}(0.846324,0.0950159,0.554226)  RGB{Float64}(0.738027,0.452477,0.949546)  RGB{Float64}(0.773454,0.658177,1.27723)  RGB{Float64}(0.931846,0.419483,1.03056)

Obviously, it’s up to you to figure out how you want to map those 4-5 channels to an RGB color. But once you’ve decided that, it’s a 1-liner.

If your channels are discretized (e.g., 0 or 1), then consider using the 4-5 channels as a bit pattern to make an integer and look up a color from a list, e.g., as returned by distinguishable_colors:

cv5 = mappedarray((u,v,w,x,y)->colorlist[1 + u + Int(v)<<1 + Int(w)<<2 + Int(x)<<3 + Int(y)<<4], a, b, c, d, e)
6 Likes

Thank you @tim.holy, and a nice code snippet - mapped arrays are the way to go!

1 Like

Noob question, how would you plot this new RGB array? It’s 2 dimensional and Plots.heatmap() shows the wrong thing.

ImageView should handle it fine. I am less sure about Plots since I am not a heavy user. For PyPlot you have to use channelview to split out a separate color axis.