myGUIDE

|

random notes, random thoughts

Archive for the ‘Howto’ Category

Splitting 4D Nifti file into a series of 3D files in Matlab

Saturday, November 12th, 2011

NIfTI-1 is a data format proposed by the NifTI (Neuroimaging Informatics Technology Initiative) DFWG (Data Format Working Group) that can be used for storing neuroimaging datasets (e.g. MRI, functional MRI, etc.). This format is adapted from the widely used ANALYZE 7.5 format from Mayo Clinic. A Nifti file consists of a header part containing the meta-data and the raw image data itself. The header and the image data can be stored together in the same file with an *.nii extension or can be split into two files with an *.hdr extension for the header and an *.img extension for the image data.

For functional MRI, it is not uncommon to store the dataset in a single 4D (3D space + time) Nifti file, especially when experiments involved several hundreds of image volumes. Just imagine the convenience of working with a single file as compared to several hundreds of files. However, there are also instances when it is convenient to manipulate datasets one volume at a time. In this case, having a single volume stored in a single file is very handy.

In this post, I will outline a simple Matlab script that can be used to extract the 3D images from the 4D NifTI file. The script uses some SPM functions such as spm_select()spm_vol(), spm_read_vols(), and spm_write_vol(). Type “help function_name” in Matlab for a description of each function. Be sure that SPM is in Matla’s path before using this script. Without further ado, here is the full function list:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function abk_4Dto3D(fname)
% function abk_4Dto3D(fname)
%
% Splits a 4D nifti file into a series of 3D nifti files.
% Needs SPM functions to work. If input file is fdata.nii,
% the output files will have filenames like fdata_001.nii,
% fdata_002.nii, etc.
 
if (nargin < 1)
    [fname,sts] = spm_select;
    if (sts == 0)
        fprintf('abk_4Dto3D: Operation cancelled.\n');
        return;
    end
end
 
vol = spm_vol(fname);
img = spm_read_vols(vol);
sz = size(img);
 
tvol = vol(1);
tvol = rmfield(tvol,'private');
tvol.descrip = 'generated by abk_4Dto3D.m';
 
[dn,fn,ext] = fileparts(fname);
 
for ctr=1:sz(4)
    tvol.fname = sprintf('%s%s%s_%.3d%s',dn,filesep,fn,ctr,ext);
    fprintf('Writing %s\n',tvol.fname);
    spm_write_vol(tvol,img(:,:,:,ctr));
end
fprintf('done.\n');
end

The script accepts a filename, given by the fname input parameter, of the 4D Nifti file. If fname is not specified, the script will prompt the user to select a file using spm_select(). Once the filename is specified, it will then read the 4D image data, and save it back one 3D image at a time.

Now to the code. Line 1 is simply the usual Matlab function declaration. This is followed by several lines of comments starting with the % character (lines 2 – 7). In line 9, the script checks if the function was called with an input parameter. If none was provided, it prompts the user to make a selection (line 10). It then checks if the user cancelled the operation (lines 11 – 14) and returns if true. Otherwise, it reads the header information using SPM’s spm_vol() function (line 17), loads the 4D data into img variable (line 18) using spm_read_vols(), and gets the image dimension (line 19).

In lines 21 – 23, the header information is copied into the tvol variable (line 21), which will be used later to save the images. The private field of tvol is then removed (line 22) and the header description is changed (line 23) to indicate the new file generator. In line 25, the filename is split into three variables dn, fn, and ext correspoding to the directory location, the base filename, and the extension, respectively.

In lines 27 – 31 is where the 4D image data is actually split into a series of 3D image files. Note how the variable tvol is repeatedly used, changing only the fname field each time. This is because the 3D images have basically the same header information.

Note that the same function could also be used to extract only certain volumes from the 4D nifti files with a slight modification. To implement this, we need to change the function declaration in line 1 into:

function abk_4Dto3D(fname,idx)

where the additional parameter idx is a vector containing the volume numbers to extract. We also need to change lines 27 – 31 as follows:

1
2
3
4
5
for ctr=1:length(idx)
    tvol.fname = sprintf('%s%s%s_%.3d%s',dn,filesep,fn,ctr,ext);
    fprintf('Writing %s\n',tvol.fname);
    spm_write_vol(tvol,img(:,:,:,idx(ctr)));
end

ctr now runs from 1 to the total number of volumes to extract given by the length of idx. The filenames will still be consecutively numbered from 1 to the length of idx (line 2). But this time, the 3D images that are saved are only the volumes specified in idx (line 4).

Have questions, leave a comment.

Creating a backup SVN repository

Sunday, October 23rd, 2011

This is a brief guide on how to setup a backup SVN repository using svnsync. I will assume that subversion is already installed and the master repository is located in the machine called remote.com and in the directory /home/ruser/master_repos directory.

1. Create the mirror repository mirror_repos/ using svnadmin command

[user@mycomp] svnadmin create mirror_repos

2. Go to hooks/ subdirectory in the newly created repository

[user@mycomp] cd mirror_repos/hooks

You should see several *.tmpl files. One of these files is pre-revprop-change.tmpl. Copy it to pre-revprop-change, that is, without the .tmpl extension

[user@mycomp] cp pre-revprop-change.tmpl pre-revprop-change

Open this file using any text editor. Change it to the following:

1
2
3
4
5
6
USER="$2"
 
if [ "$USER" = "username" ]; then exit 0; fi
 
echo "Only username can change revprops" >&2
exit 1

Change “username” to the actual user name. Save the file and exit. Make sure it is also executable.

[user@mycomp] chmod 755 pre-revprop-change

3. Initialize the mirror repository using the svnsync init command

[user@mycomp] svnsync init DEST_URL SOURCE_URL
Copied properties for revision 0

In this example, DEST_URL is file:///home/user/mirror_repos and SOURCE_URL is svn+ssh://ruser@remote.com/home/ruser/master_repos

This initialize the mirror repository and it is now ready to be populated.

4. Start synchronizing. Use svnsync sync command to do it:

[user@mycomp] svnsync sync file:///home/user/mirror_repos
ruser@remote.com's password: xxxxxx
Committed revision 1.
Copied properties for revision 1.
Transmitting file data ........
Committed revision 2.
Copied properties for revision 2.
Transmitting file data ....
:

In case you want to commit back to the master repository, you can do it by issuing the command svn switch –relocate FROM_URL TO_URL before committing the changes. To do this, you also need to have the same UUID between the mirror and master repository.

Get the UUID from the master repository and copy it to the mirror repository. In the system where the master repository is running, run svnadmin dump

[ruser@remote] svnadmin dump -r0 /home/ruser/master_repos | head -n 3 > saved-uuid

Load it in the mirror repository

[user@mycomp] svnadmin load --force-uuid /home/user/mirror_repos < saved-uuid

The mirror and master repositories should now have the same UUID.

That’s it!

Accessing Windows share in Linux

Monday, August 15th, 2011

There are several ways to do this. One is GUI-based. In Fedora Core, use the Places menu in the top panel. Within this menu item, click the ‘Connect to Server…’ sub-menu. This will popup the Connect to Server dialog box shown below.

Connect to Server

In the Service type, select Windows share. Then enter the Windows server name in the Server text field. You can also fill in the other optional information, such as the Share, Folder, User Name, and Domain Name fields. After providing the needed information, click the Connect button. If the share requires a password, you will be prompted to enter one in a dialog box shown below:

Password

Enter the password and select how long will you want the password to last, then click Connect. If successful, you will be connected to the specified Windows share. If not, an error will occur. Check if all the specified information are correct. Consult your system admin if you don’t know some of the required information.

The other alternative is to automount the share every time you login. To do this, you need root permission in your Linux system. First, create the directory where you want to mount the share (e.g., in /mnt/windows):

[root@mycomp] mkdir /mnt/windows

Then edit the /etc/fstab file and add the following lines.

//myserver/myshare   /mnt/windows   cifs   username=myname,password=mypass,uid=myuid,gid=mygid 0 0

Replace myname and mypass with the actual username and password for your Windows share, and myuid and mygid with the actual Linux user id (or username) and group id. You can also specify a full path to myserver, say, //myserver.mywindows.com/. Save fstab. Type

[root@mycomp] mount /mnt/windows

in your shell to immediately mount the share. If this works, the next time you login, the share will be automatically mounted.

That’s it. Have questions, drop a comment below.

 

Matlab scripting quick tip: Dual monitor setup

Sunday, July 31st, 2011

I have been using Matlab for almost all of my computing needs. It is very easy to program especially when dealing with matrices. It has also a rich library of graphical interface. You can easily build a highly interactive graphical user interface using its GUI development environment or GUIDE. If you are familiar with event-based programming, you will have no difficulty using GUIDE.

Here is a quick tip you might find useful when programming in Matlab.

Dual-Monitor Setup

If you have a dual-monitor setup (using two monitors to extend the screen) and wanted to display all graphics-related output in the other monitor, you can use the ‘MonitorPositions’ property of the root window (0) to get the coordinates and dimensions of the two monitors as seen by Matlab. The code will look like this:

>> pos = get(0,'MonitorPositions');

If Matlab detects your dual-monitor setup, pos is a 2×4 matrix with the first row corresponding to the coordinates and dimension of your first monitor and the 2nd row that of the second monitor. The first two elements is the x and y position, while the last two correspond to the width and height. So for example, to position your figure in the second monitor in full screen, you can simply call the figure command with the position property set to pos(2,:), that is,

>> hfig = figure('Position',pos(2,:));

To make your application robust, you must first check if you have a second monitor before using the above code or else you will get an ‘Index exceeds matrix dimensions‘ error. You can accomplish this using the following code:

pos = get(0,'MonitorPositions');
sz = size(pos);
if (sz(1) > 1)
  hfig = figure('Position',pos(2,:));
else
  hfig = figure('Position',pos);
end

This will display your figure window full screen in the second monitor, if you have one. If you don’t, this will still display the figure, full screen, in the current monitor. And that’s it.

 

Installing Youtube Direct

Friday, November 20th, 2009

Youtube Direct is a new tool from Google/Youtube that makes it easier for media organizations or websites to request users to submit video clips, review submitted clips, and possibly re-broadcast these clips, just like CNN’s iReport.  It allows websites to embed Youtube’s upload functionality with ease, includes moderation panel to approve or deny submitted clips, and provides a link back to the website when videos are viewed on Youtube.

Here is a summary of my attempt to install Youtube Direct (YTD). YTD is designed to run  on Google App Engine (GAE) so you need to have a GAE account. Don’t worry, it’s free. After getting one, I downloaded the source and prepared it to be deployed in GAE. To do this, there are several things that need to be configured. One is to have Eclipse and the related Google Plugin.

Installing Eclipse. Download Eclipse IDE for Java EE Developers from the Eclipse project website. The file is eclipse-jee-galileo-SR1-win32.zip, which is about 190MB. Extract the eclipse directory from the zip file. This directory contains the eclipse.exe executable, which runs Eclipse.

Install Google Plugin for Eclipse. Run Eclipse. Then select the Help menu and choose Install New Software.. menu item. In the Work with text box, enter the following URL: http://dl.google.com/eclipse/plugin/3.5. Click the Add button and then the OK button, keeping the name blank as it will be retrieved automatically from the site. Next, click the triangle next to “Plugin” and “SDKs”. Check the boxes next to “Google Plugin for Eclipse 3.5” and “Google App Engine Java SDK.” You can also include the “Google Web Toolkit SDK” if you like. Click the Next button and accept the terms of service to install the plugin. When the installation is complete, restart Eclipse and the plugin is installed.

Install subclipse to manage SVN checkout. Subclipse can be found here. To install it in Eclipse, open Eclipse and click the Help menu. Select Install New Software menu item. In the Work with text box, enter the following URL: http://subclipse.tigris.org/update_1.6.x. Click Add button. Follow the same process as in Step 2. This time selecting Subclipse from the available options. Restart Eclipse to complete the installation.

Download the source code. Now, you are ready to download the YTD source code. In Eclipse, select File menu, then click the Import menu item. In the Import dialog box, click the small triangle next to SVN, and then select Checkout Projects from SVN. Click Next button. From the Checkout from SVN dialog box, choose Create a new repository location. Click Next. Specify the URL as follows: http://youtube-direct.googlecode.com/svn/tags/20091113. Click Next. Select the folder to be checkout by highlighting http://youtube-direct.googlecode.com/svn/tags/20091113. Click Finish. A new project called ytd[tags/20091113] should appear in the Project Explorer after Eclipse completed downloading the source code.

Copy war/WEB-INF/appengine-web_DEFAULT.xml into war/WEB_INF/appengine-web.xml. Edit this file. Put within the <application> tag the registered App Engine instance name of your application. Specify the version of the application within the <version> tag. Replace the value of com.google.ytd.YTDeveloperKey property with your registered Youtube developer key. If you don’t have a developer key, get one here.

Run the application by clicking the Run action in Eclipse. If everything is working fine, you should have a server running at http://localhost:8080/. While still in Eclipse, deploy the application to GAE by clicking the Deploy App Engine Project button in the toolbar.

At first, I encountered several errors. I didn’t have a clue why. When I tried to deploy the application to GAE, it failed because it cannot find javac in the path. As it turned out, I needed to set Java in Windows –> Preferences –> Java –> Installed JREs to the installed Java SDK, not just the JRE. Any way, after setting Java to the SDK,  all the errors disappeared and the application worked and I was able to deploy it to GAE.

GAE dashboard. Go to the admin panel of YTD by visiting http://YOUR_APP.appspot.com/admin. From this panel, you can create new assignments, moderate submitted videos, etc. You can also get the codes that you can paste into a page in your website to enable video submission for each assignment.

That’s it! I still have to see how this works. You can also check the Youtube Direct Getting Started Guide for more details. Till next post.

NinfG and Firewall

Tuesday, October 20th, 2009

The latest version of NinfG (version 5.0) has a lot to offer in terms of allowing client-server connectivity. First, it introduces the invoke server module separating components used to invoke remote applications from the core. This does not only decouple NinfG from the Globus Toolkit like the previous versions, but it also allows the use of other middlewares such as SSH to execute remote commands. Since most Linux distributions already include some form of SSH, this makes the installation of NinfG more useful and a lot easier. You don’t have to install the Globus Toolkit just to install NinfG. The other feature I like is the communication proxy module, which tries to solve firewall-related problems. Unfortunately, NinfG currently only supports a communication proxy for the Globus Toolkit. If you don’t want to install the Globus Toolkit and only employ SSH invoke server, you are out of luck.

In this post, I would like to introduce a work-around of the firewall problem using only the SSH invoke server. The assumption is that the NinfG client and server applications are separated by a firewall. We would like also to assume that the client machine, where the client application is running, can connect to the server machine via SSH directly or via virtual private network. In this way, we can then use the SSH invoke server. If not, we are completely out of luck.

Although NinfG version 5 can now use SSH to invoke remote commands, it still has the same firewall problem inherent when using the Globus Toolkit. The reason is that the server application still needs to connect back to the client application. So if there is a firewall in-between client and server applications, this connection always fails and thus the entire application fails even if the client can connect to the server via SSH (since SSH connection is usually only allowed one way). This is further compounded if the client machine is in a NAT network.

To solve the problem, we should find a way such that the server application does not have to connect back directly to the client application, which causes the connection failure. My solution is therefore to use SSH port forwarding. Since we can connect to the server via SSH, we can instruct SSH to forward a port from the server machine to a local port in the client machine. We can then configure the server application to connect to this local port (in the server machine) instead of connecting directly to the client machine. At the same time, we can also configure the client application to listen to this specific local port.

Fortunately, NinfG allows us to do this using the configuration file. The specific section to use is the CLIENT section. The CLIENT section of the configuration file can look like this:

1
2
3
4
<CLIENT>
  hostname    localhost
  listen_port 2222
</CLIENT>

This will trick the server application to connect to port 2222 in localhost (which is set to be the hostname of the connecting client). But local port 2222 in the server machine is forwarded to the client’s local port 2222 via SSH. So when the server application connects to this port, it is indirectly connected to the client application and thereby establishing the server and the client connection with SSH working in the background. The SSH command to achieve this is as follows (run from the client machine)

[user@client]$ ssh –R 2222:localhost:2222 remoteserver.com

For a more general setting, you can use a third machine which is globally accessible via SSH. You can then use SSH to forward a port from this third machine to a local port in the client machine and configure NinfG’s server applications to connect back to this machine in the forwarded port. The CLIENT section of the configuration file will now look like this assuming thirdserver.com is the hostname of the third machine.

<CLIENT>
hostname   thirdserver.com
listen_port 2222
</CLIENT>

And the SSH command is

[user@client]$ ssh –R 2222:localhost:2222 thirdserver.com

This also assumes that you have an account in thirdserver.com machine. Or else, you will not be able to connect to it. In general, you should allow the forwarded port to be available to any machine, not just thirdserver.com. In this way, other server applications residing in other machines can connect to your client application via thirdserver.com. To do this, you can set GatewayPorts to yes in /etc/sshd_config in thirdserver.com.

How to change hostname in Linux

Wednesday, October 7th, 2009

In order to change the hostname of your linux machine, you can do the following:

1) Edit file /etc/sysconfig/network using your favourite editor, say using vi.

[root@mycomp]# vi /etc/sysconfig/network

2) Look for the line with HOSTNAME in it and change the entry to the desired hostname

HOSTNAME=mycomp.mydomain.com

3) Save the file and restart xinetd service

[root@mycomp]# /sbin/service xinetd restart

That’s it! You have just changed your hostname in Linux platform

Regular expression

Tuesday, October 6th, 2009

Especial Characters:

[
\
^
$
|
?
*
+
.
(
)

If you want to use any of the above characters as a literal in a regular expression, you need to escape them with a backslash. For example, 1\+1=2 will search 1+1=2.

With a character class or character set, you can tell the regex engine to match only one out of several characters. This can be done by simply placing the characters you want to match  between square brackets. For example, if you want to match ‘a’ or ‘e’, simply use [ae]. Thus, gr[ae]y will match either gray or grey,  but not graay, or graey, or similar strings.

To specify a range of characters or numbers, you can use a hyphen inside a character class. For example, [a-z] will match a single character from a to z, and [0-9] will match a single digit from 0 to 9.

Typing  a ^ after the opening bracket will negate the character class. For example, q[^u] means a ‘q’ followed by characters not a ‘u’.

Note that the only metacharacters inside a character class are the closing bracket ], the backslash \, the caret ^, and the hyphen –. To include these characters inside a character class, you need to escape them with a backslash.

Short hand character classes include \d for [0-9], \w for “word character” or [0-9a-zA-Z_], and \s for “whitespace character”. Negated short hand character classes include \D for ^\d, \W for ^\w, and \S for ^\s.

The dot (.) matches a single character without caring what that character is, except new line characters.

Anchors match position before, between, or after characters. The caret ^ matches the position before the first character in the string. For example, applying ^a to “abc” matches ‘a’. But ^b will not match “abc” at all because ‘b’ cannot be matched at the start of the string. Similarly $ matches right after the last character in the string. c$ matches ‘c’ in “abc”, while a$ does not match at all.

\b for word boundaries. For example, \bis\b will match “is” in “This island is beautiful.”

Alternation or | is used to match one regular expression out of several regular expressions. For example, apple|banana|oranges. Note, you can use round brackets for regex grouping (e.g., \b(cat|dog)\b.

The question mark ? makes the preceding token in a regular expression optional. For example, colou?r  matches both colour and color. You can make several tokens optional by grouping them together and attaching the question mark at the closing round bracket, such as Nov(ember)? which will match “Nov” and “November”.

The asterisk or star or * tells the engine to match the preceding token zero or more times. The plus + tells the engine to match the preceding token once or more times. You can limit the repetition by using {min,max} where min is a positive integer number indicating the minimum number of matches and max is an integer equal to or greater than min indicating the maximum number of matches. If comma is present but max is omitted, the maximum number of matches is infinite.

All about SVN

Monday, September 15th, 2008

Subversion (or SVN) is an open source version control system that can be used to maintain several versions of files such as program source codes, webpages, and other documentation. It has several features including most CVS features, versioned copy, delete, renames, etc. Most Linux installations already include SVN so that you can use it immediately if you like. 

I started using SVN only recently to track changes I made to the source codes of programs I have written, and hence this note. SVN is quite handy as it allows you to access your source code from any computer which can access your repository. You can commit changes you made to the code in one workstation, and when you work on those codes in another workstation,  you can just update the local copy to reflect the changes you made. In the following, I’ll assume that SVN is already installed.

1. Creating an SVN Repository

   1: [user@mycomp] cd
   2: [user@mycomp] mkdir Repos
   3: [user@mycomp] svnadmin create Repos

First, create a directory which will serve as the SVN repository. In the example above, a directory called Repos/ is created in the user’s home directory (line 2). To tell SVN to use this as the repos, use svnadmin create command passing the directory name as the target (line 3).  After executing this command, SVN will place several directories and files in this directory. You can check the contents of Repos/ by running ls -al Repos.

2. Storing codes into the repository

   1: [user@mycomp] mkdir appwebcam
   2: [user@mycomp] mkdir appwebcam/trunk
   3: [user@mycomp] mkdir appwebcam/logs
   4: [user@mycomp] mkdir appwebcam/branches
   5: [user@mycomp] mkdir appwebcam/trunk/SRC
   6: [user@mycomp] mkdir appwebcam/trunk/bin

Now that you have your repository setup, it is time to add some data into it. You can structure the data within the repository any way you like. But usually, you’ll have a main directory for a project (say, appwebcam/), then within this, you have subdirectories for trunk/, logs/, and branches/. Within the trunk/ subdirectory, you can have SRC/ subdirectory to hold your source codes, bin/ subdirectory for the binaries, etc. Lines 1-6 simply create this directory structure. 

3. Import your codes to the repository

   1: [user@mycomp] svn import ./appwebcam file:///home/user/Repos/appwebcam -m "Initial import"
   2: Adding appwebcam/trunk
   3: Adding appwebcam/trunk/SRC
   4: Adding appwebcam/trunk/bin
   5: Adding appwebcam/logs
   6: Adding appwebcam/branches

To import the created directory structure into the repository, use svn import as shown in line 1 giving the directory you want to import (./appwebcam), the location of the repository (file:///home/user/Repos/appwebcam), and an import comment option (-m "Initial import") as parameters. This will create an "invisible" directory appwebcam/ under the Repos/ directory.

4. Checking out codes from the repository

   1: [user@mycomp] svn co file:///home/user/Repos/appwebcam/trunk appwebcam
   2: A    appwebcam/SRC
   3: A    appwebcam/bin
   4: Checked out revision 1.

Finally, if you want to checkout a certain portion of your repository, use svn checkout (or svn co). In the above example, only the contents of the trunk/ subdirectory under appwebcam/ are being checkout into the appwebcam/ directory in the user’s current directory. After the checkout, appwebcam/ will contain the SRC/ and bin/ subdirectories.

Generating self-signed certificate using ADT to sign AIR applications

Friday, September 5th, 2008

To package an Adobe AIR application using ADT (AIR Developer Tool), a certificate is needed. Digitally signing your application with a certificate from a recognized certificate authority identifies you as the publisher. It also provides assurance to users that your application has not been accidentally or maliciously altered. For these purposes, you can get a certificate from known certificate providers such as VeriSign or Thawte. But if you’re just developing an application for personal or friends’ use, you can use a self-signed certificate, which can be generated using ADT. You can then use this certificate to sign your personal AIR applications :-) .

To generate the certificate, run the following command:

adt -certificate -cn name [-ou org_unit][-o org_name][-c country] key_type pfx_file password

where -cn name is the common name that will be used for the generated certificate, -ou org_unit refers to the organizational unit issuing the certificate, -o org_name is the name of the organization issuing the certificate, -c country is a two-letter ISO-3166 country code, key_type can either be "1024-RSA" or "2048-RSA" pfx_file is the name of the file where the certificate will be stored, and password is the certificate’s password. The parameters inside square brackets are optional.

As an example, the actual command may look like this:

   1: adt -certificate -cn "My Certificate" -ou bagarinao.com -o "ABK Co" -c US 2048-RSA myCert.p12 sd#$wd23

   2: adt -certificate -cn "AIR App" 1024-RSA MyCert.p12 sd#$wd23

The first line illustrates how to generate a certificate specifying all the possible parameters. On the other hand, the second line only uses the required parameters in generating the certificate. Both will generate a new certificate and will store it in a file called myCert.p12 with password sd#$wd23.

To use the generated certificate to sign an AIR application, use adt using the -package option instead of the -certificate option. Use -storetype pkcs12, -keystore myCert.p12, and -keypass sd#$wd23 to specify the certificate that will be used to sign the application. The command is something like

adt -package -storetype pkcs12 -keystore myCert.p12 -keypass sd#$wd23 <airfile> <app-desc> other-files ..

where <airfile> is the name of your package, <app-desc> specifies the XML application descriptor file, and other-files are the files and directories that you want to package with your air application.