Back to Express Pearl's Homepage

Pearls of Wisdom

or How to do All the Stuff the Manual Should Have Told You

Hints'n'Tips

Contents

Data I/O

Development and C/C++ Coding

Using Visualization Macros

V code


Data I/O

Providing Explicit Coordinates for Uniform AVS Fields

Though this is implied in the AVS/Express documentation it is not explicitly mentioned nor any examples given. The problem is how to map (i,j,k) data to coordinates of (xmin,ymin,zmin)->(xmax, ymax,zmax). This might be because simply the data's origin is not at (0,0,0) or that the spacing differs in each dimension (eg medical data's Z spacing).

This can be done by supplying the coordinates in a separate file or appended immediately after the node data. The layout should be in the form:

xmin xman ymin ymax .....

and can be ascii or binary (where the values are floats).

The attached files are a modified hydrogen.fld (looks at default Express install on Windows for actual data) and a separate ascii coordinates file which changes the origin and the spacing.

hydrogen_coords.v :

# AVS field file 
# this is a header file for a field to be 
# used in conjunction with the  build a field module of AVS
#
ndim = 3
dim1 = 64
dim2 = 64
dim3 = 64
nspace = 3
veclen = 1
data = byte
field = uniform

coord 1 file=hydrogen.coordxyz filetype=ascii
coord 2 file=hydrogen.coordxyz filetype=ascii offset=2
coord 3 file=hydrogen.coordxyz filetype=ascii offset=4
variable 1 file=c:/Express/data/volume/hydrogen.dat filetype=binary skip=3

hydrogen_coords.v :

-10.0 10 -20.0 20 -30.0 30

Use of min_ext, max_ext Keywords in AVS Field Files

Again this is documented but is far from clear as it uses the terms minimum/maximum_ext to mean subtley different things.

The min_ext and max_ext keywords are used to define a bounding box that may describe for example the original extents of the data before it was cropped in a preprocessing stage. This bounding box is then used for centering and rotation and hence causes some strange and unintuative behaviour behaviour when inappropiately set, particularly if the bounding box is much greater than the extent of the cropped data. Specifically the values override the Read_Field.out.coordinates.min/max_vec[] arrays.


Development and C/C++ Coding

Compiling a Standalone Project

Here a just a few tips for compiling projects after I tried doing one myself. There is basically one choice to be made depending on how you've set up your project and applications. Whether to save selected objects or save current application.

save selected objects - this way you end up with an executable which you tell to load an application, the binary containing all the necessary components. This is the most flexible as you can call it with different applications that use the same components. Also you can you it when save current application doesn't work, see below. If you want to select libraries that you've created containing you objects you should do so from the Templates library page as if you select a NE library that maps from the Templates to compilation seems to fail. You can either supply your applications as separate .v/.vo files or it is possible to combine them into the executable (or the appl.vo file if build v into executable is not selected). Create an flibrary called something like MYAPPS somewhere and then reference your application files:

flibrary MYAPPS {
   "../apps/my_app1.v" MyApp1;
   "../apps/my_app2.v" MyApp2;
};

You can then run which ever application you wish using a small v file for each application e.g. bin/$MACHINE/express runapp1.v which looks like:

MYPROJ.MYAPPS.MyApp1 MyApp1;

save current application - this lets you have a executable that fires up with the chosen application if you select instance objects automatically on restore. Also you can use it to reduce the number of objects that are compiled in to the executable since it will only add those used in the application, useful say if your project contains lot of IAC objects but only a few a used in the app (and its too fiddly to go and find and select all the individual objects). Of course though a different app may well require additional objects. Though the project I compiled only had a single app that I wanted to launch straight away, I found a problem since I had used a single gen.cxx file for all my modules. Not all of which are used in the application and hence weren't linked in, the gen.cxx then had unresolved symbols for these modules. A rough solution would be to copy over the .o files and modify the express.mk then relink but I choose the method described above.

Finally I did this under Linux and Win32. Linux was fine though surprise, surprise there were niggles under Win32. Mainly the gen.* files not referenced from the parent project and neither did it linked external libraries that I'd place in lib/ these all had to be copied to the same locations but under the complied project tree. Grrrrr!


IAC Documentation

Got a lot of IAC projects but fed a with having to find the the docs for each module? Well no more troubles with this handy script, just run it from the Express project directory:


#!/bin/sh
# makeindex
# search for html files in iac_proj tree and build a index.html file 

echo "<html><head><title>IAC Express Projects - Documentation</title></head>" > index.html
echo "<body bgcolor=\"#92bfbf\">" >> index.html
echo "<h2 align="center">IAC Express Projects - Documentation</h2>" >> index.html
echo "<ul>" >> index.html
cd iac_proj
find  -name "*.html" -printf "<li><a href=\"iac_proj/%p\">%p</a></li>\n" >> ../index.html
cd  ..
echo "</ul>" >> index.html
echo "<br><a href=\"http://www.iavsc.org\">IAC Homepages</a>" >> index.html
echo "</body></html>" >> index.html

If you're using Windoze then you could install Cygwin :).


Problems Using notify_deinst with cxxmodules

If using external libraries in a module that need to be closing when the module is deinstanced or have allocated memory that exists for the duration of the module you will want to have a method called when the module is deinstanced so as to free these resources. The notify_deinst attribute is what you need to set on this "cleanup" method. All well and good except that under C++ there is a bug in AVS/Express which means that the C++ object representing the module is deleted before the "cleanup" method is called. This usually mainfests itself as internal pointers that you have allocated being set to zero or worse being completely overwritten causing memory heap errors when you try and deallocate the pointer. Thankfully the solution is simple; replace the "cleanup" method with and explict C++ destructor for your module's C++ class. This can be done using the cxx_member property e.g.,

library MYLIB {
   cxxmodule mymod<...., cxx_member="~MYLIB_mymod();", ....> {
   ....
   };
};

You can now close libraries and deallocate memory as you would have done in the Express method.


The Scheduler

This object sits in Root and keeps track of whats happening while the Object Manager runs the application. This is what it looks like by default:


scheduler Scheduler {
   sched_list notifiers;
   run_list running;
   int disable = 0;
   int interrupt = 0;
   int percent_done = 100;
   string current_mod = "";
   boolean status_check = 1;
};
notifiers
a list of the methods to be notified - don't touch
running
a list of running methods - don't touch
disable
disable the scheduler, i.e., no functions are called but the UI still works
interrupt
does the same as disable but also sets the interrupt flag when OMstatus_check() is called, in the Single/Multiwindow Apps this is toggled by the interrupt execution button
percent_done
The percentage of the function performed Duh! This value is set by OMstatus_check() and its accuracy depends on the individual implementation in each method.
current_mod
the name supplied for the executing module by OMstatus_check()
status_check
if this is 0 then percent_done and current_mod aren't updated, this can be toggled in the Single/Multiwindow Apps by the enable status button. The problem with the status being updated is that especially under Motif the overhead of it updating the UI is quite heavy the frame-rate of the viewer can be reduced by 10% or more. To this end its probably best set it by default to 0 in templ.v:

"$XP_PATH<0>/v/templ.v" Templates {
   Scheduler {
      status_check = 0;
   };
};
  

Using Visualization Macros

Volume Rendering

Anyone who has used AVS/Express will agree that the volume rendering is poor to say the least. This however is not the case the volume renderer is actually pretty good its the appalling user interface and macro that's the problem.

As is the Volume_Render module provides the provide the following functionality. According to the UI you can only have two data ranges delineated by the Range Position, i.e., only a ramped stepped transfer function. Further more the min/max of the data range is not set to be the min/max of your data but calculated to the nearest 8/12/16 bit data range (i.e., 0-255/0-4095/0-65536) this lead to problems if the majority of your data was constrained to only a small part of the range, a common occurrence in medical data. What you would get was some green blob as 95% of your data lay between 200 and 500 but was rendered with a datamap extending from 0-4095.

The problem of the min/max data range I addressed with my volren2 macro available from the IAC which allowed you to set the data range. However with the release of 5.1 and the new ColourMapEditor its painfully obvious that the UI has crippled the use of the volume render as you can edit the colour map to your hearts content, creating also sort of complex transfer functions.

The lousy UI is compounded by the fact that the volume render object uses VolumeRenderDatamap, this is the criminal piece of coding that chooses the inappropriate data range rather than using the data's actual min/max. The only thing to do with it is rip it out, yes I want you to do it now! In its place simply use a DefaultLinear datamap. You can also scrap half the UI and just hook up the ColourMapEditor. Of course the ColourMapEditor needs to be kicked into shape, but don't get me started....


Changing the Default DataObject Cache Size

Are you fed up with Express complaining that the object cache size is too small? Have to wait ages for Express to render the uncached object before carefully increasing the cache size? Simply use or modify the following templ.v file in your project and you can change the the default setting for all DataObject related objects.

NOTE: the cache size is just an upper limit and does not waste memory. In generally you should only not cache objects when they are so big the system does not have enough memory to cache the object (mesh_size->memory_size) but you still want to try and render it.

@AVS: it would be nice if the default cache_size could be set using and environment variable or maybe by adding as define to a project avsenv file(?)

// Change the DataObject default cache size to something
// sensible for a high performance system
#define DEFAULT_CACHE_SIZE 128
"$XP_PATH<0>/v/templ.v" Templates {
   GD {
      DefaultObject {
	 cache_size = DEFAULT_CACHE_SIZE;
      };
      // these other objects derive from DefaultObject but are already
      // created before this project modifies DefaultObject
      AltObject {
	 Obj {
	    cache_size = DEFAULT_CACHE_SIZE;
	 };
      };
      DataObject {
	 Obj {
	    cache_size = DEFAULT_CACHE_SIZE;
	 };
      };
      GroupObject {
	 Top {
	    cache_size = DEFAULT_CACHE_SIZE;
	 };
      };
      DataObjectLite {
	 Obj {
	    cache_size = DEFAULT_CACHE_SIZE;
	 };
      };
      DataObjectNoTexture {
	 Obj {
	    cache_size = DEFAULT_CACHE_SIZE;
	 };
      };
      DataObjects {
	 dos {
	    Obj {
	       cache_size = DEFAULT_CACHE_SIZE;
	    };
	 };
      };
      DataObjectArr {
	 dos {
	    cache_size = DEFAULT_CACHE_SIZE;
	 };
      };
      DataObjectNoTextureArr {
	 dos {
	    cache_size = DEFAULT_CACHE_SIZE;
	 };
      };
      DataObjectLightArr {
	 dos {
	    cache_size = DEFAULT_CACHE_SIZE;
	 };
      };
   };
};

V Code

Multi Dimensional Arrays

To explicitly set a 2D array in the NE you need to use => rather than = otherwise Express simplfies it to 1D array on pressing Ctrl-Enter. this can cause errors in macros that examine the dimensionality of the array such as the Mapping macros. For example typing into an int arr[][]:


= {{0,0}, {1,1}, {2,2}} Ctrl-Enter becomes = {0,0,1,1,2,2}

and the object becomes int a[6], whereas

=> {{0,0}, {1,1}, {2,2}} Ctrl-Enter

remains unchanged and the object becomes int a[3][2].




More C/C++!

Obviously you can go and lookup function in the header files (e.g. om.h) yourself but here I shall draw your attention to the ones that I've found useful and hopeful give correct information on their usage.

Functions

OMtype_size(int type)
returns the sizeof() for OM_TYPE_XXXX defines
OMvalid_obj(OMobj_id obj_id, OMobj_id templ, int mode)
returns true if object has a valid value or reference. Use the templ for sub-objects to check against, you should usually pass OMnull_obj. The mode should be set to 0. The OMX version is mentioned in the developer's C++ guide but not listed in the OM reference



More V!

This is a list of undocumented or underdocumented V-code objects.

NOTE: Functions marked » aren't actually V code functions but are func objects see the Types section below.

Functions

is_valid(Obj obj)
useful function that returns 0 or 1 if obj is valid or not. However this only works as expected on prims and strings i.e., does the obj have a value, e.g.,
string text;
UIlabel text_label {
   label => switch(is_valid(<-.text)+1,"Undefined", text);
};
A group will always return 1 even if its sub-objects are not, a group reference is 1 if its connected to an existing group and only 0 if it doesn't reference anything.
»get_coords_unif(int ndim, int dims[], int nspace, float points[])
»get_coords_rect(int ndim, int dims[], int nspace, float points[])
these are the function that will create the actual coordinate values from implicit structured meshes i.e. uniform and rectilinear. The are useful if you write a module that creates has Mesh_Struct as an output but there is the possibility that it could be uniform or rectilinear. In which case you set the points and then use something like:
OMparse_obj_ref(out.coordinates.values.obj_id(),
                "get_coords_rect(ndim,dims,nspace,points)");
»get_coords_cyl_unifint ndim, int dims[], int nspace, float points[])
»get_coords_cyl_rect(int ndim, int dims[], int nspace, float points[])
»get_coords_spher_unifint ndim, int dims[], int nspace, float points[])
»get_coords_spher_rect(int ndim, int dims[], int nspace, float points[])
same as above but provide but compute cylindrical and spherical coordinates

Attributes

sort
Actually a type but works more like an attribute. Its only applicable to libraries & groups and sorts the contents alphabetically e.g.,
library+sort MYMACS { ...

Types

func
A function object that once defined and associated with a C/C++ function call can be used directly from the V or even instanced the object will have the value of whatever the function returns. The File Access objects are examples of func objects. I hope later to be able to provide a description of how to write your own.

Properties

animate
the keyframe animator will record changes to any object with this property set to 1, hence all Xforms have it. Add it to parameters like IsoLevel to animate a dataset exploration. NOTE: possibly doesn't work when set on UIwidget sub-objects e.g. UIslider.value
init_code
As the documentation explains the code segment supplied will be inserted into the Express start up code after BaseInit has run i.e. after the Template library has loaded. This is useful if you've added a config group to Templates to store some global information, you can then initialize the config values for your project. So where does Express place your code? Well if you define a library using this attribute the code is place in a function called <library_name>_Init which will be found in xexpress.cxx. The very useful thing is that <library_name>_Init is passed the command-line arguments:
void <library_name>_Init(int *argc,char **argv
#ifdef MSDOS
, HINSTANCE *hInst, HINSTANCE *hPrevInst
#endif
)
So your initialization code can read its own command-line arguments. Express should ignore arguments it doesn't recognize, though it may silently try (and fail) to load them as an application. In which case you may need to explicitly pass -ne (open Network Editor).
notify_val
I'm not entirely sure how this differs from notify
pre_init_code
This code get called before the Templates library is built. A function called PRE<library_name>_Init is created in xexpress.cxx and is called by PREinit in express.cxx. As with init_code its wrapper function is passed the command-line arguments:
void PRE<library_name>_Init(int *argc,char **argv
#ifdef MSDOS
, HINSTANCE *hInst, HINSTANCE *hPrevInst
#endif
)
virtual
Hurrah I found the answer! virtual is used to add additional subobjects based on the matching of existing subobjects. For example lets define a base group object:
group base {
   int type;
};

We can then add additional subobjects based on the value of type:
base foo {
   type = 1;
   float+virtual a = 4.1; 
}

base bar {
   type = 2;
   float+virtual a = 1.4; 
}
Any object that uses base as a template will gain float a with a different default value depending on the value of type:
base b {
   type = 2;
};
$float b.a
1.400000
you can use more than one subobject to form the templates. If the templates have similarities the OM will choose the template that has the closest match to any obect you define. Finally the virtual objects don't need to be of the same object type i.e., bar could have defined a virtual int. A common use of virtual object is the render and pick methods that are selected according to the Mesh and Cell_Set type.
weight
The documentation says weight orders execution of methods in a module that share the same input parameter. Firstly to be clear, the method with the least value executes first the method with the greatest last and the default value is 1. So a method with weight=-10 will execute before a method with weight=10 when both have been triggered by a single input parameter changing. Secondly there is no requirement the methods to be in the same module, hence weight can be used to make two or more separate modules execute in order if they all have the same trigger.

Commands

$invoke <obj>
This command given a method obj forces the method to run. Unfortunately it also print a message to stdout e.g.
invoke of parse_v_relative-<null> returns 1



OMX in Full

OMXobj

Described here are the functions of the OMXobj base class. All objects with a C++ class representation will be derived from this class e.g. OMXgroup, OMXint. Developers should also refer to the omx.hxx include file. Though many are documented under their OM equivalent there is no complete list of them.

virtual const char *class_name()
Return a string describing the OMXobj derived class e.g., "int", "string", "group".
const char *name()
Return the name of this object as string.
OMobj_id obj_id(int mode = OM_OBJ_RW)
Return the OMobj_id of this object, this is called by the (OMobj_id) overloaded cast.
void set_obj_id (OMobj_id obj_id)
Does what it says, however DO NOT USE THIS FUNCTION.
OMobj_id obj_val()
This is the equivalent of OMget_obj_val() and returns the OMobj_id of the object at the end of the chain of references. If no valid object found return OMnull_obj.
int set_obj_val(OMobj_id conn_id,int mode = 0)
Equivalent to OMset_obj_val().
operator OMobj_id ()
Defines the (OMobj_id) overloaded cast and returns this object's OMobj_id.
int operator== (OMobj_id rhs)
Compare this object's OMobj_id with another object's OMobj_id.
int operator!= (OMobj_id rhs)
Compare this object's OMobj_id with another object's OMobj_id.
int ret_num_subobjs()
Presumably returns the number of sub-objects.
OMobj_id ret_subobj_id(int i, int mode = OM_OBJ_RD)
Return the OMobj_id of the ith sub-object.
unsigned int push_ctx (int state = 0, int event_mode = 0, int child_event_mode = 0)
Equivalent to OMpush_ctx().
int pop_ctx ()
Equivalent to OMpop_ctx().
int add_obj_ref(OMobj_id conn_id,int mode = 0)
Equivalent to OMadd_obj_ref().
int del_obj_ref(OMobj_id conn_id,int mode = 0)
Equivalent to OMdel_obj_ref().
int set_obj_ref(OMobj_id conn_id,int mode = 0)
Equivalent to OMset_obj_ref().
int insert_obj_ref(int ind, OMobj_id conn_id, int mode = 0)
Equivalent to OMinsert_obj_ref().
int valid_obj(OMobj_id templ = OMnull_obj, int mode = 0)
Equivalent to OMvalid_obj()returns true if object has a valid value or reference. Use the templ for sub-objects to check against, you should usually pass OMnull_obj. The mode should be set to 0.
int changed(int seq_num, OMobj_id templ = OMnull_obj, int mode = OM_SEQ_VAL)
Equivalent to OMchanged, returns true is the seq_num of this object is greater than the value passed.
int get_obj_seq(OMobj_id templ = OMnull_obj, int mode = OM_SEQ_VAL)
Equivalent to OMget_obj_seq()<./dd>
int is_type_of(OMtype_id type)
Is this object of base type type.

The following functions should be called only if the object is an array.

int get_array(int *type, void **array, int *ndim, int *dims, int mode)
Equivalent to OMget_array().
int set_array(int type, void *array, int len, int mode = OM_SET_ARRAY_COPY)
Equivalent to OMset_array().
void free_array(void *array)
Simply calls ARRfree() in the array pointer.
void *ret_array_ptr(int mode = OM_GET_ARRAY_RW, int *size = NULL, int *type = NULL)
Equivalent to OMret_array_ptr().
void *ret_typed_array_ptr(int mode = OM_GET_ARRAY_RW, int type = OM_TYPE_INT, int *size = NULL)
Equivalent to OMret_typed_array_ptr().
char *ret_str_array_val(int index, char *buffer = NULL, int buffer_size = 0 )
Equivalent to OM*ret_str_array_val().
int set_str_array_val(int index, const char *value)
Equivalent to OMset_str_array_val().
int get_array_size(int *size)
Equivalent to OMget_array_size().
int set_array_size(int size)
Equivalent to OMset_array_size().
int ret_array_size()
Slightly more convenient than get_array_size() as it return the size directly.