imwrite() saved colormap incorrectly

imwrite() was used to save indexed images along with its colormap. Suppose we use 'givenMap' to denote the colormap provided to imwrite() and 'savedMap' to denote the colormap obtained using imread() on the saved image.
The weird things were that:
1) For some colormaps, exact values in 'givenMap' and 'savedMap' were different. The magnitude of differences was in the order of 10%-20%;
2) But for other colormaps, the content in 'givenMap' and 'savedMap' were the same.
Below are exemplar codes:
% file path for saving image
savedFilePath = 'D:\BMP\imwriteTest';
if ~exist(savedFilePath,'dir')
mkdir(savedFilePath);
end
% load image data
givenData = load('clown.mat');
givenData = givenData.X;
% set range of image to 0-1
minImg = min(givenData(:));
maxImg = max(givenData(:));
givenData = (givenData-minImg)./(maxImg-minImg);
% set image to uint8 type
givenData = givenData*255;
givenData = uint8(givenData);
Exemplar colormap that remained the same after imwrite():
% create a colormap
defaultMap = (0:255)'*[1,1,1]; % 256*3
defaultMap = defaultMap./255;
givenMap = defaultMap;
% use imwrite to save both data and colormap
imwrite(givenData,givenMap,[savedFilePath,'\copperclown.bmp'],'bmp');
% load saved data and colormap
[~,savedMap] = imread([savedFilePath,'\copperclown.bmp']);
% sum of differences
diffMap = sum(abs(givenMap(:)-savedMap(:)));
Exemplar colormap that changed after imwrite():
% create a colormap
defaultMap = (0:255)'*[1,1,1]; % 256*3
defaultMap = defaultMap./255;
givenMap = defaultMap;
% modify centeral part of colormap
centralMapValue = (0:200)/200;
givenMap(2:202,:) = repmat(centralMapValue(:),1,3);
% use imwrite to save both data and colormap
imwrite(givenData,givenMap,[savedFilePath,'\copperclown.bmp'],'bmp');
% load saved data and colormap
[~,savedMap] = imread([savedFilePath,'\copperclown.bmp']);
% sum of differences
diffMap = sum(abs(givenMap(:)-savedMap(:)));
Below are first 10 rows of 'givenMap' and 'savedMap', it's obvious that they were quite different:
Moreever, we can see that differences of adjacent rows was fixed at 0.05 in 'givenMap', but the value oscillate at '0.39' and '0.78' in 'savedMap'.
% show differences in adjacent rows of colormap
modifiedMapIndex = 2:202;
diffGivenMap_adjacentRow = diff(givenMap(modifiedMapIndex,1));
diffSavedMap_adjacentRow = diff(savedMap(modifiedMapIndex,1));
diffGivenMap_adjacentRow = [0;diffGivenMap_adjacentRow(:)];
diffSavedMap_adjacentRow = [0;diffSavedMap_adjacentRow(:)];
maxDiffMap_adjacentRow = max(abs([diffGivenMap_adjacentRow(:);diffSavedMap_adjacentRow(:)]));
maxDiffMap_adjacentRow = maxDiffMap_adjacentRow*(1+0.05*sign(maxDiffMap_adjacentRow));
minDiffMap_adjacentRow = 0;
figure;
fontSize = 15;
subplot(1,2,1)
plot(modifiedMapIndex,diffGivenMap_adjacentRow,'ko')
xlim([2 202])
ylim([minDiffMap_adjacentRow maxDiffMap_adjacentRow])
xlabel('#Row','FontSize',fontSize)
ylabel('mapDiff\_adjacentRow','FontSize',fontSize)
title('Difference in Adjacent Row@givenMap','FontSize',fontSize)
subplot(1,2,2)
plot(modifiedMapIndex,diffSavedMap_adjacentRow,'ro')
xlim([2 202])
ylim([minDiffMap_adjacentRow maxDiffMap_adjacentRow])
xlabel('#Row','FontSize',fontSize)
ylabel('mapDiff\_adjacentRow','FontSize',fontSize)
title('Difference in Adjacent Row@savedMap','FontSize',fontSize)
More detailled comparison can see the attached code.
My question is: are these differences caused by
1) the innate 'imprecision' of imwrite() or imread()?
or
2) my misuse of imwrite() or imread()?

5 Commenti

"are these differences caused by 1) the innate 'imprecision' of imwrite() or imread()? or 2) my misuse of imwrite() or imread()?"
The differences are caused by the fact that you have not taken into account that BMP files only store integer values, not floating point values. The exact map values in your second example cannot be stored in a BMP file, instead they are converted to the nearest suitable integer values before saving to the file.
Thank you for your comment.
"BMP files only store integer values, not floating point values".
I know this is correct for data elements of the image, but do you think that this is also the case for corresponding colormap?
Moreover, values in the two exemplar colormaps I gave are both of floating point type, but only one of them were correctly saved using imwrite(). So I still don't understand what imwrite() does so as to result in such opposite results or what a correct/proper colormap should be so as to be faithfully saved?
"values in the two exemplar colormaps I gave are both of floating point type, but only one of them were correctly saved using imwrite()"
The values are the cause, not their class. All that really matters is that in your first example, all of the values could be scaled and converted exactly to integer values. In your second example, not all of them to whole numbers, so rounding occurs.
"So I still don't understand what imwrite() does so as to result in such opposite results or what a correct/proper colormap should be so as to be faithfully saved?"
What makes you think that any image file format is required to "faithfully save" all of the exact values that you want?
BMP supports some integer values. Some of your values cannot be represented exactly using those integers.
Actually there is a very simple answer to your question: the map must only include values which when scaled by 255 are whole numbers. Thus no rounding will occur when they are converted to 8 bit integer (perhaps the map can support other integer precisions, I did not check).
"I still don't understand what imwrite() does so as to result in such opposite results..."
Lets take a look at some values are you trying to save, for example (quite randomly) the fifth row:
Example 1:
defaultMap = (0:255)'*[1,1,1]; % 256*3
defaultMap = defaultMap./255;
defaultMap(5,:) % for comparison with "imported" value
ans = 1×3
0.0157 0.0157 0.0157
tmp = defaultMap(5,:)*255 % scaled -> these are whole numbers!
tmp = 1×3
4 4 4
bmp = uint8(tmp) % saved as integer inside BMP file
bmp = 1×3
4 4 4
double(bmp)./255 % file import
ans = 1×3
0.0157 0.0157 0.0157
Example 2:
givenMap = defaultMap;
centralMapValue = (0:200)/200;
givenMap(2:202,:) = repmat(centralMapValue(:),1,3);
givenMap(5,:) % for comparison with "imported" value
ans = 1×3
0.0150 0.0150 0.0150
int = givenMap(5,:)*255 % scaled -> are these whole numbers? (hint: no)
int = 1×3
3.8250 3.8250 3.8250
imp = uint8(int) % saved as integer inside BMP file
imp = 1×3
4 4 4
double(imp)./255 % file import
ans = 1×3
0.0157 0.0157 0.0157
Walter Roberson
Walter Roberson il 9 Lug 2021
Modificato: Walter Roberson il 9 Lug 2021
Representing colors using at most one byte per component (0:255) is inherent in bitmap format. With some options, even fewer bits are used.
I have to wonder about that first map. 0.005 * 255 is about 1 1/4 so if that table 0.005 apart were to continue to 256 entries you would be dealing with relative intensity greater than 1.
Thanks for all your patience and detailed answers.
Following Stephen's comment, I think I know how imwrite() deal with provided colormaps: for giivenMap(in range 0-1), it will first be multiplied by 255 to be in range 0-255, and then be rounded to nearest integer(like the function of uint8). In the last step, 255 will be divided to obtain savedMap which is supposed to be in range 0-1.
The logic behind such processing, as is pointed out by both Stephen and Walter, is that: for any color channel of an image, the maximum number of different values is 256 (I don't know if this is also true for display devices/monitor). Thus, values in the colormap must be discretized within [0 255].
Lastly, I really appreciate the help of Stephen and Walter. Thanks for your help.
Below are my code to demonstrate the discretization of imwrite():
% pre-define some parameter
savedFilePath = 'D:\BMP\imwriteTest';
if ~exist(savedFilePath,'dir')
mkdir(savedFilePath);
end
% load image
givenData = load('clown.mat');
givenData = givenData.X;
% set range of image to 0-1
minImg = min(givenData(:));
maxImg = max(givenData(:));
givenData = (givenData-minImg)./(maxImg-minImg);
% set image to uint8 type
givenData = givenData*255;
givenData = uint8(givenData);
% create a colormap in which each row is scaled by values in [0 255]
givenMap = ones(256,3);
givenGain = 1./linspace(0,255,256);
givenGain = repmat(givenGain(:),1,3);
givenMap = givenMap.*givenGain;
givenMap(givenMap > 1) = 1;
% use imwrite to save both data and colormap
imwrite(givenData,givenMap,[savedFilePath,'\copperclown.bmp'],'bmp');
% load saved data and colormap
[savedData,savedMap] = imread([savedFilePath,'\copperclown.bmp']);
% multiply givenMap and savedMap with 255
givenMap_float = givenMap*255;
savedMap_float = savedMap*255;
diffMap_float = abs(givenMap_float-savedMap_float);
% set givenMap and savedMap to type uint8
givenMap_int = uint8(givenMap_float);
savedMap_int = uint8(savedMap_float);
diffMap_int = abs(givenMap_int-savedMap_int);
diffMap_int = double(diffMap_int);
% show results before and after discretization
figure;
plot(1:256,diffMap_float(:,1),'ro')
hold on;
plot(1:256,diffMap_int(:,1),'ko')
plot([0 257],0.5+[0 0],'k-.','linewidth',2)
currLegend = legend('float','integer','location','northeast');
set(currLegend,'fontsize',15);
xlabel('#Row','FontSize',15)
ylabel('absDiff','FontSize',15)
xlim([0 257])
ylim([0 1])
title('abs(givenMap-savedMap)','FontSize',15)
Output of above code is:

Accedi per commentare.

Risposte (0)

Categorie

Scopri di più su Images in Centro assistenza e File Exchange

Prodotti

Release

R2017a

Richiesto:

il 8 Lug 2021

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by