Undocumented Matlab
  • SERVICES
    • Consulting
    • Development
    • Training
    • Gallery
    • Testimonials
  • PRODUCTS
    • IQML: IQFeed-Matlab connector
    • IB-Matlab: InteractiveBrokers-Matlab connector
    • EODML: EODHistoricalData-Matlab connector
    • Webinars
  • BOOKS
    • Secrets of MATLAB-Java Programming
    • Accelerating MATLAB Performance
    • MATLAB Succinctly
  • ARTICLES
  • ABOUT
    • Policies
  • CONTACT
  • SERVICES
    • Consulting
    • Development
    • Training
    • Gallery
    • Testimonials
  • PRODUCTS
    • IQML: IQFeed-Matlab connector
    • IB-Matlab: InteractiveBrokers-Matlab connector
    • EODML: EODHistoricalData-Matlab connector
    • Webinars
  • BOOKS
    • Secrets of MATLAB-Java Programming
    • Accelerating MATLAB Performance
    • MATLAB Succinctly
  • ARTICLES
  • ABOUT
    • Policies
  • CONTACT

Auto-scale image colors

February 21, 2018 One Comment

I deal extensively in image processing in one of my consulting projects. The images are such that most of the interesting features are found in the central portion of the image. However, the margins of the image contain z-values that, while not interesting from an operational point-of-view, cause the displayed image’s color-limits (axes CLim property) to go wild. An image is worth a thousand words, so check the following raw image (courtesy of Flightware, Inc.), displayed by the following simple script:

hImage = imagesc(imageData); colormap(gray); colorbar;

hImage = imagesc(imageData); colormap(gray); colorbar;

Raw image with default Matlab CLim
Raw image with default Matlab CLim

Rescaling the axes color-limits

As you can see, this image is pretty useless for human-eye analysis. The reason is that while all of the interesting features in the central portion of the image have a z-value of ~-6, the few pixels in the margins that have a z-value of 350+ screw up the color limits and ruin the perceptual resolution (image contrast). We could of course start to guess (or histogram the z-values) to get the interesting color-limit range, and then manually set hAxes.CLim to get a much more usable image:

hAxes = hImage.Parent; hAxes.CLim = [-7.5,-6];

hAxes = hImage.Parent; hAxes.CLim = [-7.5,-6];

Raw image with a custom CLim
Raw image with a custom CLim

Auto-scaling the axes color-limits

Since the z-values range and distribution changes between different images, it would be better to automatically scale the axes color-limits based on an analysis of the image. A very simple technique for doing this is to take the 5%,95% or 10%,90% percentiles of the data, clamping all outlier data pixels to the extreme colors. If you have the Stats Toolbox you can use the prctile function for this, but if not (or even if you do), here’s a very fast alternative that automatically scales the axes color limits based on the specified threshold (a fraction between 0-0.49):

% Rescale axes CLim based on displayed image portion's CData
function rescaleAxesClim(hImage, threshold)
    % Get the displayed image portion's CData
    CData = hImage.CData;
    hAxes = hImage.Parent;
    XLim = fix(hAxes.XLim);
    YLim = fix(hAxes.YLim);
    rows = min(max(min(YLim):max(YLim),1),size(CData,1)); % visible portion
    cols = min(max(min(XLim):max(XLim),1),size(CData,2)); % visible portion
    CData = CData(unique(rows),unique(cols));
    CData = CData(:);  % it's easier to work with a 1d array
    % Find the CLims from this displayed portion's CData
    CData = sort(CData(~isnan(CData)));  % or use the Stat Toolbox's prctile()
    thresholdVals = [threshold, 1-threshold];
    thresholdIdxs = fix(numel(CData) .* thresholdVals);
    CLim = CData(thresholdIdxs);
    % Update the axes
    hAxes.CLim = CLim;
end

% Rescale axes CLim based on displayed image portion's CData function rescaleAxesClim(hImage, threshold) % Get the displayed image portion's CData CData = hImage.CData; hAxes = hImage.Parent; XLim = fix(hAxes.XLim); YLim = fix(hAxes.YLim); rows = min(max(min(YLim):max(YLim),1),size(CData,1)); % visible portion cols = min(max(min(XLim):max(XLim),1),size(CData,2)); % visible portion CData = CData(unique(rows),unique(cols)); CData = CData(:); % it's easier to work with a 1d array % Find the CLims from this displayed portion's CData CData = sort(CData(~isnan(CData))); % or use the Stat Toolbox's prctile() thresholdVals = [threshold, 1-threshold]; thresholdIdxs = fix(numel(CData) .* thresholdVals); CLim = CData(thresholdIdxs); % Update the axes hAxes.CLim = CLim; end

Note that a threshold of 0 uses the full color range, resulting in no CLim rescaling at all. At the other extreme, a threshold approaching 0.5 reduces the color-range to a single value, basically reducing the image to an unusable B/W (rather than grayscale) image. Different images might require different thresholds for optimal contrast. I believe that a good starting point for the threshold is a value of 0.10, which corresponds to the 10-90% range of CData values.

Dynamic auto-scaling of axes color-limits

This is very nice for the initial image display, but if we zoom-in, or pan a sub-image around, or update the image in some way, we would need to repeat calling this rescaleAxesClim() function every time the displayed image portion changes, otherwise we might still get unusable images. For example, if we zoom into the image above, we will see that the color-limits that were useful for the full image are much less useful on the local sub-image scale. The first (left) image uses the static custom color limits [-7.5,-6] above (i.e., simply zooming-in on that image, without modifying CLim again); the second (right) image is the result of repeating the call to rescaleAxesClim(), which improves the image contrast:

Zoomed-in image with a custom static CLim
Zoomed-in image with a custom static CLim
Zoomed-in image with a re-applied custom CLim
Zoomed-in image with a re-applied custom CLim

We could in theory attach the rescaleAxesClim() function as a callback to the zoom and pan functions (that provide such callback hooks). However, we would still need to remember to manually call this function whenever we modify the image or its containing axes programmatically.
A much simpler way is to attach our rescaleAxesClim() function as a callback to the image’s undocumented MarkedClean event:

% Instrument image: add a listener callback to rescale upon any image update
addlistener(hImage, 'MarkedClean', @(h,e)rescaleAxesClim(hImage,threshold));

% Instrument image: add a listener callback to rescale upon any image update addlistener(hImage, 'MarkedClean', @(h,e)rescaleAxesClim(hImage,threshold));

In order to avoid callback recursion (potentially caused by modifying the axes CLim within the callback), we need to add a bit of code to the callback that prevents recursion/reentrancy (details). Here’s one simple way to do this:

% Rescale axes CLim based on displayed image portion's CData
function rescaleAxesClim(hImage, threshold)
    % Check for callback reentrancy
    inCallback = getappdata(hImage, 'inCallback');
    if ~isempty(inCallback), return, end
    try
        setappdata(hImage, 'inCallback',1);  % prevent reentrancy
        % Get the displayed image portion's CData
        ...  (copied from above)
        % Update the axes
        hAx.CLim = CLim;
        drawnow; pause(0.001);  % finish all graphic updates before proceeding
    catch
    end
    setappdata(hImage, 'inCallback',[]);  % reenable this callback
end

% Rescale axes CLim based on displayed image portion's CData function rescaleAxesClim(hImage, threshold) % Check for callback reentrancy inCallback = getappdata(hImage, 'inCallback'); if ~isempty(inCallback), return, end try setappdata(hImage, 'inCallback',1); % prevent reentrancy % Get the displayed image portion's CData ... (copied from above) % Update the axes hAx.CLim = CLim; drawnow; pause(0.001); % finish all graphic updates before proceeding catch end setappdata(hImage, 'inCallback',[]); % reenable this callback end

The result of this dynamic automatic color-scaling can be seen below:

Zoomed-in image with dynamic CLim
Zoomed-in image with dynamic CLim

autoScaleImageCLim utility

I have created a small utility called autoScaleImageCLim, which includes all the above, and automatically sets the specified input image(s) to use auto color scaling. Feel free to download this utility from the Matlab File Exchange. Here are a few usage examples:

autoScaleImageCLim()           % auto-scale the current axes' image
autoScaleImageCLim(hImage,5)   % auto-scale image using 5%-95% CData limits
autoScaleImageCLim(hImage,.07) % auto-scale image using 7%-93% CData limits

autoScaleImageCLim() % auto-scale the current axes' image autoScaleImageCLim(hImage,5) % auto-scale image using 5%-95% CData limits autoScaleImageCLim(hImage,.07) % auto-scale image using 7%-93% CData limits

(note that the hImage input parameter can be an array of image handles)
Hopefully one day the so-useful MarkedClean event will become a documented and fully-supported event for all HG objects in Matlab, so that we won’t need to worry that it might not be supported by some future Matlab release…

Related posts:

  1. Image Easter egg – The default image presented by Matlab's image function has a very interesting undocumented story....
  2. Auto-completion widget – Matlab includes a variety of undocumented internal controls that can be used for an auto-completion component. ...
  3. Persisting transparent colors in HG2 – We can set semi- and fully-transparent colors in HG2 for multiple graphic objects, but making these settings stick is non-trivial. ...
  4. Uitable cell colors – A few Java-based customizations can transform a plain-looking data table into a lively colored one. ...
  5. Uitab colors, icons and images – Matlab's semi-documented tab panels can be customized using some undocumented hacks...
  6. Changing Matlab's Command Window colors – Matlab's Command Window foreground and background colors can be modified programmatically, using some of Matlab's undocumented internal Java classes. Here's how....
HG2 image Listener Undocumented feature
Print Print
« Previous
Next »
One Response
  1. Peter Cook February 22, 2018 at 17:18 Reply

    Great utility. I do something quite similar in one of my applications. A couple remarks:

    1. If the user of this function has read your “Accelerating MATLAB Performance” book, it’s likely they have set the CLimMode of the parent axes and (if using a colorbar) the LimitsMode property of the colorbar axes peer to 'manual'. If the listener triggers the callback like this, the colorbar will display color ranges correctly but will look kind of funny (as it retains previous limits) so replacing

    hAxes.CLim = CLim;

    hAxes.CLim = CLim;

    with

    [hAxes.CLim, hCb.Limits] = deal(CLim);

    [hAxes.CLim, hCb.Limits] = deal(CLim);

    would be necessary.

    2. It may be the case that XData and YData are mapped to some values that don’t round nicely (like datetime/MATLAB serial datetime), so the fix() function will cause problems. There’s a couple cases to consider – in what you’ve shown, the XData and YData are mapped to integer image indices and this function works fine even though XData and YData are (1×2) arrays. A less desirable case would be mapped XData and YData, but with only corners specified so that XData and YData are (1×2) arrays, but are not integers and may not fix nicely. If an (mxn) CData array is mapped to (mx1) YData and (nx1) XData then logical masking works i.e.

    CData = hImage.CData(hImage.YData>=hAxes.YLim(1) & hImage.YData<=hAxes.YLim(2), ...
                         hImage.XData>=hAxes.XLim(1) & hImage.XData<=hAxes.XLim(2));
    CData = CData(:);

    CData = hImage.CData(hImage.YData>=hAxes.YLim(1) & hImage.YData<=hAxes.YLim(2), ... hImage.XData>=hAxes.XLim(1) & hImage.XData<=hAxes.XLim(2)); CData = CData(:);

    3. For threshold values, depending on the dynamic range of the CData and zoom level the user has chosen, it may be safer to say

    thresholdIdx = [ceil(threshold*numel(CData)), floor((1-threshold)*numel(CData))]

    thresholdIdx = [ceil(threshold*numel(CData)), floor((1-threshold)*numel(CData))]

    to avoid the occasional zero-index attempt error.

Leave a Reply
HTML tags such as <b> or <i> are accepted.
Wrap code fragments inside <pre lang="matlab"> tags, like this:
<pre lang="matlab">
a = magic(3);
disp(sum(a))
</pre>
I reserve the right to edit/delete comments (read the site policies).
Not all comments will be answered. You can always email me (altmany at gmail) for private consulting.

Click here to cancel reply.

Useful links
  •  Email Yair Altman
  •  Subscribe to new posts (feed)
  •  Subscribe to new posts (reader)
  •  Subscribe to comments (feed)
 
Accelerating MATLAB Performance book
Recent Posts

Speeding-up builtin Matlab functions – part 3

Improving graphics interactivity

Interesting Matlab puzzle – analysis

Interesting Matlab puzzle

Undocumented plot marker types

Matlab toolstrip – part 9 (popup figures)

Matlab toolstrip – part 8 (galleries)

Matlab toolstrip – part 7 (selection controls)

Matlab toolstrip – part 6 (complex controls)

Matlab toolstrip – part 5 (icons)

Matlab toolstrip – part 4 (control customization)

Reverting axes controls in figure toolbar

Matlab toolstrip – part 3 (basic customization)

Matlab toolstrip – part 2 (ToolGroup App)

Matlab toolstrip – part 1

Categories
  • Desktop (45)
  • Figure window (59)
  • Guest bloggers (65)
  • GUI (165)
  • Handle graphics (84)
  • Hidden property (42)
  • Icons (15)
  • Java (174)
  • Listeners (22)
  • Memory (16)
  • Mex (13)
  • Presumed future risk (394)
    • High risk of breaking in future versions (100)
    • Low risk of breaking in future versions (160)
    • Medium risk of breaking in future versions (136)
  • Public presentation (6)
  • Semi-documented feature (10)
  • Semi-documented function (35)
  • Stock Matlab function (140)
  • Toolbox (10)
  • UI controls (52)
  • Uncategorized (13)
  • Undocumented feature (217)
  • Undocumented function (37)
Tags
AppDesigner (9) Callbacks (31) Compiler (10) Desktop (38) Donn Shull (10) Editor (8) Figure (19) FindJObj (27) GUI (141) GUIDE (8) Handle graphics (78) HG2 (34) Hidden property (51) HTML (26) Icons (9) Internal component (39) Java (178) JavaFrame (20) JIDE (19) JMI (8) Listener (17) Malcolm Lidierth (8) MCOS (11) Memory (13) Menubar (9) Mex (14) Optical illusion (11) Performance (78) Profiler (9) Pure Matlab (187) schema (7) schema.class (8) schema.prop (18) Semi-documented feature (6) Semi-documented function (33) Toolbar (14) Toolstrip (13) uicontrol (37) uifigure (8) UIInspect (12) uitable (6) uitools (20) Undocumented feature (187) Undocumented function (37) Undocumented property (20)
Recent Comments
Contact us
Captcha image for Custom Contact Forms plugin. You must type the numbers shown in the image
Undocumented Matlab © 2009 - Yair Altman
This website and Octahedron Ltd. are not affiliated with The MathWorks Inc.; MATLAB® is a registered trademark of The MathWorks Inc.
Scroll to top