matlabmatlab-figurecolorbarmatlab-hg2

Showing a new image inside existing axes w/o deleting the colorbar


I am working on a GUI that is being initialized by creating several axes along with an invisible colorbar for each one (this is done so that axes maintain their predefined Position)1. The handles to all axes and colorbars are stored.

Interactions with the UI may result in images being plotted on any one axes. I would like only the colorbar of the active axes to be shown at any given time, by appropriately setting the Visible property of all colorbars.

I am having a problem with this approach, because I'm using imagesc to update my axes, and this deletes any colorbar associated with the axes, making the stored handles invalid.

My question is: how can I use imagesc or image to update an axes that is associated with a colorbar, without the colorbar being deleted?

Here's how to reproduce this problem:

dbclear in newplot %// Needed for the code to be properly re-runnable
%// Create an example figure containing a colorbar:
figure(); imagesc(imread('cameraman.tif')); colorbar;
%// "Refreshing" the displayed image:
uiwait(msgbox('The image will now be refreshed. A breakpoint will be set in newplot.m'));
dbstop in newplot at 124 %// The line responsible for deleting the colorbar in R2015A/B
imagesc(imread('cameraman.tif'));

The line on which the breakpoint in newplot.m is set reads:

cla(ax, 'reset', hsave);

Which is (un)surprisingly an undocumented way to invoke cla (with 3 parameters) that preserves objects whose handles are found in hsave.


Some ideas I had assuming colorbar deletion is inevitable (which I will pursue in case a "sane" solution is not found):

  1. Binding a DeleteFcn to the colorbar that saves its data to some struct. Creating a new colorbar after imagesc has finished, then iterating over the fields of the struct and assigning all properties to the new colorbar object.
  2. Check "every once in a while" that all colorbars exist using either findall(hFig,'type','colorbar') or verify that each of the axes has a valid ColorbarPeerHandle as per the Appendix below. Recreate the CB if invalid.
  3. Delete all colorbars whenver different axes become active and create only the CB I'd like to show.

Appendix - ColorBar/Axes association:


Solution

  • I've managed to come up with a couple of solutions:

    1. safeUpdateImage1 - Based on the circumvention of cla(...) via the creation of a new Image object inside the axes after using hold. This is good for cases when an Image doesn't necessarily exist in the axes.
    2. safeUpdateImage2 - Following mikkola's suggestion, based on updating an existing Image object. This is good for cases when there already is a ColorBar and an Image associated with the axes.

    Here's a demonstration of both solutions alongside the original problem:

    function [] = Problem
    dbclear in newplot %// Needed for the code to be properly re-runnable
    %// Create an example figure containing a colorbar:
    Img = imread('cameraman.tif');
    figure(); imagesc(Img); hAx = gca; colorbar;
    %// Refreshing the displayed image (comment/uncomment as needed):
    switch questdlg('Please select an image update method:','Update method selection',...
                    'Broken','Safe1','Safe2','Safe1')
      case 'Broken'
        brokenUpdateImage(hAx,255-Img);
      case 'Safe1'
        safeUpdateImage1(hAx,255-Img);
      case 'Safe2'
      safeUpdateImage2(hAx,255-Img);  
    end
    end
    
    function brokenUpdateImage(hAx,newImg)
    uiwait(msgbox('The image will now be refreshed. A breakpoint will be set in NEWPLOT'));
    dbstop in newplot at 124 %// The line responsible for deleting the colorbar in R2015A/B
    imagesc(newImg,'Parent',hAx);
    end
    
    % For cases when the only desired child is an Image and the axes contents are unknown
    function safeUpdateImage1(hAx,newImg,imgType,imgUpdateFcn)
    if nargin < 4 || isempty(imgUpdateFcn)
      imgUpdateFcn = @imagesc;
    end
    if nargin < 3 || isempty(imgType)
      imgType = 'Image';  
    end
    if strcmp(hAx.NextPlot,'replace') %// Equivalent to checking "ishold == false"
      hAx.NextPlot = 'add'; %// Equivalent to "hold on"
      %// hCurrImgs = get(hAx,'Children'); %// Deletes all types of Children
      hCurrImgs = findall(hAx,'type','Image'); %// Deletes only graphical objects of type 
                                               %// "matlab.graphics.primitive.Image"
      for ind1=1:numel(hCurrImgs)
        delete(hCurrImgs(ind1));
      end
      imgUpdateFcn(newImg,'Parent',hAx);
      if strcmpi(imgType,'Image')
        axis(hAx,'tight','ij');    
    %// 'tight' - XLimMode, YLimMode, and ZLimMode change to 'auto'. The limits automatic- 
    %//           ally update to incorporate new data added to the axes. To keep the limits
    %//           from changing when using hold on, use axis tight manual.
    %// 'ij'  —   Reverse direction. For axes with a 2-D view, the y-axis is vertical with
    %//           values increasing from top to bottom.    
      end
    end
    end
    
    %// When it's known that the axes contains at least one Image:
    function safeUpdateImage2(hAx,newImg,~,~)
    %// <Input checking code>
    hCurrImgs = findall(hAx,'type','Image');
    %// <Optional code to decide which Image child to update>
    hCurrImgs(1).CData = newImg; %// In this example, only update the "topmost" child
    end