This chapter covers two important topics that are part of any computer programming language. The first part of the chapter is about how variables come in all sorts of flavors, called 'variable classes' that allow you to organize your data in a more efficient and intuitive manner. The second part is about 'functions', which are little programs that you can write to effectively outsource procedures that you can then call from various programs. Proper use of these two topics will help to make your programs much easier to read and modify.
So far we've talked about two kinds of variables: strings and matrices. A string holds a list of characters and a matrix holds a list or array of numbers. Recall that the way you define the variable tells Matlab what type of variable it is. For example,
str = 'Every good boy does fine.';
Defines the variable str as a string of characters, while
Defines the variable x as a list of numbers. Matlab calls a variabiable's type it's 'class'. One way to see what class a variable is is with the function class:
char
The variable str is of class 'char', which means it's a list of characters.
double
The variable x is of class 'double'. A variable that is of class 'double' contains numbers that are 'double-precision floating point numbers'. This means that each number in the matrix uses up 8 'bytes' of memory.
A byte is a group of eight 'bits' of memory, where each bit represents a zero or one. So a single byte can represent integers between zero and 2^8, or:
256
A variable of class double has 8 bytes, sets of 8 bits and can therefore represent integers between zero and 256^8, which is:
1.8447e+019
This number is so big that it's represented in scientific notation. It's about 18,447,000,000,000,000,000. As you've seen, a variable of class double can represent decimal values as well as integers – it's just a matter of where you put the decimal point. The important thing to know is that a variable of class double can represent a huge range of numbers. For standard computers, this is the maximum amount of memory a variable can use up. It's also the default class of a variable in Matlab – whenever you define a variable that contains numbers, the variable will be of class double.
Another way that we can ask Matlab to tell us the class of variables we have in memory is with the whos command, which we've seen before:
Name Size Bytes Class Attributes
ans 1x6 12 char
bytebits 1x1 8 double
doublebits 1x1 8 double
str 1x25 50 char
x 1x20 160 double
The fourth column tells us each variable's 'class'.
The command whos also tells us how much memory is devoted to each variable. You can see above that the variable x uses up 160 bytes of memory. That's because it contains 20 numbers that each use up 8 bytes. The variable str has 25 characters uses up 50 Bytes of memory. I'll bet you've already figured out that single character uses up two bytes of memory.
It is a unique quirk in a programming language that the default variable type is double. This is usually a good thing; it is generally useful to be using the most precise type of variable because then you don't have to worry much about rounding errors and other sorts of imprecisions in your calculations. But the drawback is that doubles use up lots of memory. For example, if I want to generate two huge matrices of random numbers:
x = rand(10000,10000);
y = rand(10000,10000);
Out of memory. Type HELP MEMORY for your options.
I run into memory problems on my computer, even though I cleared all the variables in memory beforehand with the clear all command.
Fortunatlely, there are other classes of variables that don't use up as much memory – with the limitation that they also can't represent as many unique numbers and therefore don't carry as much precision. Often that's OK. For example, a grayscale image presented on your computer screen can contain pixels with values ranging between zero and 255 (unless you have special hardware). So you don't need any more than a single byte (eight bits) of memory for every pixel on the screen.
For example, the class of variable that uses a single byte for each element is called uint8, for 'Unsigned 8-bit integer'. Variables of class uint8 contain positive (unsigned) integers between 0 annd 255. You can define a matrix of zeros with class uint8 this way (on't forget the semicolon!)
img = zeros(1000,1000,'uint8');
You can also turn the class of any variable into a uint8 with the command of the same name:
class(x)
1 2
3 4 5
6 7 8
9 10
ans
=
uint8
Be careful with uint8s. Matlab doesn't allow certain calculations with them:
??? Undefined function or method 'sin' for input
arguments of type
'uint8'.
Some operations are allowed, like addition and subtraction, but screwy things can happen:
0 0 0 0 0 1 2 3 4 5
Because uint8s can not be negative, anything below zero gets truncated to zero. This sort of problem is tough to debug, so use uint8s carefully!
There is a whole host of classes for variables. You can see a list with the command:
CLASS Return
class name of object.
S = CLASS(OBJ) returns the name of the class of object OBJ.
Possibilities are:
double --
Double precision floating point number array
(this
is the traditional MATLAB matrix or array)
single --
Single precision floating point number array
logical --
Logical array
char --
Character array
cell --
Cell array
struct -- Structure array
function_handle -- Function Handle
int8 -- 8-bit signed integer array
uint8 -- 8-bit unsigned integer array
int16 -- 16-bit signed integer array
uint16 -- 16-bit unsigned
integer array
int32 -- 32-bit signed integer array
uint32 -- 32-bit unsigned
integer array
int64 -- 64-bit signed integer array
uint64 -- 64-bit unsigned
integer array
<class_name> --
MATLAB class name for MATLAB objects
<java_class> --
Java class name for java objects
%Example 1:
Obtain the name of the class of value PI
name = class(PI);
%Example 2:
Obtain the full name of a package-based java class
import java.lang.*;
obj
= String('mystring');
class(obj)
For classes
created without a CLASSDEF statement (pre-MATLAB version
7.6 syntax),
CLASS invoked within a constructor method creates an
object of type 'class_name'. Constructor methods are functions saved
in a file named <class_name>.m
and placed in a directory named
@<class_name>. Note that 'class_name'
must be the second argument to
CLASS. Uses of CLASS for this purpose are shown
below.
O = CLASS(S,'class_name') creates an object of class 'class_name'
from the structure S.
O =
CLASS(S,'class_name',PARENT1,PARENT2,...) also
inherits the
methods and fields of the parent objects PARENT1, PARENT2,
...
O = CLASS(struct([]),'class_name',PARENT1,PARENT2,...),
specifying
an empty structure S, creates an object that inherits the
methods and
fields from one or more parent classes, but does not have
any
additional fields beyond those inherited from the parents.
See also
ISA, SUPERIORTO, INFERIORTO, CLASSDEF, STRUCT.
Overloaded
methods:
scribehandle/class
serial/class
doc class
We'll now discuss some classes that you might find useful.
The tiniest variable is of class logical. A logical is a single bit, so it contains either a zero or 1. These are used in statements with 'logical operators' like and (&) and or (|) where zero is false and 1 is true.
a = logical(1);
b = logical(0);
a | b
a & b
1
ans
=
0
By the way, Engligh can be ambiguous with the word 'or'. For example, if you say 'Bring me your tired or hungry', you'd probably include those who are both tired and hungry. But if you say 'Eat or be eaten.' you probably don't mean both eat and be eaten. Computer languages hate imbiguity, so to deal with this there's another kind of 'or'. The logical operator 'xor' (for 'exclusive or' ) is true if either a or b is true but not both:
hungry = logical(1);
tired | hungry
1
0
Sometimes it is useful to clump related variables together. For example, suppose that you want to organize information about your experimental subjects, such as id number, age, handeness, and gender into a single variable. This can be done using a structures, which belong to a class of variables called struct. A structure is a variable with associated sub-variables called fields. Here's how to define the variable of class struct:
subject.age = 28;
subject.hand = 0;
subject.gender = 1;
We've defined a single variable subject that has fields age, hand and gender. Fields are defined using the period '.' To see the whole structure, type the name of the variable without a semicolon:
id: 213
age: 28
hand: 0
gender: 1
Here we'll define handeness as 0 or 1 for right and
left, and gender as 0 or 1 for male and female. To see the value of a single
field, type the name of the structure and field without a semicolon:
28
Like any variable, a variable of class struct can be an array or vector. So additional subjects can be entered this way:
subject(2).age = 25;
subject(2).hand = 0;
subject(2).gender = 0;
subject(3).age = 43;
subject(3).id = 200;
subject(3).hand = 1;
subject(3).gender = 0;
We can associate data with each subject with additional fields. These fields can be vectors or arrays:
subject(2).data = rand(1,7);
subject(3).data = rand(1,4);
(We don't recommend that you make up and publish your own data like this)
Once the subject data is entered this way, we can do things like calculate the data means for just the right-handed subjects:
for i=1:length(subject)
if subject(i).hand
== 0
count = count+1;
meanData(count) = mean(subject(i).data);
end
end
meanData
0.3933 0.3834
You can go crazy and have fields of structures be structures themselves. First. let's set up a new structure to hold data for two animal subjects:
animalSubject(1).data = rand(1,5);
animalSubject(2).id = 46;
animalSubject(2).data = rand(1,4);
We can create a new structure that has the human and animal structures as fields:
allSubjects.animal = animalSubject ;
human: [1x3 struct]
animal: [1x2 struct]
The structure allSubjects has two fields, one is a stucture array human that holds information for the numan subjects, and the other is a structure array animal that holds information for the animal subjects. We can access, for example, the third human subject's gender like this:
0
Another way to group together variables is with a cell array. Cell arrays are matrices with elements can have any class. Just as a matrix or vector is a list of numbers, a variable of class cell is a list of variables. Instead of using round brackets '(' and ')' to access the elements of a cell array, we use curly brackets '{' and '}'. For example, to put a string of characters in the first element of a cell array, we can do this:
myCell{1} = 'this is a string of characters.';
Other elements of this cell array can be of any type. Let's make the second element a vector of numbers:
The third element could be a structure, like the one from the last section:
Just as we defined the elements of the cell array with curly brackets, we use curly brackets to access them too:
human: [1x3 struct]
animal: [1x2 struct]
Suppose we want to know the third value of the second element of this cell array. One way is to pull out the second element as a new variable, and then access its third value:
newVector(3)
8
Another way which is shorter but a bit unconventional is to use a curlybrackets followed by round brackets:
8
Cell arrays are probably most useful for holding lists of character strings because each element can have a different length:
days = {'Monday','Tuesday','Wednesday','Thursday','Friday'};
A function is a self-contained block of commands that performs a coherent task of some kind. It’s like outsourcing in business - sending a task away to be performed somewhere else. We send materials to Singapore, they make the sneakers, they send back the sneakers and we really don’t know much about what happened in Singapore. When you send a task to a function, you give the function all the variables it needs to know, and it returns the variables that you want. The calculations and variables within the function are hidden.
There are three main reasons for writing functions.
Whenever you write code, think about whether you will ever need that particular piece of code again or whether you will be using that code more than twice in your current program. If either of those things are the case, the "correct" thing to do as far as programming style is concerned is to make it a function. Even if it’s just two lines – it will be easier finding a function then rummaging through old code for those precious two lines.
Here is a very simple function.
function out=SimpleFunction(in)
% a very simple function
% written by if 4/2007
out=10*in;
The first line of your m-file defines this piece of code as a function. The terminology is that any variables you want to send into your function (the raw materials for sneakers) go inside the brackets after the function name (in). Any variable you want to return from the function (the finished sneakers) comes before the equals sign (out). Variables sent in and out of functions are also called input and output arguments.Inside the function all that is happening is that your input is being multiplied by 10, and that value is your output.
This is how you use SimpleFunction.m. Type the following into your command line.
30
Provided SimpleFunction.m is in your path, you should now have a variable my_out which will be 30.
Note that the names of the variables
returned by SimpleFunction don't have to match the
names of the variables within SimpleFunction (the
output of the function is called out inside the function and my_out at the command
window).
The analogy would be that the
outsourcing factory might call the sneaker the no.34532 while the company sending the materials and receiving the
sneaker might call it the SuperDuperRebox™.
The same is true for sending in
variables, as you'll see from the example below.
my_out=SimpleFunction(inval);
my_out
50
Functions can take more than one variable in, and send more than one variable out.
Create a new m-file:
function
[output1, output2]=SimpleFunction2(input1, input2)
%
% another very simple function
%
% written by if 4/2007
output1=input1.*input2;
output2=input1./input2;
Then in the command window try the following.
myinput2=4;
[myoutput1, myoutput2]=SimpleFunction2(myinput1, myinput2)
12
myoutput2 =
0.7500
Because we used the '.*' and './' commands for multiplying and dividing, SimpleFunction2 can take vectors as well as inputs:
vect2 = [2:2:10];
[myoutput1, myoutput2]=SimpleFunction2(vect1, vect2)
2 8
18 32 50
myoutput2 =
0.5000 0.5000 0.5000 0.5000 0.5000
You have been actually using functions
already - most of the commands you have been using already are functions
written by the programmers at Mathworks. For example,
round – you give
the command round any number as the input argument and it returns the closest
integer as the output argument.
b=round(a)
3
scaleMat
Next we'll write a program that will be useful later in this book. The function scaleMat will scale the values of a matrix to range between to preset values. The first argument sent to the function is the unscaled matrix. The next two values are the min and max values for the scaled matrix that is returned. If no min and max values are sent, then the matrix is scaled by default to be between 0 and 1.
function outMat = scaleMat(inMat,range)
%outMat = scaleMat(inMat,[range])
%
%Scales
a matrix to a new range of values.
%
%Inputs:
% inMat: unscaled matrix
% range:
1x2 vector containing the low
and high values for the
% scaled matrix. Default is [0,1].
%
%Output: scaled matrix with minimum value range(1), and max value
% range(2)
%Written
9/08 by G.M. Boynton and I. Fine
%Deal
with default values for range if none are provided
if ~exist('range','var')
range = [0,1];
end
%minimum
value of input matrix
minVal = min(inMat(:));
%max
value of input matrix
maxVal = max(inMat(:));
%scale the matrix to range between zero and 1.
outMat =
(inMat-minVal)/(maxVal-minVal);
%scale
this new matrix to values in 'range'
outMat = (range(2)-range(1))*outMat+range(1);
We can call this function in two ways. First, by defining the new range:
scaleMat(mat,[0,12])
1 2
3 4
ans
=
0 4
8 12
Or by using the default range of zero to 1
0 0.3333
0.6667 1.0000
This example illustrates how to set default values for parameters if the user does not provide them. It relies on the function exist. When the second argument into exist is the string 'var', the function returns a logical variable (0 or 1) depending on whether the variable exists in memory. For example:
x = pi;
1
0
The function exist returned a 1 for the variable x and a zero for the variable y because y does not exist in memory.
Note that in the help documentation for the function scaleMat, the input variable range is placed in square brackets. This is a convention for documentation to let the user know that this variable is optional and will be set to a default value if not provided.
Now let's create a new version of SineInAperture that uses a new function called 'MakeSineInAperture' to create the windowed sinusoids. We'll use the vector x, the spatial frequency (sf), and the radius of the aperture (rad) as input arguments. The 2D image of the sinusoid is the output.
SineInAperture3.m
% SineInAperture3
%
% creates an image of apertured vertical sinusoids in a
% tiled array
% information about the sinusoids
clear all
close all
x=linspace(-pi, pi, 100);
sf=[3 6 9 12]; % spatial freq in cycles per image
rad=2;
%
creates a 100x100xlength(sf) matrix containing two 2D apertured
sinusoids,
% of different spatial
frequencies.
for s=1:length(sf)
sinewave2D(:, :, s)=MakeSineAperture(x, sf(s), rad);
end
% initialize the tilematrix by
filling it with zeros
imgsize=length(x);
ntiles=4;
sep=30;
tilesize=(ntiles*(imgsize+sep))+sep;
tilematrix=zeros(tilesize);
startpos=sep:sep+imgsize:length(tilematrix)-1;
for ctile=1:ntiles
tilematrix(startpos(rtile):startpos(rtile)+imgsize-1, startpos(ctile):startpos(ctile)+imgsize-1)=sinewave2D(:,
:, rtile);
end
end
imagesc(tilematrix);
colormap(gray(256))
axis off;
The function MakeSineAperture.m
function sinewave2D
= MakeSineAperture(xval, sf, radius)
% function that creates a vertical sinewave
windowed by a circular aperture
%
% takes as input arguments:
% xval: for
which the sinusoid is computed
% sf: the
spatial frequency of the sinusoid
% radius: the radius of the aperture in pixels
%
% returns as output the 2D sinusoidal grating image
onematrix=ones(size(xval));
sinewave=sin(xval*sf);
sinewave2D=(onematrix'*sinewave);
for r=1:length(xval)
for c=1:length(xval)
if xval(r).^2+xval(c)^2>radius^2
sinewave2D(r, c)=0;
end
end
end
insertGabor
Structures are useful for organizing and grouping variables for sending into functions. If a function takes in a lot of variables the command can be unweildy. But if you clump variables together into structures, the function calls can be much simpler. For example, in the last chapter we wrote a program that creates an oriented Gabor. Let's modify that code so that it adds a Gabor into an existing image, and we'll all the paramters that define the Gabor in a single structure. Here's the function - much of it should look familiar:
function outImg = insertGabor(params,inImg)
%Use
meshgrid to define matrices x and y that range from
-pi to pi;
[x,y] = meshgrid(linspace(-pi,pi,params.n));
if ~exist('inImg','var')
inImg
= zeros(params.n);
end
%Create
an oriented 'ramp' matrix as a linear combination of x and y. For
%example,
when params.orientation = 0, cos
= 1 and sin = 0 so ramp = x. When
%params.orientation is pi/2 then cos
= 0; sin = 1 and ramp = y.
ramp
= cos(params.orientation)*(x-params.center(1)) + sin(params.orientation)*(y-params.center(2));
%Sinusoidal
carrier is a sinusoid on the matrix 'ramp'
sinusoid
= params.contrast*sin(params.sf*ramp-params.phase);
%Gaussian
envelope:
Gaussian = exp( -((x-params.center(1)).^2+(y-params.center(2)).^2)/params.width^2);
%A
Gabor is the product of the sinusoid and the Gaussian
Gabor =
sinusoid.*Gaussian;
outImg = inImg +
Gabor;
Here's some code that calls the function we just wrote. Note how we define all of the parameters for the Gabor in a single structure, which is passed into the function insertGabor as a single variable.
params.n = 400; %size of image (pixels)
img = zeros(params.n);
%start with blank image
%Loop through five times, adding a new Gabor to the image each time
for i=1:5
%Pick random parameters for
each Gabor
params.center
= 2*pi*(rand(1,2)-.5);
params.orientation
= pi*rand(1); %radians (pi/4 = 45 degrees)
params.width
= rand(1); %1/e
half params.width of Gaussian
params.sf
= 12*rand(1);
%spatial frequency of Sinewave carrier
(cycles/image)
params.phase
= 2*pi*rand(1); %spatial params.phase
of sinewave carrier (radians)
params.contrast
= rand(1); %params.contrast ranges from 0 to 1;
img =insertGabor(params,img);
end
%Show the image using the usual commands:
figure(1)
clf
%scale Gabor between 0 and 255
img = scaleMat(img,[0,255]);
image( img);
axis equal
axis off
colormap(gray(256));