Files
2026-08-07 15:56:42 +09:00

523 lines
17 KiB
Matlab

%IMAGESC3D 3D wrapper for imagesc
% imagesc3D supports the same parameters as Matlab's imagesc. In addition, the following
% parameters can be set
%
% init_frame... starting frame number (default 1)
% slider_axis... axis along which you want to use imagesc (default 3)
% fps... frames per second (default 25); will be adjusted by a factor of 1.2 to account for internal overhead
% title_list... individual title for each frame (default {})
% loop... run in a loop (default false)
% reset_frame... stop resets frame to init_frame (default false)
% autoplay... stark movie automatically (default false)
% slider_position... slider position [left bottom width height] (default center of axis)
% play_position... play button position [left bottom width height]
% edit_position... edit box position [left bottom width height]
% show_play_button... show/hide button; needs to be visible if loop=true; (default true)
% show_edit_box... show/hide box
% fnct... data processing function
% order... change slice order in stack
% save_movie... specify filename if a movie shall be written
% movie_quality... image quality of the saved movie
%
% Complex images will be converted to RGB using c2image.
%
% If you are not using 'autplay', you can also set a global title instead
% of a title list (similar to imagesc) and use '%d' to get the slice number
% title('Random block - slice %d')
%
%
% EXAMPLES:
% imagesc3D(rand(256, 256, 100), 'fps', 10, 'loop', true)
% imagesc3D(rand(256, 256)*1j)
% imagesc3D(rand(20, 256, 256), 'slider_axis', 1);
%
%
% Additionally, you can use imagesc/imagesc3D routines and trigger the movie by
% calling the play method of a specified axis:
%
% figure(1);
% imagesc3D(rand(256, 256, 100), 'fps', 20);
% title('Random block - slice %d');
% colorbar();
% ax = gca;
% ax.play();
%
%*-----------------------------------------------------------------------*
%| |
%| Except where otherwise noted, this work is licensed under a |
%| Creative Commons Attribution-NonCommercial-ShareAlike 4.0 |
%| International (CC BY-NC-SA 4.0) license. |
%| |
%| Copyright (c) 2017 by Paul Scherrer Institute (http://www.psi.ch) |
%| |
%| Author: CXS group, PSI |
%*-----------------------------------------------------------------------*
% You may use this code with the following provisions:
%
% If the code is fully or partially redistributed, or rewritten in another
% computing language this notice should be included in the redistribution.
%
% If this code, or subfunctions or parts of it, is used for research in a
% publication or if it is fully or partially rewritten for another
% computing language the authors and institution should be acknowledged
% in written form in the publication: “Data processing was carried out
% using the “cSAXS matlab package” developed by the CXS group,
% Paul Scherrer Institut, Switzerland.”
% Variations on the latter text can be incorporated upon discussion with
% the CXS group if needed to more specifically reflect the use of the package
% for the published work.
%
% A publication that focuses on describing features, or parameters, that
% are already existing in the code should be first discussed with the
% authors.
%
% This code and subroutines are part of a continuous development, they
% are provided “as they are” without guarantees or liability on part
% of PSI or the authors. It is the user responsibility to ensure its
% proper use and the correctness of the results.
function imagesc3D(varargin)
import math.isint
import plotting.c2image
ax_img = {};
if nargin == 1
img = varargin{1};
vararg = {};
elseif nargin == 3 && isnumeric(varargin{1}) && isnumeric(varargin{2}) && (islogical(varargin{3}) || isnumeric(varargin{3}))
img = varargin{3};
ax_img = varargin(1:2);
vararg = {};
elseif (islogical(varargin{1}) || isnumeric(varargin{1})) && ischar(varargin{2})
% assume that first argument is images, and next are
% string+arguments
img = varargin{1};
vararg = varargin(2:end);
elseif isnumeric(varargin{1}) && isnumeric(varargin{2}) && (islogical(varargin{3}) || isnumeric(varargin{3}))
% assume that first two arguments are axis,and third is images, and next are
% string+arguments
ax_img = varargin(1:2);
img = varargin{3};
vararg = varargin(4:end);
else
error('Unknown combination of parameters')
end
ax = gca;
pos = ax.Position;
slider_default = [pos(1)+pos(3)/2-0.06 pos(2)-0.1 0.14 0.05];
play_default = [slider_default(1)-0.1 pos(2)-0.1 0.08 0.05];
edit_default = [slider_default(1)+slider_default(3)+0.01 slider_default(2) 0.08 0.05];
par = inputParser;
par.addParameter('fps', 25, @isnumeric) % maximal frame rate
par.addParameter('init_frame', 1, @isnumeric) % starting frame number
par.addParameter('title_list', {}, @iscell) % list of titles for each frame
par.addParameter('slider_axis',3, @isnumeric) % array axis
par.addParameter('loop', false, @islogical) % loop
par.addParameter('reset_frame', false, @islogical) % stop resets frame to init_frame
par.addParameter('autoplay', false, @islogical) % start loop automatically
par.addParameter('slider_position',slider_default, @isnumeric) % slider position; [left bottom width height]
par.addParameter('play_position',play_default, @isnumeric) % slider position; [left bottom width height]
par.addParameter('edit_position', edit_default, @isnumeric) % edit position; [left bottom width height]
par.addParameter('show_play_button',true, @islogical) % array axis
par.addParameter('show_edit_box', true, @islogical) % edit box
par.addParameter('fnct', @(x)x) % data processing function
par.addParameter('order', 1:size(img,3), @isnumeric) % change slice order in stack
par.addParameter('plot_residua', false, @islogical) % plot residua in the image
par.addParameter('save_movie', '', @ischar) % specify filename if a movie shall be written
par.addParameter('movie_quality', 80, @isnumeric) % movie quality
par.parse(vararg{:})
vars = par.Results;
vars.fps = vars.fps *1.2; % correct for overhead
% permute the array to slide along diferent axis
switch vars.slider_axis
case 1
img = rot90(permute(img,[2,3,1]));
case 2
img = rot90(permute(img,[1,3,2]));
end
if any(cellfun(@(x)(strcmpi(x, 'order')), par.UsingDefaults))
% redefine the order just in case that the axis were swapped, but only
% if there is not use preference
vars.order = 1:size(img,3);
end
if ~isempty(vars.title_list)
assert(length(vars.title_list) == size(img,3), 'Number of titles has to correspond to number of frames')
end
sz = size(img,3);
im = imhandles(gcf);
ax = gca;
if ~isprop(ax, 'index')
ax.addprop('index');
ax.index = length(im)+1;
else
if isprop(ax, 'play_handle')
delete(ax.play_handle);
end
if isprop(ax, 'slider_handle')
delete(ax.slider_handle);
end
if isprop(ax, 'edit_handle')
delete(ax.edit_handle);
end
if isprop(ax, 'vars')
ax.vars = [];
end
end
if sz>1
% checks
vars.init_frame = round(vars.init_frame);
if vars.init_frame > sz || vars.init_frame < 1
warning('Initial frame exceeds stack size.')
vars.init_frame = 1;
end
vars.vargin = ax_img;
if ~ax.isprop('img')
ax.addprop('img');
end
ax.img = img;
if ~ax.isprop('play')
ax.addprop('play');
end
ax.play = @(x)play(x);
if ~ax.isprop('stop')
ax.addprop('stop');
end
ax.stop = @(x)stop(x);
if ~ax.isprop('update_fig')
ax.addprop('update_fig');
end
ax.update_fig = @(x)update_fig(x);
%%% set handles
% slider
slider_handle=uicontrol(gcf,'Style','slider','Max',sz,'Min',1,...
'Value',vars.init_frame,'SliderStep',[1/(sz-1) 10/(sz-1)],...
'Units','normalized','Position',vars.slider_position);
if ~isprop(slider_handle, 'ax_index')
slider_handle.addprop('ax_index');
slider_handle.ax_index = ax.index;
end
if ~ax.isprop('slider_handle')
ax.addprop('slider_handle');
ax.slider_handle = slider_handle;
elseif ax.isprop('slider_handle') && ~ax.slider_handle.isvalid
ax.slider_handle = slider_handle;
end
% play button
if vars.show_play_button
visible_button = 'on';
else
visible_button = 'off';
if vars.loop
warning('Loop can not be aborted without buttons. Setting ''loop'' back to ''false''.');
vars.loop = false;
end
end
play_handle=uicontrol(gcf,'Style','pushbutton','string','Play',...
'Units','normalized','Position',vars.play_position, 'Visible', visible_button);
if ~isprop(play_handle, 'ax_index')
play_handle.addprop('ax_index');
play_handle.ax_index = ax.index;
end
if ~ax.isprop('play_handle')
ax.addprop('play_handle');
ax.play_handle = play_handle;
elseif ax.isprop('play_handle') && ~ax.play_handle.isvalid
ax.play_handle = play_handle;
end
if ~ax.isprop('vars')
ax.addprop('vars');
ax.vars = vars;
else
ax.vars = vars;
end
set(play_handle,'Callback',{@play_callback,ax});
% text edit
if vars.show_edit_box
visible_box = 'on';
else
visible_box = 'off';
end
edit_handle = uicontrol('style','edit','units','normalized', 'Position', vars.edit_position, 'Visible', visible_box);
set(edit_handle, 'Callback', {@edit_callback, ax});
if ~ax.isprop('edit_handle')
ax.addprop('edit_handle');
ax.edit_handle = edit_handle;
elseif ax.isprop('edit_handle') && ~ax.edit_handle.isvalid
ax.edit_handle = edit_handle;
end
if ~isprop(edit_handle, 'ax_index')
edit_handle.addprop('ax_index');
edit_handle.ax_index = ax.index;
end
% set callback functions
set(slider_handle,'Callback',{@slider_callback,ax});
set(edit_handle, 'String', num2str(get(ax.slider_handle,'Value')));
if vars.autoplay
play_callback(ax, ax, ax);
end
update_fig(ax)
else
% standard imagesc should be enough
if ax.isprop('update_title') || ax.isprop('play_handle') || ax.isprop('vars')
if ax.isprop('vars') && isfield(ax.vars, 'slider_handle')
ax.vars = rmfield(ax.vars, 'slider_handle');
end
if ax.isprop('play_handle')
delete(ax.play_handle);
end
cla(ax);
end
img = gather(vars.fnct(img));
if ~isreal(img)
img = c2image(img);
end
if ~isempty(ax_img)
imagesc(ax_img{:}, img);
else
imagesc(img);
end
if ~isempty(vars.title_list)
title(ax, vars.title_list{1}, 'Interpreter', 'none')
end
end
end
% plotting function
function update_fig(ax)
import math.isint
import plotting.c2image
% im = imhandles(gcf);
vars = ax.vars;
slice = round(get(ax.slider_handle,'Value'));
slice = max(1, min(length(vars.order), slice));
% FIXME: everything works better without following lines
% sl = gcbo();
% if ~isempty(sl)
% ax = findobj('index', sl.ax_index);
% end
img = gather(vars.fnct(squeeze(ax.img(:,:,vars.order(slice),:))));
if ~isreal(img)
img = c2image(img);
end
if vars.plot_residua
[residua{2},residua{1}] = find(abs(utils.findresidues(img))>0.1);
end
% if the current axis is empty, use imagesc with remaining arguments
if ~ax.isprop('update_title')
ax.addprop('update_title');
ax.addprop('user_title');
ax.update_title = true;
if ~isempty(vars.vargin)
imagesc(vars.vargin{:}, img);
else
imagesc(img);
end
hold all
if vars.plot_residua && ~isempty(residua{1})
plot(residua{:},'or')
elseif vars.plot_residua
plot(0,0,'or')
end
hold off
addlistener(ax.Title, 'String', 'PostSet', @(gt, event)callback_title_post(ax, ax));
else
% if we just need to update the figure, only update the data
ax_data = ax.findobj('Type', 'Image');
ax_data.CData = img;
if vars.plot_residua
ax_data = ax.findobj('Type', 'Line');
ax_data(1).XData = residua{1};
ax_data(1).YData = residua{2};
end
end
% write title
if isempty(vars.title_list)
ax.update_title = false;
if ~isempty(ax.user_title)
title_text = sprintf(ax.user_title, vars.order(slice));
title(ax, title_text, 'Interpreter', 'none');
end
ax.update_title = true;
else
if ~isempty(vars.title_list)
title(ax, vars.title_list{vars.order(slice)}, 'Interpreter', 'none')
end
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Callback subfunctions %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function slider_callback(~,~,ax)
% ob = gco;
% vars.ax_index = ob.ax_index;
if ax.isprop('edit_handle')
set(ax.edit_handle, 'string', num2str(round(get(ax.slider_handle,'Value'))));
end
update_fig(ax)
drawnow()
end
function callback_title_post(ax, ~, ~)
if ax.update_title
ax.user_title = ax.Title.String;
try
ax.Title.String = sprintf(ax.user_title, get(ax.slider_handle,'Value'));
end
end
end
function play_callback(~,~,ax)
% ax = findobj('index', ax.slider_handle.ax_index);
vars = ax.vars;
if ax.isprop('slider_handle') && ax.slider_handle.isvalid
update_slider = true;
else
update_slider = false;
end
if ax.isprop('edit_handle') && ax.edit_handle.isvalid
update_edit = true;
else
update_edit = false;
end
try
switch get(ax.play_handle,'string')
case 'Play'
if ~isempty(vars.save_movie)
disp(['Saving movie to ' vars.save_movie]);
writeobj = VideoWriter(vars.save_movie);
writeobj.Quality=vars.movie_quality;
writeobj.FrameRate=vars.fps;
open(writeobj);
vars.writeobj = writeobj;
end
set(ax.play_handle,'string','Stop')
sz = size(ax.img,3);
pos = round(get(ax.slider_handle,'Value'));
if pos == sz
set(ax.slider_handle,'Value',1);
pos = 1;
end
while pos <=sz
if strcmp(get(ax.play_handle,'string'), 'Play')
break
end
if update_slider
set(ax.slider_handle,'Value',pos)
end
if update_edit
set(ax.edit_handle, 'String', num2str(pos));
end
update_fig(ax)
pause(1/vars.fps)
if vars.loop && pos == sz
pos = 1;
else
pos = pos+1;
end
if vars.save_movie
currFrame = getframe;
writeVideo(vars.writeobj,currFrame);
end
end
set(ax.play_handle,'string','Play')
if vars.reset_frame
set(ax.slider_handle,'Value',vars.init_frame)
end
if vars.save_movie
close(vars.writeobj);
end
case 'Stop'
set(ax.play_handle,'string','Play')
if vars.save_movie
close(vars.writeobj);
end
end
catch
if ~ax.isprop('play_handle')
fprintf('Lost connection to figure instance.\n')
end
end
end
function edit_callback(~,~, ax)
str=get(ax.edit_handle,'String');
if isempty(str2num(str))
warndlg('Input must be numerical');
set(ax.edit_handle, 'string', num2str(round(get(ax.slider_handle,'Value'))));
else
set(ax.slider_handle,'Value',str2num(str))
update_fig(ax)
drawnow()
end
end
function play(ax)
play_callback(ax.vars, ax.vars, ax);
end
function stop(ax)
set(ax.play_handle,'string','Play')
set(ax.slider_handle,'Value',ax.vars.init_frame)
end