diff --git a/GUI/colony_census.m b/GUI/colony_census.m new file mode 100755 index 0000000..7884a90 --- /dev/null +++ b/GUI/colony_census.m @@ -0,0 +1,912 @@ +function [] = colony_census(fname) +% INSPECT_RECORDING displays a pop-up window for the user to manually identify the +% type of data contained in the different channels of a movie recording. +% +% INSPECT_RECORDING(CHANNELS) displays the window using the data +% contained in CHANNELS, updates it accordingly to the user's choice and saves +% the adequate structure for later analysis MYRECORDING. CHANNELS can either +% be a string, a cell list of strings or a 'channel' structure (see get_struct.m). +% MYRECORDING is a structure as defined by get_struct('myrecording'). +% +% INSPECT_RECORDING() prompts the user to select a recording and converts +% it before opening the GUI. +% +% Blanchoud group, UNIFR +% Simon Blanchoud +% 19.12.2018 + + % Argument checking, need to know if we ask for a recording or not. + if (nargin == 0 || isempty(fname) || ... + (isstruct(fname) && isfield(fname, 'channels') && isempty(fname.channels))) + fname = load_images(); + + % We did not get anything to handle... + if isempty(fname) + return; + end + + % Maybe we got a .mat file? + if length(fname)==1 && strncmp(fname{1}(end-3:end), '.mat', 4) + data = load(fname{1}); + colony_census(data.myrecording); + return; + end + end + + % Create the channels structure if it was not provided. + if (isstruct(fname)) + if (isfield(fname, 'experiment')) + myrecording = fname; + channels = myrecording.channels; + else + myrecording = get_struct('myrecording'); + channels = fname; + end + else + % Put everything in a cell list + if (ischar(fname)) + fname = {fname}; + end + + % Parse the list and copy its content into the channels structure + nchannels = length(fname); + channels = get_struct('census', [nchannels 1]); + for i=1:nchannels + channels(i).fname = fname{i}; + end + + myrecording = get_struct('myrecording'); + end + + % Load the required packages + pkg load image; + pkg load statistics; + + % Create the GUI + [hFig, handles] = create_figure(channels); + + % Store all the useful data in a single structure to be stored in the figure + data = struct('channels', channels, ... + 'recording', myrecording, ... + 'img', [], ... + 'orig_img', [], ... + 'img_next', [], ... + 'handles', handles); + + % Update its content + data = update_display(data); + + % Store the data structure in the main figure to be retrieved by the callbacks + guidata(hFig, data); + + % Display the figure + set(hFig,'Visible', 'on'); + + return; +end + +function [hFig, handles] = create_figure(channels) +% This function actually creates the GUI, placing all the elements +% and linking the callbacks. + + % The number of channels provided + nchannels = length(channels); + + % Initialize the possible types and compressions + typestring = {'luminescence';'brightfield'; 'dic'; 'fluorescence'}; + typecompress = {'none', 'lzw', 'deflate', 'jpeg'}; + + % Initialize the structure used for the interface + liststring = ''; + for i = 1:nchannels + + % Build the displayed list + liststring = [liststring 'Image ' num2str(i)]; + if (i < nchannels) + liststring = [liststring '|']; + end + end + + % Create a name for the experiment based on the filename + exp_name = channels(1).fname; + [junk, exp_name, junk] = fileparts(exp_name); + [junk, exp_name, junk] = fileparts(exp_name); + exp_name = regexprep(exp_name, ' ', ''); + + % Create my own grayscale map for the image display + mygray = [0:255]' / 255; + mygray = [mygray mygray mygray]; + + % We build a list of all buttons to easily block and release them + enabled = []; + + % Retrieve the resolution of the screen and infer the position of the GUI + resol = get(0, 'ScreenSize'); + img_pos = ceil([0.05*resol(3:4) 0.9*resol(3:4)]); + + % The main figure, cannot be rescaled, closed nor deleted + hFig = figure('PaperUnits', 'centimeters', ... + 'CloseRequestFcn', @cancel_CloseRequestFcn, ... + 'Colormap', mygray, ... + 'Color', [0.7 0.7 0.7], ... + 'MenuBar', 'none', ... + 'Name', 'Colony census', ... + 'NumberTitle', 'off', ... + 'Units', 'pixels', ... + 'Position', img_pos, ... + 'DeleteFcn', @gui_Callback, ... + 'HandleVisibility', 'callback', ... + 'Tag', 'channel_fig', ... + 'UserData', [], ... + 'Visible', 'off'); + + %%%%%% Now the buttons around the main panel + + % The list of channels + hChannel = uicontrol('Parent', hFig, ... + 'Units', 'normalized', ... + 'Callback', @gui_Callback, ... + 'Position', [0.01 0.11 0.1 0.79], ... + 'String', liststring, ... + 'Style', 'listbox', ... + 'Value', 1, ... + 'Tag', 'channels'); + enabled = [enabled hChannel]; + + % The OK button + hOK = uicontrol('Parent', hFig, ... + 'Units', 'normalized', ... + 'Callback', @channel_CloseRequestFcn, ... + 'Position', [0.70 0.02 0.18 0.05], ... + 'String', 'OK', ... + 'Tag', 'pushbutton11'); + enabled = [enabled hOK]; + + % The Cancel button + hCancel = uicontrol('Parent', hFig, ... + 'Units', 'normalized', ... + 'Callback', @cancel_CloseRequestFcn, ... + 'Position', [0.90 0.02 0.08 0.05], ... + 'String', 'Cancel', ... + 'Tag', 'pushbutton12'); + enabled = [enabled hCancel]; + + % The Add and Remove buttons + hAdd = uicontrol('Parent', hFig, ... + 'Units', 'normalized', ... + 'Callback', @add_channel_Callback, ... + 'Position', [0.01 0.055 0.1 0.04], ... + 'String', 'Add image', ... + 'Tag', 'pushbutton13'); + enabled = [enabled hAdd]; + + hRemove = uicontrol('Parent', hFig, ... + 'Units', 'normalized', ... + 'Callback', @remove_channel_Callback, ... + 'Position', [0.01 0.01 0.1 0.04], ... + 'String', 'Remove image', ... + 'Tag', 'pushbutton14'); + enabled = [enabled hRemove]; + +% Dragzoom help message +imghelp = regexp(help('dragzoom2D'), ... + '([ ]+Interactions:.*\S)\s+Adapted from','tokens'); +imghelp = ['DRAGZOOM2D interactions (help dragzoom2D):\n\n', imghelp{1}{1}]; + + % The experiment name and its labels + hText = uicontrol('Parent', hFig, ... + 'Units', 'normalized', ... + 'Position', [0.2 0.93 0.09 0.025], ... + 'String', 'Experiment name:', ... + 'TooltipString', sprintf(imghelp), ... + 'BackgroundColor', get(hFig, 'Color'), ... + 'FontSize', 12, ... + 'Style', 'text', ... + 'Tag', 'text1'); + + hName = uicontrol('Parent', hFig, ... + 'Units', 'normalized', ... + 'Position', [0.3 0.93 0.5 0.05], ... + 'String', exp_name, ... + 'FontSize', 12, ... + 'Style', 'edit', ... + 'Tag', 'experiment'); + enabled = [enabled hName]; + + %%%%%%% Now the main panel + + % The panel itsel + hPanel = uipanel('Parent', hFig, ... + 'Title', 'Image 1', ... + 'Tag', 'uipanel', ... + 'Clipping', 'on', ... + 'Position', [0.12 0.11 0.87 0.8]); + + % The two axes + hAxes = axes('Parent', hPanel, ... + 'Position', [0.01 0.1 0.43 0.9], ... + 'DataAspectRatio', [1 1 1], ... + 'Visible', 'off', ... + 'Tag', 'axes'); + + hAxesNext = axes('Parent', hPanel, ... + 'Position', [0.45 0.1 0.43 0.9], ... + 'DataAspectRatio', [1 1 1], ... + 'Visible', 'off', ... + 'Tag', 'axes'); + + % The two radio button groups that handle which image to display + % For the choices to be mutually exclusive, one has to put them inside + % such uibuttongroup. + + % The Add and Remove buttons + hControl = uicontrol('Parent', hPanel, ... + 'Units', 'normalized', ... + 'Callback', @add_ROI_Callback, ... + 'Position', [0.125 0.01 0.2 0.075], ... + 'String', 'Add ROI', ... + 'Tag', 'addroi'); + enabled = [enabled hControl]; + + % The Add and Remove buttons + hControl = uicontrol('Parent', hPanel, ... + 'Units', 'normalized', ... + 'Callback', @find_zooids_Callback, ... + 'Position', [0.565 0.01 0.2 0.075], ... + 'String', 'Find zooids', ... + 'Tag', 'addroi'); + enabled = [enabled hControl]; + + % The type, color and compression of the channel, along with its labels + hText = uicontrol('Parent', hPanel, ... + 'Units', 'normalized', ... + 'Position', [0.9 0.925 0.05 0.05], ... + 'String', 'Channel:', ... + 'FontSize', 12, ... + 'FontWeight', 'bold', ... + 'Style', 'text', ... + 'Tag', 'text16'); + + hText = uicontrol('Parent', hPanel, ... + 'Units', 'normalized', ... + 'Position', [0.885 0.875 0.075 0.05], ... + 'String', 'Pixel size', ... + 'TooltipString', 'Pixel resolution in um', ... + 'Style', 'text', ... + 'Tag', 'text17'); + + hResol = uicontrol('Parent', hPanel, ... + 'Units', 'normalized', ... + 'Position', [0.89 0.845 0.07 0.04], ... + 'String', '2.5', ... + 'Style', 'edit', ... + 'Tag', 'data'); + enabled = [enabled hResol]; + + hText = uicontrol('Parent', hPanel, ... + 'Units', 'normalized', ... + 'Position', [0.9 0.77 0.05 0.05], ... + 'String', 'Amplitude', ... + 'TooltipString', 'Intensity threshold required between two zooids (-1 = automatic threshold)', ... + 'Style', 'text', ... + 'Tag', 'text17'); + + hAmpl = uicontrol('Parent', hPanel, ... + 'Units', 'normalized', ... + 'Position', [0.89 0.74 0.07 0.04], ... + 'Style', 'edit', ... + 'String', '-1', ... + 'Tag', 'data'); + enabled = [enabled hAmpl]; + + % The various filters, along with their labels + hText = uicontrol('Parent', hPanel, ... + 'Units', 'normalized', ... + 'Position', [0.9 0.425 0.05 0.05], ... + 'String', 'Filters:', ... + 'FontSize', 12, ... + 'FontWeight', 'bold', ... + 'Style', 'text', ... + 'Tag', 'text16'); + + hNorm = uicontrol('Parent', hPanel, ... + 'Units', 'normalized', ... + 'Callback', @gui_Callback, ... + 'Position', [0.9 0.35 0.1 0.05], ... + 'String', 'Normalize', ... + 'Style', 'checkbox', ... + 'Tag', 'normalize'); + enabled = [enabled hNorm]; + + % The Snapshot button + hSnapshot = uicontrol('Parent', hFig, ... + 'Units', 'normalized', ... + 'Callback', @options_Callback, ... + 'Position', [0.01 0.93 0.05 0.05], ... + 'String', 'Snapshot', ... + 'Tag', 'snapshot'); + enabled = [enabled hSnapshot]; + + % We store all the useful handles into a structure to easily retrieve them, + % along with some indexes + handles = struct('uipanel', hPanel, ... + 'list', hChannel, ... + 'normalize', hNorm, ... + 'axes', [hAxes hAxesNext], ... + 'experiment', hName, ... + 'all_buttons', enabled, ... + 'resolution', hResol, ... + 'amplitude', hAmpl, ... + 'hFig', hFig, ... + 'img', -1, ... + 'scatter', -1, ... + 'roi', {{}}, ... + 'display', [1 1], ... + 'prev_channel', -1, ... + 'current', 1); + + % Link both axes to keep the same information on both sides + %linkaxes(handles.axes); + + return; +end + +function [data] = handle_rois(data, prev_indx, next_indx) + + handles = data.handles; + + % Extract the ROIs + nrois = length(handles.roi); + if (prev_indx > 0) + systems = NaN(0, 2); + for i=1:nrois + systems = [systems; handles.roi{i}.getPosition(); NaN(1,2)]; + end + data.channels(prev_indx).system = systems; + end + + if (next_indx > 0) + + systems = data.channels(next_indx).system; + sys = find(all(isnan(systems), 2)); + nsys = length(sys); + + prev = 1; + for i=1:nsys + curr_sys = systems(prev:sys(i)-1,:); + if (i > nrois) + handles.roi{i} = impoly(handles.axes(1), curr_sys, 'Closed', false); + else + handles.roi{i}.setPosition(curr_sys); + end + prev = sys(i)+1; + end + + for i=nrois:-1:nsys+1 + handles.roi{i}.delete(); + handles.roi(i) = []; + end + end + + data.handles = handles; + + return; +end + +function data = update_display(data, recompute) +% The main figure of the GUI, the one responsible for the proper display +% of its content. + + % By default we recompute everything + if (nargin < 2) + recompute = true; + end + + % Retrive the stored data + handles = data.handles; + hFig = handles.hFig; + + % Get the indexes of the current frame and channel + indx = handles.current; + + % Stop if no data at all + if (indx < 1) + return; + end + + % Here we recompute all the filtering of the frame + if (recompute || indx ~= handles.prev_channel) + % Because it takes long, display it and block the GUI + set(hFig, 'Name', 'Colony census (Filtering...)'); + set(handles.all_buttons, 'Enable', 'off'); + drawnow; + refresh(hFig); + end + + % If we have changed channel, we need to update the display of the buttons + if (indx ~= handles.prev_channel) + if (handles.prev_channel > 0) + data.channels(handles.prev_channel).pixel_size = str2double(get(handles.resolution, 'String')); + data.channels(handles.prev_channel).amplitude = str2double(get(handles.amplitude, 'String')); + end + + % Set the name of the current panel + set(handles.uipanel,'Title', ['Image ' num2str(indx)]); + + set(handles.normalize,'Value', data.channels(indx).normalize); + + set(handles.resolution, 'String', num2str(data.channels(indx).pixel_size)); + set(handles.amplitude, 'String', num2str(data.channels(indx).amplitude)); + + %if (numel(handles.img) > 1 && all(ishandle(handles.img))) + % data = handle_rois(data, handles.prev_channel, indx); + % handles = data.handles; + %end + + % And setup the indexes correctly + %handles.prev_channel = indx; + end + + % Here we recompute all the filtering of the frame + if (recompute) + + % Try to avoid reloading frames as much as possible + data.orig_img = imread(data.channels(indx).fname); + + % Copy to the working variable + data.img = data.orig_img; + + % Normalize the image ? + if (data.channels(indx).normalize) + data.img = imnorm(data.img); + end + end + + % Determine which image to display in the left panel + img1 = data.img; + img2 = data.img; + + % Get the location of the zooids + zooids = data.channels(indx).zooids; + if (isempty(zooids)) + zooids = NaN(1,2); + end + + curr_color = 'k'; + + % If we have already created the axes and the images, we can simply change their + % content (i.e. CData) + if (numel(handles.img) > 1 && all(ishandle(handles.img))) + set(handles.img(1),'CData', img1); + set(handles.img(2),'CData', img2); + set(handles.scatter, 'XData', zooids(:,1), 'YData', zooids(:,2), 'Color', curr_color); + else + + % Otherwise, we create the two images in their respective axes + handles.img = image(img1,'Parent', handles.axes(1),... + 'CDataMapping', 'scaled',... + 'Tag', 'image'); + handles.img(2) = image(img2,'Parent', handles.axes(2), ... + 'CDataMapping', 'scaled',... + 'Tag', 'image'); + + % Hide the axes and prevent a distortion of the image due to stretching + set(handles.axes,'Visible', 'off', ... + 'DataAspectRatio', [1 1 1]); + + handles.scatter = line('XData', zooids(:,1), 'YData', zooids(:,2), 'Parent', handles.axes(2), 'Color', curr_color, 'Marker', 'o', 'LineStyle', 'none'); + + % Drag and Zoom library from Evgeny Pr aka iroln + dragzoom2D(handles.axes(1)) + linkaxes(handles.axes); + end + + %if (numel(handles.img) > 1 && all(ishandle(handles.img))) + if (indx ~= handles.prev_channel) + data.handles = handles; + data = handle_rois(data, handles.prev_channel, indx); + handles = data.handles; + + % And setup the indexes correctly + handles.prev_channel = indx; + end + + if (recompute || indx ~= handles.prev_channel) + % Release the image + set(hFig, 'Name', 'Colony census'); + set(handles.all_buttons, 'Enable', 'on'); + end + + % Update the data + data.handles = handles; + + return +end + +function remove_channel_Callback(hObject, eventdata) +% This function removes ones channel from the list + + data = guidata(hObject); + handles = data.handles; + + % Get the current index for later + indx = handles.current; + + % And ask for confirmation + answer = questdlg(['Are you sure you want to remove channel ' num2str(indx) ' ?' ... + '(No data will be deleted from the disk)'], 'Removing a channel ?'); + ok = strcmp(answer, 'Yes'); + + % If it's ok, let's go + if (ok) + + % Remove the current index and select the first one + data.channels(indx) = []; + handles.current = 1; + + % If it was the only one, we need to handle this + if (isempty(data.channels)) + + % Set up the indexes as empty + handles.current = 0; + handles.prev_channel = -1; + + % As this is long, block the GUI + set(handles.all_buttons, 'Enable', 'off'); + set(handles.img, 'CData', []); + set(handles.list, 'String', ''); + + % And call the movie conversion function to load a new one + new_channel = load_images(); + + % Did we get something back ? + if (~isempty(new_channel)) + + % We provide basic default values for all fields + data.channels = get_struct('census'); + data.channels.fname = new_channel; + + % We update the list of available channels + set(handles.list, 'String', 'Image 1', 'Value', 1); + end + + % Otherwise, delete the last traces of the deleted channel + else + handles.prev_channel = -1; + tmp_list = get(handles.list, 'String'); + tmp_list(indx,:) = []; + set(handles.list, 'String', tmp_list, 'Value', 1); + end + + % Update the data + data.handles = handles; + + % Release the GUI and update + data = update_display(data, true); + + guidata(handles.hFig, data); + + set(handles.all_buttons, 'Enable', 'on'); + end + + return; +end + +function add_ROI_Callback(hObject, eventdata) + + data = guidata(hObject); + handles = data.handles; + + % As this is long, block the GUI + set(handles.all_buttons, 'Enable', 'off'); + set(handles.hFig, 'Name', 'Colony census (Draw your ROI...)'); + + h = impoly(handles.axes(1), 'Closed', false); + if (~isempty(h)) + handles.roi{end+1} = h; + end + + % Update the data + data.handles = handles; + guidata(handles.hFig, data); + + % Release the GUI + set(handles.hFig, 'Name', 'Colony census'); + set(handles.all_buttons, 'Enable', 'on'); +end + +function find_zooids_Callback(hObject, eventdata) + + data = guidata(hObject); + handles = data.handles; + + % As this is long, block the GUI + set(handles.all_buttons, 'Enable', 'off'); + + params = [8/str2double(get(handles.resolution, 'String')) str2double(get(handles.amplitude, 'String'))]; + + data = handle_rois(data, handles.current, -1); + + %keyboard + + zooids = find_zooids(data.img, data.channels(handles.current).system, params); + data.channels(handles.current).zooids = zooids; + + + % Release the GUI + data = update_display(data, false); + guidata(handles.hFig, data); + set(handles.all_buttons, 'Enable', 'on'); +end + +function add_channel_Callback(hObject, eventdata) +% This function adds a new channel to the current recording + + data = guidata(hObject); + handles = data.handles; + + % Remember how many there were + nchannels = length(data.channels); + + % As this is long, block the GUI + set(handles.all_buttons, 'Enable', 'off'); + + % And call the movie conversion function to get a new channel + new_channel = load_images(); + + % Did we get anything ? + if (~isempty(new_channel)) + + liststring = get(handles.list, 'String'); + for i=1:length(new_channel) + % We provide basic default values for all fields + data.channels(end+1) = get_struct('census'); + data.channels(end).fname = new_channel{i}; + + % We update the list of available channels + if (nchannels == 0) + liststring = ['Image ' num2str(length(data.channels))]; + else + if (size(liststring, 1) > 1) + liststring(:,end+1) = '|'; + liststring = liststring.'; + liststring = liststring(:).'; + liststring = liststring(1:end-1); + end + liststring = [liststring '|Image ' num2str(length(data.channels))]; + end + + nchannels = nchannels + 1; + end + set(handles.list, 'String', liststring); + end + + % If we just add a new first channel, we need to set it up properly and update + if (nchannels == 0 && length(channels) > 0) + handles.current = 1; + set(handles.list, 'Value', 1); + data.handles = handles; + data = update_display(data, true); + end + + % Release the GUI + guidata(handles.hFig, data) + set(handles.all_buttons, 'Enable', 'on'); + + return +end + +function options_Callback(hObject, eventdata) +% This function is responsible for handling the buttons responsible for the +% option structure + + data = guidata(hObject); + handles = data.handles; + + % Block the GUI + set(handles.all_buttons, 'Enable', 'off'); + drawnow; + refresh(handles.hFig); + + % And get the type of button which called the callback (from its tag) + type = get(hObject, 'tag'); + + % By default, recompute + recompute = true; + + % Handle all three buttons differently + switch type + + % Save a snapshot + case 'snapshot' + + % Fancy output + disp('[Select a SVG filename]'); + + curdir = ''; + if(exist('export', 'dir')) + curdir = pwd; + cd('export'); + end + + % Prompting the user for the filename + [fname, dirpath] = uiputfile({'*.svg', 'SVG vectorized image'}, ['Select a filename for your snapshot'], fullfile(pwd, 'snapshot.svg')); + + % Return back to our original folder + if(~isempty(curdir)) + cd(curdir); + end + + % Not cancelled + if (ischar(fname)) + + % This might take a while + curr_name = get(handles.hFig, 'Name'); + set(handles.hFig, 'Name', [curr_name ' (Saving snapshot...)']); + + % Get the full name and save the snapshot ! + fname = fullfile(dirpath, fname); + plot2svg(fname, handles.hFig); + %print(handles.hFig, fname, '-dsvg'); + + % And release ! + set(handles.hFig, 'Name', curr_name); + end + + recompute = false; + end + + % Release the GUI and recompute the display + data = update_display(data, recompute); + guidata(handles.hFig, data) + set(handles.all_buttons, 'Enable', 'on'); + + return +end + +function gui_Callback(hObject, eventdata) +% This function handles the callback of most buttons in the GUI ! + + data = guidata(hObject); + handles = data.handles; + + % By default we recompute the display + recompute = true; + + % Get the channel index + indx = handles.current; + + % If no more data, do nothing + if (indx < 1) + return; + end + + % And get the type of button which called the callback (from its tag) + type = get(hObject, 'tag'); + switch type + + % Each checkbox is responsible for its respective boolean fields + case {'detrend', 'cosmics', 'hot_pixels', 'normalize'} + data.channels(indx).(type) = logical(get(hObject, 'Value')); + + % A change in the channel index + case 'channels' + handles.current = get(hObject, 'Value'); + + % Otherwise, do nothing. This is used to cancel the deletion requests + otherwise + return; + end + + data.handles = handles; + + % Update the display accordingly + data = update_display(data, recompute); + + guidata(handles.hFig, data) + + return +end + +function cancel_CloseRequestFcn(hObject, eventdata) +% This function stops the current processing after confirmation + + data = guidata(hObject); + handles = data.handles; + + % Just double check that the user want to quit + answer = questdlg('Do you really want to discard all your changes ?'); + ok = strcmp(answer,'Yes'); + + % If everything is OK, release the GUI and quit + if (ok) + delete(handles.hFig); + end + + return +end + +function channel_CloseRequestFcn(hObject, eventdata) +% This function converts the various indexes back into strings to prepare +% the channels structure for its standard form before releasing the GUI +% to exit it + + % If everything is OK, release the GUI and quit + data = guidata(hObject); + handles = data.handles; + + data = handle_rois(data, handles.current, 0); + handles = data.handles; + + if (handles.current > 0) + data.channels(handles.current).pixel_size = str2double(get(handles.resolution, 'String')); + data.channels(handles.current).amplitude = str2double(get(handles.amplitude, 'String')); + end + + % Copy the channels + myrecording.channels = data.channels; + % And get the experiment name + myrecording.experiment = get(handles.experiment, 'String'); + + [fpath, fname, fext] = fileparts(myrecording.channels(1).fname); + mname = fullfile(fpath, [myrecording.experiment '.mat']); + save(mname, 'myrecording'); + + [junk, sizes] = find_zooids(myrecording); + + info = imfinfo(myrecording.channels(1).fname); + + if (exist('census.csv', 'file')) + fid = fopen('census.csv', 'a'); + else + fid = fopen('census.csv', 'a'); + fprintf(fid, 'Date, Name, Count\n'); + end + fprintf(fid, '%s, %s, %d\n', info.FileModDate, mname, sum(sizes)); + fclose(fid); + + msgbox({'The segmentation and the analysis were saved to:', mname, 'census.csv'}); + + delete(handles.hFig); + + return +end + +function fnames = load_images() + + % In case a subfolder name Movies exists, move into it for prompting + curdir = ''; + if(exist('Movies', 'dir')) + curdir = pwd; + cd('Movies'); + elseif(exist(['..' filesep 'Movies'], 'dir')) + curdir = pwd; + cd(['..' filesep 'Movies']); + end + + % Fancy output + disp('[Select image(s) of the colony]'); + + % Prompting the user for the movie file + [fnames, dirpath] = uigetfile({'*.*'}, ['Load image(s) of the colony, or one previously saved .MAT file'], 'MultiSelect', 'on'); + + % Return back to our original folder + if(~isempty(curdir)) + cd(curdir); + end + + % If no file was selected, stop here + if (isempty(fnames) || isequal(dirpath, 0)) + disp(['No image selected']); + fnames = ''; + return; + end + fnames = fullfile(dirpath, fnames); + + %% All to cells + if (ischar(fnames)) + fnames = {fnames}; + end + + return; +end diff --git a/MEX/bilinear_mex.c b/MEX/bilinear_mex.c new file mode 100755 index 0000000..7e8b6af --- /dev/null +++ b/MEX/bilinear_mex.c @@ -0,0 +1,363 @@ +#include +#include "mex.h" + +// Define the modulo in a more coherent forme than the one from math.h +#define MOD(x, y) ((x) - (y) * floor((double)(x) / (double)(y))) + +// Bilinear interpolation, main interface +void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { + + // Declare variable + int i, j, xf, yf, xc, yc, boundary_x = 0, boundary_y = 0; + int offset_img, offset_values; + double dxf, dyf, dxc, dyc, x, y, nanval, tmp_val, counter, weights; + mwSize w, h, m, n, c, nvals, dims[3]; + const mwSize *size; + double *x_indx, *y_indx, *tmp, *img, *values; + bool free_memory = false; + + // Check for proper number of input and output arguments + if (nrhs < 2) { + mexErrMsgIdAndTxt("MATLAB:bilinear:invalidInputs", + "Not enough input arguments (2 is the minimum) !"); + + // In that case, we got an image and a matrix of coordinates + } else if (nrhs == 2) { + + // Get the size of the coordinates + m = mxGetM(prhs[1]); + n = mxGetN(prhs[1]); + + // In that case, the coordinates are properly organized by rows + if (n == 2) { + x_indx = mxGetPr(prhs[1]); + y_indx = x_indx + m; + + // Correct the size of the output + n = 1; + + // Otherwise, we need to transpose the matrix + } else if (m == 2) { + + // And thus need to allocate memory for the temporary matrix + if ((x_indx = mxCalloc(n, sizeof(double))) == NULL) { + mexErrMsgIdAndTxt("MATLAB:bilinear:invalidInputs", + "Memory allocation failed !"); + } + if ((y_indx = mxCalloc(n, sizeof(double))) == NULL) { + mexErrMsgIdAndTxt("MATLAB:bilinear:invalidInputs", + "Memory allocation failed !"); + } + + // Copy the data to the new matrix + tmp = mxGetPr(prhs[1]); + for (i = 0; i < n; i++) { + x_indx[i] = tmp[i*2]; + y_indx[i] = tmp[i*2 + 1]; + } + + // Correct the size of the output + m = 1; + + free_memory = true; + + // Otherwise, something is wrong in the inputs + } else { + mexErrMsgIdAndTxt("MATLAB:bilinear:invalidInputs", + "Indexes should be organized as a Nx2 subpixel coordinates table !"); + } + + // Otherwise, X and Y coordinates are provided separately + } else if (nrhs == 3) { + + // Maybe we have both indexes together and the boundary conditions aside... + if (mxGetNumberOfElements(prhs[1]) != mxGetNumberOfElements(prhs[2])) { + + // Get the size of the coordinates + m = mxGetM(prhs[1]); + n = mxGetN(prhs[1]); + + // In that case, the coordinates are properly organized by rows + if (n == 2) { + x_indx = mxGetPr(prhs[1]); + y_indx = x_indx + m; + + // Correct the size of the output + n = 1; + + // Otherwise, we need to transpose the matrix + } else if (m == 2) { + + // And thus need to allocate memory for the temporary matrix + if ((x_indx = mxCalloc(n, sizeof(double))) == NULL) { + mexErrMsgIdAndTxt("MATLAB:bilinear:invalidInputs", + "Memory allocation failed !"); + } + if ((y_indx = mxCalloc(n, sizeof(double))) == NULL) { + mexErrMsgIdAndTxt("MATLAB:bilinear:invalidInputs", + "Memory allocation failed !"); + } + + // Copy the data to the new matrix + tmp = mxGetPr(prhs[1]); + for (i = 0; i < n; i++) { + x_indx[i] = tmp[i*2]; + y_indx[i] = tmp[i*2 + 1]; + } + + // Correct the size of the output + m = 1; + + free_memory = true; + + // Otherwise, something is wrong in the inputs + } else { + mexErrMsgIdAndTxt("MATLAB:bilinear:invalidInputs", + "Both indexes must have the same number of elements"); + } + + // Retrieve the boundary conditions + tmp = mxGetPr(prhs[2]); + boundary_x = (int)tmp[0]; + + // Maybe they are not symmetric + if (mxGetNumberOfElements(prhs[2]) > 1) { + boundary_y = (int)tmp[1]; + } else { + boundary_y = boundary_x; + } + + } else { + x_indx = mxGetPr(prhs[1]); + y_indx = mxGetPr(prhs[2]); + + m = mxGetM(prhs[1]); + n = mxGetN(prhs[1]); + } + + // Finally, maybe the boundary conditions are also provided + } else if (nrhs == 4) { + if (mxGetNumberOfElements(prhs[1]) != mxGetNumberOfElements(prhs[2])) { + mexErrMsgIdAndTxt("MATLAB:bilinear:invalidInputs", + "Both indexes must have the same number of elements"); + } + x_indx = mxGetPr(prhs[1]); + y_indx = mxGetPr(prhs[2]); + + m = mxGetM(prhs[1]); + n = mxGetN(prhs[1]); + + // Retrieve the boundary conditions + tmp = mxGetPr(prhs[3]); + boundary_x = (int)tmp[0]; + + // Maybe they are not symmetric + if (mxGetNumberOfElements(prhs[3]) > 1) { + boundary_y = (int)tmp[1]; + } else { + boundary_y = boundary_x; + } + } + + // Ensure the types of the two first arrays at least + if (!(mxIsDouble(prhs[0]) && mxIsDouble(prhs[1]))) { + mexErrMsgIdAndTxt("MATLAB:bilinear:invalidInputs", + "Input arguments must be of type double."); + } + + // The size of the image + size = mxGetDimensions(prhs[0]); + h = size[0]; + w = size[1]; + c = mxGetNumberOfElements(prhs[0]) / (h*w); + img = mxGetPr(prhs[0]); + + // Prepare the output + dims[0] = m; + dims[1] = n; + dims[2] = c; + + // Some temporary values + offset_img = h*w; + offset_values = m*n; + + // Create the array + plhs[0] = mxCreateNumericArray(3, dims, mxDOUBLE_CLASS, mxREAL); + values = mxGetPr(plhs[0]); + + // Precompute the value of NaN + nanval = mxGetNaN(); + + // The bilinear interpolation + for (i=0; i < m*n; i++) { + + // Adjust the indexes + x = x_indx[i] - 1; + y = y_indx[i] - 1; + + // Get the lower index + xf = floor(x); + yf = floor(y); + + // Its distance to the index + dxf = x - xf; + dyf = y - yf; + + // Avoid a singularity when the index are rounds, get the upper indexes + if (dxf == 0) { + xc = xf; + dxc = 1; + } else { + xc = xf + 1; + dxc = xc - x; + } + + // Same for y + if (dyf == 0) { + yc = yf; + dyc = 1; + } else { + yc = yf + 1; + dyc = yc - y; + } + + // Handle the different types of boundary conditions + switch (boundary_x) { + // Circular + case 1 : + xf = MOD(xf, w); + xc = MOD(xc, w); + + break; + + // Replicate + case 2 : + if (xf >= w) { + xf = w-1; + } else if (xf < 0) { + xf = 0; + } + if (xc >= w) { + xc = w-1; + } else if (xc < 0) { + xc = 0; + } + break; + + // Symmetric + case 3 : + xf = MOD(xf, 2*w); + xc = MOD(xc, 2*w); + + if (xf >= w) { + xf = 2*w - xf - 1; + } + if (xc >= w) { + xc = 2*w - xc - 1; + } + break; + + // NaN outside + default : + break; + } + + // The same for the y index + switch (boundary_y) { + // Circular + case 1 : + yf = MOD(yf, h); + yc = MOD(yc, h); + + break; + // Replicate + case 2 : + if (yf >= h) { + yf = h-1; + } else if (yf < 0) { + yf = 0; + } + if (yc >= h) { + yc = h-1; + } else if (yc < 0) { + yc = 0; + } + break; + // Symmetric + case 3 : + yf = MOD(yf, 2*h); + yc = MOD(yc, 2*h); + + if (yf >= h) { + yf = 2*h - yf - 1; + } + if (yc >= h) { + yc = 2*h - yc - 1; + } + break; + // NaN outside + default : + break; + } + + // Check whether all indexes are valid + if (xf >= w || yf >= h || xc < 0 || yc < 0 || xc >= w || xf < 0 || yc >= h || yf < 0) { + // All channels of the input image + for (j=0; j < c; j++) { + values[i + j*offset_values] = nanval; + } + + // Compute the bilinear interpolation + } else { + // All channels of the input image + for (j=0; j < c; j++) { + counter = 0; + weights = 0; + + tmp_val = img[xf*h + yf + j*offset_img]; + if (tmp_val != nanval) { + counter += tmp_val * dxc * dyc; + weights += dxc * dyc; + } + + tmp_val = img[xc*h + yf + j*offset_img]; + if (tmp_val != nanval) { + counter += tmp_val * dxf * dyc; + weights += dxf * dyc; + } + + tmp_val = img[xf*h + yc + j*offset_img]; + if (tmp_val != nanval) { + counter += tmp_val * dxc * dyf; + weights += dxc * dyf; + } + + tmp_val = img[xc*h + yc + j*offset_img]; + if (tmp_val != nanval) { + counter += tmp_val * dxf * dyf; + weights += dxf * dyf; + } + + if (weights == 0) { + values[i + j*offset_values] = nanval; + } else { + values[i + j*offset_values] = counter/weights; + } + + //values[i + j*offset_values] = + // img[xf*h + yf + j*offset_img] * dxc * dyc + + // img[xc*h + yf + j*offset_img] * dxf * dyc + + // img[xf*h + yc + j*offset_img] * dxc * dyf + + // img[xc*h + yc + j*offset_img] * dxf * dyf; + } + } + } + + // Free the allocated memory + if (free_memory) { + mxFree(x_indx); + mxFree(y_indx); + } + + return; +} diff --git a/MEX/bilinear_mex.m b/MEX/bilinear_mex.m new file mode 100755 index 0000000..14b7ed4 --- /dev/null +++ b/MEX/bilinear_mex.m @@ -0,0 +1,20 @@ +% BILINEAR_MEX compute a bilinear interpolation of an image at the provided +% (sub)pixel coordinates. +% +% PIXS = BILINEAR_MEX(IMG, XCOORD, YCOORD) returns the bilinear interpolation of the +% pixel values PIXS from IMG at the provided coordinates (XCOORD, YCOORD) tuples. +% PIXS has the same dimensions as XCOORD. Note that coordinates should be provided +% as carthesian coordinates, not as matrix indexes ! +% +% PIXS = BILINEAR_MEX(IMG, COORDS) where COORDS is a Nx2 matrix with each row +% corresponding to a [X_coord, X_coord] tuple. +% +% PIXS = BILINEAR_MEX(..., BOUNDARY) defines in addition the type of boundary +% conditions to be used. Available behaviors are: 0 (NaN outside), 1 (circular), +% 2 (replicate), 3 (symmteric). See imfilter for more details on such behaviors. +% If BOUNDARY has two elements, X and Y behaviors can be defined separately. +% By default, both coordinates are set to 0. +% +% Gonczy & Naef labs, EPFL +% Simon Blanchoud +% 07.07.2014 diff --git a/helpers/differentiator.m b/helpers/differentiator.m new file mode 100755 index 0000000..9d1d51b --- /dev/null +++ b/helpers/differentiator.m @@ -0,0 +1,256 @@ +function dy = differentiator(varargin) +% DIFFERENTIATOR computes the numerical derivative of a provided function using various +% methods. +% +% DY = DIFFERENTIATOR(X, Y, DIM, N, METHOD) computes the first derivative DY for the +% function Y = f(X). For multi-dimensional Y, it computes it along the dimension DIM. +% The numerical derivative is computed using METHOD with a neighborhood of N. Available +% methods are: +% - 'cfd' : Central Finite Difference (Polynomial approximation of the signal) +% - 'lanczos' : Low-noise Lanczos (a special Savitzky-Golay digital differentiator, +% CFD with least-square approximation to reduce Gaussian noise) +% - 'noise' : Smooth noise-robust (smooth Lanczos used when the noise is +% restricted to a certain range of frequencies) +% +% The implemented neighborhoods N depend on each method but typically range from 5 to +% 11 (all these methods are central and thus have an odd neighborhood). +% +% DY = DIFFERENTIATOR(..., IS_SUPER) uses the 'super' differentiator instead of the +% standard one, thus using polynomes of degree 4 instead of 2 for the noise supression. +% This option is not available for the CFD method. +% +% DY = DIFFERENTIATOR(Y) computes the derivative assuming: +% - X = uniformly-spaced points +% - DIM = first non-singleton dimension +% - N = 7 +% - METHOD = 'lanczos' +% - IS_SUPER = false +% +% Both the methods and the coefficients have been developed by Pavel Holoborodko: +% http://www.holoborodko.com/pavel/numerical-methods/numerical-derivative/ +% +% Gonczy & Naef labs, EPFL +% Simon Blanchoud +% 16.12.2011 + + [x, y, dim, nneigh, method, super] = parse_input(varargin{:}); + + if (isempty(y)) + dy = y; + + return; + end + + coefs = get_coefs(method, nneigh, super); + nrepeat = length(coefs); + center = nrepeat + 1; + + sizey = size(y); + perm_dim = [1:length(sizey)]; + perm_dim(dim) = 1; + perm_dim(1) = dim; + + y = permute(y, perm_dim); + x = permute(x, perm_dim); + + y = reshape(y, sizey(dim), []); + x = reshape(x, sizey(dim), []); + + dy = NaN(size(y)); + + valids = all(isfinite(y), 2) & all(isfinite(x), 2); + y = y(valids, :); + x = x(valids, :); + + if (nrepeat > sizey(dim)) + error('DIFFERENTIATOR: not enough datapoints to differentiate.'); + end + + index = [1:size(y,1)] + center - 1; + + % Padding for the boundary computations + y = [y(nrepeat:-1:1,:); y; y(end:-1:end-nrepeat+1, :)]; + x = [2*x(1,:) - x(nrepeat:-1:1,:) - 1; x; 2*x(end,:) - x(end:-1:end-nrepeat+1, :) + 1]; + + if (~isempty(y)) + accum = zeros(size(y) - [2*nrepeat 0]); + + for k=1:nrepeat + accum = accum + 2*k*coefs(k)* bsxfun(@rdivide, y(index+k, :) - y(index-k, :), x(index+k, :) - x(index - k, :)); + end + else + accum = []; + end + + dy(valids, :) = accum; + + dy = reshape(dy, sizey(perm_dim)); + dy = ipermute(dy, perm_dim); + + return; +end + +function coefs = get_coefs(method, nneigh, super) + + switch method + case 'cfd' + switch nneigh + case 3 + coefs = [0.5]; + case 5 + coefs = [8 -1]/12; + case 7 + coefs = [45 -9 1]/60; + case 9 + coefs = [672 -168 32 -3]/840; + otherwise + coefs = [672 -168 32 -3]/840; + warning(['The differentiator ' method ' with N=' num2str(nneigh) ' is not implemented, using N=9 instead']); + end + case 'lanczos' + switch nneigh + case 5 + coefs = [1 2]/10; + case 7 + if (super) + coefs = [58 67 -22]/252; + else + coefs = [1 2 3]/28; + end + case 9 + if (super) + coefs = [126 193 142 -86]/1188; + else + coefs = [1 2 3 4]/60; + end + case 11 + if (super) + coefs = [296 503 532 294 -300]/5148; + else + coefs = [1 2 3 4 5]/110; + end + otherwise + if (super) + coefs = [296 503 532 294 -300]/5148; + warning(['The differentiator super-' method ' with N=' num2str(nneigh) ' is not implemented, using N=11 instead']); + else + coefs = 12*[1:((nneigh-1)/2)]/(nneigh^3 - nneigh); + end + end + case 'noise' + switch nneigh + case 5 + coefs = [2 1]/8; + case 7 + if (super) + coefs = [39 12 -5]/96; + else + coefs = [5 4 1]/32; + end + case 9 + if (super) + coefs = [27 16 -1 -2]/60; + else + coefs = [14 14 6 1]/128; + end + case 11 + if (super) + coefs = [322 256 39 -32 -11]/1536; + else + coefs = [42 48 27 8 1]/512; + end + otherwise + if (super) + coefs = [322 256 39 -32 -11]/1536; + else + coefs = [42 48 27 8 1]/512; + end + warning(['The differentiator ' method ' with N=' num2str(nneigh) ' is not implemented, using N=11 instead']); + end + end + + return; +end + +function [x, y, dim, nneigh, method, super] = parse_input(varargin) + + x = []; + y = []; + dim = 0; + nneigh = 7; + method = 'lanczos'; + super = false; + + for i = 1:length(varargin) + if (isempty(varargin{i})) + dim = 1; + else + var_type = class(varargin{i}); + switch var_type + case {'double', 'single', 'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64'} + if (numel(varargin{i}) == 1) + if (dim == 0) + dim = varargin{i}; + else + nneigh = varargin{i}; + end + elseif (isempty(x)) + x = varargin{i}; + elseif (isempty(y)) + y = varargin{i}; + end + case 'logical' + super = varargin{i}; + case 'char' + switch varargin{i} + case 'super' + super = true; + otherwise + method = varargin{i}; + end + end + end + end + + if (isempty(y)) + y = x; + x = []; + end + + npts = size(y); + good_dims = (npts > 1); + first_dim = find(good_dims, 1, 'first'); + + if (~isempty(first_dim)) + if (dim == 0 || ~good_dims(dim)) + dim = first_dim; + end + + if (isempty(x)) + tmp_size = ones(size(npts)); + tmp_size(dim) = npts(dim); + x = reshape([1:npts(dim)], tmp_size); + end + end + + if (size(x, dim) ~= npts(dim)) + error(['The dimensions of x (' num2str(size(x,dim)) ') and y (' num2str(npts(dim)) ') do not correspond.']); + end + + nneigh = nneigh + 1 - mod(nneigh, 2); + + switch method + case 'cfd' + if (nneigh < 3) + nneigh = 3; + end + case {'lanczos', 'noise'} + if (super && nneigh < 7) + nneigh = 7; + elseif (~super && nneigh < 5) + nneigh = 5; + end + end + + return; +end diff --git a/helpers/get_struct.m b/helpers/get_struct.m new file mode 100755 index 0000000..4814f43 --- /dev/null +++ b/helpers/get_struct.m @@ -0,0 +1,408 @@ +function mystruct = get_struct(type, nstruct) +% GET_STRUCT retrieve custom data structures. +% +% This function is designed as a centralization for the different +% complex structures used throughout the program so that they can +% be edited consistently. +% +% MYSTRUCT = GET_STRUCT(TYPE, SIZE) returns a matrix of size SIZE +% of the custom data structure of type TYPE. SIZE can be multi- +% dimensional. +% +% MYSTRUCT = GET_STRUCT(TYPE) returns one structure (SIZE = 1). +% +% Gonczy & Naef labs, EPFL +% Simon Blanchoud +% 13.05.2014 + + % Set the default size + if (nargin == 1) + nstruct = 1; + end + + % Switch between all the different types + switch type + + % The biological properties of the colonies (in um & s) + case 'botrylloides_leachi' + gmm = get_struct('gaussian_mixture'); + + % The width distribution + vessel = gmm; + vessel.mu = [14.54; 20.63]; + vessel.sigma = cat(3, 7.38, 1.87); + vessel.proportions = [0.69 0.31]; + + % The radius / intensity of cells + cells = gmm; + cells.mu = [2.22 0.28; 1.78 0.11]; + cells.sigma = cat(3, [0.133 -0.018; -0.018 0.003], [0.084 0.002; 0.002 0.002]); + cells.proportions = [0.12 0.88]; + + % The radius / intensity of ampullae + ampullae = gmm; + %ampullae.mu = [18.96 0.36]; + ampullae.mu = [2*18.96 2.5*0.36]; + ampullae.sigma = [4.12, 0.015]; + ampullae.proportions = 1; + + % The radius / intensity of immobile cells + spots = gmm; + spots.mu = [2 0.26]; + spots.sigma = [0.16, 0.028]; + spots.proportions = 1; + + mystruct = struct('vessel_width', vessel, ... + 'ampulla', ampullae, ... + 'blood_clot', spots, ... + 'blood_cell', cells); + + % Structure used to parse the original files (reused in myrecording) + case 'census' + mystruct = struct('fname', '', ... % Name of the corresponding temporary file + 'system', NaN(0,2), ... + 'zooids', NaN(0,2), ... % Remove the hot pixels in the image (see imhotpixels.m) + 'pixel_size', 2.5, ... + 'amplitude', -1, ... + 'normalize', true); % Normalize the whole stack + + + % Structure used to parse the original files (reused in myrecording) + case 'channel' + mystruct = struct('color', 1, ... % Color of the channel (index of 'colors') + 'compression', 'none', ... % Compression used for the temporary file + 'cosmics', false, ... % Remove the cosmic rays in the image (see imcosmics.m) + 'detrend', false, ... % Detrend the image (see imdetrend.m) + 'file', '', ... % Path to the original file + 'fname', '', ... % Name of the corresponding temporary file + 'hot_pixels', true, ... % Remove the hot pixels in the image (see imhotpixels.m) + 'max', -Inf, ... % Original maximum value used for rescaling + 'min', Inf, ... % Original minimum value used for rescaling + 'metadata', '', ... % Recordings metadata + 'normalize', true, ... % Normalize the whole stack + 'type', 'dic'); % Type of channel + + % Structure storing the color codes used in the display + case 'colors' + mystruct = struct('colormaps', {{@gray, @cool, @summer, @hot, @jet}}, ... % The colors used for the images + 'spots', {{'r','k','b','g','k'}}, ... % The colors used for the detections + 'spots_next', {{'b','r','k','b', 'w'}}, ... % The second colors for the detections + 'status', {{'myg','mky','mkg','mbg','myg'}}, ... % The colors for the status of cells + 'links', {{'y','k','k','y','y'}}, ... % The colors for the links between cells + 'paths', {{@hot, @autumn, @gray, @cool, @gray}}, ... % The colors for the paths + 'text', {{'r','k','b','g','k'}}); % The colors for the text in the movies + + % Structure to store detections from segmentations + case 'detection' + mystruct = struct('carth', NaN(1, 2), ... % Cartesian position of the detections (Nx2) + 'cluster', [], ... % Temporal cluster containing the detection paths + 'noise', [], ... % Parameters of the image noise + 'properties', []); % Other properties computed on each detection, depending on the algorithm + + % Structure handling the export options + case 'exporting' + mystruct = struct('file_name', '', ... % The name of the file to create + 'low_duplicates', true, ... % Do we use low duplicates paths ? + 'full_cycles_only', false, ... % Keep only tracks that both start and end with a division + 'export_data', true, ... % Do we export a CSV table of the data ? + 'data_aligning_type', 'time', ... % How do we align the paths ? (time/start/end) + 'export_noise', false, ... % Export the parameters of the noise in each image + 'export_movie', false, ... % Do we export an AVI movie ? + 'movie_show_index', true, ... % Do we display the track indexes in the movie ? + 'movie_show_detection', true, ... % Do we display the detected radii in the movie ? + 'movie_show_paths', false, ... % Do we display the connections of the tracking in the movie ? + 'movie_show_reconstruction', false);% Do we display the reconstructed image using the detected spots ? + + case 'For3D' + mystruct = struct('filename', {{''}}, ... % Filename (default: all tif files) + 'file_log', {{}}, ... + 'detect_IHC', false, ... % Detect IHC staining (1=yes 0=no) + 'thresholds', [-1 -1 -1], ... % Red Green Blue thresholds (-1=auto) + 'sparse_thresholds', [], ... % The value thresholds used to create a sparse image + 'pixel_size', 6.5, ... % Pixel size (um) + 'colorize', false, ... % Split the 3 most proheminent colors of the stack into RGB channels ? + 'clean_borders', true, ... % Remove the data stuck to the border of the images + 'axial_smoothing', true, ... % Resize each slice to smooth the overall volume + 'splitting_colors', [], ... + 'filtering', '', ... % A user-specific filtering function that will be applied before the registration + 'filtering_params', [], ... % The parameters for the filtering function + 'smoothing_span', 0.10, ... % The percentage of datapoints spanned when smoothing + 'registration_type', 'rigidbody', ... + 'min_fraction', 100, ... % Minimum fraction of the image occupied by an object for it to be included in the registration (1/N) + 'mesh_resolution', -100, ... % The resolution of the mesh (um/fraction if <0) used to produce the reduced 3D mesh + 'min_volume', 20, ... % Minimum fraction of the volume occupied by an object for it to be included in the reconstruction (1/N) + 'Nsampling', 1, ... % The sampling rate + 'slice_width', 22, ... % Section thickness (um) + 'alpha', 0.2, ... % Equalization factor (0=none, 1=full) + 'transparency', [1/3 1/20 1/20], ... % Red Green Blue transparency ratio, use [1/3 1/3 1/10] for LN, for same transparency in red and green + 'border_erosion', [0 0 0]); % Red Green border erosion (um, ref=Blue) + + case 'fitting' + mystruct = struct('init_noise', 0, ... + 'max_iter', 10000, ... + 'max_error', 10^10, ... + 'nfits', 3, ... + 'init_pos', [], ... + 'tolerance', 1e-3, ... + 'error_function', '', ... + 'bounds', [], ... + 'logging_name', '', ... + 'error_parameters', [], ... + 'step_size', 0.1, ... + 'max_population', 50); + + + case 'gaussian_mixture' + mystruct = struct('mu', [], ... + 'sigma', [], ... + 'proportions', []); + + % The few parameters required to filter the image appropriately + case 'image_filters' + mystruct = struct('hot_pixels_threshold', 15, ... % The threshold used to detect hot pixels using MEAN(pixels) +/- THRESH*STD(pixels) + 'cosmic_rays_threshold', 15, ... % The threshold used to detect cosmic rays as being separated from lower values by a gap larger than THRESH*MAD + 'cosmic_rays_window_size', 10, ... % The size of the overlapping tiles the algorithm works with + 'detrend_meshpoints', 32); % The number of positions over the image used for detrending + + % All the infos required to rescale an image + case 'image_infos' + mystruct = struct('is_signed', false, ... % Is it a signed data type + 'offset', 0, ... % The minimal value of the datatype + 'scaling', 1); % The scaling factor + + + % Structure containing the different parameters required for tracking spots + case 'image_segmentation' + mystruct = struct('filter_max_size', 10, ... % Maximal radius (in um), see filter_spots.m + 'filter_min_size', 2, ... % Minimal radius (in um), see filter_spots.m + 'filter_min_intensity', 15, ... % Minimal intensity (multiplied by the variance of the noise), see filter_spots.m + 'filter_overlap', 0.75, ... % Minimal overlap (in percents) for spots to be fused together + 'detrend_meshpoints', 32, ... % The number of positions over the image used for detrending + 'denoise_func', @gaussian_mex, ... % Function used to denoise the image (see imdenoise.m) + 'denoise_size', -1, ... % Parameter used by the denoising function (-1: default value) + 'denoise_remove_bkg', true, ... % Removes the background uniform value (estimated using estimate_noise.m) ? + 'atrous_max_size', 10, ... % Maximal size of the spots to detect (in um), see imatrous.m + 'atrous_thresh', 10, ... % Threshold used to detect a valid spot as brighter than THRESH*MAD + 'force_estimation', 1, ... % Will force the estimation of the signal intensity on the raw data + 'estimate_thresh', 1, ... % Utilizes only the pixels brighter than this threshold (times noise variance) to estimate the spots + 'estimate_niter', 15, ... % Maximal number in the estimation procedure, see estimate_spots.m + 'estimate_stop', 1e-2, ... % Stopping criterion for the estimation procedure, see estimate_spots.m + 'estimate_weight', 0.1, ... % Convergence weight for the estiamtion procedure, see estimate_spots.m + 'estimate_fit_position', false); % Fit also the subpixel position of the spot (slower) ? + + case 'interpolation' + mystruct = struct('segments', [], ... + 'scaling', 1, ... + 'speeds', [], ... + 'dists', [], ... + 'joints', [], ... + 'parameters', []); + + case 'background' + mystruct = struct('vessel', [], ... + 'vessel_properties', [], ... + 'ampulla', [], ... + 'clot', []); + + case 'junction' + mystruct = struct('threshold', 0, ... + 'center', [], ... + 'polygon', [], ... + 'vector', []); + + case 'meshing' + mystruct = struct('nodes', [], ... + 'sorted', [], ... + 'registraton', [], ... + 'proportions', [], ... + 'bounding_box', [], ... + 'edges', []); + + % Structure used to handle the metadata provided by the microscope + case 'metadata' + mystruct = struct('acquisition_time', [], ... % Time of frame acquisition + 'channels', {{}}, ... % List of acquired channels + 'exposure_time', [], ... % Frame exposure time + 'raw_data', '', ... % Raw metadata string + 'z_position', []); % Z-position of the frame + + case 'MicroMos' + mystruct = struct('ImageFolder', '', ... % absolute or relative path where the images are stored inside the computer. + 'ImageBaseName', '', ... % name of the images to be stitched, without the final cardinal number. + 'ImageIndexs', [], ... % arabic cardinal numbers of the specific images to be stitched between the ones present in the "ImageFolder". The numbers must be reported in the order of the images to be stitched. + 'flag_Color', true, ... % (by default: 1). 0 to obtain the final mosaic in grey levels (also if the original images are RGB). 1 to obtain the final mosaic in RGB (only if the oiginal images are RGB). + 'flag_SeekBestImages', false, ... % (by default: 1). The input frame are pre-selected to optimize the registration task. If this parameter is set to 1, not all the images pointed out are stitched in the final mosaic. + 'flag_BleachingCorrection', false, ... % (by default: 0 for bright field and phase contrast images, 1 for fluorescent images). To correct intensity decay, due to the photo-bleaching effect, between the images to be stitched. This phenomenon happen especially if the images to be stitched are fluorescent images. 0 = no bleaching correction; 1 = yes bleaching correction. + 'ShiftEstimationMode', 2, ... % 1: PhaseCorrelation, 2: CornerClustering, 0: none + 'RegistrationMode', 2, ... % (Piccinini, 2013: proj and aff identical, trans better if very little overlap); (by default: uint8(2)). To choose the registration model that must be used to register the images. The registration model can be chosen between projective (suggested), affine and translative. Set: uint8(0) = 'projective' or uint8(1) = 'affine' or uint8(2) = 'translative' (computed at sub-pixel accuracy using the Lukas-Kanade feature tracker) or uint8(3) = 'translative' (computed at pixel accuracy using the phase-correlation algorithm only). + 'flag_Blending', true, ... % (Piccinini, 2013: Not required for widefield, not exactly correct, looks much better with biquadratic...) 0 = no blending; 1 = blending using a biquadratic function with the highest value in the image's centre (seams are only attenuated). 2 = linear blending with boundary values to completely avoid seams in the stitching regions (slow computation using pixel-based interpolation). 3 = linear blending with boundary values to completely avoid seams in the stitching regions (fast computation using Delaunay triangulation). + 'flag_GriddedAcquisition', false, ... % 1 if the mosaic is a predictible NxM grid + 'GridMode', 0, ... % 0: Row by row, from left to right + 'flag_WhiteBalancing', true, ... % (??) 1 to perform the white balancing of the output mosaic using the mosaic itself as colour reference. 2 to perform the white balancing of the output mosaic loading an external 3-channel image (a RGB image) that must be copied in the folder called "WHITEBALANCING". 0 otherwise. + 'flag_FlatField', true, ... % (Piccinini, 2013: crucial) 1 to flat-field correct the images using an input vignetting function. The vignetting function must be saved as matlab matrix in the folder named: "VIGNETTINGFUNCTION". In the "VIGNETTINGFUNCTION" folder must contain at maximum one vignetting function file. + 'flag_GroupVignettes', false, ... % If true, pools all vignettes from the subfolders. + 'flag_FrameToMosaic', true, ... % (Piccinini, 2013: F2M clearly better) (by default: 1). 0 for registering the images according to the Frame-to-Frame registration approach; 1 (suggested) for registering the images according to the Frame-to-Mosaic registration approach. + 'RANSACerror', 2, ... % maximum subpixel reprojection error used inside RANSAC algorithm. We suggest 2 pixels. + 'flag_Harris', false, ... % 1 to extract Harris corner points. 0 to extract Shy-tomasi corner points. We suggest 0. + 'flag_PhaseCorrelationOnly', false, ... % It can assume values 0 or 1. 1 means that the images are registered according to the Phase Correlation ALgorithm only. + 'numberCorners', 200, ... + 'flag_PCglobalORlocal', false, ... % It can assume values 0 or 1. 0 means that the metric used to determine the best shift inside the Phase Correlation ALgorithm is the global RMSE performed on the whole overlapping region. 1 means that the used metric is the RMSE performed using only the pixels with highest value. + 'PCscaleFactor', 1, ... % to speed up the computational processes. Image rescale factor applyed to optionally resize the images. The value must be a positive integer. E.g.: ceil(2) to obtain half-sized images than the original images. 1 (suggested) means: rescaling not active. + 'flag_ComputeRegistrations', true, ... % 0 for loading an external registration matrix to stitch the images. In case, the registration matrix must be saved as 3x3xn (n=number of images to be registered) Matlab matrix in the folder named: "REGISTRATIONMATRIX". + 'InterpolationMode', 'bilinear', ... % interpolation used to warp the image to be stitched. It can be: 'bicubic' or 'bilinear' (suggested) or 'nearest'. + 'flag_LookUpTable', false, ... % to use a 256 levels Look-Up-Table to map the grey levels into a single RGB conversion. + 'flag_AdjustIntensityValues', true, ... % to adjust the image intensity values + 'ScaleFactor', 1, ... % to speed up the computational processes. Image rescale factor applyed to optionally resize the images. The value must be a positive integer. E.g.: ceil(2) to obtain half-sized images than the original images. 1 (suggested) means: rescaling not active. + 'PixelAccuracy', false); % (by default: 0). Pixel-accuracy of the output mosaic. 0 = double precision (slower computation, higher precision), 1 = single precision (faster computation, lower precision). If your computer have not enough memory to build the mosaic, we suggest to try to set this parameter at the value 1 (single precision). + + % Global structure of a recording and analysis + case 'myrecording' + mychannel = get_struct('channel', 0); + mysegment = get_struct('segmentation', 0); + mytracks = get_struct('tracking', 0); + mystruct = struct('channels', mychannel, ... % Channels of the recording + 'segmentations', mysegment, ... % Segmentation data + 'trackings', mytracks, ... % Tracking data + 'experiment', ''); % Name of the experiment + + % Global structure of options/parameters for an analysis + case 'options' + myfilt = get_struct('image_filters'); + mysegm = get_struct('image_segmentation'); + mytrac = get_struct('spot_tracking'); + mytrkf = get_struct('tracks_filtering'); + mysimu = get_struct('simulation', 0); + mystruct = struct('config_files', {{}}, ... % The various configuration files loaded + 'binning', 1, ... % Pixel binning used during acquisition + 'ccd_pixel_size', 16, ... % X-Y size of the pixels in um (of the CCD camera, without magnification) + 'magnification', 20, ... % Magnification of the objective of the microscope + 'spot_tracking', mytrac, ... % Parameters for tracking the spots + 'filtering', myfilt, ... % Parameters for filtering the recordings + 'tracks_filtering', mytrkf, ... % Parameters for filtering the tracks + 'pixel_size', -1, ... % X-Y size of the pixels in um (computed as ccd_pixel_size / magnification) + 'segmenting', mysegm, ... % Parameters for segmenting the recordings + 'simulation', mysimu, ... + 'time_interval', 300, ... % Time interval between frames (in seconds) + 'verbosity', 2); % Verbosity level of the analysis + + % Parameters used for a segmentation (eggshell and cortex) (see 'segmentations') + case 'parameter' + % Retrieve the previously defined structures for the smoothness and data terms + params = get_struct('smoothness_parameters'); + weights = get_struct('data_parameters'); + + mystruct = struct('parameters', params, ... % Smoothness for the cortex + 'weights', weights, ... % Data for the cortex + 'estimate', [], ... % Field to store parameters for the initial elliptical projection + 'noise', [], ... % Field to store parameters to handle noise (filters usually) + 'safety', 1.2, ... % Additional portion projected for safety (see carthesian_coordinate.m) + 'scoring_func', {{}}); % Function handle for the scoring functions (first:eggshell, second:cortex) + + % Parameters used to compute the data part of the DP scoring function (see dynamic_programming.m) + case 'data_parameters' + mystruct = struct('filt', [], ... % Filter applied to the image + 'path', [], ... % Path of interest (usually the eggshell) + 'alpha', 0, ... % Parameters that can be used in the scoring function (7 provided) + 'beta', 0, ... % | + 'gamma', 0, ... % | + 'delta', 0, ... % | + 'epsilon', 0, ... % | + 'zeta', 0, ... % | + 'eta', 0); % | + + % Structure used to store the smoothness parameters (see 'segmentations') + case 'smoothness_parameters' + mystruct = struct('final', [], ... % Final position used for backtracking DP + 'force_circularity', true, ... % Enforces that the last row of the DP conincides with the first one + 'dp_method', 'double', ... % Dynamic programming method used (see dynamic_programming.m) + 'init', [], ... % Initial position for DP (see dynamic_programming.m) + 'nhood', 0, ... % Neighborhood explored during dynamic programming (nhood pixels on each side) + 'prohibit', 'none', ... % Prohibiting particular moves + 'spawn_percentile', [], ... % Score used when spawning a new path (as percentile of the previous step) + 'spawn_type', 'full', ... + 'alpha', 0, ... % Weights of the different smoothness terms + 'beta', 0, ... % " + 'gamma', 0, ... % " + 'delta', 0); % " + + % Structure used to segment a channel + case 'segmentation' + mydetection = get_struct('detection',0); + mystruct = struct('denoise', true, ... % Denoise the segmentation (see imdenoise) ? + 'detrend', false, ... % Detrend the segmentation (see imdetrend.m) ? + 'filter_spots', true, ... % Filter the spots (see filter_spots.m) ? + 'detections', mydetection, ... % The structure used to store the resulting detections + 'type', {{}}); % The type of segmentation + + case 'simulation' + mystruct = struct('image_size', [512 512], ... + 'image_noise', 0.02, ... + 'dt', 1, ... + 'dmax', 0.25, ... + 'init_simulation', @create_vessel, ... + 'init_params', [1 3 0.2 3], ... + 'create_cells', @vessel_init, ... + 'creation_params', [], ... + 'move_cells', @vascular_movement, ... + 'movement_params', 200, ... + 'remesh_cells', @threshold_remesh, ... + 'remesh_params', 0.75, ... + 'ndims', 2, ... + 'nprops', 2, ... + 'duration', 300, ... + 'outside_ridge', 0.2, ... + 'cell_speed', 10, ... + 'cell_variation', 0.2, ... + 'cell_density', 0.005); + + % Structure containing the different parameters required for tracking spots + case 'spot_tracking' + mystruct = struct('spot_max_speed', 0.05, ... % Maximal speed of displacement of a spot (in um/s) + 'allow_branching_gap', false, ... % Allows merging/splitting to occur at the same time as gap closing ? + 'min_section_length', 5, ... % The minimum number of contiguous frames a path "section" should last to be kept for merging/spliting/gap closing + 'bridging_max_gap', 3, ... % Considered number of frames for the gap closing algorithm (see track_spots.m) + 'max_intensity_ratio', Inf, ... % Defines an upper bound to the allowed signal ratios (see track_spots.m) + 'bridging_max_dist', Inf, ... % Maximal distance throughout the gap (see track_spots.m) + 'bridging_function', @bridging_cost_sparse_mex, ... % Function used to measure the gap-closing weight + 'joining_function', @joining_cost_sparse_mex, ... % Function used to measure the joinging weight + 'splitting_function', @splitting_cost_sparse_mex, ... % Function used to measure the splitting weight + 'linking_function', @linking_cost_sparse_mex); ... % Function used to measure the frame-to-frame linking + + % The trackings as stored after segmentation + case 'tracking' + mydetection = get_struct('detection',0); + mystruct = struct('reestimate_spots', true, ... % Do we reestimate the newly interpolated spots ? + 'filtered', mydetection, ... % The structure used to store the detections after filtering + 'detections', mydetection); % The structure used to store the resulting detections + + % Functions that are not implemented yet : + %'force_cell_behavior', true, ... % Prevent fusion and appearance of spots + %'post_processing_funcs', {{}}, ... % Allow to post-process paths + + % The options for filtering tracks + case 'tracks_filtering' + mystruct = struct('interpolate', true, ... % Interpolate the position of cells to fill gaps ? + 'max_zip_length', 3, ... % A "zip" is defined as a cell splitting and merging back together. This defines the maximum number of frames this separation can last to be closed + 'min_path_length', 10); % The minimum number of frames a path should last to be kept + + case 'vessel' + mystruct = struct('center', [], ... + 'border', [], ... + 'junction', [], ... + 'property', [], ... + 'background', [], ... + 'mesh', []); + + % If the required type of structure has not been implemented, return an empty one + otherwise + mystruct = struct(); + end + + % Compute the pixel size + mystruct = set_pixel_size(mystruct); + + % Repeat the structure to fit the size (nstruct can be multi-dimensional) + mystruct = repmat(mystruct, nstruct); + + return; +end diff --git a/helpers/perpendicular_sampling.m b/helpers/perpendicular_sampling.m new file mode 100755 index 0000000..587abfa --- /dev/null +++ b/helpers/perpendicular_sampling.m @@ -0,0 +1,96 @@ +function [perps_vals, perps_paths, dpos] = perpendicular_sampling(img, path, dpos) + + if (nargin < 3) + dpos = []; + end + + if (isempty(dpos)) + bin_dist = 64; + bin_step = 1; + dpos = [-bin_dist:bin_step:bin_dist]; + elseif (numel(dpos) == 2 & dpos(1) > dpos(2)) + bin_dist = dpos(1); + bin_step = dpos(2); + dpos = [-bin_dist:bin_step:bin_dist]; + end + + gaps = all(isnan(path), 2); + dist = sqrt(diff(path(:,1)).^2 + diff(path(:,2)).^2); + + dist(isnan(dist)) = 0; + + cum_dist = [0; cumsum(dist)]; + + stretchs = diff([0; cum_dist(gaps)]); + + dist(gaps(1:end-1)) = -stretchs(1:end-1); + cum_dist = [0; cumsum(dist)]; + + stretchs = cum_dist(gaps); + + cum_dist(gaps) = NaN; + cum_dist([true; gaps(1:end-1)]) = 0; + + stretchs = find(gaps); + nstretchs = length(stretchs); + + if (nstretchs == 0) + nstretchs = 1; + stretchs = size(path, 1) + 1; + end + + perps_vals = cell(nstretchs, 1); + perps_paths = cell(nstretchs, 1); + prev = 1; + + for i=1:nstretchs + indxs = [prev:stretchs(i)-1]; + curr_path = path(indxs,:); + curr_dist = cum_dist(indxs); + + [curr_dist, indxs] = unique(curr_dist); + curr_path = curr_path(indxs,:); + + dist = [0:floor(curr_dist(end))]; + curr_path = interp1(curr_dist.', curr_path, dist); + + dist = sqrt(diff(curr_path(:,1)).^2 + diff(curr_path(:,2)).^2); + curr_dist = [0; cumsum(dist)]; + + dist = [0:floor(curr_dist(end))]; + curr_path = interp1(curr_dist.', curr_path, dist); + + deriv = differentiator([dist(:), dist(:)], curr_path); + perp_path = [-deriv(:,2), deriv(:,1)]; + + nulls = all(perp_path == 0, 2); + + if (any(nulls)) + dpts = diff(curr_path([1:end 1], :)); + perp_path(nulls, :) = dpts(nulls, :); + end + + perp_path = bsxfun(@rdivide, perp_path, hypot(perp_path(:,1), perp_path(:,2))); + + all_pos_x = bsxfun(@plus, perp_path(:,1) * dpos, curr_path(:,1)); + all_pos_y = bsxfun(@plus, perp_path(:,2) * dpos, curr_path(:,2)); + + all_pos_x = all_pos_x(:); + all_pos_y = all_pos_y(:); + + values = bilinear_mex(double(img), all_pos_x, all_pos_y); + values = reshape(values, size(curr_path,1), []); + + perps_vals{i} = values; + perps_paths{i} = [all_pos_x, all_pos_y]; + + prev = stretchs(i)+1; + end + + if (length(perps_vals) == 1) + perps_vals = perps_vals{1}; + perps_paths = perps_paths{1}; + end + + return; +end diff --git a/helpers/set_pixel_size.m b/helpers/set_pixel_size.m new file mode 100755 index 0000000..67a9f3e --- /dev/null +++ b/helpers/set_pixel_size.m @@ -0,0 +1,97 @@ +function params = set_pixel_size(params, pixel_size) +% SET_PIXEL_SIZE computes the actual size of the pixel in the image using the CCD +% camera pixel size, the magnification and the binning. In addition, it computes the +% other values in the parameter structure which values depend on "pixel_size" +% (see get_struct.m). +% +% OPTS = SET_PIXEL_SIZE(OPTS) computes pixel_size using the fields 'ccd_pixel_size', +% 'magnification' and 'binning' of the parameter structure OPTS (get_struct('options')). +% Using this value, it then computes the actual value of the dynamic fields of OPTS. +% +% OPTS = SET_PIXEL_SIZE(OPTS, PIXEL_SIZE) uses the provided value for pixel_size +% to set the value of the dynamic fields. +% +% OPTS = SET_PIXEL_SIZE uses the default value for OPTS too. +% +% NOTES ON USING DYNAMIC FIELD VALUES +% In case the value of the parameter you want to set depends on the size of the pixel +% (in um), you can substitute its value by the expression that computes it. In this +% expression, you can then use the variable 'pixel_size' which will contain the +% appropriate value. Simply put the whole expression in a string terminated by the +% usual ';' character. This function will then evaluate this expression using the +% value of 'pixel_size' and store the result into the same field. +% +% Multiple lines can be stored in a single field separated by ';'. This technique +% allows to define your own temporary variables. In this case you will need to +% explicit the assignment as in any normal expression using '='. +% +% Gonczy & Naef labs, EPFL +% Simon Blanchoud +% 10.12.2010 + + % Check if we need to get a new option structure + if (nargin == 0) + params = get_struct('options'); + params = set_pixel_size(params); + + return; + + % Otherwise, we have only the option structure provided + elseif (nargin == 1 || isempty(pixel_size)) + + % We need at least these two fields to exist + if (isfield(params, 'ccd_pixel_size') && isfield(params, 'magnification')) + + % Maybe there is even binning + if (isfield(params, 'binning')) + pixel_size = params.binning * params.ccd_pixel_size / params.magnification; + else + pixel_size = params.ccd_pixel_size / params.magnification; + end + + % Store the pixel size + params.pixel_size = pixel_size; + else + pixel_size = []; + end + end + + % If we have no data for the pixel_size, stop here + if (isempty(params)) + return; + end + + % Otherwise, we loop recursively on the parameter structure to try to set the + % dynamic fields + fields = fieldnames(params); + for n=1:length(params) + for i=1:length(fields) + + % If it's a string, maybe we can set something here + if (ischar(params(n).(fields{i})) && ~isempty(pixel_size)) + + % Get the comamnds and split them per line + commands = params(n).(fields{i}); + indx = [0 strfind(commands, ';')]; + ncomma = length(indx) - 1; + + % Loop over each command and execute it + for j=1:ncomma + + % For the last one, we need to assign it back the structure field + if (j == ncomma) + eval(['params(n).(fields{i}) = ' commands(indx(j)+1:indx(j+1))]); + else + eval(commands(indx(j)+1:indx(j+1))); + end + end + + % If it's a structure field, call set_pixel_size recursively + elseif (isstruct(params(n).(fields{i}))) + params(n).(fields{i}) = set_pixel_size(params(n).(fields{i}), pixel_size); + end + end + end + + return; +end diff --git a/image_analysis/@impoly/delete.m b/image_analysis/@impoly/delete.m new file mode 100755 index 0000000..63fd3f1 --- /dev/null +++ b/image_analysis/@impoly/delete.m @@ -0,0 +1,15 @@ +function delete(obj) +% Deletes the impoly object +% +% Simon Blanchoud +% University of Fribourg +% 01.10.18 + + % Check if the object handle has not yet been deleted, if + % not delete it (the parameter structure is store inside it). + if (ishandle(obj.hPolygon)) + delete(obj.hPolygon); + end + + return; +end diff --git a/image_analysis/@impoly/display.m b/image_analysis/@impoly/display.m new file mode 100644 index 0000000..d23c7e9 --- /dev/null +++ b/image_analysis/@impoly/display.m @@ -0,0 +1,20 @@ +function display(obj) + + if (ishandle(obj.hPolygon)) + data = get(obj.hPolygon, 'userdata'); + + printf ("handle =\n"); + display(obj.hPolygon) + printf ("position =\n"); + display(data.position) + printf ("is_closed =\n"); + display(data.is_closed) + printf ("color =\n"); + display(data.color) + + else + display([]); + end + + return; +end diff --git a/image_analysis/@impoly/getClosed.m b/image_analysis/@impoly/getClosed.m new file mode 100755 index 0000000..d8ed820 --- /dev/null +++ b/image_analysis/@impoly/getClosed.m @@ -0,0 +1,22 @@ +function is_closed = getClosed(obj) +% GETCLOSED returns whether the polygon is closed or not. +% +% BOOL = getClosed(POLY) returns BOOL defining whether the +% IMPOLY polygon POLY is closed or not. +% BOOL = POLY.getClosed() subsref for of the same call. +% +% Simon Blanchoud +% University of Fribourg +% 01.10.18 + + % Make sure that the graphic object still exists + if (~ishandle(obj.hPolygon)) + error('impoly:invalidObject', 'impoly graphic object deleted.'); + end + + % Retrieve the data and the status of the polygon + data = get(obj.hPolygon, 'userdata'); + is_closed = data.is_closed; + + return; +end diff --git a/image_analysis/@impoly/getPosition.m b/image_analysis/@impoly/getPosition.m new file mode 100755 index 0000000..f208676 --- /dev/null +++ b/image_analysis/@impoly/getPosition.m @@ -0,0 +1,23 @@ +function pos = getPosition(obj) +% GETPOSITION returns the list of vertices that composes the polygon. +% +% POS = getPosition(POLY) returns the coordinates POS of the +% IMPOLY polygon POLY. Note that the first +% vertex is NOT duplicated in closed polygons. +% POS = POLY.getPosition() subsref for of the same call. +% +% Simon Blanchoud +% University of Fribourg +% 01.10.18 + + % Make sure that the graphic object still exists + if (~ishandle(obj.hPolygon)) + error('impoly:invalidObject', 'impoly graphic object deleted.'); + end + + % Retrieve the data and the positions + data = get(obj.hPolygon, 'userdata'); + pos = data.position; + + return; +end diff --git a/image_analysis/@impoly/impoly.m b/image_analysis/@impoly/impoly.m new file mode 100755 index 0000000..b83aadf --- /dev/null +++ b/image_analysis/@impoly/impoly.m @@ -0,0 +1,441 @@ +function h = impoly(varargin) +% IMPOLY draws a draggable and editable polygon. Freely inspired by the MATLAB +% class of the same name, to allow code intercompatibility. Functionalities +% should be similar. +% +% P = IMPOLY(HAX, POS) Draws an editable and draggable polygon on axes +% HAX using the Nx2 coordinates POS and returns an IMPOLY object P. +% IMPOLY(POS) When no handle is provided, the current axes are used. +% IMPOLY(HAX) When the coordinates are not provided, the polygon is +% interactively drawn. +% IMPOLY() Interactive drawing on the current axes. +% IMPOLY(..., 'Closed', BOOL) % Determines whether the polygon is a closed shape or not (FALSE is default). +% IMPOLY(..., 'PositionConstraintFcn', FUNC) % Not currently implemented. +% IMPOLY(..., 'Color', COL) % Determines the color of the polygon ('k' is default). +% +% Interactions with the polygon: +% Creation mode: +% LEFT-CLICK : Add a new vertex to the polygon +% RIGHT-CLICK : Toggles whether the poylgon is closed or not +% MIDDLE-CLICK : Exit creation mode +% DOUBLE LEFT-CLICK : Adds a last vertex and exit creation mode +% Edition mode: +% LEFT-CLICK : If on a vertex, moves that vertex +% : If on an edge, drags the polygon +% RIGHT-CLICK : If on a vertex, deletes that vertex +% : If on an edge, creates a new vertex there +% +% Simon Blanchoud +% University of Fribourg +% 01.10.18 + + % Check the input parameters + [reg, is_closed, constraint, color] = parseparams(varargin, 'Closed', ... + false, 'PositionConstraintFcn', [], 'Color', 'k'); + + % Sets the default values for the axes and position + if (isempty(reg)) + hax = gca(); + pos = []; + elseif (numel(reg) == 1) + if (ishghandle(reg{1})) + % Always look for an axis somewhere in the ancestors list + hax = ancestor(reg{1}, 'axes'); + pos = []; + else + hax = gca (); + pos = reg{1}; + end + else + if (numel(reg) > 2) + error('impoly:invalidInputs', 'Invalid number of arguments.'); + end + + hax = ancestor(reg{1}, 'axes'); + pos = reg{2}; + end + + % Require a valid handle to an axis + if (isempty(hax)) + error('impoly:invalidInputs', 'Not a valid axes handle.') + end + + % Prepare the interactive creation mode + is_interactive = false; + if (isempty(pos)) + pos = NaN(1,2); + is_interactive = true; + + % We don't want duplicated end points, impoly handles that internally + elseif (all(pos(1,:) == pos(end,:))) + pos = pos(1:end-1, :); + end + + % Create the polygon, with vertexes and an additional property to check for completion + hlin = line(pos(:,1), pos(:,2), 'parent', hax, 'color', color, 'marker', '+'); + addproperty('is_done', hlin, 'boolean', false); + + % The parameter structure used to store the information on the polygon. The trick here + % is that the object 'impoly' is basically a handle to the graphical object, in which + % the actual data is store. Which means that the 'impoly' object is interactively updated too. + mypoly = struct('hPolygon', hlin, ... + 'is_closed', is_closed, ... + 'color', color, ... + 'position', pos, ... + 'init', [], ... + 'is_draggable', true); + + % Store that structure in the graphical object and convert it to impoly for returning + set(hlin, 'userdata', mypoly); + mypoly = class(mypoly, 'impoly'); + + % Enter interactive creation mode + if (is_interactive) + + % Empty all callbacks to prevent other functions to run in parallel + [hfig, bkp] = lock_window(hlin, hax, mypoly); + set(hfig, 'WindowButtonDownFcn', @create_polygon); + set(hfig, 'WindowButtonMotionFcn', @draw_polygon); + + % Wait until the polygon is created + waitfor(hlin, 'is_done'); + + % Restore the callbacks + lock_window(hfig, bkp); + end + + % Set the interactive edition of the polygon + set(hlin, 'buttondownfcn', @select_polygon); + + % Return the impoly object + if (nargout > 0) + h = mypoly; + end + + return; +end + +function create_polygon(hsrc, evt) +% Callback used to create the polygon with each consecutive mouse click + + % Get the data of the polygon hidden in the graphic object + data = get(hsrc, 'userdata'); + clickType = get(hsrc, 'SelectionType'); + + % Adapt the behavior to the type of mouse click + switch clickType + + % Left-click, add a point to the polygon + case 'normal' + + % Get the current position of the mouse + pos = get(data.axes, 'currentpoint'); + + % And the vertices of the polygon + vert = getPosition(data.impoly); + + % This represents an uninitialized polygon + if (any(isnan(vert(:)))) + vert = pos(1,1:2); + else + vert = [vert; pos(1,1:2)]; + end + + % Store the new vertices + setPosition(data.impoly, vert); + + % Right-click toggles the closed/open state of the polygon + case 'alt' + setClosed(data.impoly, ~getClosed(data.impoly)); + + % Updates the polygon to the current status of edition + draw_polygon(hsrc); + + % Middle-click or double-click finish the polygon + case {'extend', 'open'} + + % Notify the end and refresh the drawn polygon + set(data.hPolygon, 'is_done', true); + setPosition(data.impoly, getPosition(data.impoly)); + end + + return; +end + +function draw_polygon(hsrc, evnt) %#ok +% Callback function used to draw the current polygon, meaning the one the +% user is drawing. So that's the stored one plus the next point to be added. + + % Get the data stored for the drawing in the figure + data = get(hsrc, 'userdata'); + % Get the internal data of the polygon + poly = get(data.hPolygon, 'userdata'); + % And the current position of the mouse + pos = get(data.axes, 'currentpoint'); + + if poly.is_closed + xpos = [poly.position(:, 1); pos(1,1); poly.position(1, 1)]; + ypos = [poly.position(:, 2); pos(1,2); poly.position(1, 2)]; + else + xpos = [poly.position(:, 1); pos(1,1)]; + ypos = [poly.position(:, 2); pos(1,2)]; + end + + set(data.hPolygon, 'xdata', xpos, 'ydata', ypos); + drawnow(); + + return; +end + +function select_polygon(hsrc, evt) + + data = get(hsrc, 'userdata'); + haxes = ancestor(hsrc, 'axes'); + pos = get(haxes, 'currentpoint'); + prev = data.position; + + u = get(haxes, 'units'); + set(haxes, 'units', 'pixels'); + dp = get(haxes, 'position'); + set(haxes, 'units', u); + dm = get(data.hPolygon, 'MarkerSize')*4/3; + dv = axis(haxes); + + prange = dp(3:4) - dp(1:2); + vrange = dv([2 4]) - dv([1 3]); + scaling = max(prange./vrange); + + x = pos(1,1); + y = pos(1,2); + xpos = prev(:,1); + ypos = prev(:,2); + + [vdist, vind] = min((xpos - pos(1,1)) .* (xpos - pos(1,1)) + ... + (ypos - pos(1,2)) .* (ypos - pos(1,2))); + + if (vdist*scaling*scaling < dm*dm) + + if (evt == 1) + pause(0.01); + drawnow(); + pause(0.01); + + [hfig, bkp] = lock_window(hsrc, haxes); + + data.init = vind; + set(hsrc, 'userdata', data); + + set(hfig, 'WindowButtonMotionFcn', @edit_polygon, ... + 'WindowButtonDownFcn', @waitforclick); + + %[x, y, btn] = ginput(1) + waitfor(hsrc, 'is_done'); + lock_window(hfig, bkp); + + data = get(hsrc, 'userdata'); + %x = data.init(1); + %y = data.init(2); + + if (~isempty(data.init)) + %if (btn == 1) + xpos(vind) = data.init(1); + ypos(vind) = data.init(2); + + end + update_polygon(data, xpos, ypos); + + data.init = []; + elseif (evt == 3) + + xpos(vind) = []; + ypos(vind) = []; + + if (isempty(xpos)) + delete(hsrc); + else + update_polygon(data, xpos, ypos); + end + end + + else + + if (evt == 1) + pause(0.01); + drawnow(); + pause(0.01); + [hfig, bkp] = lock_window(hsrc, haxes); + + data.init = pos(1,1:2); + set(hsrc, 'userdata', data); + + set(hfig, 'WindowButtonMotionFcn', @edit_polygon, ... + 'WindowButtonDownFcn', @waitforclick); + + + waitfor(hsrc, 'is_done'); + %[x, y, btn] = ginput(1) + lock_window(hfig, bkp); + data = get(hsrc, 'userdata'); + + if (~isempty(data.init)) + xpos = xpos + data.init(1) - pos(1,1); + ypos = ypos + data.init(2) - pos(1,2); + end + + update_polygon(data, xpos, ypos); + elseif (evt == 3) + + if (data.is_closed) + xpos = xpos([1:end 1]); + ypos = ypos([1:end 1]); + end + + xvect = xpos(2:end) - xpos(1:end-1); + yvect = ypos(2:end) - ypos(1:end-1); + + [dist, indx] = min(abs(yvect*x - xvect*y + xpos(2:end).*ypos(1:end-1) - ... + ypos(2:end).*xpos(1:end-1)) ./ ... + (yvect.*yvect + xvect.*xvect)); + xpos = [xpos(1:indx); x; xpos(indx+1:end)]; + ypos = [ypos(1:indx); y; ypos(indx+1:end)]; + + if (data.is_closed) + xpos = xpos(1:end-1); + ypos = ypos(1:end-1); + end + + update_polygon(data, xpos, ypos); + end + end + + return; +end + +function edit_polygon(hsrc, evnt) %#ok + + data = get(hsrc, 'userdata'); + poly = get(data.hPolygon, 'userdata'); + pos = get(data.axes, 'currentpoint'); + + xpos = poly.position(:, 1); + ypos = poly.position(:, 2); + if (numel(poly.init)==1) + xpos(poly.init) = pos(1,1); + ypos(poly.init) = pos(1,2); + else + xpos = xpos + (pos(1,1) - poly.init(1)); + ypos = ypos + (pos(1,2) - poly.init(2)); + end + + if poly.is_closed + xpos = xpos([1:end 1]); + ypos = ypos([1:end 1]); + end + + set(data.hPolygon, 'xdata', xpos, 'ydata', ypos); + refresh(hsrc); +end + + +function waitforclick(hsrc, evt) + +data = get(hsrc, 'userdata'); +poly = get(data.hPolygon, 'userdata'); +pos = get(data.axes, 'currentpoint'); + +if (evt == 1) + poly.init = pos(1, 1:2); +else + poly.init = []; +end +set(data.hPolygon, 'userdata', poly, 'is_done', true); + + return; +end + +function update_polygon(data, xpos, ypos) + + data.position = [xpos(:), ypos(:)]; + + if (data.is_closed) + xpos = xpos([1:end 1]); + ypos = ypos([1:end 1]); + end + set(data.hPolygon, 'xdata', xpos, 'ydata', ypos); + + set(data.hPolygon, 'userdata', data); + + return; +end + +function [hfig, bkp] = lock_window(hpoly, haxes, mypoly) + + % Unlock the window + if (nargout == 0) + hfig = hpoly; + bkp = haxes; + + set(hfig, 'userdata', bkp.bkp, ... + 'ButtonDownFcn', bkp.dwnfnc, ... + 'KeyPressFcn', bkp.kpressfnc, ... + 'KeyReleaseFcn', bkp.kreleafnc, ... + 'SizeChangedFcn', bkp.szchgfnc, ... + 'WindowButtonDownFcn', bkp.wbtnfnc, ... + 'WindowButtonMotionFcn', bkp.wmovfnc, ... + 'WindowButtonUpFcn', bkp.wupfnc, ... + 'WindowKeyPressFcn', bkp.wkpressfnc, ... + 'WindowKeyReleaseFcn', bkp.wkreleafnc, ... + 'WindowScrollWheelFcn', bkp.wscrllwfnc); + + set(bkp.hon, 'hittest', 'on'); + + else + if (nargin < 3) + mypoly = []; + end + + % Gets all the interactive objects to we can turn them off temporarily + hon = findobj('hittest', 'on'); + %hon = hon(hon ~= hpoly && hon ~= haxes); + set(hon, 'hittest', 'off'); + + % We also need the figure to replace its various interaction handles + hfig = ancestor(haxes, 'figure'); + + % Save everything we will be replacing + bkp = struct('dwnfnc', get(hfig, 'ButtonDownFcn'), ... + 'kpressfnc', get(hfig, 'KeyPressFcn'), ... + 'kreleafnc', get(hfig, 'KeyReleaseFcn'), ... + 'szchgfnc', get(hfig, 'SizeChangedFcn'), ... + 'wupfnc', get(hfig, 'WindowButtonUpFcn'), ... + 'wmovfnc', get(hfig, 'WindowButtonMotionFcn'), ... + 'wbtnfnc', get(hfig, 'WindowButtonDownFcn'), ... + 'wkpressfnc', get(hfig, 'WindowKeyPressFcn'), ... + 'wkreleafnc', get(hfig, 'WindowKeyReleaseFcn'), ... + 'wscrllwfnc', get(hfig, 'WindowScrollWheelFcn'), ... + 'axes', haxes, ... + 'bkp', get(hfig, 'userdata'), ... + 'hon', hon, ... + 'hPolygon', hpoly, ... + 'impoly', mypoly); + + set(hfig, 'userdata', bkp, ... + 'ButtonDownFcn', [], ... + 'KeyPressFcn', [], ... + 'SizeChangedFcn', [], ... + 'WindowButtonDownFcn', [], ... + 'WindowButtonMotionFcn', [], ... + 'WindowKeyPressFcn', [], ... + 'WindowScrollWheelFcn', []); + + % 'KeyReleaseFcn', [], ... + % 'WindowKeyReleaseFcn', [], ... + % 'WindowButtonUpFcn', [], ... + %refresh(hfig); + + set(hpoly, 'is_done', false); + end + + return; +end + diff --git a/image_analysis/@impoly/setClosed.m b/image_analysis/@impoly/setClosed.m new file mode 100755 index 0000000..42be789 --- /dev/null +++ b/image_analysis/@impoly/setClosed.m @@ -0,0 +1,33 @@ +function setClosed(obj, is_closed) + + if (~ishandle(obj.hPolygon)) + error('impolygon graphic object deleted'); + end + + data = get(obj.hPolygon, 'userdata'); + + if (is_closed ~= data.is_closed) + pos = data.position; + if (is_closed) + + pos = pos([1:end 1], :); + + %set(obj.hVertex, 'xdata', pos(:,1), 'ydata', pos(:,2)); + else + + pos = data.position; + %pos = pos(1:end-1, :); + + %set(obj.hPolygon, 'xdata', pos(:,1), 'ydata', pos(:,2)); + %set(obj.hVertex, 'xdata', pos(:,1), 'ydata', pos(:,2)); + end + + set(obj.hPolygon, 'xdata', pos(:,1), 'ydata', pos(:,2)); + data.is_closed = is_closed; + %data.position = pos; + + set(obj.hPolygon, 'userdata', data); + end + + return; +end diff --git a/image_analysis/@impoly/setPosition.m b/image_analysis/@impoly/setPosition.m new file mode 100755 index 0000000..37876e8 --- /dev/null +++ b/image_analysis/@impoly/setPosition.m @@ -0,0 +1,26 @@ +function setPosition(obj, pos, ypos) + + if (~ishandle(obj.hPolygon)) + error('impolygon graphic object deleted'); + end + + if (nargin == 3) + pos = [pos(:), ypos(:)]; + end + + data = get(obj.hPolygon, 'userdata'); + + data.position = pos; + if (data.is_closed) + pos = pos([1:end 1], :); + end + + set(obj.hPolygon, 'xdata', pos(:,1), 'ydata', pos(:,2)); + %set(obj.hVertex, 'xdata', pos(:,1), 'ydata', pos(:,2)); + + %data.is_closed = (size(pos, 1)>1 && all(pos(1,:) == pos(end,:))); + + set(obj.hPolygon, 'userdata', data); + + return; +end diff --git a/image_analysis/@impoly/subsref.m b/image_analysis/@impoly/subsref.m new file mode 100755 index 0000000..798622a --- /dev/null +++ b/image_analysis/@impoly/subsref.m @@ -0,0 +1,79 @@ +## Code adapted from the one by Juan Pablo Carbajal (2012) +## to enable the class to convert the calls "class.function()" to "function(class)" + +## -*- texinfo -*- +## @deftypefn {Function File} {} function_name () +## @end deftypefn + +function varargout = subsref (obj, idx) + + persistent __method__ method4field typeNotImplemented + if isempty(__method__) + + __method__ = struct(); + + # List all the subfunctions to be converted + __method__.getPosition = @(o,varargin) getPosition (o, varargin{:}); + __method__.setPosition = @(o,varargin) setPosition (o, varargin{:}); + __method__.getClosed = @(o,varargin) getClosed (o, varargin{:}); + __method__.setClosed = @(o,varargin) setClosed (o, varargin{:}); + __method__.delete = @(o,varargin) delete (o, varargin{:}); + __method__.display = @(o,varargin) display (o, varargin{:}); + + # Error strings + method4field = "Class #s has no field #s. Use #s() for the method."; + typeNotImplemented = "#s no implemented for class #s."; + + end + + # Make sure the object is of the proper type + if ( !strcmp (class (obj), 'impoly') ) + error ("Object must be of the impoly class but '#s' was used", class (obj) ); + elseif ( idx(1).type != '.' ) + error ("Invalid index for class #s", class (obj) ); + endif + + # Retrive the function handle to the proper subfunction + method = idx(1).subs; + if ~isfield(__method__, method) + error('Unknown method #s.',method); + else + fhandle = __method__.(method); + end + + # methods have two arguments, properties only one. + if numel (idx) == 1 # can't access properties, only methods + + error (method4field, class (obj), method, method); + + end + + # Defines a function call + if strcmp (idx(2).type, '()') + + args = idx(2).subs; + + # Only getPosition can return a value + if (method(1) == 'g') + if isempty(args) + out = fhandle (obj); + else + out = fhandle (obj, args{:}); + end + varargout{1} = out; + else + + if isempty(args) + fhandle (obj); + else + fhandle (obj, args{:}); + end + end + + else + + error (typeNotImplemented,[method idx(2).type], class (obj)); + + end + +endfunction diff --git a/image_analysis/find_zooids.m b/image_analysis/find_zooids.m new file mode 100755 index 0000000..f36e262 --- /dev/null +++ b/image_analysis/find_zooids.m @@ -0,0 +1,189 @@ +function [all_coords, sizes] = find_zooids(img, systems, params) + + sizes = []; + if (nargin == 1 && isstruct(img)) + mycolony = img; + nchannels = length(mycolony.channels); + sizes = zeros(1, nchannels); + + fprintf('Locating zooids : '); + + for i=1:nchannels + fprintf('\b\b\b%3d', i); + + img = imread(mycolony.channels(i).fname); + if (mycolony.channels(i).normalize) + img = imnorm(img); + end + + params = [8/mycolony.channels(i).pixel_size mycolony.channels(i).amplitude]; + systems = mycolony.channels(i).system; + + zooids = find_zooids(img, systems, params); + sizes(i) = size(zooids, 1); + + mycolony.channels(i).zooids = zooids; + end + + fprintf('\b\b\b\bdone\n'); + fprintf('Total found in %s : %d\n', mycolony.experiment, sum(sizes)); + + all_coords = mycolony; + + return; + end + + orig_img = img; + orig_sys = systems; + if (isempty(img) || isempty(systems)) + all_coords = NaN(1,2); + + return; + end + +% keyboard + if (size(img, 3) > 1) + img = rgb2gray(img); + end + + rads = 3; + ampl = -1; + + if (nargin > 2) + rads = params(1); + + if (numel(params) > 1) + ampl = params(2); + end + end + + pext = 5*rads; + + vects = [1 1; diff(systems)]; + dist = sqrt(sum(vects.^2, 2)); + vects = bsxfun(@rdivide, vects, dist); + + goods = (dist~=0); + + vects = vects(goods, :); + dist = dist(goods); + systems = systems(goods, :); + + gaps = all(isnan(systems), 2); + + first = [true; gaps(1:end-1)]; + second = [false; first(1:end-1)]; + + systems(first,:) = systems(second,:) + bsxfun(@times, -vects(second,:), dist(second)+pext); + + last = [gaps(2:end); false]; + prev = [last(2:end); false]; + + systems(last,:) = systems(prev,:) + bsxfun(@times, vects(last,:), dist(last)+pext); + + [perp, paths, dpos] = perpendicular_sampling(img, systems); + + if (~iscell(perp)) + perp = {perp}; + paths = {paths}; + end + + weight = exp(-(dpos.^2)/(2*(2*rads)^2)); + all_coords = NaN(0,5); + + for i=1:length(perp) + + proj = perp{i}; + pos = paths{i}; + pos = reshape(pos, [size(proj) 2]); + + if (rads>0) + proj = gaussian_mex(double(proj), rads); + else + proj = double(proj); + end + + evol = proj(:,(end-1)/2 + 1); + [xmax, imax, xmin, imin] = local_extrema(evol, rads); + + center_proj = 1-bsxfun(@times, 1-imnorm(proj), weight); + + nzooids = length(imin); + coords = NaN(nzooids, 5); + + prev_min = NaN; + prev_pos = 0; + for j=1:length(imin) + indxi = imin(j); + curr_row = center_proj(indxi,:); + [val, indxj] = min(curr_row); + + coords(j,1:2) = squeeze(pos(indxi, indxj, :)); + + min_val = proj(indxi, indxj); + max_val = max([xmax(imax > prev_pos & imax < indxi); min_val]); + + coords(j,3) = 2*max_val - min_val - prev_min; + prev_min = min_val; + prev_pos = indxi; + + coords(j,4) = min_val; + coords(j,5) = max_val; + end + + all_coords = [all_coords; coords]; + end + + orig_coords = all_coords; + + mval = nanmedian(all_coords(:,3:5), 1); + %sval = 1.4826 * mad(all_coords(:,3:5), 0, 1); + sval = 1.4826 * nanmean(abs(bsxfun(@minus, all_coords(:,3:5), nanmean(all_coords(:,3:5),1))), 1); + + if (any(isnan(mval) | isnan(sval))) + all_coords = NaN(1,2); + + return; + end + + all_coords = all_coords(all_coords(:,4) <= mval(2) + 3*sval(2), :); + + %rval = range(img(:)); + %keyboard + + if (ampl < 0) + ampl = ((nanmax(all_coords(:,5)) - nanmin(all_coords(:,4))) - ... + (mval(3)+sval(3) - (mval(2)-sval(2)))) / 15; + end + + bads = (all_coords(:,3) < ampl); + prev = [bads(2:end); false]; + + all_coords(prev,:) = (all_coords(bads,:) + all_coords(prev,:))*0.5; + all_coords(bads & ~prev, :) = []; + %all_coords = all_coords(:,1:2); + + dists = bsxfun(@minus, all_coords(:,1), all_coords(:,1).').^2 + bsxfun(@minus, all_coords(:,2), all_coords(:,2).').^2; + dists = triu(dists, 1); + dists(dists == 0) = Inf; + + [bads, prev] = find(dists < (4*rads)^2); + + all_coords(prev,:) = (all_coords(bads,:) + all_coords(prev,:))*0.5; + all_coords(bads(~ismember(bads, prev)), :) = []; + all_coords = all_coords(:,1:2); + + sizes = size(all_coords, 1); + + %{ + figure; + imagesc(orig_img); + hold on; + plot(systems(:,1), systems(:,2), 'w'); + plot(orig_sys(:,1), orig_sys(:,2), 'y'); + scatter(orig_coords(:,1), orig_coords(:,2), 'r'); + scatter(all_coords(:,1), all_coords(:,2), 'k'); + %} + + return; +end diff --git a/image_analysis/imnorm.m b/image_analysis/imnorm.m new file mode 100755 index 0000000..d3b93bf --- /dev/null +++ b/image_analysis/imnorm.m @@ -0,0 +1,145 @@ +function [img, minval, maxval] = imnorm(img, minval, maxval, mode, minrange, maxrange) +% IMNORM normalizes the pixel value of an image. +% +% [IMG] = IMNORM(ORIG) normalizes the pixel value of ORIG such that it ranges +% between 0 and 1 in IMG. A linear scaling between the min and max values of ORIG +% is applied. Non-finite values are replaced by NaN. +% +% [...] = IMNORM(STACK) normalizes the entire stack as a whole. +% +% [IMG, MIN, MAX] = IMNORM(...) returns in addition the MIN and MAX values found. +% +% [...] = IMNORM(ORIG, MIN, MAX) normalizes the values between MIN and MAX in ORIG +% such that it ranges between 0 and 1 in IMG. Values outside [MIN, MAX] are clipped. +% +% [...] = IMNORM(ORIG, MIN, MAX, MODE) performs the normalization according to the +% defined MODE: either 'column', 'row' or 'slice' -wise. Provide empty values for +% MIN and MAX to utilize the extrema values in ORIG. +% +% [...] = IMNORM(ORIG, MIN, MAX, MODE, LBOUND, UBOUND) performs the normalization +% such that the pixel value in IMG ranges between LBOUND and UBOUND. Provide an +% empty string for MODE to ignore this parameter. +% +% Gonczy & Naef labs, EPFL +% Simon Blanchoud +% 15.05.2014 + + % Input checking and default values + if (nargin == 1) + minval = []; + maxval = []; + mode = ''; + elseif (nargin < 4) + mode = ''; + end + + % In case we have no data + if (nargin == 0 || isempty(img)) + return; + end + + % The size of the image + [h, w, p] = size(img); + + % Get the type of img as we need to convert it to double for the division + class_type = class(img); + is_double = (class_type(1) == 'd'); + + % In that case, convert it + if (~is_double) + img = double(img); + end + + % More input checking + if (nargin < 6) + if (is_double) + minrange = 0; + maxrange = 1; + else + minrange = intmin(class_type); + maxrange = intmax(class_type); + end + end + + % And where there are non-finite elements, we replace them with NaN which do not + % conflict with min() and max() + img(~isfinite(img)) = NaN; + + % Convert to lower case, just in case ! + mode = lower(mode); + + % In case we do not have a minimal value, we need to compute it + if (isempty(minval)) + + % The simplest case, we get the overall minimum + if (isempty(mode)) + minval = min(img(:)); + else + + % Otherwise, we get it along the corresponding dimension. In addition, we + % need to reconstruct a matrix of the proper size for the normalization. + switch mode(1) + case 'r' + minval = repmat(min(img,[],2), [1, w, 1]); + case 'c' + minval = repmat(min(img,[],1), [h, 1, 1]); + case 's' + minval = repmat(min(img,[],3), [1, 1, p]); + case 'a' + minval = min(img(:)); + + % Here we have a problem, so notify it and ignore the provided mode + otherwise + warning('CAST:imnorm', ['Normalization mode ' mode ' is unknown. Ignoring it.']); + minval = min(img(:)); + + % And remove the weird mode + mode = 'a'; + end + end + end + + % Here we have exactly the same, except for the max value + if (isempty(maxval)) + if (isempty(mode)) + maxval = max(img(:)); + else + switch mode(1) + case 'r' + maxval = repmat(max(img,[],2), [1, w, 1]); + case 'c' + maxval = repmat(max(img,[],1), [h, 1, 1]); + case 's' + maxval = repmat(max(img,[],3), [1, 1, p]); + case 'a' + maxval = max(img(:)); + + % Here we have a problem, so notify it and ignor the provided mode + otherwise + warning('CAST:imnorm', ['Normalization mode ' mode ' is unknown. Ignoring it.']); + maxval = max(img(:)); + end + end + end + + % Cast all variables to double just in case + minval = double(minval); + maxval = double(maxval); + minrange = double(minrange); + maxrange = double(maxrange); + + % Now for the normalization per se. Because we kept matrices of the proper size, + % we can compute it element-wise. + img = (img - (minval - minrange)) .* ((maxrange - minrange) ./ (maxval - minval)); + + % Clip the values outside of the defined range + img(img < minrange) = minrange; + img(img > maxrange) = maxrange; + + % And convert back to the original type + if (~is_double) + img = cast(img, class_type); + end + + return; +end diff --git a/install_SiBoRG.m b/install_SiBoRG.m index b9d6e40..c12cb76 100755 --- a/install_SiBoRG.m +++ b/install_SiBoRG.m @@ -1,111 +1,122 @@ function install_SiBoRG(recompile) % INSTALL_SIBORG adds the package to the work path, creates the required directories, % handles the dependencies and compiles the necessary MEX libraries. % % INSTALL_SIBORG('UNINSTALL') removes this package from the work path. % % INSTALL_SIBORG(RECOMPILE) if RECOMPILE is TRUE, recompiles all MEX files. % % Blanchoud group, University of Fribourg % Simon Blanchoud % 04.11.2019 % By default, do not recompile everything if (nargin == 0) recompile = false; % In that particular case, delete the directories elseif (strcmp(recompile, 'uninstall')) cell_folder = which('install_SiBoRG'); if (~isempty(cell_folder)) [current_dir, junk, junk] = fileparts(cell_folder); folders = strsplit(path(), ':'); % Remove the used directories from the work path for i=1:length(folders) if (strncmp(folders{i}, current_dir, length(current_dir))) rmpath(folders{i}); end end savepath; % Move to the root of SiBoRG [root_dir, junk, junk] = fileparts(current_dir); cd(root_dir); % Actually delete this packageif required btn = questdlg ("Do you want to delete SiBoRG completely (cannot be undone)?", "DELETE FILES??", "Yes", "No", "No"); if (strcmp (btn, "Yes")) disp('DELETED') % rmdir(current_dir, 's'); end end return; end % Start by moving inside the containing folder cell_folder = which('install_SiBoRG'); [current_dir, junk, junk] = fileparts(cell_folder); [root_dir, junk, junk] = fileparts(current_dir); cd(current_dir); % Add the proper directories to the work path subdirs = ls(); folders = strsplit(path(), ':'); for i=1:size(subdirs, 1) dir_name = strtrim(subdirs(i,:)); if (isdir(dir_name)) if (dir_name(1) ~= '.' && dir_name(1) ~= '_') full_path = fullfile(current_dir, dir_name); if (~any(strncmp(folders, full_path, length(full_path)))) addpath(full_path); end end end end addpath(current_dir); savepath; + % Install the required packages + if isempty(pkg('list', 'image')) + pkg install -forge image + end + if isempty(pkg('list', 'io')) + pkg install -forge io + end + if isempty(pkg('list', 'statistics')) + pkg install -forge statistics + end + % Try to compile the necessary MEX files cd('MEX'); files = ls('*_mex.c*'); troubles = false; for i=1:size(files, 1) fname = strtrim(files(i,:)); [junk, no_ext, ext] = fileparts(fname); if (recompile || exist(no_ext) ~= 3) failure = mex(fname); if (failure == 1) troubles = true; wtharning('SiBoRG:MEX', {'Could not compile the required MEX function!' ME.message}); end end end cd(root_dir); % These folders are required as well if (~exist('TmpData', 'dir')) mkdir('TmpData'); end if (~exist('export', 'dir')) mkdir('export'); end % Confirm to the user that everything went fine if (troubles) disp('Installation (almost) successful...'); else disp('Installation successful !'); end % Gnu GPL notice fprintf(1, ['\nSimon''s Botrylloides Regeneration Group plateform, Copyright (C) 2019 Simon Blanchoud\n', ... 'This program comes with ABSOLUTELY NO WARRANTY;\n', ... 'This is free software, and you are welcome to redistribute it\n', ... 'under certain conditions; read licence.txt for details.\n\n']); return; end diff --git a/libraries/absolutepath.m b/libraries/absolutepath.m new file mode 100755 index 0000000..712070d --- /dev/null +++ b/libraries/absolutepath.m @@ -0,0 +1,58 @@ +function abs_path = absolutepath( rel_path, act_path, throwErrorIfFileNotExist ) +%ABSOLUTEPATH returns the absolute path relative to a given startpath. +% The startpath is optional, if omitted the current dir is used instead. +% Both argument must be strings. +% +% Syntax: +% abs_path = ABSOLUTEPATH( rel_path, start_path ) +% +% Parameters: +% rel_path - Relative path +% start_path - Start for relative path (optional, default = current dir) +% +% Examples: +% absolutepath( '.\data\matlab' , 'C:\local' ) = 'c:\local\data\matlab\' +% absolutepath( 'A:\MyProject\' , 'C:\local' ) = 'a:\myproject\' +% +% absolutepath( '.\data\matlab' , cd ) is the same as +% absolutepath( '.\data\matlab' ) +% +% See also: RELATIVEPATH PATH + +% Jochen Lenz + +% Jonathan karr 12/17/2010 +% - making compatible with linux +% - commented out lower cases +% - switching findstr to strfind +% - fixing mlint warnings +% Jonathan karr 1/11/2011 +% - Per Abel Brown's comments adding optional error checking for absolute path of directories that don't exist +% Jonathan karr 1/12/2011 +% - fixing bugs and writing test + +% Simon Blanchoud 11/20/2014 +% - Modified to accept already absolute paths +% - Allowed non-existing absolute paths + +% 2nd parameter is optional: +if nargin < 3 + throwErrorIfFileNotExist = false; + if nargin < 2 + act_path = pwd; + end +end + +%build absolute path +file = java.io.File(rel_path); +if (file.isAbsolute()) + abs_path = rel_path; +else + file = java.io.File([act_path filesep rel_path]); + abs_path = char(file.getCanonicalPath()); +end + +%check that file exists +if throwErrorIfFileNotExist && ~exist(abs_path, 'file') + throw(MException('absolutepath:fileNotExist', 'The path %s or file %s doesn''t exist', abs_path, abs_path(1:end-1))); +end diff --git a/libraries/brewermap.m b/libraries/brewermap.m new file mode 100755 index 0000000..98eacf9 --- /dev/null +++ b/libraries/brewermap.m @@ -0,0 +1,428 @@ +function [map,num,typ] = brewermap(N,scheme) +% The complete selection of ColorBrewer colorschemes (RGB colormaps). +% +% (c) 2015 Stephen Cobeldick +% +% Returns any RGB colormap from the ColorBrewer colorschemes, especially +% intended for mapping and plots with attractive, distinguishable colors. +% +% Syntax (basic): +% map = brewermap(N,scheme); % Select colormap length, select any colorscheme. +% brewermap('demo') % View a figure showing all ColorBrewer colorschemes. +% schemes = brewermap('list')% Return a list of all ColorBrewer colorschemes. +% [map,num,typ] = brewermap(...); % The current colorscheme's number of nodes and type. +% +% Syntax (preselect colorscheme): +% old = brewermap(scheme); % Preselect any colorscheme, return the previous scheme. +% map = brewermap(N); % Use preselected scheme, select colormap length. +% map = brewermap; % Use preselected scheme, length same as current figure's colormap. +% +% This product includes color specifications and designs developed by Cynthia Brewer. +% See the ColorBrewer website for further information about each colorscheme, +% colour-blind suitability, licensing, and citations: http://colorbrewer.org/ +% +% See also CUBEHELIX RGBPLOT3 RGBPLOT COLORMAP COLORBAR PLOT PLOT3 SURF IMAGE AXES SET JET LBMAP PARULA +% +% ### Color Schemes ### +% +% To reverse the colormap sequence simply prefix the string token with '*'. +% +% Each colorscheme is defined by a set of hand-picked RGB values (nodes). +% If is greater than the requested colorscheme's number of nodes then: +% * Sequential and Diverging schemes are interpolated to give larger colormaps. +% * Qualitative schemes throw an error. +% Else: +% * Exact values from the ColorBrewer sequences are returned for all schemes. +% +% # Diverging # +% +% Scheme|'BrBG'|'PRGn'|'PiYG'|'PuOr'|'RdBu'|'RdGy'|'RdYlBu'|'RdYlGn'|'Spectral'| +% ------|------|------|------|------|------|------|--------|--------|----------| +% Nodes | 11 | 11 | 11 | 11 | 11 | 11 | 11 | 11 | 11 | +% +% # Qualitative # +% +% Scheme|'Accent'|'Dark2'|'Paired'|'Pastel1'|'Pastel2'|'Set1'|'Set2'|'Set3'| +% ------|--------|-------|--------|---------|---------|------|------|------| +% Nodes | 8 | 8 | 12 | 9 | 8 | 9 | 8 | 12 | +% +% # Sequential # +% +% Scheme|'Blues'|'BuGn'|'BuPu'|'GnBu'|'Greens'|'Greys'|'OrRd'|'Oranges'|'PuBu'| +% ------|-------|------|------|------|--------|-------|------|---------|------| +% Nodes | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | +% +% Scheme|'PuBuGn'|'PuRd'|'Purples'|'RdPu'|'Reds'|'YlGn'|'YlGnBu'|'YlOrBr'|'YlOrRd'| +% ------|--------|------|---------|------|------|------|--------|--------|--------| +% Nodes | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | +% +% ### Examples ### +% +% % Plot a scheme's RGB values: +% rgbplot(brewermap(9,'Blues')) % standard +% rgbplot(brewermap(9,'*Blues')) % reversed +% +% % View information about a colorscheme: +% [~,num,typ] = brewermap(0,'Paired') +% num = 12 +% typ = 'Qualitative' +% +% % Multi-line plot using matrices: +% N = 6; +% axes('ColorOrder',brewermap(N,'Pastel2'),'NextPlot','replacechildren') +% X = linspace(0,pi*3,1000); +% Y = bsxfun(@(x,n)n*sin(x+2*n*pi/N), X(:), 1:N); +% plot(X,Y, 'linewidth',4) +% +% % Multi-line plot in a loop: +% N = 6; +% set(0,'DefaultAxesColorOrder',brewermap(N,'Accent')) +% X = linspace(0,pi*3,1000); +% Y = bsxfun(@(x,n)n*sin(x+2*n*pi/N), X(:), 1:N); +% for n = 1:N +% plot(X(:),Y(:,n), 'linewidth',4); +% hold all +% end +% +% % New colors for the "colormap" example: +% load spine +% image(X) +% colormap(brewermap([],'YlGnBu')) +% +% % New colors for the "surf" example: +% [X,Y,Z] = peaks(30); +% surfc(X,Y,Z) +% colormap(brewermap([],'RdYlGn')) +% axis([-3,3,-3,3,-10,5]) +% +% % New colors for the "contourcmap" example: +% brewermap('PuOr'); % preselect the colorscheme. +% load topo +% load coast +% figure +% worldmap(topo, topolegend) +% contourfm(topo, topolegend); +% contourcmap('brewermap', 'Colorbar','on', 'Location','horizontal',... +% 'TitleString','Contour Intervals in Meters'); +% plotm(lat, long, 'k') +% +% ### Input and Output Arguments ### +% +% Inputs (*==default): +% N = NumericScalar, N>=0, an integer to define the colormap length. +% = *[], use the length of the current figure's colormap (see "colormap"). +% = StringToken, to preselect this ColorBrewer scheme for later use. +% = 'demo', create a figure showing all of the ColorBrewer schemes. +% = 'list', return a cell array of strings listing all ColorBrewer schemes. +% scheme = StringToken, a ColorBrewer scheme name to select the colorscheme. +% = *none, use the preselected colorscheme (must be set previously!). +% +% Outputs: +% map = NumericMatrix, size Nx3, a colormap of RGB values between 0 and 1. +% num = NumericScalar, the number of nodes defining the ColorBrewer scheme. +% typ = String, the colorscheme type: 'Diverging'/'Qualitative'/'Sequential'. +% OR +% schemes = CellArray of Strings, a list of every ColorBrewer scheme. +% +% [map,num,typ] = brewermap(*N,*scheme) +% OR +% schemes = brewermap('list') + +% ### Input Wrangling ### +% +persistent dcs +% +str = 'A colorscheme must be preselected before calling without a scheme token.'; +% +if nargin==0 % Current figure's colormap length and the preselected colorscheme. + assert(~isempty(dcs),str) + [map,num,typ] = cbSample([],dcs); +elseif nargin==2 % Input colormap length and colorscheme. + assert(isnumeric(N),'The first argument must be a numeric scalar, or empty.') + assert(ischar(scheme)&&isrow(scheme),'The second argument must be a string.') + [map,num,typ] = cbSample(N,scheme); +elseif isnumeric(N) % Input colormap length and the preselected colorscheme. + assert(~isempty(dcs),str) + [map,num,typ] = cbSample(N,dcs); +else% String + assert(ischar(N)&&isrow(N),'The first argument must be a string or numeric.') + % The order of : case-insensitive sort by type and then name. + vec = {'BrBG';'PiYG';'PRGn';'PuOr';'RdBu';'RdGy';'RdYlBu';'RdYlGn';'Spectral';'Accent';'Dark2';'Paired';'Pastel1';'Pastel2';'Set1';'Set2';'Set3';'Blues';'BuGn';'BuPu';'GnBu';'Greens';'Greys';'OrRd';'Oranges';'PuBu';'PuBuGn';'PuRd';'Purples';'RdPu';'Reds';'YlGn';'YlGnBu';'YlOrBr';'YlOrRd'}; + switch lower(N) + case 'demo' % Plot all colorschemes in a figure. + cbDemoFig(vec) + case 'list' % Return a list of all colorschemes. + [num,typ] = cellfun(@cbSelect,vec,'UniformOutput',false); + num = cat(1,num{:}); + map = vec; + otherwise % Store the preselected colorscheme token. + map = dcs; + isr = strncmp('*',N,1); + scm = N(1+isr:end); + [num,typ] = cbSelect(scm); + dcs = [N(1:+isr),vec{strcmpi(vec,scm)}]; + end +end +% +end +%----------------------------------------------------------------------END:brewermap +function [map,num,typ] = cbSample(N,tok) +% Pick a colorscheme, downsample/interpolate to the requested colormap length. +% +if isempty(N) + N = size(get(gcf,'colormap'),1); +else + assert(isscalar(N)&&isreal(N),'First argument must be a real numeric scalar, or empty.') +end +% +% obtain nodes: +isr = strncmp('*',tok,1); +tok = tok(1+isr:end); +[num,typ,rgb] = cbSelect(tok); +% downsample: +map = rgb(cbIndex(N,num,typ,tok,isr),:); +% interpolate: +if N>num + map = interp1(1:num,map,linspace(1,num,N),'pchip'); +end +% +end +%----------------------------------------------------------------------END:cbSample +function cbDemoFig(seq) +% Creates a figure showing all of the ColorBrewer colorschemes. +% +persistent cbh axh +% +xmx = max(cellfun(@cbSelect,seq)); +ymx = numel(seq); +% +if ishghandle(cbh) + figure(cbh); + delete(axh); +else + cbh = figure('HandleVisibility','callback', 'IntegerHandle','off',... + 'NumberTitle','off', 'Name',[mfilename,' Demo'],'Color','white'); +end +% +axh = axes('Parent',cbh, 'Color','none',... + 'XTick',0:xmx, 'YTick',0.5:ymx, 'YTickLabel',seq, 'YDir','reverse'); +title(axh,['ColorBrewer Color Schemes (',mfilename,'.m)'], 'Interpreter','none') +xlabel(axh,'Scheme Nodes') +ylabel(axh,'Scheme Name') +axf = get(axh,'FontName'); +% +for y = 1:ymx + [num,typ,rgb] = cbSelect(seq{y}); + rgb = rgb(cbIndex(num,num,typ,seq{y},false),:); % downsample + for x = 1:num + patch([x-1,x-1,x,x],[y-1,y,y,y-1],1, 'FaceColor',rgb(x,:), 'Parent',axh) + end + text(xmx+0.1,y-0.5,typ, 'Parent',axh, 'FontName',axf) +end +% +end +%----------------------------------------------------------------------END:cbDemoFig +function idx = cbIndex(N,num,typ,tok,isr) +% Ensure exactly the same colors as in the online ColorBrewer schemes. +% +if strcmp(typ,'Qualitative') + assert(N<=num,'Colorscheme "%s" maximum colormap length: %d. Requested: %d.',tok,num,N) + idx = 1:N; +elseif strcmp(typ,'Diverging') + switch min(N,num) + case 1 % extrapolated + idx = 8; + case 2 % extrapolated + idx = [4,12]; + case 3 + idx = [5,8,11]; + case 4 + idx = [3,6,10,13]; + case 5 + idx = [3,6,8,10,13]; + case 6 + idx = [2,5,7,9,11,14]; + case 7 + idx = [2,5,7,8,9,11,14]; + case 8 + idx = [2,4,6,7,9,10,12,14]; + case 9 + idx = [2,4,6,7,8,9,10,12,14]; + case 10 + idx = [1,2,4,6,7,9,10,12,14,15]; + case 11 + idx = [1,2,4,6,7,8,9,10,12,14,15]; + end +elseif strcmp(typ,'Sequential') + switch min(N,num) + case 1 % extrapolated + idx = 6; + case 2 % extrapolated + idx = [4,8]; + case 3 + idx = [3,6,9]; + case 4 + idx = [2,5,7,10]; + case 5 + idx = [2,5,7,9,11]; + case 6 + idx = [2,4,6,7,9,11]; + case 7 + idx = [2,4,6,7,8,10,12]; + case 8 + idx = [1,3,4,6,7,8,10,12]; + case 9 + idx = [1,3,4,6,7,8,10,11,13]; + end +else + error('The colorscheme type "%s" is not recognized',typ) +end +% +if isr + idx = idx(end:-1:1); +end +% +end +%----------------------------------------------------------------------END:cbIndex +function [num,typ,rgb] = cbSelect(tok) +% Return the length, type and RGB values of any colorscheme. +% +switch lower(tok) % ColorName +case 'brbg' % BrBG + rgb = [84,48,5;140,81,10;166,97,26;191,129,45;216,179,101;223,194,125;246,232,195;245,245,245;199,234,229;128,205,193;90,180,172;53,151,143;1,133,113;1,102,94;0,60,48]; + typ = 'Diverging'; +case 'piyg' % PiYG + rgb = [142,1,82;197,27,125;208,28,139;222,119,174;233,163,201;241,182,218;253,224,239;247,247,247;230,245,208;184,225,134;161,215,106;127,188,65;77,172,38;77,146,33;39,100,25]; + typ = 'Diverging'; +case 'prgn' % PRGn + rgb = [64,0,75;118,42,131;123,50,148;153,112,171;175,141,195;194,165,207;231,212,232;247,247,247;217,240,211;166,219,160;127,191,123;90,174,97;0,136,55;27,120,55;0,68,27]; + typ = 'Diverging'; +case 'puor' % PuOr + rgb = [127,59,8;179,88,6;230,97,1;224,130,20;241,163,64;253,184,99;254,224,182;247,247,247;216,218,235;178,171,210;153,142,195;128,115,172;94,60,153;84,39,136;45,0,75]; + typ = 'Diverging'; +case 'rdbu' % RdBu + rgb = [103,0,31;178,24,43;202,0,32;214,96,77;239,138,98;244,165,130;253,219,199;247,247,247;209,229,240;146,197,222;103,169,207;67,147,195;5,113,176;33,102,172;5,48,97]; + typ = 'Diverging'; +case 'rdgy' % RdGy + rgb = [103,0,31;178,24,43;202,0,32;214,96,77;239,138,98;244,165,130;253,219,199;255,255,255;224,224,224;186,186,186;153,153,153;135,135,135;64,64,64;77,77,77;26,26,26]; + typ = 'Diverging'; +case 'rdylbu' % RdYlBu + rgb = [165,0,38;215,48,39;215,25,28;244,109,67;252,141,89;253,174,97;254,224,144;255,255,191;224,243,248;171,217,233;145,191,219;116,173,209;44,123,182;69,117,180;49,54,149]; + typ = 'Diverging'; +case 'rdylgn' % RdYlGn + rgb = [165,0,38;215,48,39;215,25,28;244,109,67;252,141,89;253,174,97;254,224,139;255,255,191;217,239,139;166,217,106;145,207,96;102,189,99;26,150,65;26,152,80;0,104,55]; + typ = 'Diverging'; +case 'spectral' % Spectral + rgb = [158,1,66;213,62,79;215,25,28;244,109,67;252,141,89;253,174,97;254,224,139;255,255,191;230,245,152;171,221,164;153,213,148;102,194,165;43,131,186;50,136,189;94,79,162]; + typ = 'Diverging'; +case 'accent' % Accent + rgb = [127,201,127;190,174,212;253,192,134;255,255,153;56,108,176;240,2,127;191,91,23;102,102,102]; + typ = 'Qualitative'; +case 'dark2' % Dark2 + rgb = [27,158,119;217,95,2;117,112,179;231,41,138;102,166,30;230,171,2;166,118,29;102,102,102]; + typ = 'Qualitative'; +case 'paired' % Paired + rgb = [166,206,227;31,120,180;178,223,138;51,160,44;251,154,153;227,26,28;253,191,111;255,127,0;202,178,214;106,61,154;255,255,153;177,89,40]; + typ = 'Qualitative'; +case 'pastel1' % Pastel1 + rgb = [251,180,174;179,205,227;204,235,197;222,203,228;254,217,166;255,255,204;229,216,189;253,218,236;242,242,242]; + typ = 'Qualitative'; +case 'pastel2' % Pastel2 + rgb = [179,226,205;253,205,172;203,213,232;244,202,228;230,245,201;255,242,174;241,226,204;204,204,204]; + typ = 'Qualitative'; +case 'set1' % Set1 + rgb = [228,26,28;55,126,184;77,175,74;152,78,163;255,127,0;255,255,51;166,86,40;247,129,191;153,153,153]; + typ = 'Qualitative'; +case 'set2' % Set2 + rgb = [102,194,165;252,141,98;141,160,203;231,138,195;166,216,84;255,217,47;229,196,148;179,179,179]; + typ = 'Qualitative'; +case 'set3' % Set3 + rgb = [141,211,199;255,255,179;190,186,218;251,128,114;128,177,211;253,180,98;179,222,105;252,205,229;217,217,217;188,128,189;204,235,197;255,237,111]; + typ = 'Qualitative'; +case 'blues' % Blues + rgb = [247,251,255;239,243,255;222,235,247;198,219,239;189,215,231;158,202,225;107,174,214;66,146,198;49,130,189;33,113,181;8,81,156;8,69,148;8,48,107]; + typ = 'Sequential'; +case 'bugn' % BuGn + rgb = [247,252,253;237,248,251;229,245,249;204,236,230;178,226,226;153,216,201;102,194,164;65,174,118;44,162,95;35,139,69;0,109,44;0,88,36;0,68,27]; + typ = 'Sequential'; +case 'bupu' % BuPu + rgb = [247,252,253;237,248,251;224,236,244;191,211,230;179,205,227;158,188,218;140,150,198;140,107,177;136,86,167;136,65,157;129,15,124;110,1,107;77,0,75]; + typ = 'Sequential'; +case 'gnbu' % GnBu + rgb = [247,252,240;240,249,232;224,243,219;204,235,197;186,228,188;168,221,181;123,204,196;78,179,211;67,162,202;43,140,190;8,104,172;8,88,158;8,64,129]; + typ = 'Sequential'; +case 'greens' % Greens + rgb = [247,252,245;237,248,233;229,245,224;199,233,192;186,228,179;161,217,155;116,196,118;65,171,93;49,163,84;35,139,69;0,109,44;0,90,50;0,68,27]; + typ = 'Sequential'; +case 'greys' % Greys + rgb = [255,255,255;247,247,247;240,240,240;217,217,217;204,204,204;189,189,189;150,150,150;115,115,115;99,99,99;82,82,82;37,37,37;37,37,37;0,0,0]; + typ = 'Sequential'; +case 'orrd' % OrRd + rgb = [255,247,236;254,240,217;254,232,200;253,212,158;253,204,138;253,187,132;252,141,89;239,101,72;227,74,51;215,48,31;179,0,0;153,0,0;127,0,0]; + typ = 'Sequential'; +case 'oranges' % Oranges + rgb = [255,245,235;254,237,222;254,230,206;253,208,162;253,190,133;253,174,107;253,141,60;241,105,19;230,85,13;217,72,1;166,54,3;140,45,4;127,39,4]; + typ = 'Sequential'; +case 'pubu' % PuBu + rgb = [255,247,251;241,238,246;236,231,242;208,209,230;189,201,225;166,189,219;116,169,207;54,144,192;43,140,190;5,112,176;4,90,141;3,78,123;2,56,88]; + typ = 'Sequential'; +case 'pubugn' % PuBuGn + rgb = [255,247,251;246,239,247;236,226,240;208,209,230;189,201,225;166,189,219;103,169,207;54,144,192;28,144,153;2,129,138;1,108,89;1,100,80;1,70,54]; + typ = 'Sequential'; +case 'purd' % PuRd + rgb = [247,244,249;241,238,246;231,225,239;212,185,218;215,181,216;201,148,199;223,101,176;231,41,138;221,28,119;206,18,86;152,0,67;145,0,63;103,0,31]; + typ = 'Sequential'; +case 'purples' % Purples + rgb = [252,251,253;242,240,247;239,237,245;218,218,235;203,201,226;188,189,220;158,154,200;128,125,186;117,107,177;106,81,163;84,39,143;74,20,134;63,0,125]; + typ = 'Sequential'; +case 'rdpu' % RdPu + rgb = [255,247,243;254,235,226;253,224,221;252,197,192;251,180,185;250,159,181;247,104,161;221,52,151;197,27,138;174,1,126;122,1,119;122,1,119;73,0,106]; + typ = 'Sequential'; +case 'reds' % Reds + rgb = [255,245,240;254,229,217;254,224,210;252,187,161;252,174,145;252,146,114;251,106,74;239,59,44;222,45,38;203,24,29;165,15,21;153,0,13;103,0,13]; + typ = 'Sequential'; +case 'ylgn' % YlGn + rgb = [255,255,229;255,255,204;247,252,185;217,240,163;194,230,153;173,221,142;120,198,121;65,171,93;49,163,84;35,132,67;0,104,55;0,90,50;0,69,41]; + typ = 'Sequential'; +case 'ylgnbu' % YlGnBu + rgb = [255,255,217;255,255,204;237,248,177;199,233,180;161,218,180;127,205,187;65,182,196;29,145,192;44,127,184;34,94,168;37,52,148;12,44,132;8,29,88]; + typ = 'Sequential'; +case 'ylorbr' % YlOrBr + rgb = [255,255,229;255,255,212;255,247,188;254,227,145;254,217,142;254,196,79;254,153,41;236,112,20;217,95,14;204,76,2;153,52,4;140,45,4;102,37,6]; + typ = 'Sequential'; +case 'ylorrd' % YlOrRd + rgb = [255,255,204;255,255,178;255,237,160;254,217,118;254,204,92;254,178,76;253,141,60;252,78,42;240,59,32;227,26,28;189,0,38;177,0,38;128,0,38]; + typ = 'Sequential'; +otherwise + error('Colorscheme "%s" is not supported. Check the token tables.',tok) +end +% +rgb = rgb./255; +% +switch typ +case 'Diverging' + num = 11; +case 'Qualitative' + num = size(rgb,1); +case 'Sequential' + num = 9; +otherwise + error('The colorscheme type "%s" is not recognized',typ) +end +% +end +%----------------------------------------------------------------------END:cbSelect +% Copyright (c) 2015 Stephen Cobeldick +% Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University. +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and limitations under the License. +%----------------------------------------------------------------------END:license diff --git a/libraries/dragzoom2D.m b/libraries/dragzoom2D.m new file mode 100644 index 0000000..69eab5a --- /dev/null +++ b/libraries/dragzoom2D.m @@ -0,0 +1,712 @@ +function dragzoom2D(hAx) +%DRAGZOOM2D Drag and zoom tool (simplified from dragzoom.m by Evgeny Pr) +% +% Description: +% DRAGZOOM2D allows you to interactively manage the axes in a figure +% through draging and zooming using the mouse and keyboard shortcuts. +% +% Using: +% dragzoom() : manages the axis from gca() +% dragzoom(hAx) : manages the axis from the handle hAx +% +% Interactions: +% Mouse actions: +% single-click and holding LB : Activation Drag mode +% single-click and holding RB : Activation Rubber Band for region zooming +% single-click MB : Activation 'Extend' Zoom mode +% double-click LB, RB, MB : Reset to Original View +% +% Hotkeys: +% '+' or '>' or ',' : Zoom plus +% '-' or '<' or '.' : Zoom minus +% '0' or '=' : Set default axes (reset to original view) +% 'uparrow' : Up drag +% 'downarrow' : Down drag +% 'leftarrow' : Left drag +% 'rightarrow' : Right drag +% 'x' : Toggles zoom and drag only for X axis +% 'y' : Toggles zoom and drag only for Y axis +% +% Adapted from dragzoom.m, version 0.9.7 by Evgeny Pr aka iroln +% Simon Blanchoud +% University of Fribourg +% 28.09.2018 + +% The initial objective was to make dragzoom.m compatible with Octave by solving the +% nested callback problems. Given the tremendous amount of code written by Evgeny, I +% decided to simplify it as much as possible and stick to 2D. In addition, I tried +% to compact the original code and linearize it as much as possible. Original calls +% to linearized functions are indicated by "%---". To circumvent the limitations of +% removing nested functions, a parameter structure has been created and is stored in +% the parent figure. + + % Get the handles to the axis and the parent figure + if (nargin == 0) + hAx = gca(); + hFig = ancestor(hAx, 'figure'); + elseif (nargin == 1 && ishandle(hAx) && strncmp(get(hAx, 'Type'), 'axes', 4) && length(hAx) == 1) + hFig = ancestor(hAx, 'figure'); + else + error('dragzoom2D:invalidInputs', ... + 'Input must be a single axes handle.') + end + + %---params = Setup(hAx); + % Setup options + % Used to test whether the axis has an image in it + h = findobj(hAx, 'Type', 'Image'); + + % Default values for the zoom grid + mZoomMinPow = 0; + mZoomMaxPow = 5; + mZoomNum = 51; + mZoomIndex = 11; % index of 100% + [mDefaultZoomGrid, mDefaultZoomSteps] = ... + ZoomLogGrid(mZoomMinPow, mZoomMaxPow, mZoomNum); + + % Parameters structure with handles and default values + params = struct('hAx', hAx, ... + 'mStartX', [], ... + 'mStartY', [], ... + 'mBindX', [], ... + 'mBindY', [], ... + 'mDragShiftStep', 3, ... + 'mDragSaveShiftStep', 3, ... + 'mDragShiftStepInc', 1, ... + 'mZoomMinPow', mZoomMinPow, ... + 'mZoomMaxPow', mZoomMaxPow, ... + 'mZoomNum', mZoomNum, ... + 'mZoomExtendNum', 301, ... + 'mZoomKeysNum', 181, ... + 'mDefaultZoomGrid', mDefaultZoomGrid, ... + 'mDefaultZoomSteps', mDefaultZoomSteps, ... + 'mZoomGrid', mDefaultZoomGrid, ... + 'mZoomSteps', mDefaultZoomSteps, ... + 'mZoomIndexX', mZoomIndex, ... + 'mZoomIndexY', mZoomIndex, ... + 'mDefaultXLim', get(hAx, 'XLim'), ... + 'mDefaultYLim', get(hAx, 'YLim'), ... + 'mRubberBand', [], ... + 'mRbEdgeColor', 'k', ... + 'mRbFaceColor', 'none', ... + 'mRbFaceAlpha', 1, ... + 'fIsDragAllowed', false, ... + 'fIsZoomExtendAllowed', false, ... + 'fIsRubberBandOn', false, ... + 'fIsEnableDragX', true, ... + 'fIsEnableDragY', true, ... + 'fIsEnableZoomX', true, ... + 'fIsEnableZoomY', true, ... + 'fIsImage', ~isempty(h)); + + % Defines the handles to the callback functions and the parameter structure + set(hFig, 'CurrentAxes', hAx, ... + 'userdata', params, ... + 'WindowButtonDownFcn', {@WindowButtonDownCallback2D}, ... + 'WindowButtonUpFcn', {@WindowButtonUpCallback2D}, ... + 'WindowButtonMotionFcn', {@WindowButtonMotionCallback2D}, ... + 'KeyPressFcn', {@WindowKeyPressCallback2D}, ... + 'KeyReleaseFcn', {@WindowKeyReleaseCallback2D}, ... + 'WindowKeyPressFcn', {@WindowKeyPressCallback2D}, ... + 'WindowKeyReleaseFcn', {@WindowKeyReleaseCallback2D}); + + return; +end +%-------------------------------------------------------------------------- + +%========================================================================== +function WindowButtonDownCallback2D(src, evnt) %#ok + %WindowButtonDownCallback2D called when the mouse clicks. Typically, + % mouse position will be stored to be compared during the movement to + % update the axis according to the amplitude of the movement. + + % Retrieve the parameter structure and the type of event + params = get(src, 'userdata'); + clickType = get(src, 'SelectionType'); + + % Defines the type of mouse click + switch clickType + + % Left click: dragging + case 'normal' + %----DragMouseBegin(); + if (~params.fIsDragAllowed) + [cx, cy] = GetCursorCoordOnWindow(src); + + params.mStartX = cx; + params.mStartY = cy; + + params.fIsDragAllowed = true; + end + + % Double click: reset + case 'open' + %---ResetAxesToOrigView(); + SetAxesLimits(params.hAx, params.mDefaultXLim, params.mDefaultYLim); + params.mZoomIndexX = find(params.mZoomGrid == 100); + params.mZoomIndexY = params.mZoomIndexX; + + % Right click: rubber band zoom + case 'alt' + %---RubberBandBegin(); + if (~params.fIsRubberBandOn) + [acx, acy] = GetCursorCoordOnAxes(params.hAx); + + %---RubberBandSetup(); + h1 = patch([acx acx acx acx], [acy acy acy acy], 'k', + 'Parent', params.hAx, ... + 'EdgeColor', 'w', ... + 'FaceColor', 'none', ... + 'LineWidth', 1.5, ... + 'LineStyle', '-'); + + h2 = patch([acx acx acx acx], [acy acy acy acy], 'k', + 'Parent', params.hAx, ... + 'EdgeColor', params.mRbEdgeColor, ... + 'FaceColor', params.mRbFaceColor, ... + 'FaceAlpha', params.mRbFaceAlpha, ... + 'LineWidth', 0.5, ... + 'LineStyle', '-'); + + % create rubber band struct + params.mRubberBand = struct(... + 'obj', [h1 h2], ... + 'x1', acx, ... + 'y1', acy, ... + 'x2', acx, ... + 'y2', acy); + + params.fIsRubberBandOn = true; + end + + % Middle click: zooming + case 'extend' + %---ZoomMouseExtendBegin(); + if ~params.fIsZoomExtendAllowed + + % set new zoom grid for extend zoom + [params.mZoomGrid, params.mZoomSteps] = ZoomLogGrid(params.mZoomMinPow, params.mZoomMaxPow, params.mZoomExtendNum); + + %---UpdateCurrentZoomAxes(); + [xLim, yLim] = GetAxesLimits(params.hAx); + [curentZoomX, curentZoomY] = GetCurrentZoomAxesPercent(params.hAx, xLim, yLim, params.mDefaultXLim, params.mDefaultYLim); + [nu, params.mZoomIndexX] = min(abs(params.mZoomGrid - curentZoomX)); %#ok ([~, ...]) + [nu, params.mZoomIndexY] = min(abs(params.mZoomGrid - curentZoomY)); %#ok ([~, ...]) + + [wcx, wcy] = GetCursorCoordOnWindow(src); + [acx, acy] = GetCursorCoordOnAxes(params.hAx); + + params.mStartX = wcx; + params.mStartY = wcy; + + params.mBindX = acx; + params.mBindY = acy; + + params.fIsZoomExtendAllowed = true; + end + end + + % Stores the updated parameter structure + set(src, 'userdata', params); +end +%-------------------------------------------------------------------------- + +%========================================================================== +function WindowButtonUpCallback2D(src, evnt) %#ok + %WindowButtonUpCallback2D called when the mouse click is released. + % Typically this is where we end the updating of the axis. + + params = get(src, 'userdata'); + + if (isempty(params) || ~isfield(params, 'fIsDragAllowed')) + return; + end + + %---DragMouseEnd(); + if params.fIsDragAllowed + params.fIsDragAllowed = false; + end + + %---ZoomMouseExtendEnd(); + if params.fIsZoomExtendAllowed + %---SetDefaultZoomGrid(); + params.mZoomGrid = params.mDefaultZoomGrid; + params.mZoomSteps = params.mDefaultZoomSteps; + + params.mZoomIndexX = find(params.mZoomGrid == 100); + params.mZoomIndexY = params.mZoomIndexX; + + params.fIsZoomExtendAllowed = false; + end + + %---RubberBandEnd(); + if params.fIsRubberBandOn + params.fIsRubberBandOn = false; + delete(params.mRubberBand.obj); + + %---RubberBandZoomAxes(); + xLim = sort([params.mRubberBand.x1, params.mRubberBand.x2]); + yLim = sort([params.mRubberBand.y1, params.mRubberBand.y2]); + + % Fix the final zoom factor to one of the values in the grid + if (range(xLim) ~= 0 && range(yLim) ~= 0) + [zoomPctX, zoomPctY] = GetCurrentZoomAxesPercent(params.hAx, ... + xLim, yLim, params.mDefaultXLim, params.mDefaultYLim); + + if params.fIsImage + zoomPctX = min(zoomPctX, zoomPctY); + zoomPctY = zoomPctX; + end + + cx = mean(xLim); + cy = mean(yLim); + + xLim = RecalcZoomAxesLimits(xLim, params.mDefaultXLim, ... + cx, zoomPctX, strcmp(get(params.hAx, 'xscale'), 'log')); + yLim = RecalcZoomAxesLimits(yLim, params.mDefaultYLim, ... + cy, zoomPctY, strcmp(get(params.hAx, 'yscale'), 'log')); + + SetAxesLimits(params.hAx, xLim, yLim); + end + + params.mRubberBand = []; + end + + % Store the updated parameters structure + set(src, 'userdata', params); +end +%-------------------------------------------------------------------------- + +%========================================================================== +function WindowButtonMotionCallback2D(src, evnt) %#ok + %WindowButtonMotionCallback2D is called when the mouse is moved on + % the figure. Typically this is where the axes are updated according + % to the amplitude of the movement and update the position for the + % next iteration. + + % Retrieve the parameter structure + params = get(src, 'userdata'); + + %---DragMouse(); + if params.fIsDragAllowed + [cx, cy] = GetCursorCoordOnWindow(src); + + pdx = params.mStartX - cx; + pdy = params.mStartY - cy; + + params.mStartX = cx; + params.mStartY = cy; + + if (params.fIsImage) + pdy = -pdy; + end + + DragAxes(params.hAx, pdx, pdy, ... + params.fIsEnableDragX, params.fIsEnableDragY); + end + + %---RubberBandUpdate(); + if params.fIsRubberBandOn + [acx, acy] = GetCursorCoordOnAxes(params.hAx); + + params.mRubberBand.x2 = acx; + params.mRubberBand.y2 = acy; + + %---RubberBandSetPos(); + x1 = params.mRubberBand.x1; + y1 = params.mRubberBand.y1; + + set(params.mRubberBand.obj, ... + 'XData', [x1 acx acx x1], ... + 'YData', [y1 y1 acy acy]); + end + + %---ZoomMouseExtend(); + if params.fIsZoomExtendAllowed + + % Heuristic for pixel change to camera zoom factor + % (taken from function ZOOM, used in dragzoom.m) + [wcx, wcy] = GetCursorCoordOnWindow(src); + + xy(1) = wcx - params.mStartX; + xy(2) = wcy - params.mStartY; + q = max(-0.9, min(0.9, sum(xy)/70)) + 1; + + % Move one step along the zooming grid + if (q < 1) + dz = -1; + elseif (q > 1) + dz = 1; + else + return; + end + + %---ZoomAxes(direction, mZoom3DBindX, mZoom3DBindY) + [xLim, yLim] = GetAxesLimits(params.hAx); + + % Keep a fixed axis ratio for images + if params.fIsImage + params.mZoomIndexX = params.mZoomIndexX + dz; + params.mZoomIndexY = params.mZoomIndexY; + else + if params.fIsEnableZoomX + params.mZoomIndexX = params.mZoomIndexX + dz; + end + if params.fIsEnableZoomY + params.mZoomIndexY = params.mZoomIndexY + dz; + end + end + + % Make sure we stay within the zooming grid + nz = length(params.mZoomGrid); + params.mZoomIndexX = min(max(params.mZoomIndexX, 1), nz); + params.mZoomIndexY = min(max(params.mZoomIndexY, 1), nz); + + xLim = RecalcZoomAxesLimits(xLim, params.mDefaultXLim, ... + params.mBindX, params.mZoomGrid(params.mZoomIndexX), ... + strcmp(get(params.hAx, 'xscale'), 'log')); + yLim = RecalcZoomAxesLimits(yLim, params.mDefaultYLim, ... + params.mBindY, params.mZoomGrid(params.mZoomIndexY), ... + strcmp(get(params.hAx, 'yscale'), 'log')); + + SetAxesLimits(params.hAx, xLim, yLim); + + params.mStartX = wcx; + params.mStartY = wcy; + end + + % Store the updated parameter structure + set(src, 'userdata', params); +end +%-------------------------------------------------------------------------- + +%========================================================================== +function WindowKeyPressCallback2D(src, evnt) %#ok + %WindowKeyPressCallback2D is called when a keyboard key is pressed. + % Typically used as an alterative to the mouse interactions and applies + % discrete changes to the axis. + + % Get the parameter structure + params = get(src, 'userdata'); + + % We need a default value for the zoom factor to avoid duplicating + % the rather length code for zooming. + dz = 0; + + % Switch through the keys + switch evnt.Key + case {'0', 'equal'} + %---ResetAxesToOrigView(); + SetAxesLimits(params.hAx, params.mDefaultXLim, params.mDefaultYLim); + params.mZoomIndexX = find(params.mZoomGrid == 100); + params.mZoomIndexY = params.mZoomIndexX; + case {'add', 'plus', 'greater', 'comma'} + %---ZoomKeys('plus'); + dz = 1; + case {'hyphen', 'subtract', 'minus', 'less', 'period'} + %---ZoomKeys('minus'); + dz = -1; + case {'leftarrow', 'rightarrow', 'uparrow', 'downarrow', ...} + 'left', 'right', 'up', 'down'} + %---DragKeys('...'); + dx = params.mDragShiftStep; + dy = params.mDragShiftStep; + + % Increment of speed when you hold the button + params.mDragShiftStep = params.mDragShiftStep + params.mDragShiftStepInc; + + % Determine which movement increment to keep + switch evnt.Key(1) + case 'r' + dx = -dx; + dy = 0; + case 'l' + dy = 0; + case 'd' + dx = 0; + case 'u' + dx = 0; + dy = -dy; + end + + % Y-axis is inverted in images + if (params.fIsImage) + pdy = -pdy; + end + + DragAxes(params.hAx, dx, dy, ... + params.fIsEnableDragX, params.fIsEnableDragY); + + case 'x' + % Toggles zoom & drag restriction along the X axis + % by forbidding movements along the Y axis + params.fIsEnableDragY = ~params.fIsEnableDragY; + params.fIsEnableZoomY = ~params.fIsEnableZoomY; + + case 'y' + % Toggles along the Y axis + params.fIsEnableDragX = ~params.fIsEnableDragX; + params.fIsEnableZoomX = ~params.fIsEnableZoomX; + end + + % Merged both zoom actions into a single one + if (dz ~= 0) + %---UpdateCurrentZoomAxes(); + + % set new zoom grid for extend zoom + [params.mZoomGrid, params.mZoomSteps] = ZoomLogGrid(params.mZoomMinPow, params.mZoomMaxPow, params.mZoomExtendNum); + + %---UpdateCurrentZoomAxes(); + [xLim, yLim] = GetAxesLimits(params.hAx); + [curentZoomX, curentZoomY] = GetCurrentZoomAxesPercent(params.hAx, xLim, yLim, params.mDefaultXLim, params.mDefaultYLim); + [nu, params.mZoomIndexX] = min(abs(params.mZoomGrid - curentZoomX)); %#ok ([~, ...]) + [nu, params.mZoomIndexY] = min(abs(params.mZoomGrid - curentZoomY)); %#ok ([~, ...]) + + [acx, acy] = GetCursorCoordOnAxes(params.hAx); + + if params.fIsImage + params.mZoomIndexX = params.mZoomIndexX + dz; + params.mZoomIndexY = params.mZoomIndexX; + else + if params.fIsEnableZoomX + params.mZoomIndexX = params.mZoomIndexX + dz; + end + if params.fIsEnableZoomY + params.mZoomIndexY = params.mZoomIndexY + dz; + end + end + + % Make sure we stay within the zooming grid + nz = length(params.mZoomGrid); + params.mZoomIndexX = min(max(params.mZoomIndexX, 1), nz); + params.mZoomIndexY = min(max(params.mZoomIndexY, 1), nz); + + xLim = RecalcZoomAxesLimits(xLim, params.mDefaultXLim, ... + acx, params.mZoomGrid(params.mZoomIndexX), ... + strcmp(get(params.hAx, 'xscale'), 'log')); + yLim = RecalcZoomAxesLimits(yLim, params.mDefaultYLim, ... + acy, params.mZoomGrid(params.mZoomIndexY), ... + strcmp(get(params.hAx, 'yscale'), 'log')); + + SetAxesLimits(params.hAx, xLim, yLim); + end + + % Store the updated parameter structure + set(src, 'userdata', params); +end +%-------------------------------------------------------------------------- + +%========================================================================== +function WindowKeyReleaseCallback2D(src, evnt) %#ok + %WindowKeyReleaseCallback2D is called when a pressed key is released. + % The only usage is to restore the incremented drag shift to its + % original value. + + switch evnt.Key + case {'leftarrow', 'rightarrow', 'uparrow', 'downarrow', ...} + 'left', 'right', 'up', 'down'} + params = get(src, 'userdata'); + params.mDragShiftStep = params.mDragSaveShiftStep; + set(src, 'userdata', params); + end +end +%-------------------------------------------------------------------------- + +%========================================================================== +function DragAxes(hAx, pdx, pdy, fIsEnableDragX, fIsEnableDragY) + %DragAxes a subfunction responsible for calculating the new axes limits + % based on the provided movement increment. + + [xLim, yLim] = GetAxesLimits(hAx); + + %---pos = GetObjPos(hAx, 'Pixels'); + dfltUnits = get(hAx, 'Units'); + set(hAx, 'Units', 'Pixels'); + pos = get(hAx, 'Position'); + set(hAx, 'Units', dfltUnits); + + pbar = get(hAx, 'PlotBoxAspectRatio'); + + % Heuristic for replacing the PAN function that has some type + % of bug according to Evgeny (used in dragzoom.m) + imAspectRatioX = pbar(2) / pbar(1); + if (imAspectRatioX ~= 1) + posAspectRatioX = pos(3) / pos(4); + arFactorX = imAspectRatioX * posAspectRatioX; + if (arFactorX < 1) + arFactorX = 1; + end + else + arFactorX = 1; + end + + imAspectRatioY = pbar(1) / pbar(2); + if (imAspectRatioY ~= 1) + posAspectRatioY = pos(4) / pos(3); + arFactorY = imAspectRatioY * posAspectRatioY; + if (arFactorY < 1) + arFactorY = 1; + end + else + arFactorY = 1; + end + + if fIsEnableDragX + % For log plots, transform to linear scale + if strcmp(get(hAx, 'xscale'), 'log') + xLim = log10(xLim); + xLim = FixInfLogLimits('x', xLim); + isXLog = true; + else + isXLog = false; + end + + dx = pdx * range(xLim) / (pos(3) / arFactorX); + xLim = xLim + dx; + + % For log plots, untransform limits + if isXLog + xLim = 10.^(xLim); + end + end + if fIsEnableDragY + if strcmp(get(hAx, 'yscale'), 'log') + yLim = log10(yLim); + yLim = FixInfLogLimits('y', yLim); + isYLog = true; + else + isYLog = false; + end + + dy = pdy * range(yLim) / (pos(4) / arFactorY); + + yLim = yLim + dy; + + if isYLog + yLim = 10.^(yLim); + end + end + + % Applies the new limits to the axes + SetAxesLimits(hAx, xLim, yLim); +end +%-------------------------------------------------------------------------- + +%========================================================================== +function [curentZoomX, curentZoomY] = GetCurrentZoomAxesPercent(hAx, xLim, yLim, mDefaultXLim, mDefaultYLim) + %GetCurrentZoomAxesPercent determines given the current axes limits + % which zoom factor we are using. + + if strcmp(get(hAx, 'xscale'), 'log') + xLim = log10(xLim); + defaultXLim = log10(mDefaultXLim); + else + defaultXLim = mDefaultXLim; + end + if strcmp(get(hAx, 'yscale'), 'log') + yLim = log10(yLim); + defaultYLim = log10(mDefaultYLim); + else + defaultYLim = mDefaultYLim; + end + + curentZoomX = range(defaultXLim) * 100 / range(xLim); + curentZoomY = range(defaultYLim) * 100 / range(yLim); +end +%-------------------------------------------------------------------------- + +%========================================================================== +function [x, y] = GetCursorCoordOnAxes(hAx) + %GetCursorCoordOnAxImg helper function to get the position of the + % mouse in the coordinates of the axis. + + crd = get(hAx, 'CurrentPoint'); + x = crd(2,1); + y = crd(2,2); +end +%-------------------------------------------------------------------------- + +%========================================================================== +function [x, y] = GetCursorCoordOnWindow(hFig) + %GetCursorCoordOnWindow get the position of the mouse on the figure + % in pixels + + dfltUnits = get(hFig, 'Units'); + set(hFig, 'Units', 'pixels'); + + crd = get(hFig, 'CurrentPoint'); + x = crd(1); + y = crd(2); + + set(hFig, 'Units', dfltUnits); +end +%-------------------------------------------------------------------------- + +%========================================================================== +function [xLim, yLim] = GetAxesLimits(hAx) + %GetAxesLimits + + xLim = get(hAx, 'XLim'); + yLim = get(hAx, 'YLim'); +end +%-------------------------------------------------------------------------- + +%========================================================================== +function SetAxesLimits(hAx, xLim, yLim) + %SetAxesLimits + + set(hAx, 'XLim', xLim); + set(hAx, 'YLim', yLim); +end +%-------------------------------------------------------------------------- + +%========================================================================== +function axLim = RecalcZoomAxesLimits(axLim, axLimDflt, zcCrd, zoomPct, isLog) + %RecalcZoomAxesLimits recalculates the axes limits + + if isLog + axLim = log10(axLim); + %---axLim = FixInfLogLimits(ax, axLim); + axLimDflt = log10(axLimDflt); + zcCrd = log10(zcCrd); + + % Simple hack to avoid using the original undocumented + % axis function + if (~all(isfinite(axLim)) || ~all(isreal(axLim))) + axLim = axLimDflt; + end + end + + if (zcCrd < axLim(1)), zcCrd = axLim(1); end + if (zcCrd > axLim(2)), zcCrd = axLim(2); end + + rf = range(axLim); + ra = range([axLim(1), zcCrd]); + rb = range([zcCrd, axLim(2)]); + + cfa = ra / rf; + cfb = rb / rf; + + newRange = range(axLimDflt) * 100 / zoomPct; + dRange = newRange - rf; + + axLim(1) = axLim(1) - dRange * cfa; + axLim(2) = axLim(2) + dRange * cfb; + + if isLog + axLim = 10.^axLim; + end +end +%-------------------------------------------------------------------------- + +%========================================================================== +function [zg, st] = ZoomLogGrid(a, b, n) + %ZoomLogGrid creates the log zoom grid + + zg = unique(round(logspace(a, b, n))); + + zg(zg<10) = []; % begin zoom == 10% + st = length(zg); + +end +%-------------------------------------------------------------------------- diff --git a/libraries/local_extrema.m b/libraries/local_extrema.m new file mode 100755 index 0000000..ca7cdf4 --- /dev/null +++ b/libraries/local_extrema.m @@ -0,0 +1,151 @@ +function [xmax,imax,xmin,imin] = local_extrema(x, nneigh) +% LOCAL_EXTREMA Gets the local extrema points from a time series. +% +% [XMAX,IMAX,XMIN,IMIN] = LOCAL_EXTREMA(X) returns the local minima +% and maxima points of the vector X ignoring NaN's, where +% XMAX - maxima points +% IMAX - indexes of the XMAX +% XMIN - minima points +% IMIN - indexes of the XMIN +% +% [...] = LOCAL_EXTREMA(X, NNEIGHBORS) returns only the maximal/minimal +% extrama values in a [-NNEIGHBORS +NNEIGHBORS] local window. +% +% Based on EXTRAM by +% Lic. on Physics Carlos Adrián Vargas Aguilera +% Physical Oceanography MS candidate +% UNIVERSIDAD DE GUADALAJARA +% Mexico, 2004 +% +% Wilson lab, University of Otago +% Simon Blanchoud +% 25.06.2015 + + % No local average + if (nargin < 2) + nneigh = 1; + end + + xmax = []; + imax = []; + xmin = []; + imin = []; + + % Vector input? + Nt = numel(x); + if Nt ~= length(x) + error('Entry must be a vector.') + end + + % NaN's: + inan = find(isnan(x)); + indx = 1:Nt; + if ~isempty(inan) + indx(inan) = []; + x(inan) = []; + Nt = length(x); + end + + % Difference between subsequent elements: + x = x(:); + dx = differentiator(x, 'super'); + + % Local maxima and minima for windowing + nneigh = ceil(nneigh); + if (nneigh > 1) + window = 2*nneigh + 1; + indxs = [1:nneigh nneigh+2:window].'; + ssize = [window, 1]; + lmax = colfilt(x, ssize, 'sliding', @(y)(max(y(indxs,:), [], 1))); + + if (size(x, 1) == 1) + lmax = lmax.'; + end + + % Only if needed + if (nargout > 2) + lmin = colfilt(x, ssize, 'sliding', @(y)(min(y(indxs,:), [], 1))); + + if (size(x, 1) == 1) + lmin = lmin.'; + end + end + else + lmax = x-1; + lmin = x+1; + end + + % Is an horizontal line? + if ~any(dx) + return + end + + % Flat peaks? Put the middle element: + a = find(dx~=0); % Indexes where x changes + lm = find(diff(a)~=1) + 1; % Indexes where a do not changes + d = a(lm) - a(lm-1); % Number of elements in the flat peak + a(lm) = a(lm) - floor(d/2); % Save middle elements + a(end+1) = Nt; + + % Peaks? + xa = x(a); % Serie without flat peaks + b = (diff(xa) > 0); % 1 => positive slopes (minima begin) + % 0 => negative slopes (maxima begin) + xb = diff(b); % -1 => maxima indexes (but one) + % +1 => minima indexes (but one) + + a = a(1:end-1); + + % Compensate for the missing datapoints after diff + if (size(x,1)==1) + xb = [0 xb]; + else + xb = [0; xb]; + end + + imax = find((xb == -1) & (x(a) > lmax(a))); % maxima indexes + imax = a(imax); + + % Only if needed + if (nargout > 2) + imin = find((xb == +1) & (x(a) < lmin(a))); % minima indexes + imin = a(imin); + else + imin = []; + end + + nmaxi = length(imax); + nmini = length(imin); + + % Maximum or minumim on a flat peak at the ends? + if (nmaxi==0) && (nmini==0) + if x(1) > x(Nt) + xmax = x(1); + imax = indx(1); + xmin = x(Nt); + imin = indx(Nt); + elseif x(1) < x(Nt) + xmax = x(Nt); + imax = indx(Nt); + xmin = x(1); + imin = indx(1); + end + return + end + xmax = x(imax); + xmin = x(imin); + + % NaN's: + if (nargout > 1) + if ~isempty(inan) + imax = indx(imax); + imin = indx(imin); + end + + % Same size as x: + imax = reshape(imax,size(xmax)); + imin = reshape(imin,size(xmin)); + end + + return; +end diff --git a/libraries/plot2svg.m b/libraries/plot2svg.m new file mode 100755 index 0000000..d7c4b62 --- /dev/null +++ b/libraries/plot2svg.m @@ -0,0 +1,3577 @@ +function varargout = plot2svg(param1,id,pixelfiletype) +% Matlab to SVG converter +% Prelinary version supporting 3D plots as well +% +% Usage: plot2svg(filename,graphic handle,pixelfiletype) +% optional optional optional +% or +% +% plot2svg(figuresize,graphic handle,pixelfiletype) +% optional optional optional +% +% pixelfiletype = 'png' (default), 'jpg' +% +% Juerg Schwizer 23-Oct-2005 +% See http://www.zhinst.com/blogs/schwizer/ to get more informations + +% 07.06.2005 - Bugfix axxindex (Index exceeds matrix dimensions) +% 19.09.2005 - Added possibility to select output format of pixel graphics +% 23.10.2005 - Bugfix cell array strings (added by Bill) +% Handling of 'hggroups' and improved grouping of objects +% Improved handling of pixel images (indexed and true color pictures) +% 23.10.2005 - Switched default pixelfromat to 'png' +% 07.11.2005 - Added handling of hidden axes for annotations (added by Bill) +% 03.12.2005 - Bugfix of viewBox to make Firefox 1.5 working +% 04.12.2005 - Improved handling of exponent values for log-plots +% Improved markers +% 09.12.2005 - Bugfix '<' '>' '?' '"' +% 22.12.2005 - Implementation of preliminary 3D version +% Clipping +% Minor tick marks +% 22.01.2005 - Removed unused 'end' +% 29.10.2006 - Bugfix '°','±','µ','²','³','¼''½','¾','©''®' +% 17-04-2007 - Bugfix 'projection' in hggroup and hgtransform +% 27-01-2008 - Added Octave functionality (thanks to Jakob Malm) +% Bugfixe cdatamapping (thanks to Tom) +% Bugfix image data writing (thanks to Tom) +% Patches includes now markers as well (needed for 'scatter' +% plots (thanks to Phil) +% 04-02-2008 - Bugfix markers for Octave (thanks to Jakob Malm) +% 30-12-2008 - Bugfix image scaling and orientation +% Bugfix correct backslash (thanks to Jason Merril) +% 20-06-2009 - Improvment of image handling (still some remaining issues) +% Fix for -. line style (thanks to Ritesh Sood) +% 28-06-2009 - Improved depth sorting for patches and surface +% - Bugfix patches +% - Bugfix 3D axis handling +% 11-07-2009 - Support of FontWeight and FontAngle properties +% - Improved markers (polygon instead of polyline for closed markers) +% - Added character encoding entry to be fully SVG 1.1 conform +% 13-07-2009 - Support of rectangle for 2D +% - Added preliminary support for SVG filters +% - Added preliminary support for clipping with pathes +% - Added preliminary support for turning axis tickmarks +% 18-07-2009 - Line style scaling with line width (will not match with png +% output) +% - Small optimizations for the text base line +% - Bugfix text rotation versus shift +% - Added more SVG filters +% - Added checks for filter strings +% 21-07-2009 - Improved bounding box calculation for filters +% - Bugfixes for text size / line distance +% - Support of background box for text +% - Correct bounding box for text objects +% 31-07-2009 - Improved support of filters +% - Experimental support of animations +% 16-08-2009 - Argument checks for filters +% - Rework of latex string handling +% - 'sub' and 'super' workaround for Firefox and Inkscape +% 31-10-2009 - Bugfix for log axes (missing minor grid for some special +% cases) +% 24-01-2010 - Bugfix nomy line #1102 (thanks to Pooya Jannaty) +% 17-02-2010 - Bugfix minor tickmarks for log axis scaling (thanks to +% Harke Pera) +% - Added more lex symbols +% 06-03-2010 - Automatic correction of illegal axis scalings by the user +% (thanks to Juergen) +% - Renamed plot2svg_beta to plot2svg +% 12-04-2010 - Improved Octave compatibility +% 05-05-2010 - Bugfix for ticklabels outside of the axis limits (thanks to +% Ben Scandella) +% 30-10-2010 - Improved handling of empty cells for labels (thanks to +% Constantine) +% - Improved HTML character coding (thanks to David Mack) +% - Bugfix for last ')' (thanks to Jonathon Harding and Benjamin) +% - Enabled scatter plots using hggroups +% - Closing patches if they do not contain NaNs +% 10-11-2010 - Support of the 'Layer' keyword to but the grid on top of +% of the other axis content using 'top' (Many thanks to Justin +% Ashmall) +% - Tiny optimization of the grid display at axis borders +% 25-08-2011 - Fix for degree character (thanks to Manes Recheis) +% - Fix for problems with dash-arrays in Inkscape (thanks to +% Rüdiger Stirnberg) +% - Modified shape of driangles (thanks to Rüdiger Stirnberg) +% 22-10-2011 - Removed versn as return value of function fileparts (thanks +% to Andrew Scott) +% - Fix for images (thanks to Roeland) +% 20-05-2012 - Added some security checks for empty data +% - Fixed rotation for multiline text +% 25-08-2012 - Special handling of 1xn char arrays for tick labels +% (thanks to David Plavcan) +% - Fix for 'Index exceeds matrix dimensions' of axis labels +% (thanks to Aslak Grinsted) +% - Fix for another axis label problem (thanks to Ben Mitch) +% 15-09-2012 - Fix for linestyle none of rectangles (thanks to Andrew) +% - Enabled scatter plot functionality +% 16-02-2013 - Fix for manual tick labels if count differs from +% number of ticks (thanks to Anna) +% 18-07-2013 - Small fix to exclude change log from help +% (thanks to Stuart Layton) +% 30-11-2014 - Preliminary partial support for contour objects +% Edits made by Jonathon Harding +% 18-02-2015 - Removed calls to `find` inside variable indexes, as these +% are not necessary for MATLAB +% 18-02-2015 - Added MException catching to try/catch blocks and the +% assocciated warning messages +% 19-02-2015 - Updated line2svg to never create line segments longer than +% 5000 points, even if the data includes some NaN elements +% 19-02-2015 - convertunit now properly converts between MATLAB pixels +% (which are variable) and SVG pixels (fixed at 90 ppi) in +% all cases. Text will be appropriately sized when MATLAB is +% run in High-DPI mode +% 19-02-2015 - Updated text rendering code. Translation and rotation are +% now applied in a single step, directly to the text object +% (rather than in groups surrounding it). Super- and +% sub-scripts are now generated using SVG standard values for +% baseline-shift and font-size percentages +% 19-02-2015 - Support for MATLAB 2014b grids added (Grids can have custom +% opacity and colors). line2svg now supports opacity, +% although MATLAB does not allow ordinary line objects to +% have an alpha value, as far as I know +% 19-02-2015 - Update for MATLAB 2015b: use axes OuterPosition for axes +% placement +% 19-02-2015 - Properly calculate the axes limits when the user has +% specified infinite limits +% 19-02-2015 - Added bug fix for when ticks are specified outside the plot +% window +% 19-02-2015 - Bug fix for log scale minor ticks not showing above the +% largest power of 10. Inspired by Valentin, but used a +% simpler solution +% Edits made by Juerg Schwizer +% 14-05-2015 - Reworked fix for ticks specified outside the plot window +% - Added css property image-rendering: pixelated; +% - Removed type 'image' from AxesChildBounds() as it triggers +% an error under Matlab 2010. +% 14-05-2015 - Reverted part of the text rendering of 19-02-2015 as it +% failed to handle exponents. +% 14-05-2015 - Removed undefined variable which was obsolete +% Simon Blanchoud +% 18-12-2014 - Modified to render uipanels properly +% - Save now all images to a dedicated folder + +global PLOT2SVG_globals +global colorname +progversion='14-May-2015'; +PLOT2SVG_globals.runningIdNumber = 0; +PLOT2SVG_globals.octave = false; +PLOT2SVG_globals.checkUserData = true; +PLOT2SVG_globals.ScreenPixelsPerInch = 90; % Default 90ppi +PLOT2SVG_globals.MainFigure = -1; +PLOT2SVG_globals.used_dir = false; +try + PLOT2SVG_globals.ScreenPixelsPerInch = get(0, 'ScreenPixelsPerInch'); +catch + % Keep the default 90ppi +end +if nargout==1 + varargout={0}; +end +disp([' Matlab/Octave to SVG converter version ' progversion ', Juerg Schwizer (converter@bluewin.ch).']) +matversion=version; +if exist('OCTAVE_VERSION','builtin') + PLOT2SVG_globals.octave = true; + disp(' Info: PLOT2SVG runs in Octave mode.') +else + if str2num(matversion(1))<6 % Check for matlab version and print warning if matlab version lower than version 6.0 (R.12) + disp(' Warning: Future versions may no more support older versions than MATLAB R12.') + end +end +if nargout > 1 + error('Function returns only one return value.') +end +if nargin<2 % Check if handle was included into function call, otherwise take current figure + id=gcf; +end +PLOT2SVG_globals.MainFigure = id; +if nargin==0 || isnumeric(param1) + + [filename, pathname] = uiputfile( {'*.svg', 'SVG File (*.svg)'},'Save Figure as SVG File', pwd); + + if ~( isequal( filename, 0) || isequal( pathname, 0)) + % yes. add backslash to path (if not already there) + %pathname = addBackSlash( pathname); + % check, if extension is allrigth + if ( ~strcmpi( getFileExtension( filename), '.svg')) + filename = [ filename, '.svg']; + end + finalname=fullfile(pathname, filename); + %finalname=[pathname filename]; + else + disp(' Cancel button was pressed.') + return + end +end + +if ~isnumeric(param1) + finalname=param1; +end +% needed to see annotation axes +originalShowHiddenHandles = get(0, 'ShowHiddenHandles'); +set(0, 'ShowHiddenHandles', 'on'); +originalFigureUnits=get(id,'Units'); +set(id,'Units','pixels'); % All data in the svg-file is saved in pixels +paperpos=get(id,'Position'); +if ( nargin > 0) + if isnumeric(param1) + paperpos(3)=param1(1); + paperpos(4)=param1(2); + end +end +paperpos = convertunit(paperpos, 'pixels', 'pixels'); +if (nargin < 3) + PLOT2SVG_globals.pixelfiletype = 'png'; +else + PLOT2SVG_globals.pixelfiletype = pixelfiletype; +end +cmap=get(id,'Colormap'); +colorname=''; +for i=1:size(cmap,1) + colorname(i,:)=sprintf('%02x%02x%02x',fix(cmap(i,1)*255),fix(cmap(i,2)*255),fix(cmap(i,3)*255)); +end + +% Open SVG-file +[pathstr,name] = fileparts(finalname); +dirname = fullfile(pathstr, name); +if (~isdir(dirname)) + mkdir(dirname); +end +%PLOT2SVG_globals.basefilename = fullfile(pathstr,name); +PLOT2SVG_globals.basefilepath = pathstr; +PLOT2SVG_globals.basedirname = dirname; +PLOT2SVG_globals.reldirname = fullfile('.', name); +PLOT2SVG_globals.basefilename = name; +PLOT2SVG_globals.figurenumber = 1; +fid=fopen(finalname,'wt'); % Create a new text file +fprintf(fid,'\n'); % Insert file header +fprintf(fid,'\n'); +fprintf(fid,' Matlab Figure Converted by PLOT2SVG written by Juerg Schwizer\n'); +%fprintf(fid,' \n'); +fprintf(fid,' \n'); +group=1; +groups=[]; +% Frame of figure +figcolor = searchcolor(id,get(id, 'Color')); +if (~ strcmp(figcolor, 'none')) + % Draw rectangle in the background of the graphic frame to cover all + % other graphic elements + try % Octave does not have support for InvertHardcopy yet -- Jakob Malm + if strcmp(get(id,'InvertHardcopy'),'on') + fprintf(fid,' \n',paperpos(3),paperpos(4)); + else + fprintf(fid,' \n',paperpos(3),paperpos(4),figcolor); + end + catch + fprintf(fid,' \n',paperpos(3),paperpos(4),figcolor); + end +end +% Search all axes +group = children2svg(fid, id, id, group, paperpos); +fprintf(fid,' \n'); +fprintf(fid,'\n'); +fclose(fid); % close text file + +if (~PLOT2SVG_globals.used_dir) + rmdir(dirname); +end + +if nargout==1 + varargout={0}; +end +set(id,'Units',originalFigureUnits); +set(0, 'ShowHiddenHandles', originalShowHiddenHandles); + +function group = children2svg(fid, id, child, group, paperpos) +global PLOT2SVG_globals +% Search all axes +groups = []; +ax=get(child,'Children'); +for j=length(ax):-1:1 + currenttype = get(ax(j),'Type'); + if strcmp(currenttype,'axes') + group=group+1; + groups=[groups group]; + group=axes2svg(fid,id,ax(j),group,paperpos); + elseif strcmp(currenttype,'uicontrol') + if strcmp(get(ax(j),'Visible'),'on') + control2svg(fid,id,ax(j),group,paperpos); + end + elseif strcmp(currenttype, 'uicontextmenu') || ... + strcmp(currenttype, 'uimenu') || ... + strcmp(currenttype, 'hgjavacomponent') || ... + strcmp(currenttype, 'uitoolbar') + % ignore these types + elseif strcmp(currenttype, 'uipanel') + + %set(ax(j),'Units','pixels'); % All data in the svg-file is saved in pixels + childpos=get(ax(j),'Position'); + tmp_pos = childpos(1:2).*paperpos(3:4); + %childpos = childpos * 90 / PLOT2SVG_globals.ScreenPixelsPerInch; + childpos(2) = (1 - (childpos(2) + childpos(4))); + childpos = childpos .* [paperpos(3:4) paperpos(3:4)]; + + group = group + 1; + fprintf(fid,' \n', createId, childpos(1), childpos(2)); + + fprintf(fid,' \n',childpos(3),childpos(4)); + children2svg(fid, id, ax(j), group, childpos); + fprintf(fid,' \n'); + else + disp([' Warning: Unhandled main figure child type: ' currenttype]); + end +end + +function clippingIdString = clipping2svg(fid, id, ax, paperpos, axpos, projection, clippingIdString) +global PLOT2SVG_globals +if PLOT2SVG_globals.checkUserData && isstruct(get(id,'UserData')) + struct_data = get(id,'UserData'); + if isfield(struct_data,'svg') + if isfield(struct_data.svg,'ClippingPath') + clip = struct_data.svg.ClippingPath; + if ~isempty(clip) + if size(clip, 2) ~=3 + if size(clip, 2) ==2 + clipx = clip(:, 1); + clipy = clip(:, 2); + clipz = zeros(size(clip,1),1); + else + error('The clipping vector has to be a nx3 or nx2 matrix.'); + end + else + clipx = clip(:, 1); + clipy = clip(:, 2); + clipz = clip(:, 3); + end + if strcmp(get(ax,'XScale'),'log') + clipx(clipx<=0) = NaN; + clipx=log10(clipx); + end + if strcmp(get(ax,'YScale'),'log') + clipy(clipy<=0) = NaN; + clipy=log10(clipy); + end + if strcmp(get(ax,'ZScale'),'log') + clipz(clipz<=0) = NaN; + clipz=log10(clipz); + end + [x,y,z] = project(clipx,clipy,clipz,projection); + x = (x*axpos(3)+axpos(1))*paperpos(3); + y = (1-(y*axpos(4)+axpos(2)))*paperpos(4); + clippingIdString = createId; + fprintf(fid,'\n \n\n'); + end + end + end +end + + +function [angle, align] = improvedXLabel(id, angle, align) +global PLOT2SVG_globals +if PLOT2SVG_globals.checkUserData && isstruct(get(id,'UserData')) + struct_data = get(id,'UserData'); + if isfield(struct_data,'svg') + if isfield(struct_data.svg,'XTickLabelAngle') + angle = struct_data.svg.XTickLabelAngle; + align = 'Left'; + end + end +end + +function [angle, align] = improvedYLabel(id, angle, align) +global PLOT2SVG_globals +if PLOT2SVG_globals.checkUserData && isstruct(get(id,'UserData')) + struct_data = get(id,'UserData'); + if isfield(struct_data,'svg') + if isfield(struct_data.svg,'YTickLabelAngle') + angle = struct_data.svg.YTickLabelAngle; + align = 'Left'; + end + end +end + +function animation2svg(fid, id) +global PLOT2SVG_globals +if PLOT2SVG_globals.checkUserData && isstruct(get(id,'UserData')) + struct_data = get(id,'UserData'); + if isfield(struct_data,'svg') + if isfield(struct_data.svg,'Animation') + animation = struct_data.svg.Animation; + for i = 1:length(animation) + if ~isfield(animation(i).SubAnimation, 'Type') + error(['Missing field ''Type'' for animation.']); + end + switch animation(i).SubAnimation.Type + case 'Opacity', type = 'opacity'; animationType = 0; + case 'Translate', type = 'translate'; animationType = 2; + case 'Scale', type = 'scale'; animationType = 2; + case 'Rotate', type = 'rotate'; animationType = 1; + case 'skewX', type = 'skewX'; animationType = 1; + case 'skewY', type = 'skewY'; animationType = 1; + otherwise, error(['Unknown animation type ''' animation(i).SubAnimation.Type '''.']); + end + %fprintf(fid,' ',... + % 'opacity' , 0, 1, 5, 'indefinite'); + if animationType == 0 + fprintf(fid,' ', 'indefinite'); + elseif animationType == 1 + fprintf(fid,' ', 'indefinite'); + elseif animationType == 2 + fprintf(fid,' ', 'indefinite'); + end + end + end + end +end + +function [filterString, boundingBox] = filter2svg(fid, id, boundingBoxAxes, boundingBoxElement) +global PLOT2SVG_globals +filterString = ''; +boundingBox = boundingBoxAxes; +if PLOT2SVG_globals.checkUserData && isstruct(get(id,'UserData')) + struct_data = get(id,'UserData'); + if isfield(struct_data,'svg') + boundingBox = boundingBoxElement; + absolute = true; + offset = 0; + if isfield(struct_data.svg,'BoundingBox') + if isfield(struct_data.svg.BoundingBox, 'Type') + switch struct_data.svg.BoundingBox.Type + case 'axes', boundingBox = boundingBoxAxes; absolute = true; + case 'element', boundingBox = boundingBoxElement; absolute = true; + case 'relative', boundingBox = boundingBoxElement; absolute = false; + otherwise + error(['Unknown bounding box type ''' struct_data.svg.BoundingBox.Type '''.']); + end + end + if isfield(struct_data.svg.BoundingBox, 'Overlap') + overlap = struct_data.svg.BoundingBox.Overlap; + if absolute + boundingBox(1) = boundingBox(1) - overlap; + boundingBox(2) = boundingBox(2) - overlap; + boundingBox(3) = boundingBox(3) + 2 * overlap; + boundingBox(4) = boundingBox(4) + 2 * overlap; + else + boundingBox(1) = boundingBox(1) - boundingBox(3) * overlap; + boundingBox(2) = boundingBox(2) - boundingBox(4) * overlap; + boundingBox(3) = boundingBox(3) + 2 * boundingBox(3) * overlap; + boundingBox(4) = boundingBox(4) + 2 * boundingBox(4) * overlap; + end + end + if isfield(struct_data.svg.BoundingBox, 'Visible') && strcmp(struct_data.svg.BoundingBox.Visible, 'on') + % This functionality is very interesting for debugging of + % bounding boxes of filters + fprintf(fid,'\n', boundingBox(1), boundingBox(2), boundingBox(3), boundingBox(4)); + end + end + if isfield(struct_data.svg,'Filter') + % Predefined filter sources. Additional filter sources will be + % added later. + predefinedSources = {'SourceGraphic','SourceAlpha','BackgroundImage','BackgroundAlpha','FillPaint','StrokePaint'}; + resultStrings = predefinedSources; + filterId = createId; + filterString = ['filter="url(#' filterId ')"']; + fprintf(fid,'\n'); + fprintf(fid,' \n', 0, 0, 100, 100, filterId); + %if absolute + % fprintf(fid,' \n', boundingBox(1), boundingBox(2), boundingBox(3), boundingBox(4), filterId); + %else + % fprintf(fid,' \n', -(offset * 100), -(offset * 100), 100 + (offset * 200), 100 + (offset * 200), filterId); + % % Note: use default -10% for attribute x + % % use default -10% for attribute y + % % use default 120% for attribute width + % % use default 120% for attribute height + %end + filter = struct_data.svg.Filter; + for i = 1:length(filter) + if isfield(filter(i).Subfilter, 'Type') + fprintf(fid,' <%s', filter(i).Subfilter.Type); + else + error(['Missing field ''Type'' for filter.']) + end + try + if isfield(filter(i).Subfilter, 'Position') + printAttributeArray(fid, {'x','y','width','height'}, filter(i).Subfilter, 'Position'); + end + printAttributeString(fid, 'result', filter(i).Subfilter, 'Result'); + % Add result string to the list in order to check the in + % strings. + resultStrings{length(resultStrings) + 1} = filter(i).Subfilter.Result; + % The strmatch below is a very inefficient search (Matlab limitation) + if ~isempty(strmatch(filter(i).Subfilter.Result, predefinedSources)) + error('Usage of a predefined filter source as filter result string is not allowed.'); + end + switch (filter(i).Subfilter.Type) + case 'feGaussianBlur' + printAttributeIn(fid, 'in', filter(i).Subfilter, 'Source', 'SourceGraphic', resultStrings); + printAttributeDouble(fid, 'stdDeviation', filter(i).Subfilter, 'Deviation'); + fprintf(fid,' />\n'); + case 'feImage' + printAttributeString(fid, 'xlink:href', filter(i).Subfilter, 'File'); + printAttributeString(fid, 'preserveAspectRatio', filter(i).Subfilter, 'AspectRatio', 'xMidYMid meet'); + fprintf(fid,' />\n'); + case 'feComposite' + printAttributeIn(fid, 'in', filter(i).Subfilter, 'Source1', 'SourceGraphic', resultStrings); + printAttributeIn(fid, 'in2', filter(i).Subfilter, 'Source2', 'SourceGraphic', resultStrings); + printAttributeList(fid, 'operator', filter(i).Subfilter, 'Operator', {'over','in','out','atop','xor','arithmetic'}, 'over'); % 'over' | 'in' | 'out' | 'atop' | 'xor' | 'arithmetic' + if isfield(filter(i).Subfilter, 'Operator') && strcmp(filter(i).Subfilter.Operator, 'arithmetic') + printAttributeArray(fid, {'k1','k2','k3','k4'}, filter(i).Subfilter, 'k'); + end + fprintf(fid,' />\n'); + case 'feSpecularLighting' + printAttributeDouble(fid, 'specularConstant', filter(i).Subfilter, 'SpecularConstant'); + printAttributeDouble(fid, 'specularExponent', filter(i).Subfilter, 'SpecularExponent'); + printAttributeDouble(fid, 'surfaceScale', filter(i).Subfilter, 'SurfaceScale'); + fprintf(fid,' style="lighting-color:white"'); + printAttributeIn(fid, 'in', filter(i).Subfilter, 'Source', 'SourceGraphic', resultStrings); + fprintf(fid,' >\n'); + if isfield(filter(i).Subfilter, 'LightType') + fprintf(fid,' <%s', filter(i).Subfilter.LightType); + switch filter(i).Subfilter.LightType + case 'feDistantLight' + printAttributeDouble(fid, 'azimuth', filter(i).Subfilter, 'Azimuth'); + printAttributeDouble(fid, 'elevation', filter(i).Subfilter, 'Elevation'); + case 'fePointLight' + printAttributeArray(fid, {'x','y','z'}, filter(i).Subfilter, 'Position'); + case 'feSpotLight' + printAttributeArray(fid, {'x','y','z'}, filter(i).Subfilter, 'Position'); + printAttributeArray(fid, {'pointsAtX','pointsAtY','pointsAtZ'}, filter(i).Subfilter, 'PositionsAt'); + printAttributeDouble(fid, 'specularExponent', filter(i).Subfilter, 'LightSpecularExponent'); + printAttributeDouble(fid, 'limitingConeAngle', filter(i).Subfilter, 'LimitingConeAngle'); + otherwise, error(['Unknown light type ''' filter(i).Subfilter.LightType ''.']); + end + fprintf(fid,' />\n'); + else + error('Missing field ''LightType''.'); + end + fprintf(fid,'\n',filter(i).Subfilter.Type); + case 'feOffset' + printAttributeIn(fid, 'in', filter(i).Subfilter, 'Source', 'SourceGraphic', resultStrings); + printAttributeArray(fid, {'dx','dy'}, filter(i).Subfilter, 'Offset'); + fprintf(fid,' />\n'); + case 'feBlend' + printAttributeIn(fid, 'in', filter(i).Subfilter, 'Source1', 'SourceGraphic', resultStrings); + printAttributeIn(fid, 'in2', filter(i).Subfilter, 'Source2', 'SourceGraphic', resultStrings); + printAttributeList(fid, 'mode', filter(i).Subfilter, 'Mode', {'normal','multiply','screen','darken','lighten'}, 'normal'); % 'normal' | 'multiply' | 'screen' | 'darken' | 'lighten' + fprintf(fid,' />\n'); + case 'feTurbulence' + printAttributeDouble(fid, 'baseFrequency', filter(i).Subfilter, 'BaseFrequency'); + printAttributeDouble(fid, 'numOctaves', filter(i).Subfilter, 'NumOctaves'); + printAttributeDouble(fid, 'seed', filter(i).Subfilter, 'Seed'); + printAttributeList(fid, 'stitchTiles', filter(i).Subfilter, 'StitchTiles', {'stitch','noStitch'}, 'noStitch'); % stitch | noStitch + printAttributeList(fid, 'type', filter(i).Subfilter, 'TurbulenceType', {'fractalNoise','turbulence'}, 'turbulence'); % 'fractalNoise' | 'turbulence' + fprintf(fid,' />\n'); + case 'feColorMatrix' + printAttributeIn(fid, 'in', filter(i).Subfilter, 'Source', 'SourceGraphic', resultStrings); + printAttributeList(fid, 'type', filter(i).Subfilter, 'ColorType', {'matrix','saturate','hueRotate','luminanceToAlpha'}); % 'matrix' | 'saturate' | 'hueRotate' | 'luminanceToAlpha' + if isfield(filter(i).Subfilter, 'ColorType') && strcmp(filter(i).Subfilter.ColorType, 'matrix') + if isfield(filter(i).Subfilter, 'Matrix') && (lenght(filter(i).Subfilter.Matrix) == 20) + fprintf(fid,' values="'); + fprintf(fid,' %0.3f', filter(i).Subfilter.Matrix); + fprintf(fid,'"'); + else + error('Field ''Matrix'' is missing or not a 5x4 matrix.'); + end + end + if isfield(filter(i).Subfilter, 'ColorType') && (strcmp(filter(i).Subfilter.ColorType, 'saturate') || strcmp(filter(i).Subfilter.ColorType, 'hueRotate')) + printAttributeDouble(fid, 'values', filter(i).Subfilter, 'Matrix'); + end + fprintf(fid,' />\n'); + case 'feFlood' + printAttributeColor(fid, 'flood-color', filter(i).Subfilter, 'Color'); + printAttributeDouble(fid, 'flood-opacity', filter(i).Subfilter, 'Opacity'); + fprintf(fid,' />\n'); + case 'feDisplacementMap' + printAttributeIn(fid, 'in', filter(i).Subfilter, 'Source1', 'SourceGraphic', resultStrings); + printAttributeIn(fid, 'in2', filter(i).Subfilter, 'Source2', 'SourceGraphic', resultStrings); + printAttributeDouble(fid, 'scale', filter(i).Subfilter, 'Scale'); + printAttributeList(fid, 'xChannelSelector', filter(i).Subfilter, 'xChannel', {'R','G','B','A'}, 'A'); % 'R' | 'G' | 'B' | 'A' + printAttributeList(fid, 'yChannelSelector', filter(i).Subfilter, 'yChannel', {'R','G','B','A'}, 'A'); % 'R' | 'G' | 'B' | 'A' + fprintf(fid,' />\n'); + case 'feMerge' + printAttributeIn(fid, 'in', filter(i).Subfilter, 'Source1', 'SourceGraphic', resultStrings); + printAttributeIn(fid, 'in2', filter(i).Subfilter, 'Source2', 'SourceGraphic', resultStrings); + printAttributeList(fid, 'mode', filter(i).Subfilter, 'Mode', {'normal','multiply','screen','darken','lighten'}); % 'normal' | 'multiply' | 'screen' | 'darken' | 'lighten' + fprintf(fid,' />\n'); + case 'feMorphology' + printAttributeIn(fid, 'in', filter(i).Subfilter, 'Source', 'SourceGraphic', resultStrings); + printAttributeList(fid, 'operator', filter(i).Subfilter, 'Operator', {'erode','dilate'}); % 'erode' | 'dilate' + printAttributeDouble(fid, 'radius', filter(i).Subfilter, 'Radius'); + fprintf(fid,' />\n'); + case 'feTile' + printAttributeIn(fid, 'in', filter(i).Subfilter, 'Source', 'SourceGraphic', resultStrings); + fprintf(fid,' />\n'); + case 'feDiffuseLighting' + printAttributeIn(fid, 'in', filter(i).Subfilter, 'Source', 'SourceGraphic', resultStrings); + fprintf(fid,' style="lighting-color:white"'); + printAttributeDouble(fid, 'surfaceScale', filter(i).Subfilter, 'SurfaceScale'); + printAttributeDouble(fid, 'diffuseConstant', filter(i).Subfilter, 'DiffuseConstant'); + printAttributeDouble(fid, 'kernelUnitLength', filter(i).Subfilter, 'KernelUnitLength'); + fprintf(fid,' />\n'); + + % case 'feConvolveMatrix' + % + % printAttributeDouble(fid, 'order', filter(i).Subfilter); + % kernelMatrix + % printAttributeDouble(fid, 'divisor', filter(i).Subfilter, 1.0); + % printAttributeDouble(fid, 'bias', filter(i).Subfilter, 0); + % targetX + % targetY + % printAttributeString(fid, 'edgeMode', filter(i).Subfilter, 'EdgeMode', 'duplicate'); + % fprintf(fid,' kernelUnitLength="1 1"'); + % printAttributeString(fid, 'preserveAlpha', filter(i).Subfilter, 'PreserveAlpha', 'true'); + % fprintf(fid,' />\n'); + case {'feComponentTransfer','feConvolveMatrix'} + error('Filter not yet implemented.'); + otherwise + error(['Unknown filter ''' filter(i).Subfilter.Type '''.']); + end + catch ME + errStr = ME.identifier; + if isempty(errStr) + errStr = ME.message; + end + error([errStr ' Error is caused by filter type ''' filter(i).Subfilter.Type '''.']); + end + end + fprintf(fid,' \n'); + fprintf(fid,'\n'); + end + end +end + +function printAttributeArray(fid, names, svgstruct, svgfield) +if isfield(svgstruct, svgfield) + if isnumeric(svgstruct.(svgfield)) + if length(svgstruct.(svgfield)) ~= length(names) + error(['Length mismatch for field ''' svgfield '''.']) + end + for i = 1:length(names) + fprintf(fid,' %s="%0.3f"', names{i}, svgstruct.(svgfield)(i)); + end + else + error(['Field ''' svgfield ''' must be numeric.']); + end +else + if nargin < 5 + error(['Missing field ''' svgfield '''.']) + else + for i = 1:length(names) + fprintf(fid,' %s="%0.3f"', names{i}, default(i)); + end + end +end + +function printAttributeDouble(fid, name, svgstruct, svgfield, default) +if isfield(svgstruct, svgfield) + if isnumeric(svgstruct.(svgfield)) + fprintf(fid,' %s="%0.3f"', name, svgstruct.(svgfield)); + else + error(['Field ''' svgfield ''' must be numeric.']); + end +else + if nargin < 5 + error(['Missing field ''' svgfield '''.']) + else + fprintf(fid,' %s="%0.3f"', name, default); + end +end + +function printAttributeIn(fid, name, svgstruct, svgfield, default, resultStrings) +if isfield(svgstruct, svgfield) + if ischar(svgstruct.(svgfield)) + % The strmatch below is a very inefficient search (Matlab limitation) + if isempty(strmatch(svgstruct.(svgfield), resultStrings)) + error(['The source string ''' svgstruct.(svgfield) ''' was never a result string of a previous filter. Check for correct spelling.']); + else + fprintf(fid,' %s="%s"', name, svgstruct.(svgfield)); + end + else + error(['Field ''' svgfield ''' must be a string.']); + end +else + if nargin < 5 + error(['Missing field ''' svgfield '''.']) + else + fprintf(fid,' %s="%s"', name, default); + end +end + +function printAttributeString(fid, name, svgstruct, svgfield, default) +if isfield(svgstruct, svgfield) + if ischar(svgstruct.(svgfield)) + fprintf(fid,' %s="%s"', name, svgstruct.(svgfield)); + else + error(['Field ''' svgfield ''' must be a string.']); + end +else + if nargin < 5 + error(['Missing field ''' svgfield '''.']) + else + fprintf(fid,' %s="%s"', name, default); + end +end + +function printAttributeList(fid, name, svgstruct, svgfield, list, default) +if isfield(svgstruct, svgfield) + if ischar(svgstruct.(svgfield)) + if isempty(strmatch(svgstruct.(svgfield), list)) + listString = strcat(list, ''' | '''); + listString = [listString{:}]; + error(['Illegal string identifier ''' svgstruct.(svgfield) '''. Must be one out of the list: ''' listString(1:end-4) '.']); + else + fprintf(fid,' %s="%s"', name, svgstruct.(svgfield)); + end + else + error(['Field ''' svgfield ''' must be a string.']); + end +else + if nargin < 6 + error(['Missing field ''' svgfield '''.']) + else + fprintf(fid,' %s="%s"', name, default); + end +end + +function printAttributeColor(fid, name, svgstruct, svgfield, default) +if isfield(svgstruct, svgfield) + if isnumeric(svgstruct.(svgfield)) + if length(svgstruct.(svgfield)) ~= 3 + error(['Color must be a 1x3 vector for field ''' svgfield '''.']) + else + fprintf(fid,' %s="%s"', name, searchcolor(gca, svgstruct.(svgfield))); + end + else + error(['Field ''' svgfield ''' must be a 1x3 vector.']); + end +else + if nargin < 5 + error(['Missing field ''' svgfield '''.']) + else + fprintf(fid,' %s="%s"', name, default); + end +end + + +function frontTicks(fid, grouplabel, axpos, x, y, scolorname, linewidth, tick, index, edge_neighbours, c, valid_ticks, ticklength, tick_ratio, lim, drawBorder) +for k = 1:length(index) + x_tick_end1 = interp1([0 1],[x(index(k)) x(edge_neighbours(index(k),c(1)))],ticklength*tick_ratio(c(3)),'linear','extrap'); + y_tick_end1 = interp1([0 1],[y(index(k)) y(edge_neighbours(index(k),c(1)))],ticklength*tick_ratio(c(3)),'linear','extrap'); + x_tick_end2 = interp1([0 1],[x(edge_neighbours(index(k),c(2))) x(edge_neighbours(edge_neighbours(index(k),c(2)),c(1)))],ticklength*tick_ratio(c(3)),'linear','extrap'); + y_tick_end2 = interp1([0 1],[y(edge_neighbours(index(k),c(2))) y(edge_neighbours(edge_neighbours(index(k),c(2)),c(1)))],ticklength*tick_ratio(c(3)),'linear','extrap'); + xg_line_start = interp1(lim,[x(index(k)) x(edge_neighbours(index(k),c(2)))],tick); + yg_line_start = interp1(lim,[y(index(k)) y(edge_neighbours(index(k),c(2)))],tick); + xg_line_end = interp1(lim,[x_tick_end1 x_tick_end2],tick); + yg_line_end = interp1(lim,[y_tick_end1 y_tick_end2],tick); + for i = valid_ticks + line2svg(fid,grouplabel,axpos,[xg_line_start(i) xg_line_end(i)],[yg_line_start(i) yg_line_end(i)],scolorname,'-',linewidth) + end + if drawBorder + line2svg(fid,grouplabel,axpos,[x(index(k)) x(edge_neighbours(index(k),c(2)))],[y(index(k)) y(edge_neighbours(index(k),c(2)))],scolorname,'-',linewidth) + end +end + +function gridLines(fid, grouplabel, axpos, x, y, scolorname, gridlinestyle, linewidth, axlim, axtick, axindex_inner, corners, c, gridAlpha) +xg_line_start = interp1([axlim(1) axlim(2)],[x(corners(c(1))) x(corners(c(2)))], axtick); +yg_line_start = interp1([axlim(1) axlim(2)],[y(corners(c(1))) y(corners(c(2)))], axtick); +xg_line_end = interp1([axlim(1) axlim(2)],[x(corners(c(3))) x(corners(c(4)))], axtick); +yg_line_end = interp1([axlim(1) axlim(2)],[y(corners(c(3))) y(corners(c(4)))], axtick); +if nargin < 14 || isempty(gridAlpha) + gridAlpha = 1; +end +for i = axindex_inner + line2svg(fid, grouplabel, axpos, [xg_line_start(i) xg_line_end(i)],[yg_line_start(i) yg_line_end(i)], scolorname, gridlinestyle, linewidth, gridAlpha) +end + +function minorGridLines(fid, grouplabel, axpos, x, y, scolorname, minor_gridlinestyle, linewidth, axlim, minor_axtick, corners, c, gridAlpha) +xg_line_start = interp1([axlim(1) axlim(2)],[x(corners(c(1))) x(corners(c(2)))], minor_axtick); +yg_line_start = interp1([axlim(1) axlim(2)],[y(corners(c(1))) y(corners(c(2)))], minor_axtick); +xg_line_end = interp1([axlim(1) axlim(2)],[x(corners(c(3))) x(corners(c(4)))], minor_axtick); +yg_line_end = interp1([axlim(1) axlim(2)],[y(corners(c(3))) y(corners(c(4)))], minor_axtick); +if nargin < 14 || isempty(gridAlpha) + gridAlpha = 1; +end +for i = 1:length(xg_line_start) + line2svg(fid, grouplabel, axpos, [xg_line_start(i) xg_line_end(i)],[yg_line_start(i) yg_line_end(i)], scolorname, minor_gridlinestyle, linewidth, gridAlpha) +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SUBFUNCTIONS %%%%% +% Create axis frame and insert all children of this axis frame +function group=axes2svg(fid,id,ax,group,paperpos) +global colorname +global PLOT2SVG_globals +originalAxesUnits=get(ax,'Units'); +set(ax,'Units','normalized'); +axpos=get(ax,'Position'); +faces = [1 2 4 3; 2 4 8 6; 3 4 8 7; 1 2 6 5; 1 5 7 3; 5 6 8 7]; +% x-y ; y-z ; x-z ; y-z ; x-z ; x-y +corners(:,:,1) = [1 1 2 3 4; 2 1 3 2 4]; +corners(:,:,2) = [2 2 4 6 8; 3 2 6 4 8]; +corners(:,:,3) = [1 3 4 7 8; 3 3 7 4 8]; +corners(:,:,4) = [1 1 2 5 6; 3 1 5 2 6]; +corners(:,:,5) = [2 1 3 5 7; 3 1 5 3 7]; +corners(:,:,6) = [1 5 6 7 8; 2 5 7 6 8]; +edge_neighbours = [2 3 5; 1 4 6; 4 1 7; 3 2 8; 6 7 1; 5 8 2; 8 5 3; 7 6 4]; +edge_opposite = [8 7 6 5 4 3 2 1]; +nomx = [0 1 0 1 0 1 0 1]; +nomy = [0 0 1 1 0 0 1 1]; +nomz = [0 0 0 0 1 1 1 1]; +[projection,edges] = get_projection(ax,id); +x = (edges(1,:)*axpos(3)+axpos(1))*paperpos(3); +y = (1-(edges(2,:)*axpos(4)+axpos(2)))*paperpos(4); +% Depth Sort of view box edges +[edge_z,edge_index]=sort(edges(3,:)); +most_back_edge_index = edge_index(1); +% Back faces are plot box faces that are behind the plot (as seen by the +% view point) +back_faces = find(any(faces == most_back_edge_index,2)); +front_faces = find(all(faces ~= most_back_edge_index,2)); +groupax=group; +axlimx=get(ax,'XLim'); +axlimy=get(ax,'YLim'); +axlimz=get(ax,'ZLim'); +[axinflimx, axinflimy, axinflimz] = AxesChildBounds(ax); +axlimx(isinf(axlimx)) = axinflimx(isinf(axlimx)); +axlimy(isinf(axlimy)) = axinflimy(isinf(axlimy)); +axlimz(isinf(axlimz)) = axinflimz(isinf(axlimz)); +axlimxori=axlimx; +axlimyori=axlimy; +axlimzori=axlimz; +if strcmp(get(ax,'XScale'),'log') + axlimx=log10(axlimx); + axlimx(isinf(axlimx))=0; +end +if strcmp(get(ax,'YScale'),'log') + axlimy=log10(axlimy); + axlimy(isinf(axlimy))=0; +end +if strcmp(get(ax,'ZScale'),'log') + axlimz=log10(axlimz); + axlimz(isinf(axlimz))=0; +end +if strcmp(get(ax,'XDir'),'reverse') + axlimx = fliplr(axlimx); +end +if strcmp(get(ax,'YDir'),'reverse') + axlimy = fliplr(axlimy); +end +if strcmp(get(ax,'ZDir'),'reverse') + axlimz = fliplr(axlimz); +end +axlimori = [axlimxori(1) axlimyori(1) axlimzori(1) axlimxori(2)-axlimxori(1) axlimyori(2)-axlimyori(1) axlimzori(2)-axlimzori(1)]; +fprintf(fid,' \n', createId); +axIdString = createId; +boundingBoxAxes = [min(x) min(y) max(x)-min(x) max(y)-min(y)]; +fprintf(fid,' \n',axIdString); +fprintf(fid,' \n',... + boundingBoxAxes(1), boundingBoxAxes(2), boundingBoxAxes(3), boundingBoxAxes(4)); +fprintf(fid,' \n'); +if strcmp(get(ax,'Visible'),'on') + group=group+1; + grouplabel=group; + axxtick=get(ax,'XTick'); + axytick=get(ax,'YTick'); + axztick=get(ax,'ZTick'); + axlabelx=get(ax,'XTickLabel'); + axlabely=get(ax,'YTickLabel'); + axlabelz=get(ax,'ZTickLabel'); + % Workaround for Octave + if PLOT2SVG_globals.octave + if isempty(axlabelx) + if strcmp(get(ax,'XScale'),'log') + axlabelx = num2str(log10(axxtick)'); + else + axlabelx = num2str(axxtick'); + end + end + if isempty(axlabely) + if strcmp(get(ax,'YScale'),'log') + axlabely = num2str(log10(axytick)'); + else + axlabely = num2str(axytick'); + end + end + if isempty(axlabelz) + if strcmp(get(ax,'ZScale'),'log') + axlabelz = num2str(log10(axztick)'); + else + axlabelz = num2str(axztick'); + end + end + if projection.xyplane + axlabelz = []; + end + end + gridlinestyle=get(ax,'GridLineStyle'); + minor_gridlinestyle=get(ax,'MinorGridLineStyle'); + try % Octave does not have 'TickLength' yet. --Jakob Malm + both_ticklength = get(ax,'TickLength'); + catch + both_ticklength = [ 0.01 0.025 ]; + end + gridBehind = true; % Default setting + try + if strcmp(get(ax, 'Layer'), 'top') && projection.xyplane + gridBehind = false; + end + catch + gridBehind = true; + end + if projection.xyplane + ticklength = both_ticklength(1); + xy_ratio = axpos(3)*paperpos(3)/ (axpos(4)*paperpos(4)); + if xy_ratio < 1 + tick_ratio = [1 1/xy_ratio 1]; + else + tick_ratio = [xy_ratio 1 1]; + end + if strcmp(get(ax,'TickDir'),'out') + label_distance = -(0.02 + ticklength); + else + label_distance = -0.02; + end + else + ticklength = both_ticklength(2); + label_distance = -2*abs(ticklength); + tick_ratio = [1 1 1]; + end + linewidth = get(ax,'LineWidth'); + axxindex=find((axxtick >= axlimori(1)) & (axxtick <= (axlimori(1)+axlimori(4)))); + axyindex=find((axytick >= axlimori(2)) & (axytick <= (axlimori(2)+axlimori(5)))); + axzindex=find((axztick >= axlimori(3)) & (axztick <= (axlimori(3)+axlimori(6)))); + % remove sticks outside of the axes (-1 of legends) + axxtick=axxtick(axxindex); + axytick=axytick(axyindex); + axztick=axztick(axzindex); + if length(axxtick) > 1 + minor_lin_sticks = (0.2:0.2:0.8)*(axxtick(2)-axxtick(1)); + minor_axxtick = []; + for stick = [2*axxtick(1)-axxtick(2) axxtick] + minor_axxtick = [minor_axxtick minor_lin_sticks + stick]; + end + minor_axxtick = minor_axxtick(minor_axxtick > min(axlimx) & minor_axxtick < max(axlimx)); + else + minor_axxtick = []; + end + if length(axytick) > 1 + minor_lin_sticks = (0.2:0.2:0.8)*(axytick(2)-axytick(1)); + minor_axytick = []; + for stick = [2*axytick(1)-axytick(2) axytick] + minor_axytick = [minor_axytick minor_lin_sticks + stick]; + end + minor_axytick = minor_axytick(minor_axytick > min(axlimy) & minor_axytick < max(axlimy)); + else + minor_axytick = []; + end + if length(axztick) > 1 + minor_lin_sticks = (0.2:0.2:0.8)*(axztick(2)-axztick(1)); + minor_axztick = []; + for stick = [2*axztick(1)-axztick(2) axztick] + minor_axztick = [minor_axztick minor_lin_sticks + stick]; + end + minor_axztick = minor_axztick(minor_axztick > min(axlimz) & minor_axztick < max(axlimz)); + else + minor_axztick = []; + end + if strcmp(get(ax,'Box'),'on') + axxindex_inner = find((axxtick > axlimori(1)) & (axxtick < (axlimori(1)+axlimori(4)))); + axyindex_inner = find((axytick > axlimori(2)) & (axytick < (axlimori(2)+axlimori(5)))); + axzindex_inner = find((axztick > axlimori(3)) & (axztick < (axlimori(3)+axlimori(6)))); + else + axxindex_inner = find((axxtick >= axlimori(1)) & (axxtick <= (axlimori(1)+axlimori(4)))); + axyindex_inner = find((axytick >= axlimori(2)) & (axytick <= (axlimori(2)+axlimori(5)))); + axzindex_inner = find((axztick >= axlimori(3)) & (axztick <= (axlimori(3)+axlimori(6)))); + end + minor_log_sticks = log10(0.2:0.1:0.9); + if strcmp(get(ax,'TickDir'),'out') + ticklength=-ticklength; + valid_xsticks = 1:length(axxindex); + valid_ysticks = 1:length(axyindex); + valid_zsticks = 1:length(axzindex); + else + valid_xsticks = axxindex_inner; + valid_ysticks = axyindex_inner; + valid_zsticks = axzindex_inner; + end + if strcmp(get(ax,'XScale'),'log') + axxtick = log10(get(ax,'XTick')); + minor_axxtick = []; + if ~isempty(axxtick) + all_axxtick = axxtick(1):1:(axxtick(end) + 1); + for stick = all_axxtick + minor_axxtick = [minor_axxtick minor_log_sticks + stick]; + end + end + minor_axxtick = minor_axxtick(minor_axxtick > min(axlimx) & minor_axxtick < max(axlimx)); + end + if strcmp(get(ax,'YScale'),'log') + axytick=log10(get(ax,'YTick')); + minor_axytick = []; + if ~isempty(axytick) + all_axytick = axytick(1):1:(axytick(end) + 1); + for stick = all_axytick + minor_axytick = [minor_axytick minor_log_sticks + stick]; + end + end + minor_axytick = minor_axytick(minor_axytick > min(axlimy) & minor_axytick < max(axlimy)); + end + if strcmp(get(ax,'ZScale'),'log') + axztick=log10(get(ax,'ZTick')); + minor_axztick = []; + if ~isempty(axztick) + all_axztick = axztick(1):1:(axztick(end) + 1); + for stick = all_axztick + minor_axztick = [minor_axztick minor_log_sticks + stick]; + end + end + minor_axztick = minor_axztick(minor_axztick > min(axlimz) & minor_axztick < max(axlimz)); + end + % Draw back faces + linewidth=get(ax,'LineWidth'); + if ~strcmp(get(ax,'Color'),'none') + background_color = searchcolor(id,get(ax,'Color')); + background_opacity = 1; + else + background_color = '#000000'; + background_opacity = 0; + end + for p=1:size(back_faces) + patch2svg(fid, group, axpos, x(faces(back_faces(p),:)), y(faces(back_faces(p),:)), background_color, '-', linewidth, 'none', background_opacity, 1.0, true) + end + for pindex = 1:size(back_faces) + p = back_faces(pindex); + for k = 1:size(corners,1) + selectedCorners = squeeze(corners(k,:,p)); + gridAlpha = get(ax, 'GridAlpha'); + minorGridAlpha = get(ax, 'MinorGridAlpha'); + switch corners(k,1,p) + case 1 % x + % Draw x-grid + if strcmp(get(ax, 'GridColorMode'), 'auto') + scolorname = get(ax, 'XColor'); + else + scolorname = get(ax, 'GridColor'); + end + scolorname = searchcolor(id,scolorname); + if strcmp(get(ax,'XGrid'),'on') && gridBehind + if axlimx(1)~=axlimx(2) + gridLines(fid, grouplabel, axpos, x, y, scolorname, gridlinestyle, linewidth, axlimx, axxtick, axxindex_inner, selectedCorners, [2 3 4 5], gridAlpha) + if strcmp(get(ax,'XTickMode'),'auto') && strcmp(get(ax,'XMinorGrid'),'on') && ~isempty(minor_axxtick) + minorGridLines(fid, grouplabel, axpos, x, y, scolorname, minor_gridlinestyle, linewidth, axlimx, minor_axxtick, selectedCorners, [2 3 4 5], minorGridAlpha) + end + end + end + if projection.xyplane == false + if strcmp(get(ax,'Box'),'on') + line2svg(fid,grouplabel,axpos,[x(corners(k,2,p)) x(corners(k,3,p))],[y(corners(k,2,p)) y(corners(k,3,p))],scolorname,'-',linewidth); + line2svg(fid,grouplabel,axpos,[x(corners(k,4,p)) x(corners(k,5,p))],[y(corners(k,4,p)) y(corners(k,5,p))],scolorname,'-',linewidth); + else + if strcmp(get(ax,'XGrid'),'on') + line2svg(fid,grouplabel,axpos,[x(corners(k,2,p)) x(corners(k,3,p))],[y(corners(k,2,p)) y(corners(k,3,p))],scolorname,gridlinestyle,linewidth); + line2svg(fid,grouplabel,axpos,[x(corners(k,4,p)) x(corners(k,5,p))],[y(corners(k,4,p)) y(corners(k,5,p))],scolorname,gridlinestyle,linewidth); + end + end + end + case 2 % y + % Draw y-grid + if strcmp(get(ax, 'GridColorMode'), 'auto') + scolorname = get(ax, 'YColor'); + else + scolorname = get(ax, 'GridColor'); + end + scolorname = searchcolor(id,scolorname); + if strcmp(get(ax,'YGrid'),'on') && gridBehind + if axlimy(1)~=axlimy(2) + gridLines(fid, grouplabel, axpos, x, y, scolorname, gridlinestyle, linewidth, axlimy, axytick, axyindex_inner, selectedCorners, [2 3 4 5], gridAlpha) + if strcmp(get(ax,'YTickMode'),'auto') && strcmp(get(ax,'YMinorGrid'),'on') && ~isempty(minor_axytick) + minorGridLines(fid, grouplabel, axpos, x, y, scolorname, minor_gridlinestyle, linewidth, axlimy, minor_axytick, selectedCorners, [2 3 4 5], minorGridAlpha) + end + end + end + if projection.xyplane == false + if strcmp(get(ax,'Box'),'on') + line2svg(fid,grouplabel,axpos,[x(corners(k,2,p)) x(corners(k,3,p))],[y(corners(k,2,p)) y(corners(k,3,p))],scolorname,'-',linewidth); + line2svg(fid,grouplabel,axpos,[x(corners(k,4,p)) x(corners(k,5,p))],[y(corners(k,4,p)) y(corners(k,5,p))],scolorname,'-',linewidth); + else + if strcmp(get(ax,'YGrid'),'on') + line2svg(fid,grouplabel,axpos,[x(corners(k,2,p)) x(corners(k,3,p))],[y(corners(k,2,p)) y(corners(k,3,p))],scolorname,gridlinestyle,linewidth); + line2svg(fid,grouplabel,axpos,[x(corners(k,4,p)) x(corners(k,5,p))],[y(corners(k,4,p)) y(corners(k,5,p))],scolorname,gridlinestyle,linewidth); + end + end + end + case 3 % z + % Draw z-grid + if strcmp(get(ax, 'GridColorMode'), 'auto') + scolorname = get(ax, 'ZColor'); + else + scolorname = get(ax, 'GridColor'); + end + scolorname = searchcolor(id,scolorname); + if strcmp(get(ax,'ZGrid'),'on') && gridBehind + if axlimz(1)~=axlimz(2) + gridLines(fid, grouplabel, axpos, x, y, scolorname, gridlinestyle, linewidth, axlimz, axztick, axzindex_inner, selectedCorners, [2 3 4 5], gridAlpha) + if strcmp(get(ax,'ZTickMode'),'auto') && strcmp(get(ax,'ZMinorGrid'),'on') && ~isempty(minor_axztick) + minorGridLines(fid, grouplabel, axpos, x, y, scolorname, minor_gridlinestyle, linewidth, axlimz, minor_axztick, selectedCorners, [2 3 4 5], minorGridAlpha) + end + end + end + if projection.xyplane == false + if strcmp(get(ax,'Box'),'on') + line2svg(fid,grouplabel,axpos,[x(corners(k,2,p)) x(corners(k,3,p))],[y(corners(k,2,p)) y(corners(k,3,p))],scolorname,'-',linewidth); + line2svg(fid,grouplabel,axpos,[x(corners(k,4,p)) x(corners(k,5,p))],[y(corners(k,4,p)) y(corners(k,5,p))],scolorname,'-',linewidth); + else + if strcmp(get(ax,'ZGrid'),'on') + line2svg(fid,grouplabel,axpos,[x(corners(k,2,p)) x(corners(k,3,p))],[y(corners(k,2,p)) y(corners(k,3,p))],scolorname,gridlinestyle,linewidth); + line2svg(fid,grouplabel,axpos,[x(corners(k,4,p)) x(corners(k,5,p))],[y(corners(k,4,p)) y(corners(k,5,p))],scolorname,gridlinestyle,linewidth); + end + end + end + end + end + end +end +fprintf(fid,' \n'); +axchild=get(ax,'Children'); +if ~PLOT2SVG_globals.octave +%if ~verLessThan('matlab','8.4.0') + % Matlab h2 engine + axchild = [axchild; ax.Title; ax.XLabel; ax.YLabel; ax.ZLabel]; +end +group = axchild2svg(fid,id,axIdString,ax,group,paperpos,axchild,axpos,groupax,projection,boundingBoxAxes); +fprintf(fid,' \n'); +if strcmp(get(ax,'Visible'),'on') + fprintf(fid,' \n'); + % Search axis for labeling + if projection.xyplane + [x_axis_point_value, x_axis_point_index_top] = min(y); + [x_axis_point_value, x_axis_point_index_bottom] = max(y); + if strcmp(get(ax,'Box'),'on') + if strcmp(get(ax,'XAxisLocation'),'top') + x_axis_point_index = [x_axis_point_index_top x_axis_point_index_bottom]; + else + x_axis_point_index = [x_axis_point_index_bottom x_axis_point_index_top]; + end + else + if strcmp(get(ax,'XAxisLocation'),'top') + x_axis_point_index = x_axis_point_index_top; + else + x_axis_point_index = x_axis_point_index_bottom; + end + end + [y_axis_point_value, y_axis_point_index_left] = min(x); + [y_axis_point_value, y_axis_point_index_right] = max(x); + if strcmp(get(ax,'Box'),'on') + if strcmp(get(ax,'YAxisLocation'),'right') + y_axis_point_index = [y_axis_point_index_right y_axis_point_index_left]; + else + y_axis_point_index = [y_axis_point_index_left y_axis_point_index_right]; + end + else + if strcmp(get(ax,'YAxisLocation'),'right') + y_axis_point_index = y_axis_point_index_right; + else + y_axis_point_index = y_axis_point_index_left; + end + end + [z_axis_point_value, z_axis_point_index] = min(x); + else + [x_axis_point_value, x_axis_point_index] = max(y); + [y_axis_value, y_axis_point_index] = max(y); + [z_axis_point_value, z_axis_point_index] = min(x); + end + % Draw grid + for pindex = 1:size(front_faces) + p = front_faces(pindex); + for k = 1:size(corners,1) + selectedCorners = squeeze(corners(k,:,p)); + switch corners(k,1,p) + case 1 % x + % Draw x-grid + scolorname = searchcolor(id,get(ax,'XColor')); + if strcmp(get(ax,'XGrid'),'on') && gridBehind == false + if axlimx(1)~=axlimx(2) + gridLines(fid, grouplabel, axpos, x, y, scolorname, gridlinestyle, linewidth, axlimx, axxtick, axxindex_inner, selectedCorners, [2 3 4 5]) + if strcmp(get(ax,'XTickMode'),'auto') && strcmp(get(ax,'XMinorGrid'),'on') && ~isempty(minor_axxtick) + minorGridLines(fid, grouplabel, axpos, x, y, scolorname, minor_gridlinestyle, linewidth, axlimx, minor_axxtick, selectedCorners, [2 3 4 5]) + end + end + end + case 2 % y + % Draw y-grid + scolorname = searchcolor(id,get(ax,'YColor')); + if strcmp(get(ax,'YGrid'),'on') && gridBehind == false + if axlimy(1)~=axlimy(2) + gridLines(fid, grouplabel, axpos, x, y, scolorname, gridlinestyle, linewidth, axlimy, axytick, axyindex_inner, selectedCorners, [2 3 4 5]) + if strcmp(get(ax,'YTickMode'),'auto') && strcmp(get(ax,'YMinorGrid'),'on') && ~isempty(minor_axytick) + minorGridLines(fid, grouplabel, axpos, x, y, scolorname, minor_gridlinestyle, linewidth, axlimy, minor_axytick, selectedCorners, [2 3 4 5]) + end + end + end + case 3 % z + % Draw z-grid + scolorname = searchcolor(id,get(ax,'ZColor')); + if strcmp(get(ax,'ZGrid'),'on') && gridBehind == false + if axlimz(1)~=axlimz(2) + gridLines(fid, grouplabel, axpos, x, y, scolorname, gridlinestyle, linewidth, axlimz, axztick, axzindex_inner, selectedCorners, [2 3 4 5]) + if strcmp(get(ax,'ZTickMode'),'auto') && strcmp(get(ax,'ZMinorGrid'),'on') && ~isempty(minor_axztick) + minorGridLines(fid, grouplabel, axpos, x, y, scolorname, minor_gridlinestyle, linewidth, axlimz, minor_axztick, selectedCorners, [2 3 4 5]) + end + end + end + end + end + end + scolorname=searchcolor(id,get(ax,'XColor')); + % Draw 'box' of x-axis + if projection.xyplane == false + if strcmp(get(ax,'Box'),'on') + edge_line_index = [edge_opposite(most_back_edge_index) edge_neighbours(edge_opposite(most_back_edge_index),1)]; + line2svg(fid,grouplabel,axpos,x(edge_line_index),y(edge_line_index),scolorname,'-',linewidth) + end + end + % Draw x-tick labels + if (strcmp(get(ax,'XTickLabelMode'),'auto') && strcmp(get(ax,'XScale'),'log')) + exponent = 1; + else + exponent = 0; + end + % Draw x-tick marks + if (ticklength(1) ~= 0) + if axlimx(1)~=axlimx(2) + if (nomx(x_axis_point_index(1))) + lim = [axlimx(2) axlimx(1)]; + else + lim = [axlimx(1) axlimx(2)]; + end + x_label_end1 = interp1([0 1],[x(x_axis_point_index(1)) x(edge_neighbours(x_axis_point_index(1),2))],label_distance,'linear','extrap'); + y_label_end1 = interp1([0 1],[y(x_axis_point_index(1)) y(edge_neighbours(x_axis_point_index(1),2))],label_distance,'linear','extrap'); + x_label_end2 = interp1([0 1],[x(edge_neighbours(x_axis_point_index(1),1)) x(edge_neighbours(edge_neighbours(x_axis_point_index(1),1),2))],label_distance,'linear','extrap'); + y_label_end2 = interp1([0 1],[y(edge_neighbours(x_axis_point_index(1),1)) y(edge_neighbours(edge_neighbours(x_axis_point_index(1),1),2))],label_distance,'linear','extrap'); + xg_label_end = interp1(lim,[x_label_end1 x_label_end2],axxtick); + yg_label_end = interp1(lim,[y_label_end1 y_label_end2],axxtick); + frontTicks(fid, grouplabel, axpos, x, y, scolorname, linewidth, ... + axxtick, x_axis_point_index, edge_neighbours, [2 1 1], ... + valid_xsticks, ticklength, tick_ratio, lim, true); + if strcmp(get(ax,'XTickMode'),'auto') && (strcmp(get(ax,'XMinorGrid'),'on') || strcmp(get(ax,'XScale'),'log')) && ~isempty(minor_axxtick) + frontTicks(fid, grouplabel, axpos, x, y, scolorname, linewidth, ... + minor_axxtick, x_axis_point_index, edge_neighbours, [2 1 1], ... + 1:length(minor_axxtick), 0.5 * ticklength, tick_ratio, lim, false); + end + if ~isempty(axlabelx) && ~(iscell(axlabelx) && all(cellfun(@isempty,axlabelx))) + if ischar(axlabelx) && size(axlabelx, 1) == 1 + % Special handling of 1xn char arrays -> duplicate data + % for all ticks. Strange behavior but follows the + % behavior of Matlab + axlabelx = repmat(axlabelx, length(axxindex), 1); + end + % Note: 3D plot do not support the property XAxisLocation + % setting 'top'. + [angle, align] = improvedXLabel(ax, 0, 'Center'); + if (strcmp(get(ax,'XTickLabelMode'),'manual')) + axlabelx = axlabelx(axxindex,:); + end + if strcmp(get(ax,'XAxisLocation'),'top') && (projection.xyplane == true) + for i = 1:length(axxindex) + if iscell(axlabelx) + label2svg(fid,grouplabel,axpos,ax,xg_label_end(i),yg_label_end(i),convertString(axlabelx{i}),align,angle,'bottom',1,paperpos,scolorname,exponent); + else + label2svg(fid,grouplabel,axpos,ax,xg_label_end(i),yg_label_end(i),convertString(axlabelx(i,:)),align,angle,'bottom',1,paperpos,scolorname,exponent); + end + end + else + for i = 1:length(axxindex) + if iscell(axlabelx) + label2svg(fid,grouplabel,axpos,ax,xg_label_end(i),yg_label_end(i),convertString(axlabelx{i}),align,angle,'top',1,paperpos,scolorname,exponent); + else + label2svg(fid,grouplabel,axpos,ax,xg_label_end(i),yg_label_end(i),convertString(axlabelx(i,:)),align,angle,'top',1,paperpos,scolorname,exponent); + end + end + end + end + end + end + scolorname=searchcolor(id,get(ax,'YColor')); + % Draw 'box' of y-axis + if projection.xyplane == false + if strcmp(get(ax,'Box'),'on') + edge_line_index = [edge_opposite(most_back_edge_index) edge_neighbours(edge_opposite(most_back_edge_index),2)]; + line2svg(fid,grouplabel,axpos,x(edge_line_index),y(edge_line_index),scolorname,'-',linewidth) + end + end + % Draw y-tick labels + if (strcmp(get(ax,'YTickLabelMode'),'auto') && strcmp(get(ax,'YScale'),'log')) + exponent = 1; + else + exponent = 0; + end + % Draw y-tick marks + if (ticklength(1) ~= 0) + if axlimy(1)~=axlimy(2) + if (nomy(y_axis_point_index(1))) + lim = [axlimy(2) axlimy(1)]; + else + lim = [axlimy(1) axlimy(2)]; + end + x_label_end1 = interp1([0 1],[x(y_axis_point_index(1)) x(edge_neighbours(y_axis_point_index(1),1))],label_distance,'linear','extrap'); + y_label_end1 = interp1([0 1],[y(y_axis_point_index(1)) y(edge_neighbours(y_axis_point_index(1),1))],label_distance,'linear','extrap'); + x_label_end2 = interp1([0 1],[x(edge_neighbours(y_axis_point_index(1),2)) x(edge_neighbours(edge_neighbours(y_axis_point_index(1),2),1))],label_distance,'linear','extrap'); + y_label_end2 = interp1([0 1],[y(edge_neighbours(y_axis_point_index(1),2)) y(edge_neighbours(edge_neighbours(y_axis_point_index(1),2),1))],label_distance,'linear','extrap'); + xg_label_end = interp1(lim,[x_label_end1 x_label_end2],axytick); + yg_label_end = interp1(lim,[y_label_end1 y_label_end2],axytick); + frontTicks(fid, grouplabel, axpos, x, y, scolorname, linewidth, ... + axytick, y_axis_point_index, edge_neighbours, [1 2 2], ... + valid_ysticks, ticklength, tick_ratio, lim, true); + if strcmp(get(ax,'YTickMode'),'auto') && (strcmp(get(ax,'YMinorGrid'),'on') || strcmp(get(ax,'YScale'),'log')) && ~isempty(minor_axytick) + frontTicks(fid, grouplabel, axpos, x, y, scolorname, linewidth, ... + minor_axytick, y_axis_point_index, edge_neighbours, [1 2 2], ... + 1:length(minor_axytick), 0.5 * ticklength, tick_ratio, lim, false); + end + if ~isempty(axlabely) && ~(iscell(axlabely) && all(cellfun(@isempty,axlabely))) + if ischar(axlabely) && size(axlabely, 1) == 1 + % Special handling of 1xn char arrays -> duplicate data + % for all ticks. Strange behavior but follows the + % behavior of Matlab + axlabely = repmat(axlabely, length(axyindex), 1); + end + % Note: 3D plot do not support the property YAxisLocation + % setting 'right'. + if (strcmp(get(ax,'YTickLabelMode'),'manual')) + axlabely = axlabely(axyindex,:); + end + if (projection.xyplane == true) + if strcmp(get(ax,'YAxisLocation'),'right') + [angle, align] = improvedYLabel(ax, 0, 'Left'); + for i = 1:length(axyindex) + if iscell(axlabely) + label2svg(fid,grouplabel,axpos,ax,xg_label_end(i),yg_label_end(i),convertString(axlabely{i}),align,angle,'middle',1,paperpos,scolorname,exponent); + else + label2svg(fid,grouplabel,axpos,ax,xg_label_end(i),yg_label_end(i),convertString(axlabely(i,:)),align,angle,'middle',1,paperpos,scolorname,exponent); + end + end + else + [angle, align] = improvedYLabel(ax, 0, 'Right'); + for i = 1:length(axyindex) + if iscell(axlabely) + label2svg(fid,grouplabel,axpos,ax,xg_label_end(i),yg_label_end(i),convertString(axlabely{i}),align,angle,'middle',1,paperpos,scolorname,exponent); + else + label2svg(fid,grouplabel,axpos,ax,xg_label_end(i),yg_label_end(i),convertString(axlabely(i,:)),align,angle,'middle',1,paperpos,scolorname,exponent); + end + end + end + else + for i = 1:length(axyindex) + label2svg(fid,grouplabel,axpos,ax,xg_label_end(i),yg_label_end(i),convertString(axlabely(i,:)),'Center',0,'top',1,paperpos,scolorname,exponent); + end + end + end + end + end + scolorname=searchcolor(id,get(ax,'ZColor')); + % Draw 'box' of z-axis + if projection.xyplane == false + if strcmp(get(ax,'Box'),'on') + edge_line_index = [edge_opposite(most_back_edge_index) edge_neighbours(edge_opposite(most_back_edge_index),3)]; + line2svg(fid,grouplabel,axpos,x(edge_line_index),y(edge_line_index),scolorname,'-',linewidth) + end + end + if (strcmp(get(ax,'ZTickLabelMode'),'auto') && strcmp(get(ax,'ZScale'),'log')) + exponent = 1; + else + exponent = 0; + end + % Draw z-tick marks + if (ticklength(1) ~= 0) + if axlimz(1)~=axlimz(2) + if (nomz(z_axis_point_index(1))) + lim = [axlimz(2) axlimz(1)]; + else + lim = [axlimz(1) axlimz(2)]; + end + x_tick_end1 = interp1([0 1],[x(z_axis_point_index) x(edge_neighbours(z_axis_point_index,2))],ticklength*tick_ratio(3),'linear','extrap'); + y_tick_end1 = interp1([0 1],[y(z_axis_point_index) y(edge_neighbours(z_axis_point_index,2))],ticklength*tick_ratio(3),'linear','extrap'); + x_tick_end2 = interp1([0 1],[x(edge_neighbours(z_axis_point_index,3)) x(edge_neighbours(edge_neighbours(z_axis_point_index,3),2))],ticklength*tick_ratio(3),'linear','extrap'); + y_tick_end2 = interp1([0 1],[y(edge_neighbours(z_axis_point_index,3)) y(edge_neighbours(edge_neighbours(z_axis_point_index,3),2))],ticklength*tick_ratio(3),'linear','extrap'); + x_label_end1 = interp1([0 1],[x(z_axis_point_index) x(edge_neighbours(z_axis_point_index,2))],label_distance,'linear','extrap'); + y_label_end1 = interp1([0 1],[y(z_axis_point_index) y(edge_neighbours(z_axis_point_index,2))],label_distance,'linear','extrap'); + x_label_end2 = interp1([0 1],[x(edge_neighbours(z_axis_point_index,3)) x(edge_neighbours(edge_neighbours(z_axis_point_index,3),2))],label_distance,'linear','extrap'); + y_label_end2 = interp1([0 1],[y(edge_neighbours(z_axis_point_index,3)) y(edge_neighbours(edge_neighbours(z_axis_point_index,3),2))],label_distance,'linear','extrap'); + xg_line_start = interp1(lim,[x(z_axis_point_index) x(edge_neighbours(z_axis_point_index,3))],axztick); + yg_line_start = interp1(lim,[y(z_axis_point_index) y(edge_neighbours(z_axis_point_index,3))],axztick); + xg_line_end = interp1(lim,[x_tick_end1 x_tick_end2],axztick); + yg_line_end = interp1(lim,[y_tick_end1 y_tick_end2],axztick); + xg_label_end = interp1(lim,[x_label_end1 x_label_end2],axztick); + yg_label_end = interp1(lim,[y_label_end1 y_label_end2],axztick); + for i = valid_zsticks + line2svg(fid,grouplabel,axpos,[xg_line_start(i) xg_line_end(i)],[yg_line_start(i) yg_line_end(i)],scolorname,'-',linewidth) + end + line2svg(fid,grouplabel,axpos,[x(z_axis_point_index) x(edge_neighbours(z_axis_point_index,3))],[y(z_axis_point_index) y(edge_neighbours(z_axis_point_index,3))],scolorname,'-',linewidth) + if ~isempty(axlabelz) && ~(iscell(axlabelz) && all(cellfun(@isempty,axlabelz))) + if ischar(axlabelz) && size(axlabelz, 1) == 1 + % Special handling of 1xn char arrays -> duplicate data + % for all ticks. Strange behavior but follows the + % behavior of Matlab + axlabelz = repmat(axlabelz, length(axzindex), 1); + end + if (strcmp(get(ax,'ZTickLabelMode'),'manual')) + axlabelz = axlabelz(axzindex,:); + end + for i = 1:length(axzindex) + label2svg(fid,grouplabel,axpos,ax,xg_label_end(i),yg_label_end(i),convertString(axlabelz(i,:)),'Right',0,'middle',1,paperpos,scolorname,exponent); + end + end + end + end + exponent2svg(fid,groupax,axpos,paperpos,ax,axxtick,axytick,axztick) + fprintf(fid,' \n'); +end +fprintf(fid,' \n'); +set(ax,'Units',originalAxesUnits); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% take any axis children and create objects for them +function group=axchild2svg(fid,id,axIdString,ax,group,paperpos,axchild,axpos,groupax,projection,boundingBoxAxes) +global colorname +global PLOT2SVG_globals +for i=length(axchild):-1:1 + if strcmp(get(axchild(i), 'Visible'), 'off') + % do nothing + elseif strcmp(get(axchild(i),'Type'),'scatter') + linewidth=get(axchild(i),'LineWidth'); + marker=get(axchild(i),'Marker'); + markeredgecolor=get(axchild(i),'MarkerEdgeColor'); + if ischar(markeredgecolor) + if strcmp(markeredgecolor, 'flat') + markeredgecolorname=searchcolor(id,get(axchild(i), 'CData')); + else + markeredgecolorname=markeredgecolor; + end + else + markeredgecolorname=searchcolor(id,markeredgecolor); + end + markerfacecolor=get(axchild(i),'MarkerFaceColor'); + if ischar(markerfacecolor) + markerfacecolorname=markerfacecolor; + else + markerfacecolorname=searchcolor(id,markerfacecolor); + end + markersize=get(axchild(i),'SizeData'); + switch marker + case 'none'; + case {'.','o','*'},markersize=sqrt(markersize/pi); + otherwise,markersize=sqrt(markersize); + end + linex = get(axchild(i),'XData'); + linex = linex(:)'; % Octave stores the data in a column vector + if strcmp(get(ax,'XScale'),'log') + linex(linex<=0) = NaN; + linex=log10(linex); + end + liney=get(axchild(i),'YData'); + liney = liney(:)'; % Octave stores the data in a column vector + if strcmp(get(ax,'YScale'),'log') + liney(liney<=0) = NaN; + liney=log10(liney); + end + linez=get(axchild(i),'ZData'); + linez = linez(:)'; % Octave stores the data in a column vector + if isempty(linez) + linez = zeros(size(linex)); + end + if strcmp(get(ax,'ZScale'),'log') + linez(linez<=0) = NaN; + linez=log10(linez); + end + [x,y,z] = project(linex,liney,linez,projection); + x = (x*axpos(3)+axpos(1))*paperpos(3); + y = (1-(y*axpos(4)+axpos(2)))*paperpos(4); + markerOverlap = 0; + if ~strcmp(marker, 'none') + markerOverlap = max(markerOverlap, convertunit(markersize, 'points', 'pixels', axpos(4))); + end + boundingBoxElement = [min(x)-markerOverlap min(y)-markerOverlap max(x)-min(x)+2*markerOverlap max(y)-min(y)+2*markerOverlap]; + [filterString, boundingBox] = filter2svg(fid, axchild(i), boundingBoxAxes, boundingBoxElement); + % put a line into a group with its markers + if strcmp(get(axchild(i),'Clipping'),'on') + clippingIdString = clipping2svg(fid, axchild(i), ax, paperpos, axpos, projection, axIdString); + fprintf(fid,'\n', createId, clippingIdString, filterString); + else + fprintf(fid,'\n', createId, filterString); + end + if ~isempty(filterString) + % Workaround for Inkscape filter bug + fprintf(fid,'\n', boundingBox(1), boundingBox(2), boundingBox(3), boundingBox(4)); + end + % put the markers into a subgroup of the lines + if ~strcmp(marker, 'none') % but only do it if we actually are drawing markers + fprintf(fid,'\n'); + switch marker + case 'none'; + case '.',group=group+1;circle2svg(fid,group,axpos,x,y,markersize*0.25,'none',markeredgecolorname,linewidth); + case 'o',group=group+1;circle2svg(fid,group,axpos,x,y,markersize,markeredgecolorname,markerfacecolorname,linewidth); + case '+',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,5)+ones(length(linex),1)*[-1 1 NaN 0 0]*markersize,y'*ones(1,5)+ones(length(liney),1)*[0 0 NaN -1 1]*markersize,markeredgecolorname,'-',linewidth,markeredgecolorname, 1, 1, false); + case '*',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,11)+ones(length(linex),1)*[-1 1 NaN 0 0 NaN -0.7 0.7 NaN -0.7 0.7]*markersize,y'*ones(1,11)+ones(length(liney),1)*[0 0 NaN -1 1 NaN 0.7 -0.7 NaN -0.7 0.7]*markersize,markeredgecolorname,'-',linewidth,markeredgecolorname, 1, 1, false); + case 'x',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,5)+ones(length(linex),1)*[-0.7 0.7 NaN -0.7 0.7]*markersize,y'*ones(1,5)+ones(length(liney),1)*[0.7 -0.7 NaN -0.7 0.7]*markersize,markeredgecolorname,'-',linewidth,markeredgecolorname, 1, 1, false); + %% Octave keeps s, d, p and h in the HandleGraphics object, for the square, diamond, pentagram, and hexagram markers, respectively -- Jakob Malm + case {'square', 's'},group=group+1;patch2svg(fid,group,axpos,x'*ones(1,5)+ones(length(linex),1)*[-1 -1 1 1 -1]*markersize,y'*ones(1,5)+ones(length(liney),1)*[-1 1 1 -1 -1]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case {'diamond', 'd'},group=group+1;patch2svg(fid,group,axpos,x'*ones(1,5)+ones(length(linex),1)*[-0.7071 0 0.7071 0 -0.7071]*markersize,y'*ones(1,5)+ones(length(liney),1)*[0 1 0 -1 0]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case {'pentagram', 'p'},group=group+1;patch2svg(fid,group,axpos,... + x'*ones(1,11)+ones(length(linex),1)*[0 0.1180 0.5 0.1910 0.3090 0 -0.3090 -0.1910 -0.5 -0.1180 0]*1.3*markersize,... + y'*ones(1,11)+ones(length(liney),1)*[-0.5257 -0.1625 -0.1625 0.0621 0.4253 0.2008 0.4253 0.0621 -0.1625 -0.1625 -0.5257]*1.3*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case {'hexagram', 'h'},group=group+1;patch2svg(fid,group,axpos,... + x'*ones(1,13)+ones(length(linex),1)*[0 0.2309 0.6928 0.4619 0.6928 0.2309 0 -0.2309 -0.6928 -0.4619 -0.6928 -0.2309 0]*1*markersize,... + y'*ones(1,13)+ones(length(liney),1)*[0.8 0.4 0.4 0 -0.4 -0.4 -0.8 -0.4 -0.4 0 0.4 0.4 0.8]*1*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case '^',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,4)+ones(length(linex),1)*[-1 1 0 -1]*markersize,y'*ones(1,4)+ones(length(liney),1)*[0.577 0.577 -1.155 0.577]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case 'v',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,4)+ones(length(linex),1)*[-1 1 0 -1]*markersize,y'*ones(1,4)+ones(length(liney),1)*[-0.577 -0.577 1.155 -0.577]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case '<',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,4)+ones(length(linex),1)*[0.577 0.577 -1.155 0.577]*markersize,y'*ones(1,4)+ones(length(liney),1)*[-1 1 0 -1]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case '>',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,4)+ones(length(linex),1)*[-0.577 -0.577 1.155 -0.577]*markersize,y'*ones(1,4)+ones(length(liney),1)*[-1 1 0 -1]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + end + % close the marker group + fprintf(fid,'\n'); + end + % close the scatter group + fprintf(fid,'\n'); + elseif strcmp(get(axchild(i),'Type'),'line') + scolorname=searchcolor(id,get(axchild(i),'Color')); + linestyle=get(axchild(i),'LineStyle'); + linewidth=get(axchild(i),'LineWidth'); + marker=get(axchild(i),'Marker'); + markeredgecolor=get(axchild(i),'MarkerEdgeColor'); + if ischar(markeredgecolor) + switch markeredgecolor + case 'none',markeredgecolorname='none'; + otherwise,markeredgecolorname=scolorname; % if markeredgecolorname is 'auto' or something else set the markeredgecolorname to the line color + end + else + markeredgecolorname=searchcolor(id,markeredgecolor); + end + markerfacecolor=get(axchild(i),'MarkerFaceColor'); + if ischar(markerfacecolor) + switch markerfacecolor + case 'none',markerfacecolorname='none'; + otherwise,markerfacecolorname=scolorname; % if markerfacecolorname is 'auto' or something else set the markerfacecolorname to the line color + end + else + markerfacecolorname=searchcolor(id,markerfacecolor); + end + markersize=get(axchild(i),'MarkerSize')/1.5; + linex = get(axchild(i),'XData'); + linex = linex(:)'; % Octave stores the data in a column vector + if strcmp(get(ax,'XScale'),'log') + linex(linex<=0) = NaN; + linex=log10(linex); + end + liney=get(axchild(i),'YData'); + liney = liney(:)'; % Octave stores the data in a column vector + if strcmp(get(ax,'YScale'),'log') + liney(liney<=0) = NaN; + liney=log10(liney); + end + linez=get(axchild(i),'ZData'); + linez = linez(:)'; % Octave stores the data in a column vector + if isempty(linez) + linez = zeros(size(linex)); + end + if strcmp(get(ax,'ZScale'),'log') + linez(linez<=0) = NaN; + linez=log10(linez); + end + [x,y,z] = project(linex,liney,linez,projection); + x = (x*axpos(3)+axpos(1))*paperpos(3); + y = (1-(y*axpos(4)+axpos(2)))*paperpos(4); + markerOverlap = 0; + if ~strcmp(linestyle, 'none') + markerOverlap = max(markerOverlap, convertunit(linewidth*0.5, 'points', 'pixels', axpos(4))); + end + if ~strcmp(marker, 'none') + markerOverlap = max(markerOverlap, convertunit(markersize, 'points', 'pixels', axpos(4))); + end + boundingBoxElement = [min(x)-markerOverlap min(y)-markerOverlap max(x)-min(x)+2*markerOverlap max(y)-min(y)+2*markerOverlap]; + [filterString, boundingBox] = filter2svg(fid, axchild(i), boundingBoxAxes, boundingBoxElement); + % put a line into a group with its markers + if strcmp(get(axchild(i),'Clipping'),'on') + clippingIdString = clipping2svg(fid, axchild(i), ax, paperpos, axpos, projection, axIdString); + fprintf(fid,'\n', createId, clippingIdString, filterString); + else + fprintf(fid,'\n', createId, filterString); + end + if ~isempty(filterString) + % Workaround for Inkscape filter bug + fprintf(fid,'\n', boundingBox(1), boundingBox(2), boundingBox(3), boundingBox(4)); + end + line2svg(fid,groupax,axpos,x,y,scolorname,linestyle,linewidth) + % put the markers into a subgroup of the lines + if ~strcmp(marker, 'none') % but only do it if we actually are drawing markers + fprintf(fid,'\n'); + switch marker + case 'none'; + case '.',group=group+1;circle2svg(fid,group,axpos,x,y,markersize*0.25,'none',markeredgecolorname,linewidth); + case 'o',group=group+1;circle2svg(fid,group,axpos,x,y,markersize*0.75,markeredgecolorname,markerfacecolorname,linewidth); + case '+',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,5)+ones(length(linex),1)*[-1 1 NaN 0 0]*markersize,y'*ones(1,5)+ones(length(liney),1)*[0 0 NaN -1 1]*markersize,markeredgecolorname,'-',linewidth,markeredgecolorname, 1, 1, false); + case '*',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,11)+ones(length(linex),1)*[-1 1 NaN 0 0 NaN -0.7 0.7 NaN -0.7 0.7]*markersize,y'*ones(1,11)+ones(length(liney),1)*[0 0 NaN -1 1 NaN 0.7 -0.7 NaN -0.7 0.7]*markersize,markeredgecolorname,'-',linewidth,markeredgecolorname, 1, 1, false); + case 'x',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,5)+ones(length(linex),1)*[-0.7 0.7 NaN -0.7 0.7]*markersize,y'*ones(1,5)+ones(length(liney),1)*[0.7 -0.7 NaN -0.7 0.7]*markersize,markeredgecolorname,'-',linewidth,markeredgecolorname, 1, 1, false); + %% Octave keeps s, d, p and h in the HandleGraphics object, for the square, diamond, pentagram, and hexagram markers, respectively -- Jakob Malm + case {'square', 's'},group=group+1;patch2svg(fid,group,axpos,x'*ones(1,5)+ones(length(linex),1)*[-1 -1 1 1 -1]*markersize,y'*ones(1,5)+ones(length(liney),1)*[-1 1 1 -1 -1]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case {'diamond', 'd'},group=group+1;patch2svg(fid,group,axpos,x'*ones(1,5)+ones(length(linex),1)*[-0.7071 0 0.7071 0 -0.7071]*markersize,y'*ones(1,5)+ones(length(liney),1)*[0 1 0 -1 0]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case {'pentagram', 'p'},group=group+1;patch2svg(fid,group,axpos,... + x'*ones(1,11)+ones(length(linex),1)*[0 0.1180 0.5 0.1910 0.3090 0 -0.3090 -0.1910 -0.5 -0.1180 0]*1.3*markersize,... + y'*ones(1,11)+ones(length(liney),1)*[-0.5257 -0.1625 -0.1625 0.0621 0.4253 0.2008 0.4253 0.0621 -0.1625 -0.1625 -0.5257]*1.3*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case {'hexagram', 'h'},group=group+1;patch2svg(fid,group,axpos,... + x'*ones(1,13)+ones(length(linex),1)*[0 0.2309 0.6928 0.4619 0.6928 0.2309 0 -0.2309 -0.6928 -0.4619 -0.6928 -0.2309 0]*1*markersize,... + y'*ones(1,13)+ones(length(liney),1)*[0.8 0.4 0.4 0 -0.4 -0.4 -0.8 -0.4 -0.4 0 0.4 0.4 0.8]*1*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case '^',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,4)+ones(length(linex),1)*[-1 1 0 -1]*markersize,y'*ones(1,4)+ones(length(liney),1)*[0.577 0.577 -1.155 0.577]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case 'v',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,4)+ones(length(linex),1)*[-1 1 0 -1]*markersize,y'*ones(1,4)+ones(length(liney),1)*[-0.577 -0.577 1.155 -0.577]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case '<',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,4)+ones(length(linex),1)*[0.577 0.577 -1.155 0.577]*markersize,y'*ones(1,4)+ones(length(liney),1)*[-1 1 0 -1]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case '>',group=group+1;patch2svg(fid,group,axpos,x'*ones(1,4)+ones(length(linex),1)*[-0.577 -0.577 1.155 -0.577]*markersize,y'*ones(1,4)+ones(length(liney),1)*[-1 1 0 -1]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + end + % close the marker group + fprintf(fid,'\n'); + end + animation2svg(fid, axchild(i)); + % close the line group + fprintf(fid,'\n'); + elseif strcmp(get(axchild(i),'Type'),'contour') + clim = get(ax,'CLim'); + cmap = get(id,'Colormap'); + c = get(axchild(i),'ContourMatrix'); + linestyle = get(axchild(i),'LineStyle'); + linewidth = get(axchild(i),'LineWidth'); + edge_opacity = 1.0; + face_opacity = 1.0; + index = 1; + while index < size(c,2) + patchIndices = (1:c(2,index))+index; + % Close a patch if the coordinates do not contain NaNs + x = c(1,patchIndices); + y = c(2,patchIndices); + if (x(1) == x(end)) && (y(1) == y(end)) + closed = true; + else + closed = false; + end + [x,y,z] = project(x,y,ones(1,c(2,index))*c(1,index),projection); + x = (x*axpos(3)+axpos(1))*paperpos(3); + y = (1-(y*axpos(4)+axpos(2)))*paperpos(4); + pointc = c(1,index); + pointc = round((pointc-clim(1))/(clim(2)-clim(1))*(size(cmap,1)-1)+1); + % Limit index to smallest or biggest color index + pointc = max(pointc,1); + pointc = min(pointc,size(cmap,1)); + if ischar(get(axchild(i),'LineColor')) + if strcmp(get(axchild(i),'LineColor'),'none') + edgecolorname = 'none'; + else + edgecolor = c(1,index); + if ~isnan(edgecolor) + if strcmp(get(axchild(i),'LineColor'),'flat') % Bugfix 27.01.2008 + edgecolorname = searchcolor(id,cmap(pointc,:)); + else + edgecolorname = searchcolor(id,edgecolor); + end + else + edgecolorname = 'none'; + end + end + else + edgecolorname = searchcolor(id,get(axchild(i),'EdgeColor')); + end + if strcmp(get(axchild(i),'Fill'),'on') + facecolorname = searchcolor(id,cmap(pointc,:)); + end + patch2svg(fid, group, axpos, x, y, facecolorname, linestyle, linewidth, edgecolorname, face_opacity, edge_opacity, closed) + index = index+c(2,index)+1; + end + elseif strcmp(get(axchild(i),'Type'),'patch') + flat_shading = 1; + cmap=get(id,'Colormap'); + pointc = get(axchild(i),'FaceVertexCData'); + if isempty(pointc) + % Workaround for octave + pointc=get(axchild(i),'CData'); + end + % Scale color if scaled color mapping is turned on + if strcmp(get(axchild(i),'CDataMapping'),'scaled') + clim=get(ax,'CLim'); + pointc=(pointc-clim(1))/(clim(2)-clim(1))*(size(cmap,1)-1)+1; + end + % Limit index to smallest or biggest color index + if (all(pointc <= 1 & pointc >= 0) && length(pointc)==3) + pointc=searchcolor(id, pointc); + else + pointc=max(pointc,1); + pointc=min(pointc,size(cmap,1)); + end + if ~ischar(get(axchild(i),'FaceAlpha')) + face_opacity = get(axchild(i),'FaceAlpha'); + else + face_opacity = 1.0; + end + if ~ischar(get(axchild(i),'EdgeAlpha')) + edge_opacity = get(axchild(i),'EdgeAlpha'); + else + edge_opacity = 1.0; + end + linestyle = get(axchild(i),'LineStyle'); + linewidth = get(axchild(i),'LineWidth'); + marker = get(axchild(i),'Marker'); + markeredgecolor=get(axchild(i),'MarkerEdgeColor'); + markersize=get(axchild(i),'MarkerSize')/1.5; + points=get(axchild(i),'Vertices')'; + if strcmp(get(ax,'XScale'),'log') + points(1,:)=log10(points(1,:)); + end + if strcmp(get(ax,'YScale'),'log') + points(2,:)=log10(points(2,:)); + end + % TODO LogZ + if size(points,1)==2 + [x,y,z] = project(points(1,:),points(2,:),zeros(size(points(1,:))),projection); + else + [x,y,z] = project(points(1,:),points(2,:),points(3,:),projection); + end + x = (x*axpos(3)+axpos(1))*paperpos(3); + y = (1-(y*axpos(4)+axpos(2)))*paperpos(4); + faces = get(axchild(i),'Faces'); + face_index = 1:size(faces,1); + if size(points,1)==3; + [z,face_index]=sort(sum(z(faces(:,:)),2)); + faces=faces(face_index,:); + end + markerOverlap = 0; + if ~strcmp(linestyle, 'none') + markerOverlap = max(markerOverlap, convertunit(linewidth*0.5, 'points', 'pixels', axpos(4))); + end + if ~strcmp(marker, 'none') + markerOverlap = max(markerOverlap, convertunit(markersize, 'points', 'pixels', axpos(4))); + end + boundingBoxElement = [min(x)-markerOverlap min(y)-markerOverlap max(x)-min(x)+2*markerOverlap max(y)-min(y)+2*markerOverlap]; + [filterString, boundingBox] = filter2svg(fid, axchild(i), boundingBoxAxes, boundingBoxElement); + if strcmp(get(axchild(i),'Clipping'),'on') + clippingIdString = clipping2svg(fid, axchild(i), ax, paperpos, axpos, projection, axIdString); + fprintf(fid,'\n', createId, clippingIdString, filterString); + else + fprintf(fid,'\n', createId, filterString); + end + if ~isempty(filterString) + % Workaround for Inkscape filter bug + fprintf(fid,'\n', boundingBox(1), boundingBox(2), boundingBox(3), boundingBox(4)); + end + for p = 1:size(faces,1) + if ischar(get(axchild(i),'FaceColor')) + if strcmp(get(axchild(i),'FaceColor'),'texturemap') + facecolorname='none'; % TO DO: texture map + elseif strcmp(get(axchild(i),'FaceColor'),'none') + facecolorname='none'; + else + if size(pointc,1)==1 + facecolor = pointc; + elseif size(pointc,1)==size(faces,1) + if strcmp(get(axchild(i),'FaceColor'),'flat') + facecolor = pointc(face_index(p),:); + else + facecolor = pointc(face_index(p),:); + cdata = pointc(face_index(p),:); % TO DO: color interpolation + flat_shading = 0; + end + elseif size(pointc,1)==size(points,2) + if strcmp(get(axchild(i),'FaceColor'),'flat') + facecolor = pointc(faces(p,1),:); + else + facecolor = pointc(faces(p,1)); + cdata = pointc(faces(p,:),:); + flat_shading = 0; + end + else + error('Unsupported color handling for patches.'); + end + if ~isnan(facecolor) + if size(facecolor,2)==1 + facecolorname = ['#' colorname(ceil(facecolor),:)]; + else + if strcmp(get(axchild(i),'FaceColor'),'flat') % Bugfix 27.01.2008 + facecolorname = searchcolor(id,facecolor/64); + else + facecolorname = searchcolor(id,facecolor); + end + end + else + facecolorname='none'; + end + end + else + facecolorname = searchcolor(id,get(axchild(i),'FaceColor')); + end + if ischar(get(axchild(i),'EdgeColor')) + if strcmp(get(axchild(i),'EdgeColor'),'none') + edgecolorname = 'none'; + else + if size(pointc,1)==1 + edgecolor = pointc; + elseif size(pointc,1)==size(faces,1) + edgecolor = pointc(p,:); + elseif size(pointc,1)==size(points,2) + if strcmp(get(axchild(i),'EdgeColor'),'flat') + edgecolor = pointc(faces(p,1)); + else + edgecolor = pointc(faces(p,1)); % TO DO: color interpolation + end + else + error('Unsupported color handling for patches.'); + end + if ~isnan(edgecolor) + if size(edgecolor,2)==1 + edgecolorname = ['#' colorname(ceil(edgecolor),:)]; + else + if strcmp(get(axchild(i),'EdgeColor'),'flat') % Bugfix 27.01.2008 + edgecolorname = searchcolor(id,edgecolor/64); + else + edgecolorname = searchcolor(id,edgecolor); + end + end + else + edgecolorname = 'none'; + end + end + else + edgecolorname = searchcolor(id,get(axchild(i),'EdgeColor')); + end + % Close a patch if the coordinates do not contain NaNs + if any(isnan(x)) || any(isnan(y)) + closed = false; + else + closed = true; + end + if flat_shading + patch2svg(fid, group, axpos, x(faces(p,:)), y(faces(p,:)), facecolorname, linestyle, linewidth, edgecolorname, face_opacity, edge_opacity, closed) + else + gouraud_patch2svg(fid, group, axpos, x(faces(p,:)), y(faces(p,:)), cdata, linestyle, linewidth, edgecolorname, face_opacity, edge_opacity, id) + end + if ~strcmp(marker, 'none') + for q = 1:size(faces,2) + xmarker = x(faces(p,q)); + ymarker = y(faces(p,q)); + % put the markers into a subgroup of the lines + fprintf(fid,'\n'); + if ischar(markeredgecolor) + switch markeredgecolor + case 'none',markeredgecolorname='none'; + otherwise, + % if markeredgecolorname is 'auto' or something + % else set the markeredgecolorname to the line color + markeredgecolorname = selectColor(axchild(i), id, p, q, points, pointc, ... + colorname, faces, 'MarkerEdgeColor'); + end + else + markeredgecolorname=searchcolor(id,markeredgecolor); + end + markerfacecolor=get(axchild(i),'MarkerFaceColor'); + if ischar(markerfacecolor) + switch markerfacecolor + case 'none',markerfacecolorname='none'; + otherwise, + markerfacecolorname = selectColor(axchild(i), id, p, q, points, pointc, ... + colorname, faces,'MarkerFaceColor'); + end + else + markerfacecolorname=searchcolor(id,markerfacecolor); + end + switch marker + case 'none'; + case '.',group=group+1;circle2svg(fid,group,axpos,xmarker,ymarker,markersize*0.25,'none',markeredgecolorname,linewidth); + case 'o',group=group+1;circle2svg(fid,group,axpos,xmarker,ymarker,markersize*0.75,markeredgecolorname,markerfacecolorname,linewidth); + case '+',group=group+1;patch2svg(fid,group,axpos,xmarker'*ones(1,5)+ones(length(linex),1)*[-1 1 NaN 0 0]*markersize,ymarker'*ones(1,5)+ones(length(liney),1)*[0 0 NaN -1 1]*markersize,markeredgecolorname,'-',linewidth,markeredgecolorname, 1, 1, false); + case '*',group=group+1;patch2svg(fid,group,axpos,xmarker'*ones(1,11)+ones(length(linex),1)*[-1 1 NaN 0 0 NaN -0.7 0.7 NaN -0.7 0.7]*markersize,ymarker'*ones(1,11)+ones(length(liney),1)*[0 0 NaN -1 1 NaN 0.7 -0.7 NaN -0.7 0.7]*markersize,markeredgecolorname,'-',linewidth,markeredgecolorname, 1, 1, false); + case 'x',group=group+1;patch2svg(fid,group,axpos,xmarker'*ones(1,5)+ones(length(linex),1)*[-0.7 0.7 NaN -0.7 0.7]*markersize,ymarker'*ones(1,5)+ones(length(liney),1)*[0.7 -0.7 NaN -0.7 0.7]*markersize,markeredgecolorname,'-',linewidth,markeredgecolorname, 1, 1, false); + %% Octave keeps s, d, p and h in the HandleGraphics object, for the square, diamond, pentagram, and hexagram markers, respectively -- Jakob Malm + case {'square', 's'},group=group+1;patch2svg(fid,group,axpos,xmarker'*ones(1,5)+ones(length(linex),1)*[-1 -1 1 1 -1]*markersize,ymarker'*ones(1,5)+ones(length(liney),1)*[-1 1 1 -1 -1]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case {'diamond', 'd'},group=group+1;patch2svg(fid,group,axpos,xmarker'*ones(1,5)+ones(length(linex),1)*[-0.7071 0 0.7071 0 -0.7071]*markersize,ymarker'*ones(1,5)+ones(length(liney),1)*[0 1 0 -1 0]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case {'pentagram', 'p'},group=group+1;patch2svg(fid,group,axpos,... + xmarker'*ones(1,11)+ones(length(linex),1)*[0 0.1180 0.5 0.1910 0.3090 0 -0.3090 -0.1910 -0.5 -0.1180 0]*1.3*markersize,... + ymarker'*ones(1,11)+ones(length(liney),1)*[-0.5257 -0.1625 -0.1625 0.0621 0.4253 0.2008 0.4253 0.0621 -0.1625 -0.1625 -0.5257]*1.3*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case {'hexagram', 'h'},group=group+1;patch2svg(fid,group,axpos,... + xmarker'*ones(1,13)+ones(length(linex),1)*[0 0.2309 0.6928 0.4619 0.6928 0.2309 0 -0.2309 -0.6928 -0.4619 -0.6928 -0.2309 0]*1*markersize,... + ymarker'*ones(1,13)+ones(length(liney),1)*[0.8 0.4 0.4 0 -0.4 -0.4 -0.8 -0.4 -0.4 0 0.4 0.4 0.8]*1*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case '^',group=group+1;patch2svg(fid,group,axpos,xmarker'*ones(1,4)+ones(length(linex),1)*[-1 1 0 -1]*markersize,ymarker'*ones(1,4)+ones(length(liney),1)*[0.577 0.577 -1.155 0.577]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case 'v',group=group+1;patch2svg(fid,group,axpos,xmarker'*ones(1,4)+ones(length(linex),1)*[-1 1 0 -1]*markersize,ymarker'*ones(1,4)+ones(length(liney),1)*[-0.577 -0.577 1.155 -0.577]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case '<',group=group+1;patch2svg(fid,group,axpos,xmarker'*ones(1,4)+ones(length(linex),1)*[0.577 0.577 -1.155 0.577]*markersize,ymarker'*ones(1,4)+ones(length(liney),1)*[-1 1 0 -1]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + case '>',group=group+1;patch2svg(fid,group,axpos,xmarker'*ones(1,4)+ones(length(linex),1)*[-0.577 -0.577 1.155 -0.577]*markersize,ymarker'*ones(1,4)+ones(length(liney),1)*[-1 1 0 -1]*markersize,markerfacecolorname,'-',linewidth,markeredgecolorname, 1, 1, true); + end + % close the marker group + fprintf(fid,'\n'); + end + end + end + fprintf(fid,'\n'); + elseif strcmp(get(axchild(i),'Type'),'surface') + flat_shading = 1; + cmap=get(id,'Colormap'); + [faces,points,pointc,alpha]=surface2patch(axchild(i)); + points=points'; + % Scale color if scaled color mapping is turned on + if strcmp(get(axchild(i),'CDataMapping'),'scaled') + clim=get(ax,'CLim'); + pointc=(pointc-clim(1))/(clim(2)-clim(1))*(size(cmap,1)-1)+1; + end + % Limit index to smallest or biggest color index + pointc=max(pointc,1); + if ~ischar(get(axchild(i),'FaceAlpha')) + face_opacity = get(axchild(i),'FaceAlpha'); + elseif strcmp(get(axchild(i),'FaceAlpha'),'flat') + face_opacity = alpha; + switch get(axchild(i),'AlphaDataMapping') + case {'direct'} + face_opacity = 1.0; % TODO + case {'scaled'} + alim=get(ax,'ALim'); + face_opacity=(face_opacity-alim(1))/(alim(2)-alim(1)); + case {'none'} + % Clip alpha data + face_opacity = min(1, face_opacity); + face_opacity = max(0, face_opacity); + otherwise + error(['Unsupported AlphaDataMapping identifier ''' get(axchild(i),'AlphaDataMapping') '''.']); + end + else + face_opacity = 1.0; + end + if ~ischar(get(axchild(i),'EdgeAlpha')) + edge_opacity = get(axchild(i),'EdgeAlpha'); + else + edge_opacity = 1.0; + end + pointc=min(pointc,size(cmap,1)); + linestyle=get(axchild(i),'LineStyle'); + linewidth=get(axchild(i),'LineWidth'); + if strcmp(get(ax,'XScale'),'log') + points(1,:)=log10(points(1,:)); + end + if strcmp(get(ax,'YScale'),'log') + points(2,:)=log10(points(2,:)); + end + if size(points,1)==3 + if strcmp(get(ax,'ZScale'),'log') + points(3,:)=log10(points(3,:)); + end + end + if size(points,1)==3 + [x,y,z] = project(points(1,:),points(2,:),points(3,:),projection); + else + [x,y,z] = project(points(1,:),points(2,:),zeros(size(points(1,:))),projection); + end + x = (x*axpos(3)+axpos(1))*paperpos(3); + y = (1-(y*axpos(4)+axpos(2)))*paperpos(4); + face_index = 1:size(faces,1); + if size(points,1)==3; + [z,face_index]=sort(sum(z(faces(:,:)),2)); + faces=faces(face_index,:); + end + markerOverlap = 0; + if ~strcmp(linestyle, 'none') + markerOverlap = max(markerOverlap, convertunit(linewidth*0.5, 'points', 'pixels', axpos(4))); + end + boundingBoxElement = [min(x)-markerOverlap min(y)-markerOverlap max(x)-min(x)+2*markerOverlap max(y)-min(y)+2*markerOverlap]; + [filterString, boundingBox] = filter2svg(fid, axchild(i), boundingBoxAxes, boundingBoxElement); + if strcmp(get(axchild(i),'Clipping'),'on') + clippingIdString = clipping2svg(fid, axchild(i), ax, paperpos, axpos, projection, axIdString); + fprintf(fid,'\n',clippingIdString, createId, filterString); + else + fprintf(fid,'\n', createId, filterString); + end + if ~isempty(filterString) + % Workaround for Inkscape filter bug + fprintf(fid,'\n', boundingBox(1), boundingBox(2), boundingBox(3), boundingBox(4)); + end + for p=1:size(faces,1) + if ischar(get(axchild(i),'FaceColor')) + if strcmp(get(axchild(i),'FaceColor'),'texturemap') + facecolorname='none'; % TO DO: texture map + elseif strcmp(get(axchild(i),'FaceColor'),'none') + facecolorname='none'; + else + if size(pointc,1)==1 + facecolor = pointc; + elseif size(pointc,1)==size(faces,1) + facecolor = pointc(face_index(p),:); + elseif size(pointc,1)==size(points,2) + if strcmp(get(axchild(i),'FaceColor'),'flat') + facecolor = pointc(faces(p,1)); + else + facecolor = pointc(faces(p,1)); + cdata = pointc(faces(p,:)); + flat_shading = 0; + end + else + error('Unsupported color handling for patches.'); + end + if ~isnan(facecolor) + if size(facecolor,2)==1 + facecolorname = ['#' colorname(ceil(facecolor),:)]; + else + facecolorname = searchcolor(id,facecolor); + end + else + facecolorname='none'; + end + end + else + facecolorname = searchcolor(id,get(axchild(i),'FaceColor')); + end + if size(face_opacity,1)==1 + face_opacity_value = face_opacity; + elseif size(face_opacity,1)==size(faces,1) + face_opacity_value = face_opacity(p,:); + elseif size(face_opacity,1)==size(points,2) + face_opacity_value = face_opacity(faces(p,1)); + else + error('Unsupported face alpha value handling for patches.'); + end + if ischar(get(axchild(i),'EdgeColor')) + if strcmp(get(axchild(i),'EdgeColor'),'none') + edgecolorname = 'none'; + else + if size(pointc,1)==1 + edgecolor = pointc; + elseif size(pointc,1)==size(faces,1) + edgecolor = pointc(p,:); + elseif size(pointc,1)==size(points,2) + if strcmp(get(axchild(i),'EdgeColor'),'flat') + edgecolor = pointc(faces(p,1)); + else + edgecolor = pointc(faces(p,1)); % TO DO: color interpolation + end + else + error('Unsupported color handling for patches.'); + end + if ~isnan(edgecolor) + if size(edgecolor,2)==1 + edgecolorname = ['#' colorname(ceil(edgecolor),:)]; + else + edgecolorname = searchcolor(id,edgecolor); + end + else + edgecolorname = 'none'; + end + end + else + edgecolorname = searchcolor(id,get(axchild(i),'EdgeColor')); + end + if flat_shading + patch2svg(fid, group, axpos, x(faces(p,:)), y(faces(p,:)), facecolorname, linestyle, linewidth, edgecolorname, face_opacity_value, edge_opacity, false) + else + gouraud_patch2svg(fid, group, axpos, x(faces(p,:)), y(faces(p,:)), cdata, linestyle, linewidth, edgecolorname, face_opacity_value, edge_opacity,id) + end + end + fprintf(fid,'\n'); + elseif strcmp(get(axchild(i),'Type'),'rectangle') + scolorname = searchcolor(id,get(axchild(i),'EdgeColor')); + fcolorname = searchcolor(id,get(axchild(i),'FaceColor')); + linewidth = get(axchild(i),'LineWidth'); + position = get(axchild(i),'Position'); + posx = [position(1) position(1)+position(3)]; + if strcmp(get(ax,'XScale'),'log') + posx(posx <= 0) = NaN; + posx=log10(posx); + end + posy = [position(2) position(2)+position(4)]; + if strcmp(get(ax,'YScale'),'log') + posy(posy <= 0) = NaN; + posy=log10(posy); + end + posz=[0 0]; + linestyle = get(axchild(i),'LineStyle'); + if strcmp(linestyle,'none'); + scolorname = 'none'; + end + pattern = lineStyle2svg(linestyle, linewidth); + [x,y,z] = project(posx,posy,posz,projection); + x = (x*axpos(3)+axpos(1))*paperpos(3); + y = (1-(y*axpos(4)+axpos(2)))*paperpos(4); + rect = [min(x) min(y) max(x)-min(x) max(y)-min(y)]; + curvature = get(axchild(i),'Curvature'); + curvature(1) = curvature(1)*rect(3)*0.5; + curvature(2) = curvature(2)*rect(4)*0.5; + markerOverlap = 0; + if ~strcmp(linestyle, 'none') + markerOverlap = max(markerOverlap, convertunit(linewidth*0.5, 'points', 'pixels', axpos(4))); + end + boundingBoxElement = rect + [-markerOverlap -markerOverlap 2*markerOverlap 2*markerOverlap]; + [filterString, boundingBox] = filter2svg(fid, axchild(i), boundingBoxAxes, boundingBoxElement); + % put a rectangle into a group with its markers + if strcmp(get(axchild(i),'Clipping'),'on') + clippingIdString = clipping2svg(fid, axchild(i), ax, paperpos, axpos, projection, axIdString); + fprintf(fid,'\n', createId, clippingIdString, filterString); + else + fprintf(fid,'\n', createId, filterString); + end + if ~isempty(filterString) + % Workaround for Inkscape filter bug + fprintf(fid,'\n', boundingBox(1), boundingBox(2), boundingBox(3), boundingBox(4)); + end + fprintf(fid,'\n',... + rect(1), rect(2), rect(3), rect(4), curvature(1), curvature(2), fcolorname, scolorname, linewidth, pattern); + % close the rectangle group + fprintf(fid,'\n'); + elseif strcmp(get(axchild(i),'Type'),'text') + if PLOT2SVG_globals.octave + extent = [0 0 0 0]; + else + extent = get(axchild(i),'Extent'); + end + margin = get(axchild(i),'Margin'); + facecolor = get(axchild(i),'BackgroundColor'); + edgecolor = get(axchild(i),'EdgeColor'); + linewidth = get(axchild(i),'LineWidth'); + linestyle = get(axchild(i),'LineStyle'); + if ischar(facecolor) + if ~strcmp(facecolor,'none') + error('Illegal face color for text.'); + else + facecolorname = 'none'; + end + else + facecolorname = searchcolor(id, facecolor); + end + if ischar(edgecolor) + if ~strcmp(edgecolor,'none') + error('Illegal edge color for text.'); + else + edgecolorname = 'none'; + end + else + edgecolorname = searchcolor(id, edgecolor); + end + extentx = [extent(1) extent(1)+extent(3)]; + extenty = [extent(2) extent(2)+extent(4)]; + extentz = [0 0]; + [x,y,z] = project(extentx,extenty,extentz,projection); + x = (x*axpos(3)+axpos(1))*paperpos(3); + y = (1-(y*axpos(4)+axpos(2)))*paperpos(4); + box = [min(x)-margin min(y)-margin max(x)-min(x)+2*margin max(y)-min(y)+2*margin]; + markerOverlap = 0; + if ~strcmp(linestyle, 'none') + markerOverlap = max(markerOverlap, convertunit(linewidth*0.5, 'points', 'pixels', axpos(4))); + end + boundingBoxElement = [min(x)-markerOverlap min(y)-markerOverlap max(x)-min(x)+2*markerOverlap max(y)-min(y)+2*markerOverlap]; + [filterString, boundingBox] = filter2svg(fid, axchild(i), boundingBoxAxes, boundingBoxElement); + if strcmp(get(axchild(i),'Clipping'),'on') && ~PLOT2SVG_globals.octave + clippingIdString = clipping2svg(fid, axchild(i), ax, paperpos, axpos, projection, axIdString); + fprintf(fid,'\n', createId, clippingIdString, filterString); + else + fprintf(fid,'\n', createId, filterString); + end + if ~isempty(filterString) + % Workaround for Inkscape filter bug + fprintf(fid,'\n', boundingBox(1), boundingBox(2), boundingBox(3), boundingBox(4)); + end + if ~strcmp(edgecolorname, 'none') || ~strcmp(facecolorname, 'none') + pattern = lineStyle2svg(linestyle, linewidth); + fprintf(fid,'\n', ... + box(1), box(2), box(3), box(4), facecolorname, edgecolorname, linewidth, pattern); + end + text2svg(fid,1,axpos,paperpos,axchild(i),ax,projection) + fprintf(fid,'\n'); + elseif strcmp(get(axchild(i),'Type'),'image') + cmap=get(id,'Colormap'); + pointx=get(axchild(i),'XData'); + pointy=get(axchild(i),'YData'); + % If the XData is a vector we only use start and stop for the image + if (size(pointx, 1) > 2) || (size(pointx, 2) > 1) + pointx = [pointx(1) pointx(end)]; + end + if (size(pointy, 1) > 2) || (size(pointy, 2) > 1) + pointy = [pointy(1) pointy(end)]; + end + if (size(pointx, 1) > 1) && (size(pointy, 1) > 1) + [x,y,z] = project(pointx,zeros(size(pointx)),zeros(size(pointx)),projection); + else + [x,y_dummy,z] = project(pointx,zeros(size(pointx)),zeros(size(pointx)),projection); + [x_dummy,y,z] = project(zeros(size(pointy)),pointy,zeros(size(pointy)),projection); + end + pointc=get(axchild(i),'CData'); + %pointcclass = class(pointc); % Bugfix proposed by Tom + if strcmp(get(axchild(i),'CDataMapping'),'scaled') + clim=get(ax,'CLim'); + %pointc=(double(pointc)-clim(1))/(clim(2)-clim(1))*(size(cmap,1) - 1) + 1; % Bugfix proposed by Tom + %pointcclass = 'double'; % since range is now [0->size(cmap,1)-1] % Bugfix proposed by Tom + pointc=imnorm(pointc, clim(1), clim(2)); + end + data_aspect_ratio = get(ax,'DataAspectRatio'); + if length(x) == 2 + if size(pointc, 2) == 1 + halfwidthx = abs(x(2) - x(1)) * data_aspect_ratio(1); + else + halfwidthx = abs(x(2) - x(1))/(size(pointc,2) - 1); + end + else + halfwidthx = data_aspect_ratio(1); + end + if length(y) == 2 + if size(pointc, 1) == 1 + halfwidthy = abs(y(2) - y(1)) * data_aspect_ratio(2); + else + halfwidthy = abs(y(2) - y(1))/(size(pointc,1) - 1); + end + else + halfwidthy = data_aspect_ratio(2); + end + if length(pointx) > 1 + if xor(strcmp(get(ax,'XDir'),'reverse'), pointx(1) > pointx(2)) + if ndims(pointc) < 3 + pointc=fliplr(pointc); + elseif ndims(pointc) == 3 + for j = 1:size(pointc,3) + pointc(:,:,j)=fliplr(pointc(:,:,j)); + end + else + error('Invalid number of dimensions of data.'); + end + end + end + if length(pointy) > 1 + if xor(strcmp(get(ax,'YDir'),'reverse'), pointy(1) > pointy(2)) + if ndims(pointc) < 3 + pointc=flipud(pointc); + elseif ndims(pointc) == 3 + for j = [1:size(pointc,3)] + pointc(:,:,j)=flipud(pointc(:,:,j)); + end + else + error('Invalid number of dimensions of data.'); + end + end + end + % pointc = cast(pointc,pointcclass); % Bugfix proposed by Tom + % Function 'cast' is not supported by old Matlab versions + %if (~isa(pointc, 'double') && ~isa(pointc, 'single')) + % if strcmp(get(axchild(i),'CDataMapping'),'scaled') + % pointc = double(pointc); + % else + % pointc = double(pointc) + 1; + % end + %end + %if ndims(pointc) ~= 3 + % pointc = max(min(round(double(pointc)),size(cmap,1)),1); + %end + CameraUpVector = get(ax,'CameraUpVector'); + filename = [PLOT2SVG_globals.basefilename sprintf('%03d',PLOT2SVG_globals.figurenumber) '.' PLOT2SVG_globals.pixelfiletype]; + PLOT2SVG_globals.figurenumber = PLOT2SVG_globals.figurenumber + 1; + %if isempty(PLOT2SVG_globals.basefilepath) + if isempty(PLOT2SVG_globals.basedirname) + current_path = pwd; + rel_path = pwd; + else + %current_path = PLOT2SVG_globals.basefilepath; + current_path = PLOT2SVG_globals.basedirname; + rel_path = PLOT2SVG_globals.reldirname; + end + relname = fullfile(rel_path,filename); + filename = fullfile(current_path,filename); + if exist(filename,'file') + lastwarn(''); + delete(filename); + if strcmp(lastwarn,'File not found or permission denied.') + error('Cannot write image file. Make sure that no image is opened in an other program.') + end + end + if ndims(pointc) < 3 + pointc = flipud(pointc); + elseif ndims(pointc) == 3 + for j = 1:size(pointc,3) + pointc(:,:,j)=flipud(pointc(:,:,j)); + end + else + error('Invalid number of dimensions of data.'); + end + if ndims(pointc) == 3 + % pointc is not indexed + %imwrite(pointc,fullfile(PLOT2SVG_globals.basefilepath,filename),PLOT2SVG_globals.pixelfiletype); + imwrite(pointc,filename,PLOT2SVG_globals.pixelfiletype); + PLOT2SVG_globals.used_dir=true; + else + % pointc is probably indexed + pointc = max(min(round(double(pointc)),size(cmap,1)),1); + %if PLOT2SVG_globals.octave + % pointc = max(2, pointc); + %end + %imwrite(pointc,cmap,fullfile(PLOT2SVG_globals.basefilepath,filename),PLOT2SVG_globals.pixelfiletype); + ncmap = size(cmap,1); + if (ncmap > 256) + cmap = interp1(([0:ncmap-1].')*(255/(ncmap-1)),cmap,[0:255].'); + pointc = fix((pointc-1) * (255/(ncmap-1)))+1; + end + imwrite(pointc,cmap,filename,PLOT2SVG_globals.pixelfiletype); + PLOT2SVG_globals.used_dir=true; + end + + lx=(size(pointc,2)*halfwidthx)*axpos(3)*paperpos(3); + ly=(size(pointc,1)*halfwidthy)*axpos(4)*paperpos(4); + if strcmp(get(ax,'DataAspectRatioMode'),'manual') + pointsx=((min(x) - halfwidthx/2)*axpos(3)+axpos(1))*paperpos(3); + pointsy=(1-((max(y) + halfwidthy/2)*axpos(4)+axpos(2)))*paperpos(4); + else + pointsx=axpos(1)*paperpos(3); + pointsy=(1-(axpos(4)+axpos(2)))*paperpos(4); + end + [filterString, boundingBox] = filter2svg(fid, axchild(i), boundingBoxAxes, boundingBoxAxes); + if strcmp(get(axchild(i),'Clipping'),'on') + clippingIdString = clipping2svg(fid, axchild(i), ax, paperpos, axpos, projection, axIdString); + fprintf(fid,'\n', createId, clippingIdString, filterString); + if ~isempty(filterString) + % Workaround for Inkscape filter bug + fprintf(fid,'\n', boundingBox(1), boundingBox(2), boundingBox(3), boundingBox(4)); + end + fprintf(fid,'\n', pointsx, pointsy, lx, ly, relname); + fprintf(fid,'\n'); + else + fprintf(fid,'\n', createId, filterString); + if ~isempty(filterString) + % Workaround for Inkscape filter bug + fprintf(fid,'\n', boundingBox(1), boundingBox(2), boundingBox(3), boundingBox(4)); + end + fprintf(fid,'\n', pointsx, pointsy, lx, ly, relname); + fprintf(fid,'\n'); + end + elseif strcmp(get(axchild(i),'Type'), 'hggroup') + % handle group types (like error bars) + % FIXME: they are not yet perfectly handled, there are more options + % that are not used + [filterString, boundingBox] = filter2svg(fid, axchild(i), boundingBoxAxes, boundingBoxAxes); + if strcmp(get(axchild(i),'Clipping'),'on') + clippingIdString = clipping2svg(fid, axchild(i), ax, paperpos, axpos, projection, axIdString); + fprintf(fid,'\n', createId, clippingIdString, filterString); + else + fprintf(fid, '', createId, filterString); + end + if ~isempty(filterString) + % Workaround for Inkscape filter bug + fprintf(fid,'\n', boundingBox(1), boundingBox(2), boundingBox(3), boundingBox(4)); + end + group=axchild2svg(fid,id,axIdString,ax,group,paperpos,get(axchild(i), 'Children'),axpos,groupax,projection,boundingBoxAxes); + fprintf(fid, ''); + elseif strcmp(get(axchild(i),'Type'), 'hgtransform') + if strcmpi(get(axchild(i), 'Visible'), 'on') + [filterString, boundingBox] = filter2svg(fid, axchild(i), boundingBoxAxes, boundingBoxAxes); + if strcmp(get(axchild(i),'Clipping'),'on') + clippingIdString = clipping2svg(fid, axchild(i), ax, paperpos, axpos, projection, axIdString); + fprintf(fid,'\n', createId, clippingIdString, filterString); + else + fprintf(fid, '', createId, filterString); + end + if ~isempty(filterString) + % Workaround for Inkscape filter bug + fprintf(fid,'\n', boundingBox(1), boundingBox(2), boundingBox(3), boundingBox(4)); + end + group=axchild2svg(fid,id,axIdString,ax,group,paperpos,get(axchild(i), 'Children'),axpos,groupax,projection,boundingBoxAxes); + fprintf(fid, ''); + end + else + disp([' Warning: Unhandled child type: ' get(axchild(i),'Type')]); + end +end + +function result = selectColor(axchild, id, p, q, points, pointc, colorname, faces, type) +if (ischar(pointc)) + result = pointc; + return; +end +if size(pointc, 1) == 1 + color = pointc; +elseif size(pointc, 1) == size(faces, 1) + color = pointc(p,:); +elseif size(pointc, 1) == size(points, 2) + if strcmp(get(axchild, type), 'flat') + color = pointc(faces(p, q)); + else + color = pointc(faces(p, q)); % TO DO: color interpolation + end +else + error('Unsupported color handling for patches.'); +end +if ~isnan(color) + if size(color, 2) == 1 + result = ['#' colorname(ceil(color), :)]; + else + if strcmp(get(axchild, type), 'flat') % Bugfix 27.01.2008 + result = searchcolor(id, color / 64); + else + result = searchcolor(id, color); + end + end +else + result = 'none'; +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% create a patch (filled area) +function patch2svg(fid,group,axpos,xtot,ytot,scolorname,style,width, edgecolorname, face_opacity, edge_opacity, closed) +if closed + type = 'polygon'; +else + type = 'polyline'; +end +pattern = lineStyle2svg(style, width); +if strcmp(style, 'none') + edge_opacity = 0.0; +end +for i = 1:size(xtot, 1) + x = xtot(i, :); + y = ytot(i, :); + if all(~isnan(x)) && all(~isnan(y)) + for j = 1:20000:length(x) + xx = x(j:min(length(x), j + 19999)); + yy = y(j:min(length(y), j + 19999)); + if ~strcmp(edgecolorname,'none') || ~strcmp(scolorname,'none') + fprintf(fid,' <%s fill="%s" fill-opacity="%0.2f" stroke="%s" stroke-width="%0.1fpt" stroke-opacity="%0.2f" %s points="',... + type, scolorname, face_opacity, edgecolorname, width, edge_opacity, pattern); + fprintf(fid,'%0.3f,%0.3f ',[xx;yy]); + fprintf(fid,'"/>\n'); + end + end + else + parts = find(isnan(x) + isnan(y)); + if ~isempty(parts) && (parts(1) ~= 1) + parts = [0 parts]; + end + if parts(length(parts)) ~= length(x) + parts = [parts length(x) + 1]; + end + for j = 1:(length(parts) - 1) + xx = x((parts(j)+1):(parts(j+1)-1)); + yy = y((parts(j)+1):(parts(j+1)-1)); + if ~strcmp(edgecolorname,'none') || ~strcmp(scolorname,'none') + if ~isempty(xx) + fprintf(fid,' <%s fill="%s" fill-opacity="%0.2f" stroke="%s" stroke-width="%0.1fpt" stroke-opacity="%0.2f" %s points="',... + type, scolorname, face_opacity, edgecolorname, width, edge_opacity, pattern); + fprintf(fid,'%0.3f,%0.3f ',[xx;yy]); + fprintf(fid,'"/>\n'); + end + end + end + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% create a patch (filled area) +function gouraud_patch2svg(fid,group,axpos,xtot,ytot,cdata,style,width, edgecolorname, face_opacity, edge_opacity,id) +global colorname +pattern = lineStyle2svg(style, width); +if strcmp(style, 'none') + edge_opacity = 0.0; +end +for i=1:size(xtot,1) + x = xtot(i,:); + y = ytot(i,:); + if (any(isnan(x)) || any(isnan(y))) + fprintf('Warning: Found NaN in Gouraud patch.\n') + else + % If there are more than 2 edges always 3 edges are taken togehter + % to form a triangle + if length(x) > 2 + for j = 3:length(x) + coord = [x([1 j-1 j]);y([1 j-1 j])]; + face_color = cdata(1,:); + face_color2 = cdata(j-1,:); + face_color3 = cdata(j,:); + delta = coord(:,3)-coord(:,2); + if det([delta (coord(:,1)-coord(:,2))]) ~= 0 + if ~isnan(face_color) + IDstring1 = createId; + IDstring2 = createId; + if size(face_color2,2)==1 + face_color_name2 = ['#' colorname(ceil(face_color2),:)]; + else + face_color_name2 = searchcolor(id,face_color2/64); + end + if size(face_color3,2)==1 + face_color_name3 = ['#' colorname(ceil(face_color3),:)]; + else + face_color_name3 = searchcolor(id,face_color3/64); + end + grad_end=(delta)*(delta'*(coord(:,1)-coord(:,2)))/(delta'*delta) + coord(:,2); + if size(face_color,2)==1 + face_color_name = ['#' colorname(ceil(face_color),:)]; + else + face_color_name = searchcolor(id,face_color/64); + end + fprintf(fid,'\n'); + fprintf(fid,'\n',... + IDstring1, coord(1,2), coord(2,2), coord(1,3), coord(2,3)); + fprintf(fid,'\n',face_color_name2); + fprintf(fid,'\n',face_color_name3); + fprintf(fid,'\n'); + fprintf(fid,'\n',... + IDstring2, coord(1,1), coord(2,1), grad_end(1), grad_end(2)); + fprintf(fid,'\n',face_color_name); + fprintf(fid,'\n',face_color_name); + fprintf(fid,'\n'); + fprintf(fid,'\n'); + % Open group + temp_string = sprintf('%0.3f,%0.3f ',coord); + fprintf(fid,'\n',face_opacity); + fprintf(fid,'\n',IDstring1,temp_string); + fprintf(fid,'\n',IDstring2,temp_string); + % Close group + fprintf(fid,'\n'); + end + end + end + end + % Last we draw the line around the patch + if ~strcmp(edgecolorname,'none') + fprintf(fid,'\n'); + end + end +end + +function patternString = lineStyle2svg(lineStyle, lineWidth) +% Create the string for the line style +% Note: On Matlab the line style is not identical on screen and in a png. +% We will try to match with the screen format. +scaling = max(1, lineWidth * 0.4); +switch lineStyle + case '--', patternString = sprintf('stroke-dasharray="%0.1f,%0.1f"', 8*scaling, 2*scaling); + case ':', patternString = sprintf('stroke-dasharray="%0.1f,%0.1f"', 2*scaling, 2*scaling); + case '-.', patternString = sprintf('stroke-dasharray="%0.1f,%0.1f,%0.1f,%0.1f"', 8*scaling, 2*scaling, 2*scaling, 2*scaling); + otherwise, patternString = 'stroke-dasharray="none"'; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% create a line segment +% this algorthm was optimized for large segement counts +function line2svg(fid, ~, ~, x, y, scolorname, style, width, strokeopacity) +SEG_SIZE = 5000; +if nargin < 9 || ~isscalar(strokeopacity) + strokeopacity = 1; +end +if ~strcmp(style,'none') + pattern = lineStyle2svg(style, width); + + skip_pts = reshape(find(isnan(x) | isnan(y)), [],1); + start_pts = [1; skip_pts+1]; + end_pts = [skip_pts-1; numel(x)]; + k = 1; + while k <= numel(start_pts) + if (end_pts(k) - start_pts(k)) > SEG_SIZE + tmp_sPts = zeros(numel(start_pts)+1,1); + tmp_ePts = zeros(numel(start_pts)+1,1); + tmp_sPts(1:k) = start_pts(1:k); + tmp_ePts(1:k-1) = end_pts(1:k-1); + tmp_ePts(k) = tmp_sPts(k)+SEG_SIZE-1; + tmp_sPts(k+1) = tmp_sPts(k)+SEG_SIZE-1; + tmp_ePts(k+1:end) = end_pts(k:end); + tmp_sPts(k+2:end) = start_pts(k+1:end); + start_pts = tmp_sPts; + end_pts = tmp_ePts; + end + k = k+1; + end + for j=1:numel(start_pts) + xx=x(start_pts(j):end_pts(j)); + yy=y(start_pts(j):end_pts(j)); + fprintf(fid,' \n'); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% create a circle +function circle2svg(fid,group,axpos,x,y,radius,markeredgecolorname,markerfacecolorname,width) +npts=length(x); +if(size(markeredgecolorname,1)~=npts) +markeredgecolorname=repmat(markeredgecolorname(1,:),npts,1); +end +if(size(markerfacecolorname,1)~=npts) +markerfacecolorname=repmat(markerfacecolorname(1,:),npts,1); +end +if (length(radius)~=npts) +radius=ones(npts,1)*radius(1); +end +for j = 1:length(x) + if ~(isnan(x(j)) || isnan(y(j))) + if ~strcmp(markeredgecolorname(j,:),'none') || ~strcmp(markerfacecolorname(j,:),'none') + fprintf(fid,'\n',x(j),y(j),radius(j),markerfacecolorname(j,:),markeredgecolorname(j,:),width); + end + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function control2svg(fid,id,ax,group,paperpos) +global PLOT2SVG_globals +set(ax,'Units','pixels'); +%if verLessThan('matlab', '8.4.0') + pos=get(ax,'Position'); +%else +% pos=ax.OuterPosition; +%end +%pict=getframe(id,pos); +pict=getframe(PLOT2SVG_globals.MainFigure, pos); +if isempty(pict.colormap) + pict.colormap=colormap; +end +filename = [PLOT2SVG_globals.basefilename sprintf('%03d',PLOT2SVG_globals.figurenumber) '.' PLOT2SVG_globals.pixelfiletype]; +PLOT2SVG_globals.figurenumber = PLOT2SVG_globals.figurenumber + 1; +%if isempty(PLOT2SVG_globals.basefilepath) +if isempty(PLOT2SVG_globals.basedirname) + current_path = pwd; + rel_path = pwd; +else + %current_path = PLOT2SVG_globals.basefilepath; + current_path = PLOT2SVG_globals.basedirname; + rel_path = PLOT2SVG_globals.reldirname; +end +relname = fullfile(rel_path,filename); +filename = fullfile(current_path,filename); +if exist(filename,'file') + lastwarn(''); + delete(filename); + if strcmp(lastwarn,'File not found or permission denied.') + error('Cannot write image file. Make sure that no image is opened in an other program.') + end +end +%imwrite(pict.cdata,fullfile(PLOT2SVG_globals.basefilepath,filename),PLOT2SVG_globals.pixelfiletype); +imwrite(pict.cdata,filename,PLOT2SVG_globals.pixelfiletype); +PLOT2SVG_globals.used_dir=true; +set(ax,'Units','normalized'); +posNorm=get(ax,'Position'); +posInches(1)=posNorm(1)*paperpos(3); +posInches(2)=posNorm(2)*paperpos(4); +posInches(3)=posNorm(3)*paperpos(3); +posInches(4)=posNorm(4)*paperpos(4); +lx = posInches(3); +ly = posInches(4); +pointsx = posInches(1); +pointsy = paperpos(4)-posInches(2)-posInches(4); +fprintf(fid,'\n', pointsx, pointsy, lx, ly, relname); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% create a text in the axis frame +% the position of the text has to be adapted to the axis scaling +function text2svg(fid,group,axpos,paperpos,id,ax,projection) +global PLOT2SVG_globals; +originalTextUnits=get(id,'Units'); +originalTextPosition = get(id, 'Position'); +if PLOT2SVG_globals.octave + set(id,'Units','data'); +else + set(id,'Units','Data'); +end +textpos=get(id,'Position'); +if PLOT2SVG_globals.octave + xlim = get(ax, 'XLim'); + ylim = get(ax, 'YLim'); + zlim = get(ax, 'ZLim'); + if get(ax, 'XLabel') == id + textpos = textpos + [mean(xlim) ylim(1)-diff(ylim)./axpos(4)*0.06 0]; + elseif get(ax, 'YLabel') == id + textpos = textpos + [xlim(1)-diff(xlim)./axpos(3)*0.06 mean(ylim) 0]; + elseif get(ax, 'ZLabel') == id + if projection.xyplane + return; + end + textpos = textpos + [xlim(1)-diff(xlim)./axpos(3)*0.06 0 mean(zlim)]; + elseif get(ax, 'Title') == id + textpos = textpos + [mean(xlim) ylim(2)+diff(ylim)./axpos(4)*0.01 0]; + end +end +textfontsize = get(id,'FontSize'); +fontsize = convertunit(get(id,'FontSize'),get(id,'FontUnits'),'points', axpos(4)); % convert fontsize to inches +paperposOriginal = get(gcf,'Position'); +font_color = searchcolor(id,get(id,'Color')); +if strcmp(get(ax,'XScale'),'log') + textpos(1) = log10(textpos(1)); +end +if strcmp(get(ax,'YScale'),'log') + textpos(2) = log10(textpos(2)); +end +if strcmp(get(ax,'ZScale'),'log') + textpos(3) = log10(textpos(3)); +end +[x,y,z] = project(textpos(1), textpos(2), textpos(3), projection); +x = (x * axpos(3) + axpos(1)) * paperpos(3); +y = (1 - (y * axpos(4) + axpos(2))) * paperpos(4); +textvalign = get(id,'VerticalAlignment'); +textalign = get(id,'HorizontalAlignment'); +texttext = get(id,'String'); +textrot = get(id,'Rotation'); +dx = sin(textrot * pi / 180) * convertunit(fontsize * 1.2, 'points', 'pixels'); +dy = cos(textrot * pi / 180) * convertunit(fontsize * 1.2, 'points', 'pixels'); +lines = max(size(get(id,'String'),1),1); +if size(texttext,2)~=0 + j = 1; + for i = 0:1:(lines - 1) + if iscell(texttext) + label2svg(fid, group, axpos, id, x + i * dx, y + i * dy, convertString(texttext{j}), textalign, textrot, textvalign, lines, paperpos, font_color, 0) + else + label2svg(fid, group, axpos, id, x + i * dx, y + i * dy, convertString(texttext(j,:)), textalign, textrot, textvalign, lines, paperpos, font_color, 0) + end + j = j + 1; + end +else + label2svg(fid,group,axpos,id,x,y,'',textalign,textrot,textvalign,lines,paperpos,font_color,0) +end +set(id,'Units',originalTextUnits); +set(id,'Position', originalTextPosition); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% adds the exponents to the axis thickmarks if needed +% MATLAB itself offers no information about this exponent scaling +% the exponent have therefore to be extracted from the thickmarks +function exponent2svg(fid,group,axpos,paperpos,ax,axxtick,axytick,axztick) +global PLOT2SVG_globals +if strcmp(get(ax,'XTickLabelMode'),'auto') && strcmp(get(ax,'XScale'),'linear') + fontsize=convertunit(get(ax,'FontSize'),get(ax,'FontUnits'),'points', axpos(4)); % convert fontsize to inches + font_color=searchcolor(ax,get(ax,'XColor')); + if PLOT2SVG_globals.octave + % Octave stores XTickLabel in a cell array, which does not work nicely with str2num. --Jakob Malm + axlabelx = get(ax, 'XTickLabel'); + numlabels = zeros(length(axlabelx), 1); + for ix = 1:length (axlabelx) + numlabels(ix) = str2num(axlabelx{ix}); + end + else + numlabels = get(ax,'XTickLabel'); + if ~isempty(numlabels) + numlabels = str2double(numlabels); + end + end + labelpos = axxtick;%get(ax,'XTick'); + numlabels = numlabels(:); + labelpos = labelpos(:); + indexnz = find(labelpos ~= 0); + if (~isempty(indexnz) && ~isempty(numlabels)) + if (length(indexnz) == length(numlabels) && max(indexnz) <= length(numlabels)) + ratio = numlabels(indexnz)./labelpos(indexnz); + else + ratio = 1; + end + if round(log10(ratio(1))) ~= 0 && ratio(1) ~= 0 + exptext = sprintf('× 10%g', -log10(ratio(1))); + label2svg(fid,group,axpos,ax,(axpos(1)+axpos(3))*paperpos(3),(1-axpos(2))*paperpos(4)+3*fontsize,exptext,'right',0,'top',1,paperpos,font_color,0) + end + end +end +if strcmp(get(ax,'YTickLabelMode'),'auto') && strcmp(get(ax,'YScale'),'linear') + fontsize=convertunit(get(ax,'FontSize'),get(ax,'FontUnits'),'points', axpos(4)); + font_color=searchcolor(ax,get(ax,'YColor')); + if PLOT2SVG_globals.octave + % Octave stores YTickLabel in a cell array, which does not work nicely with str2num. --Jakob Malm + axlabely = get(ax, 'YTickLabel'); + numlabels = zeros(length(axlabely), 1); + for ix = 1:length(axlabely) + numlabels(ix) = str2num(axlabely{ix}); + end + else + numlabels = get(ax,'YTickLabel'); + if ~isempty(numlabels) + numlabels = str2double(numlabels); + end + end + labelpos = axytick;%get(ax,'YTick'); + numlabels = numlabels(:); + labelpos = labelpos(:); + indexnz = find(labelpos ~= 0); + if (~isempty(indexnz) && ~isempty(numlabels)) + if (length(indexnz) == length(numlabels) && max(indexnz) <= length(numlabels)) + ratio = numlabels(indexnz)./labelpos(indexnz); + else + ratio = 1; + end + if round(log10(ratio(1))) ~= 0 && ratio(1) ~= 0 + exptext = sprintf('× 10%g', -log10(ratio(1))); + label2svg(fid,group,axpos,ax,axpos(1)*paperpos(3),(1-(axpos(2)+axpos(4)))*paperpos(4)-0.5*fontsize,exptext,'left',0,'bottom',1,paperpos,font_color,0) + end + end +end +if strcmp(get(ax,'ZTickLabelMode'),'auto') && strcmp(get(ax,'ZScale'),'linear') + fontsize=convertunit(get(ax,'FontSize'),get(ax,'FontUnits'),'points', axpos(4)); + font_color=searchcolor(ax,get(ax,'ZColor')); + if PLOT2SVG_globals.octave + % Octave stores ZTickLabel in a cell array, which does not work nicely with str2num. --Jakob Malm + axlabelz = get (ax, 'ZTickLabel'); + numlabels = zeros(length(axlabelz), 1); + for ix = 1:length(axlabelz) + numlabels(ix) = str2num(axlabelz{ix}); + end + else + numlabels = get(ax,'ZTickLabel'); + if ~isempty(numlabels) + numlabels = str2double(numlabels); + end + end + labelpos = axztick;%get(ax,'ZTick'); + numlabels = numlabels(:); + labelpos = labelpos(:); + indexnz = find(labelpos ~= 0); + if (~isempty(indexnz) && ~isempty(numlabels)) + if (length(indexnz) == length(numlabels) && max(indexnz) <= length(numlabels)) + ratio = numlabels(indexnz)./labelpos(indexnz); + else + ratio = 1; + end + if round(log10(ratio(1))) ~= 0 && ratio(1) ~= 0 + exptext = sprintf('× 10%g', -log10(ratio(1))); + label2svg(fid,group,axpos,ax,axpos(1)*paperpos(3),(1-(axpos(2)+axpos(4)))*paperpos(4)-0.5*fontsize,exptext,'left',0,'top',1,paperpos,font_color,0) + end + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% create a label in the figure +% former versions of FrameMaker supported the commands FDY and FDX to shift the text +% this commands were replaced by a shift parameter that is normed by the font size +function label2svg(fid,group,axpos,id,x,y,tex,align,angle,valign,lines,paperpos,font_color,exponent) +if isempty(tex) + return; +end +textfontname=get(id,'FontName'); +if strcmp(textfontname, '*') + textfontname = 'Arial'; +end +set(id,'FontUnits','points'); +textfontsize=get(id,'FontSize'); +if isfield(get(id),'Interpreter') + if strcmp(get(id,'Interpreter'),'tex') + latex=1; + elseif strcmp(get(id,'Interpreter'),'latex') + latex=1; + else + latex=0; + end +else + latex=1; +end +fontsize=convertunit(get(id,'FontSize'),get(id,'FontUnits'),'points', axpos(4)); % convert fontsize to inches +fontweight = get(id,'FontWeight'); +switch lower(fontweight) + case 'bold', fweight = ' font-weight="bold"'; + case 'light', fweight = ' font-weight="lighter"'; + case 'demi', fweight = ' font-weight="lighter"'; + otherwise, fweight = ''; % default 'normal' +end +fontangle = get(id,'FontAngle'); +switch lower(fontangle) + case 'italic', fangle = ' font-style="italic"'; + case 'oblique', fangle = ' font-style="oblique"'; +otherwise, fangle = ''; % default 'normal' +end +% Note: The attribute 'alignment-baseline' cannot be used as it is often +% badly supported. Therfore, we use shifts. +switch lower(valign) + case 'top',shift=fontsize*1.18; + case 'cap',shift=fontsize*0.95; + case 'middle',shift = -((lines-1)/2*fontsize*1.25*1.2) + fontsize * 0.45; + case 'bottom',shift = -((lines-1)*fontsize*1.25*1.2) + fontsize * -0.25; + otherwise,shift=0; +end +switch lower(align) + case 'right', anchor = 'end'; + case 'center',anchor = 'middle'; + otherwise,anchor = 'start'; +end +if iscellstr(tex) + tex = strvcat(tex); +elseif ~ ischar(tex) + error('Invalid character type'); +end +if latex==1 + tex=strrep(tex,'$',''); + %if ~exponent + % try + % tex = texlabel(tex); + % catch + % fprintf(' Warning: Error during conversion to a latex string.'); + % end + %end + tex=strrep(tex,'\alpha','{α}'); + tex=strrep(tex,'\beta','{β}'); + tex=strrep(tex,'\gamma','{γ}'); + tex=strrep(tex,'\delta','{δ}'); + tex=strrep(tex,'\epsilon','{ε}'); + tex=strrep(tex,'\zeta','{ζ}'); + tex=strrep(tex,'\eta','{η}'); + tex=strrep(tex,'\theta','{θ}'); + tex=strrep(tex,'\vartheta','{ϑ}'); + tex=strrep(tex,'\iota','{ι}'); + tex=strrep(tex,'\kappa','{κ}'); + tex=strrep(tex,'\lambda','{λ}'); + tex=strrep(tex,'\mu','{µ}'); + tex=strrep(tex,'\nu','{ν}'); + tex=strrep(tex,'\xi','{ξ}'); + tex=strrep(tex,'\pi','{π}'); + tex=strrep(tex,'\rho','{ρ}'); + tex=strrep(tex,'\sigma','{σ}'); + tex=strrep(tex,'\varsigma','{ς}'); + tex=strrep(tex,'\tau','{τ}'); + tex=strrep(tex,'\upsilon','{υ}'); + tex=strrep(tex,'\phi','{φ}'); + tex=strrep(tex,'\chi','{χ}'); + tex=strrep(tex,'\psi','{ψ}'); + tex=strrep(tex,'\omega','{ω}'); + tex=strrep(tex,'\Gamma','{Γ}'); + tex=strrep(tex,'\Delta','{Δ}'); + tex=strrep(tex,'\Theta','{Θ}'); + tex=strrep(tex,'\Lambda','{Λ}'); + tex=strrep(tex,'\Xi','{Ξ}'); + tex=strrep(tex,'\Pi','{Π}'); + tex=strrep(tex,'\Sigma','{Σ}'); + tex=strrep(tex,'\Tau','{Τ}'); + tex=strrep(tex,'\Upsilon','{Υ}'); + tex=strrep(tex,'\Phi','{Φ}'); + tex=strrep(tex,'\Psi','{Ψ}'); + tex=strrep(tex,'\Omega','{Ω}'); + tex=strrep(tex,'\infty','{∞}'); + tex=strrep(tex,'\pm','{±}'); + tex=strrep(tex,'\Im','{ℑ}'); + tex=strrep(tex,'\Re','{ℜ}'); + tex=strrep(tex,'\approx','{≅}'); + tex=strrep(tex,'\leq','{≤}'); + tex=strrep(tex,'\geq','{≥}'); + tex=strrep(tex,'\times','{×}'); + tex=strrep(tex,'\leftrightarrow','{↔}'); + tex=strrep(tex,'\leftarrow','{←}'); + tex=strrep(tex,'\uparrow','{↑}'); + tex=strrep(tex,'\rightarrow','{→}'); + tex=strrep(tex,'\downarrow','{↓}'); + tex=strrep(tex,'\circ','{º}'); + tex=strrep(tex,'\propto','{∝}'); + tex=strrep(tex,'\partial','{∂}'); + tex=strrep(tex,'\bullet','{•}'); + tex=strrep(tex,'\div','{÷}'); + + tex=strrep(tex,'\sum','{∑}'); + tex=strrep(tex,'\ast','{∗}'); + tex=strrep(tex,'\sqrt','{√}'); + tex=strrep(tex,'\angle','{∠}'); + tex=strrep(tex,'\wedge','{∧}'); + tex=strrep(tex,'\land','{∧}'); + tex=strrep(tex,'\vee','{∨}'); + tex=strrep(tex,'\lor','{∨}'); + tex=strrep(tex,'\cap','{∩}'); + tex=strrep(tex,'\cup','{∪}'); + tex=strrep(tex,'\int','{∫}'); +%∴∴ + tex=strrep(tex,'\sim','{∼}'); + + tex=strrep(tex,'\forall','{∀}'); + tex=strrep(tex,'\partial','{∂}'); + tex=strrep(tex,'\exists','{∃}'); + tex=strrep(tex,'\emptyset','{∅}'); + tex=strrep(tex,'\nabla','{∇}'); + tex=strrep(tex,'\in','{∈}'); + tex=strrep(tex,'\notin','{∉}'); + tex=strrep(tex,'\ni','{∋}'); + tex=strrep(tex,'\prod','{∏}'); + + tex=strrep(tex,'\cong','{≅}'); + tex=strrep(tex,'\approx','{≈}'); + tex=strrep(tex,'\neq','{≠}'); + tex=strrep(tex,'\equiv','{≡}'); + tex=strrep(tex,'\leq','{≤}'); + tex=strrep(tex,'\geq','{≥}'); + tex=strrep(tex,'\subset','{⊂}'); + tex=strrep(tex,'\supset','{⊃}'); +%⊄⊄ + tex=strrep(tex,'\subseteq','{⊆}'); + tex=strrep(tex,'\supseteq','{⊇}'); + tex=strrep(tex,'\oplus','{⊕}'); + tex=strrep(tex,'\otimes','{⊗}'); + tex=strrep(tex,'\bot','{⊥}'); + tex=strrep(tex,'\cdot','{⋅}'); + tex=strrep(tex,'\bullet','{•}'); + tex=strrep(tex,'\ldots','{…}'); + tex=strrep(tex,'\prime','{′}'); +% ″ double prime +% ‾ oline + + + tex=strrep(tex,'\\','{\}'); + tex=strrep(tex,'\{','{{}'); + tex=strrep(tex,'\}','{}}'); + tex=strrep(tex,'\_','{_}'); + tex=strrep(tex,'\^','{^}'); + + %fprintf('%s\n', tex); + tex=latex2svg(tex, textfontname, textfontsize, 0); +else + tex=sprintf('%s', tex); +end +if isempty(tex) + return; +end +if exponent + tex=sprintf('10%s', 0.7*textfontsize, -0.7*textfontsize, tex); + shift = shift + 0.4*fontsize; % Small correction to make it look nicer +end +% Note: Obviously, Matlab is using font sizes that are rounded to decimal +% pt sizes. This may cause problems for very small figures. But we have to +% follow due to the background size. +%fprintf('%s\n', tex); +fprintf(fid,' \n', x - shift * sin(-angle/180*pi), y + shift * cos(-angle/180*pi)); +fprintf(fid,' \n',-angle); +fprintf(fid,' ', 0, 0, textfontname, anchor, textfontsize, fweight, fangle, font_color); +fprintf(fid,'%s',tex); +fprintf(fid,'\n'); +fprintf(fid,' \n'); +fprintf(fid,' \n'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% converts LATEX strings into SVG strings +function returnvalue = latex2svg(StringText, font, size, style) +returnvalue = StringText; +try +if ~isempty(StringText) + bracket = find(StringText == '{' | StringText == '}'); + bracketCounter = zeros(1,length(StringText)); + bracketCounter(StringText == '{') = 1; + bracketCounter(StringText == '}') = -1; + bracketCounter = cumsum(bracketCounter); + if bracketCounter(end) ~= 0 + fprintf(['Warning: Number of open and closed braces is not equal. Latex string ''' StringText ''' will not be converted.\n']); + elseif any(bracketCounter < 0) + fprintf(['Warning: Found a closed brace without a previously opened brace. Latex string ''' StringText ''' will not be converted.\n']); + else + if isempty(bracket) + if any(StringText == '^' | StringText == '_' | StringText == '\' ) + returnvalue = ['' singleLatex2svg(StringText) '']; + % Clean up empty tspan elements + % More could be done here, but with huge effort to make it + % match all special cases. + returnvalue = strrep(returnvalue, '>', '>'); + else + returnvalue = ['' StringText '']; + end + else + returnvalue = ''; + lastValidCharacter = 1; + for i = 1:length(bracket) + lastValidCharacterOffset = 1; + if StringText(bracket(i)) == '{' + % Found '{' + removeCharacters = 1; + localOffset = 0; + if (bracket(i) > 1) + if StringText(bracket(i) - 1) == '_' + baselineShift = 'sub'; + localFontSize = '65%%'; + localOffset = -1; + removeCharacters = 2; + elseif StringText(bracket(i) - 1) == '^' + baselineShift = 'super'; + localFontSize = '65%%'; + localOffset = 1; + removeCharacters = 2; + end + end + returnvalue = [returnvalue singleLatex2svg(StringText(lastValidCharacter:bracket(i) - removeCharacters)) '']; + else + % Found '}' + returnvalue = [returnvalue singleLatex2svg(StringText(lastValidCharacter:bracket(i) - 1)) '']; + end + lastValidCharacter = bracket(i) + lastValidCharacterOffset; + end + if lastValidCharacter <= length(StringText) + returnvalue = [returnvalue singleLatex2svg(StringText(lastValidCharacter:end))]; + end + returnvalue = [returnvalue '']; + % Clean up empty tspan elements + % More could be done here, but with huge effort to make it + % match all special cases. + returnvalue = strrep(returnvalue, '>', '>'); + returnvalue = strrep(returnvalue, '>>', '>'); + end + end +end +catch ME + errStr = ME.identifier; + if isempty(errStr) + errStr = ME.message; + end + fprintf(['Warning: Error ''' errStr ''' occurred during conversion. Latex string ''' StringText ''' will not be converted.\n']); +end + +function StringText = singleLatex2svg(StringText) +index = find(StringText == '_' | StringText == '^'); +if ~isempty(index) + if index(end) == length(StringText) + % Remove orphan '_' or '^' + index = index(1:end-1); + end + for i = length(index):-1:1 + if StringText(index(i)) == '_' + StringText = [StringText(1:index(i)-1) ... + '' ... + StringText(index(i)+1) ... + '' ... + StringText(index(i)+2:end)]; + else + StringText = [StringText(1:index(i)-1) ... + '' ... + StringText(index(i)+1) ... + '' ... + StringText(index(i)+2:end)]; + end + end +end +if ~isempty(strfind(StringText, '\bf')) + StringText = strrep(StringText, '\bf', ''); +end +if ~isempty(strfind(StringText, '\it')) + StringText = strrep(StringText, '\it', ''); +end +if ~isempty(strfind(StringText, '\sl')) + StringText = strrep(StringText, '\sl', ''); +end +if ~isempty(strfind(StringText, '\rm')) + StringText = strrep(StringText, '\rm', ''); +end + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function name=searchcolor(id,value) +if ischar(value) + name = value; +else + name=repmat('#',size(value,1), 7); + for i=1:size(value,1) + name(i,:)=sprintf('#%02x%02x%02x',fix(value(i,1)*255),fix(value(i,2)*255),fix(value(i,3)*255)); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function rvalue = convertunit(value, from, to, parentheight) +global PLOT2SVG_globals +% From SVG 1.1. Specification: +% "1pt" equals "1.25px" (and therefore 1.25 user units) +% "1pc" equals "15px" (and therefore 15 user units) +% "1mm" would be "3.543307px" (3.543307 user units) +% "1cm" equals "35.43307px" (and therefore 35.43307 user units) +% "1in" equals "90px" (and therefore 90 user units) +% Modification by Jonathon Harding: +% MATLAB however, assumes a variable number of pixels per inch, and +% assuming that the pixels match is dangerous. +if nargin < 4 + parentheight = 1.25; % Default +end +switch lower(from) % convert from input unit to points + case 'pixels', rvalue = value * 72/PLOT2SVG_globals.ScreenPixelsPerInch; + case 'points', rvalue = value; + case 'centimeters', rvalue = value / 2.54*72; + case 'inches', rvalue = value * 72; % 72 points = 1 inch + case 'normalized', rvalue = value * (parentheight * 0.8); + otherwise, error(['Unknown unit ' from '.']); +end +switch lower(to) % convert from points to specified unit + case 'pixels', rvalue = rvalue * 1.25; + case 'points'; % do nothing + case 'centimeters', rvalue = rvalue * 2.54 / 72; + case 'inches', rvalue = rvalue / 72; % 72 points = 1 inch + case 'normalized', rvalue = value / (parentheight * 0.8); + otherwise, error(['Unknown unit ' to '.']); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function strString=addBackSlash( strSlash) +% adds a backslash at the last position of the string (if not already there) +if ( strSlash(end) ~= filesep) + strString = [ strSlash filesep]; +else + strString = strSlash; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function strExt=getFileExtension( strFileName) +% returns the file extension of a filename +[path, name, strExt] = fileparts( strFileName); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function StringText=convertString(StringText) +if iscell(StringText) % Octave stores some strings in cell arrays. --Jakob Malm + StringText = StringText{1}; +end +if ~isempty(StringText) + StringText=strrep(StringText,'&','&'); % Do not change sequence !! + StringText=strrep(StringText,'\\','\'); + StringText=strrep(StringText,'<','<'); + StringText=strrep(StringText,'>','>'); + StringText=strrep(StringText,'"','"'); + % Workaround for Firefox and Inkscape + StringText=strrep(StringText,'°','°'); + %StringText=strrep(StringText,'°','°'); + StringText=strrep(StringText,'±','±'); + StringText=strrep(StringText,'µ','µ'); + StringText=strrep(StringText,'²','²'); + StringText=strrep(StringText,'³','³'); + StringText=strrep(StringText,'¼','¼'); + StringText=strrep(StringText,'½','½'); + StringText=strrep(StringText,'¾','¾'); + StringText=strrep(StringText,'©','©'); + StringText=strrep(StringText,'®','®'); + if any(StringText > 190) + StringText=strrep(StringText,'¿','¿'); + StringText=strrep(StringText,'À','À'); + StringText=strrep(StringText,'Á','Á'); + StringText=strrep(StringText,'Â','Â'); + StringText=strrep(StringText,'Ã','Ã'); + StringText=strrep(StringText,'Ä','Ä'); + StringText=strrep(StringText,'Å','Å'); + StringText=strrep(StringText,'Æ','Æ'); + StringText=strrep(StringText,'Ç','Ç'); + StringText=strrep(StringText,'È','È'); + StringText=strrep(StringText,'É','É'); + StringText=strrep(StringText,'Ê','Ê'); + StringText=strrep(StringText,'Ë','Ë'); + StringText=strrep(StringText,'Ì','Ì'); + StringText=strrep(StringText,'Í','Í'); + StringText=strrep(StringText,'Î','Î'); + StringText=strrep(StringText,'Ï','Ï'); + StringText=strrep(StringText,'Ð','Ð'); + StringText=strrep(StringText,'Ñ','Ñ'); + StringText=strrep(StringText,'Ò','Ò'); + StringText=strrep(StringText,'Ó','Ó'); + StringText=strrep(StringText,'Ô','Ô'); + StringText=strrep(StringText,'Õ','Õ'); + StringText=strrep(StringText,'Ö','Ö'); + StringText=strrep(StringText,'×','×'); + StringText=strrep(StringText,'Ø','Ø'); + StringText=strrep(StringText,'Ù','Ù'); + StringText=strrep(StringText,'Ú','Ú'); + StringText=strrep(StringText,'Û','Û'); + StringText=strrep(StringText,'Ü','Ü'); + StringText=strrep(StringText,'Ý','Ý'); + StringText=strrep(StringText,'Þ','Þ'); + StringText=strrep(StringText,'ß','ß'); + StringText=strrep(StringText,'à','à'); + StringText=strrep(StringText,'á','á'); + StringText=strrep(StringText,'â','â'); + StringText=strrep(StringText,'ã','ã'); + StringText=strrep(StringText,'ä','ä'); + StringText=strrep(StringText,'å','å'); + StringText=strrep(StringText,'æ','æ'); + StringText=strrep(StringText,'ç','ç'); + StringText=strrep(StringText,'è','è'); + StringText=strrep(StringText,'é','é'); + StringText=strrep(StringText,'ê','ê'); + StringText=strrep(StringText,'ë','ë'); + StringText=strrep(StringText,'ì','ì'); + StringText=strrep(StringText,'í','í'); + StringText=strrep(StringText,'î','î'); + StringText=strrep(StringText,'ï','ï'); + StringText=strrep(StringText,'ð','ð'); + StringText=strrep(StringText,'ñ','ñ'); + StringText=strrep(StringText,'ò','ò'); + StringText=strrep(StringText,'ó','ó'); + StringText=strrep(StringText,'ô','ô'); + StringText=strrep(StringText,'õ','õ'); + StringText=strrep(StringText,'ö','ö'); + StringText=strrep(StringText,'÷','÷'); + StringText=strrep(StringText,'ø','ø'); + StringText=strrep(StringText,'ù','ù'); + StringText=strrep(StringText,'ú','ú'); + StringText=strrep(StringText,'û','û'); + StringText=strrep(StringText,'ü','ü'); + StringText=strrep(StringText,'ý','ý'); + StringText=strrep(StringText,'þ','þ'); + StringText=strrep(StringText,'ÿ','ÿ'); + end + StringText=deblank(StringText); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function IdString = createId +global PLOT2SVG_globals +IdString = ['ID' sprintf('%06d',PLOT2SVG_globals.runningIdNumber)]; +PLOT2SVG_globals.runningIdNumber = PLOT2SVG_globals.runningIdNumber + 1; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [projection, edges] = get_projection(ax,id) +global PLOT2SVG_globals +xc = get(ax,'CameraTarget'); +phi = get(ax,'CameraViewAngle'); +vi = get(ax,'View'); +xi = get(ax,'XLim'); +yi = get(ax,'YLim'); +zi = get(ax,'ZLim'); +projection.aspect_scaling = get(ax,'DataAspectRatio'); +[xinfi, yinfi, zinfi] = AxesChildBounds(ax); +xi(isinf(xi)) = xinfi(isinf(xi)); +yi(isinf(yi)) = yinfi(isinf(yi)); +zi(isinf(zi)) = zinfi(isinf(zi)); +if strcmp(get(ax,'XScale'),'log') + if strcmp(get(ax,'XLimMode'),'manual') && any(get(ax,'XLim') == 0) + % Fix illegal scalings set by the user + % -> replace all 0 with automatic calculated values (child limits) + xlimM = get(ax,'XLim'); + set(ax,'XLimMode','auto'); + xlimA = get(ax,'XLim'); + xlimM(xlimM == 0) = xlimA(xlimM == 0); + set(ax,'XLimMode','manual'); + set(ax,'XLim', xlimM); + end + xi = log10(get(ax,'XLim')); +end +if strcmp(get(ax,'YScale'),'log') + if strcmp(get(ax,'YLimMode'),'manual') && any(get(ax,'YLim') == 0) + % Fix illegal scalings set by the user + % -> replace all 0 with automatic calculated values (child limits) + ylimM = get(ax,'YLim'); + set(ax,'YLimMode','auto'); + ylimA = get(ax,'YLim'); + ylimM(ylimM == 0) = ylimA(ylimM == 0); + set(ax,'YLimMode','manual'); + set(ax,'YLim', ylimM); + end + yi = log10(get(ax,'YLim')); +end +if strcmp(get(ax,'ZScale'),'log') + if strcmp(get(ax,'ZLimMode'),'manual') && any(get(ax,'ZLim') == 0) + % Fix illegal scalings set by the user + % -> replace all 0 with automatic calculated values (child limits) + zlimM = get(ax,'ZLim'); + set(ax,'ZLimMode','auto'); + zlimA = get(ax,'ZLim'); + zlimM(zlimM == 0) = zlimA(zlimM == 0); + set(ax,'ZLimMode','manual'); + set(ax,'ZLim', zlimM); + end + zi = log10(get(ax,'ZLim')); +end +projection.xi = xi; +projection.yi = yi; +projection.zi = zi; +xc(1) = (xc(1) - xi(1))/(xi(2)-xi(1)); +xc(2) = (xc(2) - yi(1))/(yi(2)-yi(1)); +xc(3) = (xc(3) - zi(1))/(zi(2)-zi(1)); +if strcmp(get(ax,'XScale'),'log') + x = [xi(1) xi(2) xi(1) xi(2) xi(1) xi(2) xi(1) xi(2)] - log10(projection.aspect_scaling(1)); +else + x = [xi(1) xi(2) xi(1) xi(2) xi(1) xi(2) xi(1) xi(2)]/projection.aspect_scaling(1); +end +if strcmp(get(ax,'YScale'),'log') + y = [yi(1) yi(1) yi(2) yi(2) yi(1) yi(1) yi(2) yi(2)] - log10(projection.aspect_scaling(2)); +else + y = [yi(1) yi(1) yi(2) yi(2) yi(1) yi(1) yi(2) yi(2)]/projection.aspect_scaling(2); +end +if strcmp(get(ax,'ZScale'),'log') + z = [zi(1) zi(1) zi(1) zi(1) zi(2) zi(2) zi(2) zi(2)] - log10(projection.aspect_scaling(3)); +else + z = [zi(1) zi(1) zi(1) zi(1) zi(2) zi(2) zi(2) zi(2)]/projection.aspect_scaling(3); +end +if PLOT2SVG_globals.octave + % Get the projection angle + [az, el] = view(ax); + + % Projection matrix from view + % C = viewmtx(az, el); + % NOTE: This is a subset of the MATLAB viewmtx function, as octave + % does not provide such functionality + % Make sure az and el are in the correct range. + el = rem(rem(el+180,360)+360,360)-180; % Make sure -180 <= el <= 180 + if el>90, + el = 180-el; + az = az + 180; + elseif el<-90, + el = -180-el; + az = az + 180; + end + az = rem(rem(az,360)+360,360); % Make sure 0 <= az <= 360 + + % Convert from degrees to radians. + az = az*pi/180; + el = el*pi/180; + + % View transformation matrix: + % Formed by composing two rotations: + % 1) Rotate about the z axis -AZ radians + % 2) Rotate about the x axis (EL-pi/2) radians + + C = [ cos(az) sin(az) 0 0 + -sin(el)*sin(az) sin(el)*cos(az) cos(el) 0 + cos(el)*sin(az) -cos(el)*cos(az) sin(el) 0 + 0 0 0 1 ]; + + projection.A= C; + %projection.A = get(ax,'x_viewtransform'); + %projection.A(3,:) = -projection.A(3,:); + %projection.A(1:3,4) = 0; +else + if strcmp(get(ax,'Projection'),'orthographic') + projection.A = viewmtx(vi(1),vi(2)); + else + projection.A = viewmtx(vi(1),vi(2),phi,xc); + end +end +if (vi(1) == 0) && (mod(vi(2),90) == 0) + projection.xyplane = true; +else + projection.xyplane = false; +end +axpos = get(ax,'Position'); +figpos = get(id,'Position'); +[m,n] = size(x); +x4d = [x(:),y(:),z(:),ones(m*n,1)]'; +x2d = projection.A*x4d; +x2 = zeros(m,n); y2 = zeros(m,n); z2 = zeros(m,n); +x2(:) = x2d(1,:)./x2d(4,:); +y2(:) = x2d(2,:)./x2d(4,:); +projection.ax = ax; +projection.xrange = max(x2) - min(x2); +projection.yrange = max(y2) - min(y2); +projection.xoffset = (max(x2) + min(x2))/2; +projection.yoffset = (max(y2) + min(y2))/2; +if (strcmp(get(ax,'PlotBoxAspectRatioMode'),'manual') || strcmp(get(ax,'DataAspectRatioMode'),'manual')) + if (projection.xrange*axpos(4)*figpos(4) < projection.yrange*axpos(3)*figpos(3)) + projection.xrange = projection.yrange*axpos(3)*figpos(3)/axpos(4)/figpos(4); + else + projection.yrange = projection.xrange*axpos(4)*figpos(4)/axpos(3)/figpos(3); + end +end +x2(:) = (x2d(1,:)./x2d(4,:) - projection.xoffset)/projection.xrange + 0.5; +y2(:) = (x2d(2,:)./x2d(4,:) - projection.yoffset)/projection.yrange + 0.5; +z2(:) = x2d(3,:); +edges = [x2; y2; z2]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [x2,y2,z2] = project(x,y,z,projection) +[m,n] = size(x); +if strcmp(get(projection.ax,'XDir'),'reverse') + xi = projection.xi; + x = (1 - (x - xi(1)) / (xi(2) - xi(1))) * (xi(2) - xi(1)) + xi(1); +end +if strcmp(get(projection.ax,'YDir'),'reverse') + yi = projection.yi; + y = (1 - (y - yi(1)) / (yi(2) - yi(1))) * (yi(2) - yi(1)) + yi(1); +end +if strcmp(get(projection.ax,'XScale'),'log') + x = x - log10(projection.aspect_scaling(1)); +else + x = x/projection.aspect_scaling(1); +end +if strcmp(get(projection.ax,'YScale'),'log') + y = y - log10(projection.aspect_scaling(2)); +else + y = y/projection.aspect_scaling(2); +end +if strcmp(get(projection.ax,'ZScale'),'log') + z = z - log10(projection.aspect_scaling(3)); +else + z = z/projection.aspect_scaling(3); +end +x4d = [x(:), y(:), z(:), ones(m*n,1)]'; +x2d = projection.A*x4d; +x2 = zeros(m,n); y2 = zeros(m,n); z2 = zeros(m,n); +x2(:) = (x2d(1,:)./x2d(4,:) - projection.xoffset)/projection.xrange + 0.5; +y2(:) = (x2d(2,:)./x2d(4,:) - projection.yoffset)/projection.yrange + 0.5; +z2(:) = x2d(3,:); +%x = [0 1 0 1 0 1 0 1]; +%y = [0 0 1 1 0 0 1 1]; +%z = [0 0 0 0 1 1 1 1]; + + +function [f, v, fvc, fva] = surface2patch(s) +x = get(s, 'xdata'); +y = get(s, 'ydata'); +z = get(s, 'zdata'); +c = get(s, 'cdata'); +a = get(s, 'AlphaData'); +if ~isempty(x) && ~isequal(size(x),size(z)) + x = repmat(x(:)',size(z,1),1); +end +if ~isempty(y) && ~isequal(size(y),size(z)) + y = repmat(y(:),1,size(z,2)); +end +[m n]= size(z); +if isempty(x) + [x y] = meshgrid(1:n, 1:m); +end +[cm cn cp] = size(c); +[am an ap] = size(a); +%if cm==(m-1) & cn==(n-1) +% cmode = 'f'; +%elseif cm==m & cn==n +% cmode = 'v'; +%else +% cmode = ''; +%end +v = [x(:) y(:) z(:)]; +q = [1:m*n-m-1]'; +q(m:m:end) = []; +fvc = reshape(c, [cm*cn cp]); +fva = reshape(a, [am*an ap]); +f = [q q+m q+m+1 q+1]; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Code by Jonathon Harding to detect axes child limits +function [xlims, ylims, zlims] = AxesChildBounds(ax) + % Get all the direct children of the axes that are not also axes (i.e. + % old style legends) + children = findobj(ax, '-depth', 1, '-not', 'Type', 'axes'); + % Now get all children of those objects that have data we can analyze + dataObjs = findobj(children, 'Type', 'line', ... + '-or', 'Type', 'patch', '-or', 'Type', 'Rectangle', '-or', 'Type', 'Surface'); + % Generate default limits if no objects are found + xlims = [0 1]; + ylims = [0 1]; + zlims = [0 1]; + if numel(dataObjs) == 0 + return; + end + % Iterate through each axis one at a time + axisData = {'XData', 'YData', 'ZData'}; + for i=1:numel(axisData) + % Set extreme bounds that will always be overridden + lims = [inf -inf]; + for j=1:numel(dataObjs) + % For each object, get the data for the appropriate axis + data = reshape(get(dataObjs(j), axisData{i}), [], 1); + % Remove data that is not displayed + data(isinf(data) | isnan(data)) = []; + % If any data remains, update the limits + if ~isempty(data) + lims(1) = min(lims(1), min(data)); + lims(2) = max(lims(2), max(data)); + end + end + % If the limits are not infinite (i.e. no data found), then update + % the apropriate axis limits + if ~any(isinf(lims)) + switch axisData{i} + case 'XData' + xlims = lims; + case 'YData' + ylims = lims; + case 'ZData' + zlims = lims; + end + end + end diff --git a/libraries/relativepath.m b/libraries/relativepath.m new file mode 100755 index 0000000..5e81b26 --- /dev/null +++ b/libraries/relativepath.m @@ -0,0 +1,126 @@ +function rel_path = relativepath( tgt_path, act_path ) +%RELATIVEPATH returns the relative path from an actual path to the target path. +% Both arguments must be strings with absolute paths. +% The actual path is optional, if omitted the current dir is used instead. +% In case the volume drive letters don't match, an absolute path will be returned. +% If a relative path is returned, it always starts with '.\' or '..\' +% +% Syntax: +% rel_path = RELATIVEPATH( target_path, actual_path ) +% +% Parameters: +% target_path - Path which is targetted +% actual_path - Start for relative path (optional, default = current dir) +% +% Examples: +% relativepath( 'C:\local\data\matlab' , 'C:\local' ) = '.\data\matlab\' +% relativepath( 'A:\MyProject\' , 'C:\local' ) = 'a:\myproject\' +% +% relativepath( 'C:\local\data\matlab' , cd ) is the same as +% relativepath( 'C:\local\data\matlab' ) +% +% See also: ABSOLUTEPATH PATH + +% Modified by Simon Blanchoud, 2014 +% - Added support for filenames +% - Removed the case insensitivity + +% Jochen Lenz + +if isempty(tgt_path) | isequal(tgt_path(1), '.') + rel_path = tgt_path; + + return; +else + [file_path, name, ext] = fileparts(tgt_path); + + if (~isempty(ext)) + tgt_path = file_path; + name = [name ext]; + else + name = []; + end +end + +% 2nd parameter is optional: +if nargin < 2 + act_path = cd; +end + +% Predefine return string: +rel_path = ''; + +% Make sure strings end by a filesep character: +if length(act_path) == 0 | ~isequal(act_path(end),filesep) + act_path = [act_path filesep]; +end +if length(tgt_path) == 0 | ~isequal(tgt_path(end),filesep) + tgt_path = [tgt_path filesep]; +end + +% Convert to all lowercase: +%[act_path] = fileparts( lower(act_path) ); +%[tgt_path] = fileparts( lower(tgt_path) ); +[act_path] = fileparts(act_path); +[tgt_path] = fileparts(tgt_path); + +% Create a cell-array containing the directory levels: +act_path_cell = pathparts(act_path); +tgt_path_cell = pathparts(tgt_path); + +% If volumes are different, return absolute path: +if length(act_path_cell) == 0 | length(tgt_path_cell) == 0 + return % rel_path = '' +else + if ~isequal( act_path_cell{1} , tgt_path_cell{1} ) + rel_path = tgt_path; + return + end +end + +% Remove level by level, as long as both are equal: +while length(act_path_cell) > 0 & length(tgt_path_cell) > 0 + if isequal( act_path_cell{1}, tgt_path_cell{1} ) + act_path_cell(1) = []; + tgt_path_cell(1) = []; + else + break + end +end + +% As much levels down ('..\') as levels are remaining in "act_path": +for i = 1 : length(act_path_cell) + rel_path = ['..' filesep rel_path]; +end + +% Relative directory levels to target directory: +for i = 1 : length(tgt_path_cell) + rel_path = [rel_path tgt_path_cell{i} filesep]; +end + +% Start with '.' or '..' : +if isempty(rel_path) + rel_path = ['.' filesep]; +elseif ~isequal(rel_path(1),'.') + rel_path = ['.' filesep rel_path]; +end + +if (~isempty(name)) + rel_path = [rel_path name]; +end + +return + +% ------------------------------------------------- + +function path_cell = pathparts(path_str) + +path_str = [filesep path_str filesep]; +path_cell = {}; + +sep_pos = findstr( path_str, filesep ); +for i = 1 : length(sep_pos)-1 + path_cell{i} = path_str( sep_pos(i)+1 : sep_pos(i+1)-1 ); +end + +return