Prev Up Next

Step three : more features


tricky.cc

Suppose we have a lot of 3D images in raw format and vol format. We want to rotate each image of angle pi/4 around the X-axis. It would be smart if the program could know that an image is already rotated.


#include <vol.h>

#include <time.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>

int main( int argc, char **argv ) {

    for (int i = 1; i < argc; ++i ) { 

        int len = strlen( argv[i] );
        
        Vol v;  
  • This volume object is default constructed. It is an empty volume with no data inside (an access to any voxel would cause an error).

        if (len < 4 || (strcmp( argv[i] + len - 4, ".raw" ) != 0  
                && strcmp( argv[i] + len - 4, ".vol" )) != 0 ) {
            fprintf( stderr, "Cannot find format of %s\n, skipping ...", argv[i] );
            continue;
        } else if (strcmp( argv[i] + len - 4, ".raw" ) == 0) { 
            v = Vol( argv[i], 10, 10, 10, 0 );  (1)
        } else {
            v = Vol( argv[i] );  (2)
        }

  • This code loads the file named argv[i]. We know the file type (raw or vol) with the extension (.raw or .vol).
  • In step 2, we saw how to load a vol file into a Vol object. That's what we do in (2). But in (1), we load a raw file into a Vol object. As the size of the volume is not contained in the data of a raw file, we must precise it. (Here, we assume for the example that a raw file is always a 10x10x10 volume). The last argument, optionnal, sets the alpha color (it defaults to zero).
  • In (1) and (2), we affect a temporary Vol object to an existing Vol object : the affection operator is overloaded in the Vol class. You can also copy a Vol object, but you should try to avoid this, because it is slow (tip : when possible, use const reference instead of value parameters for Vol objects).

        if (!v.isOK()) {
            fprintf( stderr, "Cannot load %s, skipping ...\n", argv[i] );
            continue;
        }       
        
        if (v.getHeaderValue( "Rotated" ) != NULL ) { 

            int date;
            int errcode = v.getHeaderValueAsInt( "Rotation-Date", &date ); 
  • Did you know that .vol files have a header ? Here is an exeample of a typical header :
[user@host ~]$ volheader example.vol
Version: 2
X: 41
Y: 41
Z: 41
Voxel-Size: 1
Alpha-Color: 0
Int-Endian: DCBA
Voxel-Endian: A
Res-X: 1.000000
Res-Y: 1.000000
Res-Z: 1.000000
.
[user@host ~]$
  • A header is terminated by a line with the single character ".". Some fields are required (such as X, Y and Z), some are optionnal (but the Vol class understands them), and some are user-defined.
  • We have choosen to add two fields in the .vol files that our program creates : "Rotated" and "Rotation-Date". Rotated, if present, tells our program that the image is already rotated and that we do not need to do it again. Rotation-Date is the date when we did the rotation. It may look like this :
Rotated: yes
Rotation-Date: 1043078350
  • The Vol class provides a few methods for reading fields :
    • const char *getHeaderValue( const char *type ) const
      returns the string value of field type, or NULL if the field does not exist.
    • int getHeaderValueAsDouble( const char *type, double *dest ) const;
      writes double value of field type in *dest or returns non-zero if the field does not exists or it cannot be converted in double.
    • int getHeaderValueAsInt( const char *type, int *dest ) const;
      idem but with int instead of double.

            fprintf( stderr, "The file %s is already a rotated file (%s), skipping ....\n", argv[i], 
                        (errcode ? "no date found" : ctime( (time_t*)&date )) );

        } else {
            char *newname = new char[ len - 4 + strlen(".rot.vol") + 1 ];
        
            if (newname == NULL) { 
                fprintf( stderr, "cannot allocate memory .. skipping.\n" );
            } else {
                v.rotate( M_PI/4, 0, 0 ); 

  • rotate is a member function which rotates the voxel of the Vol object. It takes three parameters : rotation angle around X-axis, around Y-axis and around Z-axis.

                strcpy( newname, argv[i] );
                strcpy( newname + len - 4, ".rot.vol" );

                v.setHeaderValue( "Rotated", "yes" );
                v.setHeaderValue( "Rotation-Date", (int)time(NULL) );
        
                v.dumpVol( newname );
   
                delete []newname;                                                                     
            }                                                                                         
                                                                                                      
        }                                                                                             
                                                                                                      
    }                                                                                                 

	return 0;
}
  • Now that we have rotated the volume, we add these fields in the header.
  • Three methods are available to add fields :
    • int setHeaderValue( const char *type, const char *value );
    • int setHeaderValue( const char *type, int value );
    • int setHeaderValue( const char *type, double value );
    They have the same behaviour : if the field exists, its value is overwritten with the new one. If it doesn't exist, it is created. On error, a non-zero value is returned.

Here is the results you should have :
Before
After
Original file Hey ! It worked !
Original file Hey ! It worked !
...

Files

makefile
rotate-vol.cc
A few volumes for testing

raw format :
The raw format understood by the class Vol is :
[ SizeX : 4 bytes ][ SizeY : 4 bytes ][ SizeZ : 4 bytes ]\n
[ the voxels (a dump of a C array that would be declared as unsigned char tab[SizeZ][SizeY][SizeX] ]

Valid HTML 4.0! Edited with vim