9

There is no bijection between RGB and Parula, discussed here. I am thinking how to do well the image processing of files in Parula. This challenge has been developed from this thread about removing black color from ECG images by extending the case to a generalized problem with Parula colors.

Data:

enter image description here

which is generated by

[X,Y,Z] = peaks(25);
imgParula = surf(X,Y,Z);
view(2);
axis off;

It is not the point of this thread to use this code in your solution to read the second image.

Code:

[imgParula, map, alpha] = imread('https://i.stack.imgur.com/tVMO2.png'); 

where map is [] and alpha is a completely white image. Doing imshow(imgParula) gives

enter image description here

where you see a lot of interference and lost of resolution because Matlab reads images as RGB, although the actual colormap is Parula. Resizing this picture does not improve resolution.

How can you read image into corresponding colormap in Matlab? I did not find any parameter to specify the colormap in reading.

Community
  • 1
  • 1
Léo Léopold Hertz 준영
  • 134,464
  • 179
  • 445
  • 697
  • It's not completely clear to me: do you want to retrieve the array that has been used to create the first image (with `imshow` and the parula colormap) ? – Ratbert May 01 '15 at 15:52
  • Yes. I want to retrieve the data that has been used to create the first Parula image. I want to be able to create the first image from reading the file back to Matlab and then plotting it again. – Léo Léopold Hertz 준영 May 01 '15 at 16:01
  • 1
    OK. If you don't know what was the setting of `caxis` (_i.e._ the colormap's range) there is an indetermination. For instance, the blue could be any value depending on this setting ... A solution would give you an array ranged between 0 and 1 for instance, which may not be the initial data range. – Ratbert May 01 '15 at 16:05
  • 1
    An image file is not meant to represent arbitrary data, but graphics, which may or may not be representing data. If you want to store data generated in Matlab, use a mat file or maybe another binary or text-based file format. – A. Donda May 01 '15 at 16:12
  • Can you provide code for your "Data" image? The images appear to have identical colors on my machine (OS X, Safari). Also, color profiles might be useful, but you'd have to go to TIFF instead of PNG in that case. – horchler May 01 '15 at 16:13
  • 1
    I think I see how to do it and will try a solution in a few hours / tomorrow when I'm back home, if no solution has been posted in between. – Ratbert May 01 '15 at 16:13
  • @horchler I provided the code to generate the first image but it is not the point to use this code for the solution. – Léo Léopold Hertz 준영 May 01 '15 at 16:15
  • 1
    I think it can be done in the following way: convert the black lines into white, apply regionprops on the non-white pixels, use the barycenters to map the regions on a regular grid and, finally, average the color on the region and map it to the parula colormap by looking at the closest color. Unfortunately, the first step depends too much on how the initial image has been generated (format, resolution), and the fact that the black grid is not completely black. – Ratbert May 02 '15 at 07:58
  • Can you give a practical example? How can we improve the first step? – Léo Léopold Hertz 준영 May 02 '15 at 09:03
  • 1
    @Ratbert: see my attempt below. I'm not sure I covered every aspect of the problem – at least as you detailed – but I did implement a solution to find the "closest" color. – horchler May 05 '15 at 19:57

1 Answers1

9

The Problem

There is a one-to-one mapping from indexed colors in the parula colormap to RGB triplets. However, no such one-to-one mapping exists to reverse this process to convert a parula indexed color back to RGB (indeed there are an infinite number ways to do so). Thus, there is no one-to-one correspondence or bijection between the two spaces. The plot below, which shows the R, G, and B values for each parula index, makes this clearer.

Parula to RGB plot

This is the case for most indexed colors. Any solution to this problem will be non-unique.


A Built-in Solution

I after playing around with this a bit, I realized that there's already a built-in function that may be sufficient: rgb2ind, which converts RGB image data to indexed image data. This function uses dither (which in turn calls the mex function ditherc) to perform the inverse colormap transformation.

Here's a demonstration that uses JPEG compression to add noise and distort the colors in the original parula index data:

img0 = peaks(32);                     % Generate sample data
img0 = img0-min(img0(:));
img0 = floor(255*img0./max(img0(:))); % Convert to 0-255
fname = [tempname '.jpg'];            % Save file in temp directory
map = parula(256);                    % Parula colormap
imwrite(img0,map,fname,'Quality',50); % Write data to compressed JPEG
img1 = imread(fname);                 % Read RGB JPEG file data

img2 = rgb2ind(img1,map,'nodither');  % Convert RGB data to parula colormap

figure;
image(img0);                          % Original indexed data
colormap(map);
axis image;

figure;
image(img1);                          % RGB JPEG file data
axis image;

figure;
image(img2);                          % rgb2ind indexed image data
colormap(map);
axis image;

This should produce images similar to the first three below.

Example original data and converted images


Alternative Solution: Color Difference

Another way to accomplish this task is by comparing the difference between the colors in the RGB image with the RGB values that correspond to each colormap index. The standard way to do this is by calculating ΔE in the CIE L*a*b* color space. I've implemented a form of this in a general function called rgb2map that can be downloaded from my GitHub. This code relies on makecform and applycform in the Image Processing Toolbox to convert from RGB to the 1976 CIE L*a*b* color space.

The following code will produce an image like the one on the right above:

img3 = rgb2map(img1,map);

figure;
image(img3);                          % rgb2map indexed image data
colormap(map);
axis image;

For each RGB pixel in an input image, rgb2map calculates the color difference between it and every RGB triplet in the input colormap using the CIE 1976 standard. The min function is used to find the index of the minimum ΔE (if more than one minimum value exists, the index of the first is returned). More sophisticated means can be used to select the "best" color in the case of multiple ΔE minima, but they will be more costly.


Conclusions

As a final example, I used an image of the namesake Parula bird to compare the two methods in the figure below. The two results are quite different for this image. If you manually adjust rgb2map to use the more complex CIE 1994 color difference standard, you'll get yet another rendering. However, for images that more closely match the original parula colormap (as above) both should return more similar results. Importantly, rgb2ind benefits from calling mex functions and is almost 100 times faster than rgb2map despite several optimizations in my code (if the CIE 1994 standard is used, it's about 700 times faster).

RGB image of bird converted using two methods

Lastly, those who want to learn more about colormaps in Matlab, should read this four-part MathWorks blog post by Steve Eddins on the new parula colormap.

Update 6-20-2015: rgb2map code described above updated to use different color space transforms, which improves it's speed by almost a factor of two.

horchler
  • 18,384
  • 4
  • 37
  • 73
  • `rgb2map` seems to be better to eye than `rgb2ind` in all colors. `rgb2map` seems to burn through with the yellow color. **Why does this burning through occur in `rgb2map`? Any way to avoid it?** - Another hope is that if you could maintain the grid of vertical and horizontal lines in some of your examples. I would like to see how different functions affect the lines in different environments. Those lines are particularly put there for the challenge for a particular reason. – Léo Léopold Hertz 준영 May 05 '15 at 20:41
  • 1
    @Masi: I'm guessing it's due to the use of just a simple `min` function to find the closest color match. At the extremes, i.e., near black, white, and fully-saturated colors, it's likely that there will be multiple minima, in which case the first will be used. This results in many pixels showing up as one of the extreme values on the colormap. Potential ways around this: 1) reorder the colormap so a different color is always selected, 2) randomly choose (or another scheme) when multiple minima are present, 3) use weighting to bias particular channels or combinations thereof to vary the choice. – horchler May 05 '15 at 21:23
  • 1
    The `parula` colormap doesn't include black and white. Your images appear to be screen grabs and have black lines despite using indexed colors because you're viewing the data not as an image, but as a 3-D object via `surf` (black is the default edge color irrespective of the data). A completely different process or special cases will be required to handle this. It would perhaps be better to avoid the problem in the first place by pre-processing and saving files differently. – horchler May 06 '15 at 01:51
  • 1
    The easiest method for handling artifacts like your grid lines might be to modify the colormap slightly. Try the examples in my answer with `map = [0 0 0; parula(254); 1 1 1];`. – horchler May 06 '15 at 02:35
  • Is there any standard color chosen for grids in Parula? If not, then again the choose of any color for the grids is going to be only one selection. – Léo Léopold Hertz 준영 May 06 '15 at 10:49