Chapter 6 – Variable Types (Classes) and Functions

 

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.

 

Variable Types (Classes)

 

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,

 

clear all

str = 'Every good boy does fine.';  

 

Defines the variable str as a string of characters, while

 

x= 1:5:100;  

 

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:

 

class(str)  

 

ans =

char  

 

The variable str is of class 'char', which means it's a list of characters. 

 

class(x)  

 

ans =

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:

 

bytebits = 2^8  

 

bytebits =

   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:

 

doublebits = bytebits^8  

 

doublebits =

  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:

 

whos  

 

  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:

 

clear all

x = rand(10000,10000);

y = rand(10000,10000);  

 

??? Error using ==> rand

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:

 

x = uint8 (1:10)

class(x)  

 

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:

 

sin(x)  

 

??? Undefined function or method 'sin' for input arguments of type

'uint8'.  

 

Some operations are allowed, like addition and subtraction, but screwy things can happen:

 

x – 5  

 

ans =

    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:

 

help class  

 

 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.

 

Logical variables

 

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  

 

ans =

     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:

 

tired = logical(1);

hungry = logical(1);

 

tired | hungry  

 

ans =

     1  

 

xor(tired,hungry)  

 

ans =

     0  

 

Structures

 

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.id = 213;

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:

 

subject  

 

subject =

        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:

 

subject.age  

 

ans =

    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).id = 301;

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(1).data = rand(1,5);

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:

 

count = 0;

for i=1:length(subject)

  if subject(i).hand == 0

count = count+1;

     meanData(count) = mean(subject(i).data);

end

end

meanData

 

 

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).id = 23;

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.human = subject;

allSubjects.animal = animalSubject  ;

 

allSubjects =

     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:

 

allSubjects.human(3).gender  

 

ans =

     0  

 

Cell Arrays

 

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:

 

myCell{2} = 2.^(1:5);  

 

The third element could be a structure, like the one from the last section:

 

myCell{3} = allSubjects;  

 

Just as we defined the elements of the cell array with curly brackets, we use curly brackets to access them too:

 

myCell{3}  

 

ans =

     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 = myCell{2};

newVector(3)  

 

ans =

     8  

 

Another way which is shorter but a bit unconventional is to use a curlybrackets followed by round brackets:

 

myCell{2}(3)  

 

ans =

     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'};  

 

 

Functions

 

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.

 

Why use functions?

 

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.

 

SimpleFunction

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.

 

my_out=SimpleFunction(3)  

 

my_out =

    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.

 

inval=5;

my_out=SimpleFunction(inval);

my_out  

 

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.

myinput1=3;

myinput2=4;

[myoutput1, myoutput2]=SimpleFunction2(myinput1, myinput2)  

 

myoutput1 =

    12

myoutput2 =

    0.7500  

 

Because we used the '.*' and './' commands for multiplying and dividing, SimpleFunction2 can take vectors as well as inputs:

 

vect1 = [1:5];

vect2  = [2:2:10];

[myoutput1, myoutput2]=SimpleFunction2(vect1, vect2) 

 

myoutput1 =

     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.

 

a=3.45;

b=round(a)  

 

b =

     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:

 

mat = [1,2;3,4]

scaleMat(mat,[0,12])  

 

mat =

     1     2

     3     4

ans =

     0     4

     8    12  

 

Or by using the default range of zero to 1

 

scaleMat(mat)  

 

ans =

         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:

 

clear all

x = pi;  

exist('x','var')  

 

ans =

     1  

 

exist('y','var')  

 

ans =

     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 rtile=1:ntiles

  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));