UI in Matlab to get inputs to be used in a script - matlab

I am trying to create a GUI for my script in Matlab. At the moment I am opening this dialog boxes in my script to get the inputs I need.
prompt = {'Mass [kg]:','Power [kW]:','Drag Coeff:','Lift Coeff:','Area Front [m^2]:'};
dlgtitle = 'Vehicle Parameters';
dims = [1 35];
definput = {'752','650','1','3','1'};
vehicle = inputdlg(prompt,dlgtitle,dims,definput);
prompt = {'Friction Coeff:','Air Density [kg/m^3]:'};
dlgtitle = 'External Parameters';
dims = [1 35];
definput = {'1.4','1.19'};
ambient = inputdlg(prompt,dlgtitle,dims,definput);
prompt = {'Length [m]:','Corner Radius [m]:'};
dlgtitle = 'Track Parameters';
dims = [1 35];
definput = {'1000','150'};
track = inputdlg(prompt,dlgtitle,dims,definput);
Here the code continues
laptime = formula used to get the laptime;
However I would like to create something similar to what this image shows, where I can write the parameters, push the run button to run the script and get the laptime printed. Any suggestions on how I could achieve it?
Thanks a lot in advance!

steeven:
From inspecting your image, you essentially will need:
A figure object.
Ten(10) field objects.
Ten(10) Static text objects.
A pushbutton object
A callback function.
Here's a sample script:
clc
clear
close all %Very important when making GUI's in MATLAB!
%figure object that contains GUI.
f = figure('units','normalized');
%Uicontrols devoted to static text:
m_text = uicontrol('Parent',f,'units','normalized','Style','Text');
P_text = uicontrol('Parent',f,'units','normalized','Style','Text');
FS_text = uicontrol('Parent',f,'units','normalized','Style','Text');
CD_text = uicontrol('Parent',f,'units','normalized','Style','Text');
CL_text = uicontrol('Parent',f,'units','normalized','Style','Text');
CF_text = uicontrol('Parent',f,'units','normalized','Style','Text');
rho_text = uicontrol('Parent',f,'units','normalized','Style','Text');
len_text = uicontrol('Parent',f,'units','normalized','Style','Text');
rad_text = uicontrol('Parent',f,'units','normalized','Style','Text');
%Uicontrols devoted to editable fields:
m_edit = uicontrol('Parent',f,'units','normalized','Style','edit');
P_edit = uicontrol('Parent',f,'units','normalized','Style','edit');
FS_edit = uicontrol('Parent',f,'units','normalized','Style','edit');
CD_edit = uicontrol('Parent',f,'units','normalized','Style','edit');
CL_edit = uicontrol('Parent',f,'units','normalized','Style','edit');
CF_edit = uicontrol('Parent',f,'units','normalized','Style','edit');
rho_edit = uicontrol('Parent',f,'units','normalized','Style','edit');
len_edit = uicontrol('Parent',f,'units','normalized','Style','edit');
rad_edit = uicontrol('Parent',f,'units','normalized','Style','edit');
% MLG Cell array strat.
ui_texts = {m_text,P_text,FS_text,CD_text,CL_text,CF_text,rho_text,...
len_text,rad_text};
strings = {'mass','power','Front Section','Drag Coefficient',...
'Lift Coefficient','Friction Coefficient','Air Density','Length',...
'Radius'};
ui_edits = {m_edit,P_edit,FS_edit,CD_edit,CL_edit,CF_edit,rho_edit,...
len_edit,rad_edit};
defaults = {'10','100','0.5','0.05','1.2','0.0005','1.225','5','3'};
xi = 0.05; %X-coordinate of Bottom-Left corner.
yi = 0.85; %Y-coordinate of Bottom-Left corner.
width = 0.15; %Width of object.
height = 0.075; %Height of object
deltaH = 0.3; %Horizontal Spacing between objects.
deltaV = -0.25; %Vertical Spacing between objects.
offset = 0.15;
rows = 3; %Number of rows.
cols = 3; %Number of columns.
k = 0; % Counter for number of uicontrols that populate grid pattern.
for i = 1:rows
for j = 1:cols
k = k + 1;
if k <= length(ui_texts)
x = xi + deltaH*(j-1);
y = yi + deltaV*(i-1);
xo = x + offset; %horizontally offset the editable field.
set(ui_texts{k},'Position',[x, y, width, height],'String',strings{k})
set(ui_edits{k},'Position',[xo, y, width, height],'String',defaults{k})
end
end
end
%Uicontrol for output object:
out = uicontrol('Parent',f,'Style','text','units','normalized',...
'position',[xo-deltaH,y+deltaV,width,height],'String','Val = ');
%Uicontrol for button execution:
exe = uicontrol('Parent',f,'Style','pushbutton','units','normalized',...
'position',[xo-2*deltaH,y+deltaV,width,height],'String','Calculate',...
'Callback',{#computations,ui_edits,out});
%Function executed by the "exe" uicontrol.
function computations(obj,~,ui_edits,out)
no_inputs = length(ui_edits);
summation = 0;
for i = 1:no_inputs
in = get(ui_edits{i},'String');
val = str2double(in);
summation = summation +val;
end
set(out,'String',['Val = ',num2str(summation)])
end
The above script produces:
Here's the gist:
The figure is a graphical object needed to contain the editable fields and text descriptions.
The fields are objects that enable user input (These are uicontrol objects).
The static texts establish to the user the correspondence between field and input (These are also uicontrol objects).
The button is an object that executes the callback function (also a uicontrol).
The callback function is a user-defined function that collects inputs from uicontrol objects and uses them to run code.
In this example, the "calculate" button simply sums up all the numeric values of the inputs. For your applications, you must modify the programmer-defined function at the end to suit your needs.
This kind of GUI lets users input information as strings. Said information must be converted from the string type to the double type. Other GUI objects allow for direct numerical inputs (such as sliders or buttons with underlying logical structure, ie. "if this button, then this value"). When working with editable fields, the programmer must be conscious that the inputs are strings until run through a function like str2double.
Other nuances with GUI's:
Every object that the programmer intends to be interactive must have an associated programmer-defined function.
In this case, the only interactive object is the button.
Personally, my biggest challenge when making MATLAB GUI's is dealing with the positioning of the UI controls. I have used a few for loops and cell arrays to generate the grid pattern for the texts and buttons. This is quality of life on my part, more elaborate GUI's may require more sophisticated coding.
MATLAB has a feature called "GUIDE" which helps automate the uicontrol generation. This is worth a try if the programatic method I have shared is too tedious.

Related

How can a repeated pattern of struct assignment be condensed to a simpler expression?

I am writing code to do finite volume method operations on an NI*NJ size grid, where all the values are stored in a 1D array, defined as in comment section 1. The double loop moves through each grid element and performs calculations for the grid element, which are various functions of the surrounding elements (to the east, north, west and south).
I have an example code block here below, and my challenge is to clean up the code to eliminate some of the repitition I see. I wonder if somehow employing classes that contain properties e, w, n, s would be a beneficial approach to the problem, although I'm not well-versed in class constructions.
As can be seen, I have defined an anonymous function and then called it to calculate some value at each side (e,w,n,s). This pattern recurs numerous times throughout the entire code and it would be nice to have some way of combining such code blocks of quadruplets into one line of simple code.
arrayRe = initialize; % goes from 1:(NI*NJ)
arrayRn = initialize;
for i = 2:NI-1
for j = 2:NJ-1
% 1. coordinates (P is the center; all others are located around it (north, east, west, south))
iP = NI*(j-1) + i;
iE = iP + 1;
iW = iP - 1;
iN = iP + NI;
iS = iP - NI;
% 2. interpolation ratios
R.e = arrayRe(iP);
R.w = 1 - arrayRe(iW);
R.n = arrayRn(iP);
R.s = 1 - arrayRn(iS);
% interpolate a given value
interpolatevalue = #(array, R, i) ...
R*array(i) + (1-R)*array(iP);
% ax calc
ax_e = interpolatevalue(axArray, R.e, iE);
ax_w = interpolatevalue(axArray, R.w, iW);
ay_n = interpolatevalue(ayArray, R.n, iN);
ay_s = interpolatevalue(ayArray, R.s, iS);
% bx calc
bx_e = interpolatevalue(bxArray, R.e, iE);
bx_w = interpolatevalue(bxArray, R.w, iW);
by_n = interpolatevalue(byArray, R.n, iN);
by_s = interpolatevalue(byArray, R.s, iS);
end
end
Something I tried was to cut out the 4 lines of a particular block and put them into a function m file that returned a struct. For instance:
function a = axycalc(axArray, ayArray, R, iStruct, interpfun)
iE = iStruct.e ;
iW = iStruct.w ;
iS = iStruct.s ;
iN = iStruct.n ;
a.x_e = interpfun(axArray, R_e, iE);
a.x_w = interpfun(axArray, R_w, iW);
a.y_n = interpfun(ayArray, R_n, iN);
a.y_s = interpfun(ayArray, R_s, iS);
end
And then calling the function as follows:
a = axycalc(axArray, ayArray, R, struct('e',iE,'w',iW,'n',iN,'s',iS), #interpolatevalue)
I don't know whether this adds much in terms of readability, and creates a large amount of other m 10-line m files that I then need to keep track of. Is there a better way?

Define Matlab Class with Attributes of Another Class

I'm trying to construct a class framework for a neural network (ANN) in Matlab by defining a Node class:
function NodeObject = Node(Input, Weights, Activation)
Features.Input = [];
Features.Weights = [];
Features.Activation = [];
Features.Input = Input;
Features.Weights = Weights;
Features.Activation = Activation;
NodeObject = class(Features, 'Node');
Where here the input is an integer (expected number of inputs), Weights is a vector of length Features.Input, and Features.Activation is a string that references an activation function stored in the methods.
What I want to do next is construct a cell array of nodes and define a Network class based on this array:
function Architecture = Network(NodeArray)
ANN.Layers = []; % Number of layers in architecture
ANN.LayerWidths = []; % Vector of length ANN.Layers containing width of each layer
ANN.NodeArray = []; % Original input is cell array with layers in ascending order (input at top, output at bottom) with nodes in each row.
ANN.InputSizes = [];
% Find number of layers
ANN.Layers = length(NodeArray(:,1));
% Find width of each layer
Widths = zeros(ANN.Layers,1);
for i = 1:length(Widths)
Widths(i) = length(NodeArray(:,i));
end
ANN.LayerWidths = Widths;
% Pass NodeArray to Network class
ANN.NodeArray = NodeArray;
% Construct cell of input sizes
InputSizes = [];
for i = 1:ANN.Layers
for j = 1:Widths(i)
InputSizes(i,j) = NodeArray{i,j}.Inputs;
end
end
ANN.InputSizes = InputSizes;
Architecture = class(ANN, 'Network');
The attribute ANN.InputSizes tries to extract the attributes from a Node object, but my code doesn't allow me to do this. How do I amend this problem, or do you recommend a different architecture to this problem all together? Currently my classes Node and Network are contained in two separate directories, but I have a feeling that there is something else I'm not seeing. For reference, I have absolutely no prior experience in OOP, and from what I've gathered it seems Matlab is not the best environment in which to implement these structures. At the moment though I don't have enough experience to implement this type of framework in another language.
Your InputSizes isn't a cell. You initialize it to a double array ([]) and then fill it as such. If you want to define it as a cell you should do something like
InputSizes = cell();
for i = 1:ANN.Layers
for j = 1:Widths(i)
InputSizes{i,j} = NodeArray{i,j}.Inputs;
end
end
All of that, you should really look into defining your classes using a classdef file, as it is much more straightforward.
Node.m
classdef Node < handle
properties
Inputs
Weights
Activation
end
methods
function obj = Node(inputs, weights, activation)
obj.Inputs = inputs;
obj.Weights = weights;
obj.Activation = activation;
end
end
end
Network.m
classdef Network < handle
properties
NodeArray
end
properties (Dependent)
Layers
LayerWidths
InputSizes
end
methods
function obj = Network(nodes)
obj.NodeArray = nodes;
end
function result = get.Layers(obj)
result = size(obj.NodeArray, 1);
end
function result = get.LayerWidths(obj)
result = size(obj.NodeArray, 2);
end
function result = get.InputSizes(obj)
result = arrayfun(#(x)x.Inputs, obj.NodeArray, 'uniformoutput', 0);
end
end
end
As far as suggesting a better layout, that's subject to the opinion of the individual developer.

Save multiple GUIdata sets to listbox and then load them again in MATLAB

I have built a calculation software in MATLAB GUIDE. What I want to do is to fill out all my calculation data in different edit fields and some dropdowns and when I press calculate a "listbox" should be populated with the text "Calculation 1".
If I then change some data in some of the input fields and press calculate again I want to populate the listbox with "Calculation 2" beneath "Calculation 1" etc...
But then I would want to be able to highligt "calculation 1" again in the listbox and press a "load input parameters" button to populate all the edit input fields with the data that was used when "calculation 1" was calculated.
I have looked all over the place for this but can't find anything.
//Robin
Here is some code which is very basic but performs what you are looking for. There are a lot of tweaks possible but I'll let you play around with them. I put explanations as comments. You can copy past into Matlab and change the GUI as you like.
function CalculatorGUI
% Dummy GUI to calculate A*B + C...
clc
clear
close all
global hTestResult hEditA hEditB hEditC CalculationList CalculationStrings
% Set up controls
CalculationList = nan(10,3); % Create array in which we store the parameters. 1st column is A, 2nd is B and 3rd is C.
CalculationStrings = cell(10,1);
ScreenSize = get(0,'ScreenSize');
hFig = figure('Visible','off','Position',[ScreenSize(3)/2,ScreenSize(4)/2,450,285]);
hCalculateButton = uicontrol('Style','Pushbutton','Position',[350,150,80,30],'String','Calculate!','Callback',#CalculateCallback);
hTitle = uicontrol('Style','Text','Position',[100,250,100,25],'String','Calculate (A * B) + C');
hTextA = uicontrol('Style','Text','Position',[125,220,70,25],'String','A');
hEditA = uicontrol('Style','Edit','Position',[125,200,70,25],'String','1');
hTextB = uicontrol('Style','Text','Position',[200,220,70,25],'String','B');
hEditB = uicontrol('Style','Edit','Position',[200,200,70,25],'String','2');
hTextC = uicontrol('Style','Text','Position',[275,220,70,25],'String','C');
hEditC = uicontrol('Style','Edit','Position',[275,200,70,25],'String','3');
hResultHeader = uicontrol('Style','Text','Position',[350,220,70,25],'String','Result');
hTestResult = uicontrol('Style','Text','Position',[350,200,70,25],'String','');
hTextCalcu = uicontrol('Style','Text','Position',[100,140,100,50],'String','Calculations');
hListCalcu = uicontrol('Style','Listbox','String','','Position',[100,120,200,50],'max',10,...
'min',1,'Callback',#ListBox_Callback);
set(hFig,'Visible','on')
%======================================================================
%======================================================================
% Callback of the pushbutton
function CalculateCallback(~,~)
% Get the values in the edit boxes. There is no ckechup to make
% sure the user entered a correct value...
A = str2double(get(hEditA,'String'));
B = str2double(get(hEditB,'String'));
C = str2double(get(hEditC,'String'));
Calculation = A*B+C;
% Display the result.
set(hTestResult,'String',sprintf('The result is %0.2f',Calculation));
% Find how many calculations have been performed and append the
% parameters to the current list
[x,~] = find(~isnan(CalculationList));
CurrentCalc = numel(unique(x)); % Get number of rows which are NOT NaNs.
CurrentValues = [A B C];
CalculationList(CurrentCalc+1,:) = CurrentValues;
CurrentString = sprintf('A = %0.2f B = %0.2f C = %0.2f',A,B,C);
% Assign the parameters to the cell array.
CalculationStrings(CurrentCalc+1) = {CurrentString};
set(hListCalcu,'String',CalculationStrings)
end
% Listbox callback: When the selection changes, the corresponding
% parameters in the edit boxes change.
function ListBox_Callback(~,~)
SelectedCalc = get(hListCalcu,'Value');
CalculationList(SelectedCalc,1)
CalculationList(SelectedCalc,2)
CalculationList(SelectedCalc,3)
set(hEditA,'String',CalculationList(SelectedCalc,1));
set(hEditB,'String',CalculationList(SelectedCalc,2));
set(hEditC,'String',CalculationList(SelectedCalc,3));
end
end
The actual interface looks like this:
Of course you can make it much more complex, but this should help you get started and understand how the different callbacks work together. Have fun!

How do I format a MATLAB figure text annotation into rows and columns?

I have a script which fits some optical data to a sum of Lorentzian oscillators, and then spits out a figure with the original data and the fit. I would also like to include a text annotation with a table of the fitting parameters, but cannot figure out how to get rows and columns in my text box.
Each peak has 3 parameters and then there are 3 more global fitting parameters. My first try was to do this:
ParamTableLabels = {'\omega_p (cm^{-1})', '\omega_0 (cm^{-1})', '\Gamma (cm^{-1})'};
ParamTableVals = num2cell(Ef);
ParamTableLabels2 = {'d (\mu{m})','\epsilon_\infty','Scale'};
ParamTableVals2 = {ThickFit,EinfFit,ScaleFit};
ParamTable = vertcat(ParamTableLabels,ParamTableVals,ParamTableLabels2,ParamTableVals2);
where Ef is my 3xN matrix of fitting parameters. After generating my figure, I try to place the table in my plot at a suitable set of coordinates X,Y using:
text(X,Y,ParamTable)
and the result is a single column of text, no rows. My second attempt, which sort of works is to break up each column:
text(X, Y,ParamTable(:,1));
text(X+dX, Y,ParamTable(:,2));
text(X+2*dX,Y,ParamTable(:,3));
This almost works, but the subscripts in the labels throw off the vertical alignment of the last few rows, and it takes an undue amount of tinkering to get the spacing correct. I'm spending more time trying to get the text box to look right than to do the actual modelling.
How can I programatically format a block of text, containing both labels and variables, into rows and columns, and then use it as a text annotation in a figure with minimal user tinkering?
This is a not well supported using basic commands. But you can at least save yourself the trouble of guessing the subsequent X positions by making Matlab do the work for you.
The key is the "Extent" read-only parameter attached to a text block. Use docsearch text properties to see the documentation.
Putting this into some code:
padFraction = 0.1; %This is roughly the unitless padding between columns, as a fraction of the column on the left.
curX = X; %Leave the initial X variable unchanged
%For each text block column, add the text block, get the extent, and adjust curX
h = text(curX, Y,ParamTable(:,1));
curExtent = get(h, 'Extent');
curX = curExtent(1) + curExtent(3)*(1+padFraction);
h = text(curX, Y,ParamTable(:,2));
curExtent = get(h, 'Extent');
curX = curExtent(1) + curExtent(3)*(1+padFraction);
text(curX,Y,ParamTable(:,3));
The full script used to generate/test is below:
ParamTableLabels = {'\omega_p (cm^{-1})', '\omega_0 (cm^{-1})', '\Gamma (cm^{-1})'};
Ef = round(rand(10,3)*100);
ParamTableVals = num2cell(Ef);
ParamTableLabels2 = {'d (\mu{m})','\epsilon_\infty','Scale'};
ParamTableVals2 = {'ThickFit','EinfFit','ScaleFit'};
ParamTable = vertcat(ParamTableLabels,ParamTableVals,ParamTableLabels2,ParamTableVals2);
X = 1; Y = 1.1;
%Put something in the plot
figure(1); clf; hold on;
plot(-10:10, randn(21,1)*20,'.');
codeblock = 3;
switch codeblock
case 1
text(X,Y,ParamTable)
case 2
dX = 3;
text(X, Y,ParamTable(:,1));
text(X+dX, Y,ParamTable(:,2));
text(X+2*dX,Y,ParamTable(:,3));
case 3
padFraction = 0.1;
curX = X;
h = text(curX, Y,ParamTable(:,1));
curExtent = get(h, 'Extent');
curX = curExtent(1) + curExtent(3)*(1+padFraction);
h = text(curX, Y,ParamTable(:,2));
curExtent = get(h, 'Extent');
curX = curExtent(1) + curExtent(3)*(1+padFraction);
text(curX,Y,ParamTable(:,3));
end

N-th largest component of bwconncomp including the background

My question has two parts. First one is:
How can I include the background as a component in the bwconncomp function, because it's default behavior doesn't include it.
Also, and this is my other question is, how can I select the n-th largest component based on what I get by using bwconncomp.
Currently I was thinking about something like this, but that doesn't work :P
function out = getComponent(im,n)
CC = bwconncomp(im,4);
%image is an binary image here
numPixels = cellfun(#numel,CC.PixelIdxList);
sortedPixels = sort(numPixels,'descend');
w = sortedPixels(n);
[largest, index] = find(numPixels==w);
im(CC.PixelIdxList{index}) = 0;
out = im;
But that doesn't work at all. But im not too sure what the CC.PixelIdxList{index} does, is it just changing elements in the array. I also find it kinda vague what exactly PixelIdxList is.
To find the background, you can use 'not' operation on the image
'PixelIdxList' is not what you need. You need the 'Area' property.
function FindBackgroundAndLargestBlob
x = imread('peppers.png');
I = x(:,:,2);
level = graythresh(I);
bw = im2bw(I,level);
b = bwlabel(bw,8);
rp = regionprops(b,'Area','PixelIdxList');
areas = [rp.Area];
[unused,indexOfMax] = max(areas);
disp(indexOfMax);
end
Update:
You can do it with bwconncomp as well:
function FindBackgroundAndLargestBlob
x = imread('peppers.png');
I = x(:,:,2);
level = graythresh(I);
bw = im2bw(I,level);
c = bwconncomp(bw,4);
numOfPixels = cellfun(#numel,c.PixelIdxList);
[unused,indexOfMax] = max(numOfPixels);
figure;imshow(bw);
bw( c.PixelIdxList{indexOfMax} ) = 0;
figure;imshow(bw);
end
Which will give the following results: