Use G'MIC to combine separate R,G,B images to create RGB image

Given that I have three separate b/w images and that each of these images is the R,G,B channel needed for a color image, is there a way to use the G’MIC command line to merge them into a color image. I currently use Photoshop and copy/paste the individual images into their appropriate channel. Looking through the G’MIC command doc I didn’t find anything so am asking here in case I missed something. Thanks.

gmic r.jpg g.jpg b.jpg to_gray append c output rgb.jpg
  • Input order matters
  • to_gray makes sure that each BW image has only one channel
  • append c makes one image from all input images as channels

Hi afre,

Perfect. Thank you. This could be a real time saver. I went back through the doc but could find nothing that hinted at this technique.

If I may ask, once I have the RGB image, what if I have a separate grayscale for luminosity? Do you know if there is a technique where I could insert a grayscale image as the “L” channel for what is otherwise an RGB image?

Took a long time to type out. Hope this is helpful. :slight_smile:

Basically, what you are asking is to replace the L* channel with another.

gmic l.jpg rgb_linear.jpg local[0] to_gray / 255 * 100 endlocal local[1] rgb2lab split c rm[0] endlocal append c lab2rgb output rgb.jpg
  • Make sure that rgb_linear.jpg is linear. If not and it is sRGB’s gamma, then do srgb2rgb right before rgb2lab
  • L*a*b* is a different colour model, so you would need to convert to it.
  • The range of L* is supposed to be [0,100]. If l.jpg is 8-bit, e.g., [3,230], you could either stretch it to [0,100] (normalize 0,100) or to keep it consistent in relation to what it was originally (/ 255 * 100). Insert the one that would best suit you.
  • localendlocal means that it only applies to the image indexed ([0] means the first image, etc.).
  • rgb2lab and lab2rgb are the conversion commands.
  • split c and append c are the channel manipulation commands.
  • What you are doing is removing the L* channel from the rgb.jpg image and appending l.jpg.
1 Like

Maybe something simpler is to deal with the YCbCr colorspace rather than Lab ?

$ gmic rgb.jpg luminance.jpg to_gray. rgb2ycbcr.. j.. . rm. ycbcr2rgb o modified.jpg
1 Like

Thank you again Afre for your help. The image below illustrates the results I obtained.

A: this is the original 16bit RGB image (a TIF file)
B: this is the RGB image created by converting it to LAB and inserting the “Ferniegair-GE3D-B8.tif” image (also 16 bit) into the L channel with Photoshop.

So ‘B’ is what I am trying to obtain via G’MIC.

C: this is the output of the command:
gmic Ferniegair-GE3D-B8.tif Ferniegair-GE3D-RGB.tif local[0] to_gray / 65535 * 100 endlocal local[1] rgb2lab split c rm[0] endlocal append c lab2rgb output Ferniegair-GE3D-RGB8T4.tif

as viewed in Irfanview.

D: the same output image from C but as viewed in RawTherapee

E: Even though I know the source is not sRGB, I gave the addition of the srgb2rgb a spin as follows:
gmic Ferniegair-GE3D-B8.tif Ferniegair-GE3D-RGB.tif local[0] to_gray / 65535 * 100 endlocal local[1] srgb2rgb rgb2lab split c rm[0] endlocal append c lab2rgb output Ferniegair-GE3D-RGB8T4.tif
with the output as viewed in Irfanview.

F: the same image as E but as seen in RawTherapee and GIMP.

I only tried the srgb2rgb option just to see how it would impact the output.

Also, removing the to_gray option from the command stream results in no changes. I knew that but tried it anyway just to see.

As I work it out the command stream operates as follows:

gmic 
Ferniegair-GE3D-B8.tif   # read in the grayscale image that represents brightness
Ferniegair-GE3D-RGB.tif  # read in the color image 
local[0]       # apply the following commands to the 1st image read in
to_gray        # convert it to a grayscale image 
/ 65535 * 100  # replace each pixel value with the result of (original_pixel_value / 65535 * 100)  to generate values in the range of 0 to 100
endlocal       # end
local[1]       # apply the following commands to the 2nd image read in
rgb2lab        # convert it from RGB to LAB
split c        # split the channels
rm[0]          # zero out channel 0 "L"
endlocal       # end
append c       # append c(what is c ?) I assume the first image in the list is inserted into the 1st channel of the last image in the list, ie 'L' 
lab2rgb        # convert image(s?) to RGB
output Ferniegair-GE3D-RGB8T4.tif

Append is a mystery to me in that the reference doc says “Append specified image to selected images, or all selected images together, along specified axis. (equivalent to shortcut command a). axis can be { x | y | z | c }”. I assume that c does not refer to channel but to how to center the individual images when they are being merged.

But how are the images appended? Are the pixel values of all images simply being averaged? How does a grayscale get appended to a color image?

I also note that the image being created on output is identified by Photoshop as being 32bit. So how do I force 16 bit output? Recalling an earlier exchange we had where you were helping me, I tried the following version of the command:
gmic Ferniegair-GE3D-B8.tif Ferniegair-GE3D-RGB.tif local[0] / 65535 * 100 endlocal local[1] rgb2lab split c rm[0] endlocal append c lab2rgb mul 257 output Ferniegair-GE3D-RGB8T4.tif,ushort

In this instance, the output image as viewed by Irfanview, Rawtherapee, and GIMP all match image ‘C’ - which is a definite improvement in terms of consistency - and the output is 16bit which is what I want.

Any suggestions on how to get G’MIC to create output that matches the view shown in ‘B’?

Thanks

2 Likes

If you are dealing with 16bits images, then I suggest you divide the values by 257 before starting the processing. RGB colors in G’MIC are assumed to be in [0,255] (which doesn’t mean 8 bits, as the values can be float-valued.
So a div 257 just after loading the image, and a mul 257 at the end, to go back to 16bits will probably help.

Hi David,
To convert to 8bit from 16bit throws out a lot of information. As I understand it one of the selling points of G’MIC is “to process flawlessly images with 8bits or 16bits integers per channel”. Following is a before and after comparison of the L channel. Clearly the problem is with how the information in the L channel is being processed by one or more of the commands in the stream. The challenge is to figure out where things are going wrong.

Would it be possible you put some images somewhere so we can download them and try doing what you want ? I’m pretty sure this should be easy to find something that works.

Clearly, I still don’t understand how j works. Didn’t know it could replace the Y channel. Would it depend on how many channels that . has? E.g., if . had 2 channels, would YCb be replaced?

Hi David,
Happy to provide the test files I’m using. Will do it in my next reply. Your comment about rgb got me thinking. I’m not sure if my approach to identifying the problem is correct but I ran the following command:

gmic Ferniegair-GE3D-RGB.tif rgb2lab lab2rgb output Ferniegair-GE3D-RGBx.tif

My thinking was that the output should look identical to the input since all I’m doing is swapping color spaces back and forth: rgb → lab → rgb.
Following is the result of the above command:

The view on the right is from Irfanview. The view in Rawtherapee is pretty much all white with some some areas of blue and yellow. And the output is 32 bit.

Clearly I must be doing something wrong here.

When I first got into G’MIC, this was the first thing that stumped me. Conversion commands expect [0,255] range input and provide output of the same range. Anything outside will be clipped. So, if you are inputting 16 bit, you need to divide by 257, etc. After the operation, just remember to do the opposite. Saving using output is a bit confusing too because you would want it to be in the format that RT and Irfanview could interpret correctly. Someone will help you once you provide the sample images.

G’MIC uses floating point representation, 0.0 - 255.0. It’s not 8-bit. The 0.0 - 255.0 might be confusing, because it happens to line up with the 8-bit integer range, but no precision is being lost

So, the div 257 just scales the 16-bit integer range to the G’MIC range, represented in floating point.

Hi David,
Here are the source files:
Ferniegair-GE3D-B8.tif is the 16bit gray image to use for brightness.
Ferniegair-GE3D-B8.tif (4.0 MB)

Ferniegair-GE3D-RGB.tif is the 16bit RGB color image. This image was created using the G’MIC command that Afre provided in the original reply to my query.
Ferniegair-GE3D-RGB.tif (11.9 MB)

I think it noteworthy that the command:
gmic Ferniegair-GE3D-RED.tif Ferniegair-GE3D-GRN.tif Ferniegair-GE3D-BLU.tif to_gray append c output Ferniegair-GE3D-RGB.tif

correctly produced a 16bit and not a 32bit tiff whereas the command stream to replace the brightness channel results in 32bit output. ???

Hi Glenn,

I just want to make sure that I correctly understand your statement so I’ll restate it. When G’MIC converts from 16 bit to 8 bit, it’s not. What it is actually doing is converting numbers in the integer range of 0 to 65,535 into numbers in a floating point range of 0.0 to 255.0 by dividing the 16 bit value by 256.0. So a middle gray value of 32,767 becomes 127.998046875. In which case post conversion the data still has 65,536 possible distinct values. I agree that if this is true then no information is being lost. But is this how G’MIC is doing it?

Yes.

As @afre intimated, this can foist some confusion when using G’MIC. However, it lets folk load the great majority of images into G’MIC and work with them in the same range as was the file data, 0-255. It just gets confusing when you start with a 16-bit 0-65535 image, or, say, a floating point TIFF, which is usually ranged from 0.0-1.0.

By the time you guys are done with me I expect that I’ll be much better at G’MIC.

Running the following command:
gmic Ferniegair-GE3D-RGB.tif / 257 rgb2lab lab2rgb mul 257 output Ferniegair-GE3D-RGBxv2.tif

does give me a perfect looking image in Irfanvew but causes RawTherapee to go belly up and opens as an all white image in GIMP and Photoshop.

Ditto for the following command:
gmic Ferniegair-GE3D-RGB.tif / 257 rgb2lab lab2rgb output Ferniegair-GE3D-RGBxv3.tif

Here is my proposal:

$ gmic Ferniegair-GE3D-RGB.tif Ferniegair-GE3D-B8.tif div 257 srgb2lab8.. j.. . rm. lab82srgb mul 257 round o output16.tiff

Several things to note here :

  • I use commands srgb2lab8 and lab82srgb rather than srgb2lab and lab2srgb. This is basically the same Lab colorspace, but renormalized so that each L,a,b channel has a [0,255] range.
  • I use round before saving. This ensures you get only integer values in your image (here in range [0,65535]), so the output .tif file is saved as 16bits-integers, and not 32bits float-valued (some viewers do not read float-valued tiff).
  • Otherwise, it uses the same technique I’ve shown before: the command j (aka image) is used to draw the scalar image of the lightness directly in the first channel of the Lab image.
    Be sure your lightness image is always a single channel one, otherwise you’ll need to use channel 0 or something similar to constrain it having a single channel.

Because the usual Lab colorspace has a range for the lightness which is [0,100] instead.

Hi David,
Thanks to your invaluable help I am able to replace a part of my workflow for which I formerly relied on Photoshop. Following is my minor mod to your command along with my commentary as to my understanding what each component is doing.

gmic Ferniegair-GE3D-RGB.tif Ferniegair-GE3D-B8.tif div 257 rgb2lab8[0] j[0] [1] rm[1] lab82srgb mul 257 round o output16d.tiff

Ferniegair-GE3D-RGB.tif # read in 1st image (color)
Ferniegair-GE3D-B8.tif  # read in 2nd image (gray for L channel)
div 257                 # divide all pixel values in both images by 257 to remap from range 0-65535 to range 0.0 - 255.0
rgb2lab8[0]             # convert the 1st image from RGB to LAB8 mode 
j[0] [1]                # Image command (not certain of) to draw the 2nd image onto the 1st image
rm[1]                   # remove the 2nd image from the list 
lab82srgb               # convert the remaining image from LAB8 to SRGB 
mul 257                 # multiply each pixel value by 257 to get back to the 0-65535 value range 
round                   # Assuming that round insures all values in the 0-65535 range are integer values 
o output16rgbl.tiff     # output final image

What I don’t understand is the image command (j) as I am unclear what it means to draw the b/w image onto the rgb image. Someone used to working with layers would conclude
that the result would be a b/w image. I am assuming that G’MIC sees the b/w image as having only one channel and simple “draws” that on top of the 1st channel of the LAB image, thus replacing that image’s L channel only since that is the 1st channel. I infer this to mean that if the second image had 3 channels, then drawing it onto the 1st image would have resulted in the 1st image becoming just a copy of the 2nd image.

Once again, thank you.

2 Likes