You are on page 1of 206

Avizo Code Book

How to use Avizo eciently and how to extend it


This document is a contribution of Hauke Bartsch, Dr. rer. nat. Updated April 17, 2010

Contents

I.

Avizo basics

9
11 13 13 14 15 15 16

1. Introduction 2. Avizo modules 2.1. Select a gray value range and extract a 2.2. Volume rendering for RGB images . . 2.3. Analysing volumetric densities . . . . 2.4. Displaying segmentation results . . . . 2.5. Align two data sets using ObliqueSlice

sub-volume . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

II. Avizo scripting

19

3. Introduction 21 3.1. Remote controlling Avizo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.2. Displaying your logos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.3. Enable the reload button . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 4. Compute modules 4.1. A standard compute . . . . . . . . . . 4.2. A real example . . . . . . . . . . . . . 4.3. Multiple output objects of a script . . 4.4. Iterating over objects in the workpool 4.5. Annotations in 3D . . . . . . . . . . . 4.6. Transformations on Data Files . . . . 4.7. Renement of surfaces . . . . . . . . . 4.8. Simplify a surface . . . . . . . . . . . . 4.9. Apply image lter . . . . . . . . . . . 25 25 25 28 30 30 32 33 35 35 45 45 46 47 49 52 53 58

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

5. File load and save 5.1. Write your own data sets . . . . . . . . 5.1.1. Write a SpreadSheet object . . . 5.2. Read your own data set . . . . . . . . . 5.2.1. Read a Surface from Trajectories 5.3. Plot some 1d curves . . . . . . . . . . . 5.4. Load data objects . . . . . . . . . . . . 5.4.1. Create a cluster object . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

Contents 5.4.2. Create a label eld . . . . . . . . . . . . . . 5.4.3. Remove a slice from a loaded volume . . . . 5.4.4. Create a scalar eld . . . . . . . . . . . . . 5.4.5. Create a line set . . . . . . . . . . . . . . . 5.4.6. Write a lineset out to the console window . 5.4.7. Add a data value to each point of a line set 5.5. Save data objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 60 61 62 66 67 69 71 71 71 72 72 72 73 73 74 75 75 76 78 80 81 84 85 85 85 86 87 87 88 88 88 89

6. General Information 6.1. Memory consumption of data and display . . . . . . . . . 6.2. Set the voxel size and the bounding box . . . . . . . . . . 6.3. Querying environment variables in Avizo . . . . . . . . . . 6.4. Start external processes . . . . . . . . . . . . . . . . . . . 6.5. Display a progress bar . . . . . . . . . . . . . . . . . . . . 6.6. Generate numbered le names . . . . . . . . . . . . . . . . 6.7. DICOM units . . . . . . . . . . . . . . . . . . . . . . . . . 6.8. DICOM scaling - HU values and how they are represented 6.9. Reading in DICOM directories without DICOMDIR . . . 6.10. Force a re-computation . . . . . . . . . . . . . . . . . . . . 6.11. Add information to data les . . . . . . . . . . . . . . . . 6.12. Add a button next to Load Data . . . . . . . . . . . . . 6.13. Add function keys . . . . . . . . . . . . . . . . . . . . . . 6.14. Announce new modules with .rc les . . . . . . . . . . . . 6.15. The current time . . . . . . . . . . . . . . . . . . . . . . . 7. Editors 7.1. Invoke 7.2. Invoke 7.2.1. 7.3. Invoke 7.4. Invoke 7.5. Invoke 7.6. Invoke 7.7. Invoke 7.8. Invoke

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

the Crop Editor . . . . . . . . . the Segmentation Editor . . . . Automatic erosion and dilation the Parameter Editor . . . . . the Transform Editor . . . . . . the Camera Path Editor . . . . the Surface Editor . . . . . . . the slice aligner . . . . . . . . . the Landmark Editor . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

III. C++ Code examples


8. General Information 8.1. First steps . . . . . . . 8.2. Compiling a package . 8.3. Display a progress bar 8.4. Accessing environment 8.5. Display the le dialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . variables and the . . . . . . . . . . . . . . . . . . . . . . . . . registry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

91
93 93 93 94 94 95

Contents 8.6. Accessing other modules . . . . . . . . . . . . . . 8.6.1. Timing modules . . . . . . . . . . . . . . 8.6.2. Create an existing module . . . . . . . . . 8.6.3. Working with ports . . . . . . . . . . . . 8.7. Textual input and output . . . . . . . . . . . . . 8.8. Debug messages (enable/disable) . . . . . . . . . 8.9. Executing Tcl commands . . . . . . . . . . . . . 8.10. Change the voxel size . . . . . . . . . . . . . . . 8.11. The camera position . . . . . . . . . . . . . . . . 8.12. Updating the viewer . . . . . . . . . . . . . . . . 8.13. Multi-threading . . . . . . . . . . . . . . . . . . . 8.14. LargeDiskdata . . . . . . . . . . . . . . . . . . . 8.15. Generating warning messages and simple dialogs 8.16. Adding additional information to elds . . . . . . 8.17. Utility functions . . . . . . . . . . . . . . . . . . 8.18. Utilizing Analytical Expressions . . . . . . . . . . 8.19. Documentation . . . . . . . . . . . . . . . . . . . 9. Compute modules 9.1. Adding another eld as input port . . . . . . . . 9.1.1. Correct behaviour for saving networks . . 9.1.2. Adding an almost innite number of ports 9.2. Adding a region of interest input . . . . . . . . . 9.3. User dened tcl commands the parse function . 9.4. Save a network le . . . . . . . . . . . . . . . . . 10.Data import and export 10.1. AmiraMesh as a general purpose le format 10.2. Read data sets . . . . . . . . . . . . . . . . 10.2.1. Formating your own data . . . . . . 10.2.2. Convert binary to ascii data . . . . . 10.2.3. Read in Curvilinear Fields . . . . . . 10.2.4. Work with oine data . . . . . . . . 10.2.5. Read in time dependent data . . . . 10.3. Multi-channel elds . . . . . . . . . . . . . . 10.4. Surface data . . . . . . . . . . . . . . . . . . 10.4.1. Write a surface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 97 98 98 101 101 102 102 103 104 104 110 111 112 112 113 114 115 117 119 119 121 121 122

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

125 . 125 . 126 . 126 . 128 . 128 . 130 . 134 . 140 . 141 . 143

11.LabelFields 149 11.1. Exporting LabelFields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 12.LineSets 151 12.1. Reading a line set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 12.2. Drawing a line set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153

Contents 13.Field access 155 13.1. Access stored values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 13.2. Access interpolated positions . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 13.3. Access coordinates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 14.Data Clusters 159 14.1. Reading in a cluster object . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 14.2. Generating a new cluster object . . . . . . . . . . . . . . . . . . . . . . . . . . 159 14.3. Adding a clusterView and a vertexView . . . . . . . . . . . . . . . . . . . . . 161 15.Surfaces 163 15.1. A simple surface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 15.2. Quadro-lateral surfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 15.3. Vector elds attached to a surface . . . . . . . . . . . . . . . . . . . . . . . . 165 16.Image lter 167 16.1. Using a pre-dened lter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 16.2. Image lters implemented by shaders . . . . . . . . . . . . . . . . . . . . . . . 168 17.Colormaps 18.SpreadSheets 19.Graphical interfaces 19.1. Screen aligned text output . . . . . . . . . . . . . . 19.2. Repeated events in time . . . . . . . . . . . . . . . 19.3. Generate geometries by Open Inventor . . . . . . . 19.4. Plot textures in 3D . . . . . . . . . . . . . . . . . . 19.5. Get the actual mouse position . . . . . . . . . . . . 19.6. Specify the type of connection of a module to some 19.7. Writing Interfaces . . . . . . . . . . . . . . . . . . 20.Use of Avizo build-in libraries 20.1. mclib . . . . . . . . . . . . . . . . . . . 20.1.1. Dynamic Arrays . . . . . . . . 20.1.2. The ood-ll algorithm . . . . 20.1.3. Line integration in vector elds 175 177 179 179 181 182 182 185 185 186 189 189 189 189 190

. . . . . . . . . . . . . . . data . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

21.Use of external libraries 193 21.1. Working with Numerical Recipes . . . . . . . . . . . . . . . . . . . . . . . . . 193 21.2. Matlab stu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 21.3. Working with TinyXML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200

Part I. Avizo basics

1. Introduction
This book is a collection of Tcl scripts and C++-programs that should help people to successfully develop their own Avizo modules. Whereas for the rst half, the tcl examples you can use the standard Avizo, the second half of the book requires the Avizo XPand extension its license and a C++ compiler. This text was written as a collection of notes to particular topics that primarily are a personal reference. It does not make any assumptions about completeness or covering every aspect of the Avizo environment. The text does not have to be read sequentially. You should be familiar with Avizo already and pick out chapters based on your needs to develop a specic module. There is a focus here on source code examples and you should be able to copy them out of the document1 .

In order to get rid of the line numbers at the end of the lines hold down the ALT-key when selecting the text in Acrobat Reader. This will allow you to select a rectangular region excluding potential line numbers.

11

1. Introduction

12

2. Avizo modules
This chapter is a collection of case examples that happend to be common hotline problems. Reading those should give you a little inside in the exibility you gain by combining dierent tools available in Avizo.

2.1. Select a gray value range and extract a sub-volume


A common worklist may require to extract a sub-volume from an initial data set. This may happen in order to do a display of a specic region of interest only without other other nearby object cluttering the eld of view. In this example we will assume that the object of interest can be dened by a grayvalue range and a seed point. First we will generate a LabelField which will contain the information about the region of interest (see Step 1). Second, we will use this LabelField to extract the region of interest from the original data set (see Step 2). Voxel by voxel a LabelField contains a natural number to identify a tissue type. There are many dierent tools in Avizo to dene such a data set and the process doing so is shortly called segmentation (see for instance LabelField, LabelVoxel, and CorrelationPlot).

Step 1 Load in your data set by using Fileload and select your data. Attach a LabellingLabelField to your data set. This will only be possible if your data set contains byte, short, or unsigned short values. In case you have color data or oating point data you need to convert the data rst into the above data types (see Channelworks and CastField). Avizo will now do two things now. First, it will create a LabelField data set which will be visible right below your orginial datas icon in the workspace. Directly afterwards Avizo will open the Segmentation Editor on this newly created LabelField. Select in the Segmentation Editor the magic wand tool. In the two text elds left select a useful data range for your region of interest. Moving the mouse over the screen you can see the data value underneath the cursor. Select now with the mouse a point inside your region of interest. Starting from this position a region growing algorithm will select all pixel that are connected and are in the data value range. You can adjust the settings until only the region of interest is drawn in a transparent red color. Now select the 3D checkbox and repeat clicking into the same general area in the current slice. The region growing will be repeated for the whole stack of images. If you click on the button labeled with a + you will add the currently selected voxel in all slices to the currently highlighed material in the material list (top left).

magic wand

13

2. Avizo modules Step 2 Given the original data and the LabelField dened above you can extract a sub-volume from the data by using the Arithmetic module. Attach a ComputeArithmetic module to the original data. Now right-mouse-click in the small rectangle in front of the name of the Arithmetic icon and select the InputB section. Move the line that appears towards the LabelField data set and left click this icon to connect the LabelField as an additional input to the Arithmetic module. The rst attached object will be called in the Arithmetic expression eld A the second connected data set will be called B. With the expression we can now execute a arithmetic-logical operation on the pair of voxels from both data sets. Especially we would like to extract from A the values for which B is our region of interest. The result of this operation will be a new data object which combines the two inputs. Enter in the expression eld (B==1)*A. This will extract the gray values from InputA where the LabelField (InputB) has a value of 1. By default the background in a LabelField has a value of 0 and all other materials get successive values starting from 1 up to a value of 255. If there is one material dened its value will be 1. The multiplication in the expression eld is working on two numbers, the rst one is the return value of (B==1) which is either true (1) or false (0). The second value is the gray value in the original value itself. This way we will multiply the values in the region of interest by 1, which leaves them unchanged and we will multiply the values in all other materials and the background by 0 which eectively replaces all those gray values in A by 0. The background value in the new data set will be 0 which is usually a good choice but in general one would like to be exible to select another background value in case that 0 is for example a valid value in the region of interest. In this case you can change the expression into: (B==1)*A+(B!=1)*42 which will set the background value to a value of 42 while keeping the original values in the rst material.

expressions

2.2. Volume rendering for RGB images


The general volume rendering for RGB images may show an opaque cube instead of the expected transparent i.e. semi-transparent colors. The reason for this is there is a alpha channel required for volume rendering. In most rgb(a) images, the alpha channel is set to 1, meaning that all voxels are opaque. For semi-transparent rendering you need to set the alpha channel of the images. This can be done very exibly with the modules ComputeArithmetic, ComputeChannelWorks and ComputeColorCombine. In most cases however, a simple TCL command will do what you need. The command is alphaAverage [min] [max] and it calculates an alpha channel that parallels the luminance of the image. The description of the command in the Online Users Guide says: alphaAverage [min] [max]. Sets the alpha value of all voxels equal to the luminance 0.3*R + 0.59*G + 0.1*B, i.e., brighter objects become more opaque. If min and max are specied the alpha values are scaled so that they ll this range. On default min and max are 0 and 255, respectively. This is a command that can be applied to any RGBA eld in Avizo and produces an alpha channel, where every bright voxel is opaque while the dark voxels become transparent. The

14

2.3. Analysing volumetric densities min value in the argument list of alphaAverage may be greater than the the max value, resulting in inverted transparency ramp.

2.3. Analysing volumetric densities


Densities in three dimensional space arrise for example in the context of segmentation. Once a segmentation is performed and it extracts a specic material one can ask questions about how to describe the distribution of the material in space. Either the material is a single object or it may also be distributed in some way throughout the volume. A single object or a small group of object can be suciently described by the properties computed by the MaterialStatistics module. It will allow us to get the number of connected objects and their mean positions and volumes. For a distributed object the list of mean positions and volumes is less informative. It is better to describe the density inside the volume by for example plotting the density of a projection of the data volume onto a plane. Wherever we have a high density in the volume we will end up with a large value. Start with exporting one label in into a seperate label eld. This is not a requirement for the next steps but we will assume that there is only a single non-background material in the label eld. Find out what is the number of material you are interested in. This is dened by the order in which the materials are listed in the Segmentation Editor. The rst material will always be the background material (Exterior) and has the number 0. Your material will be at a position 1 or higher. Attach a Arithmetic module to your label eld and enter the following expression A==1 where 1 represents the position of your material in the list. After clicking on the DoIt button the resulting object will be a normal uniform scalar eld instead of a label eld so we could convert it back into a label eld but we would have to do this again later so we will not care about the data type right now. Now change the voxel size of the object to 1 in the direction which you want to analyze. This will assure that each slice is at an integer multiple position of the slice before. By multiplying each voxel of our material (1) with its position in the data stack we can therefore produce a sorted list of materials which correspond with their material number with their position in the stack. Connect an Arithmetic module to our label eld and enter the expression A*x in order to generate such a eld. The variable x represents the location of each voxel in the x direction. The resulting label eld will contain as many materials (numbers) as there are slices in the data set. Now convert this data set back into a label eld. This can be done by the CastField module which will present the label eld option for any input object which is of type byte. On the label eld you can apply MaterialStatistics and by using the by slice methods you can get the volume for each slice. Plotting the volume over its slice position you will get a curve which scales linearily with the density in your data projected to the yz plane of your data set.

2.4. Displaying segmentation results


The result of a segmentation is a new data set called a label eld. This label eld contains for each voxel in space its material. It also contains informations about the names of

15

2. Avizo modules materials and about the colors used to display these materials. In order to display the labels one can use a slicing module like OrthoSlice. Used on a label eld it will display the labels as dierent levels of gray even if other colors are specied in the parameter section of the label eld. In order to get the correct display one needs to generate a custom colormap which can be used by OrthoSlice. This colormap can be created by a console command of the label eld called makeColormap. After attaching the created colormap to the OrthoSlice and selection of the colormap mapping type each material will be displayed in the colors specied by the label eld. Here is a resource le which adds this feature as a new display module for label elds. The name of the module is OrthoSliceLabel and it will appear in the Display section after right-mouse click on a label eld. Copy the text into a le with the extension .rc and save it in your Avizo*/share/script-object/ directory.
module -name "OrthoSliceLabel" \ -package "hxscriptobj" \ -primary HxUniformLabelField3 \ -category "Display" \ -proc { $PRIMARY makeColormap; $PRIMARY fire; set cm [lindex [all] end] set os [[create HxOrthoSlice] setLabel "OrthoSlice"] $os data connect $PRIMARY $os mappingType setValue 2 $os colormap setValue $cm $os fire }

2.5. Align two data sets using ObliqueSlice


Two data sets can be aligned by a single view using the display module ObliqueSlice. The module allows for arbitrary rotations (enable the rotate checkbox). ApplyTransform is a module that can be connected to the data and additionally to an existing ObliqueSlice module. It will use the transformation dened by the ObliqueSlice module in order to re-orient the data. In order to use this for alignment do the following: a) load the reference (1) and the to be aligned data set (2) b) attach an OrthoSlice module to 1 c) attach an ObliqueSlice module to 2 which you rotate so that it matches the image seen in the OrthoSlice module on 1 d) press the Apply button on ApplyTransform

16

2.5. Align two data sets using ObliqueSlice The resulting data set will contain the transformation you are looking for. In order to get the resampled data using this transform simply use the console command applyTransform after the name of the result data object. In order to test the alignment one can attach Ortho- or ObliqueSlice modules to the two data sets and move them in sync. The synchronization can be handled by a small script that uses the fact that the display module will notify its downstream objects (the script) of any change in its orientation.
# Amira-Script-Object v3.0 $this script hide $this proc constructor {} { $this newPortConnection con HxObliqueSlice } $this proc compute {} { set os1 [$this con source] if { $os1 == "" } { return }

set plane [$os1 getPlane] set oss [all HxObliqueSlice] foreach u $oss { if { $u == $os1 } { continue; } $u setPlane [lindex $plane 0] [lindex $plane 1] [lindex $plane 2] [lindex $plane 3] [lindex $pla $u fire } }

17

2. Avizo modules

18

Part II. Avizo scripting

19

3. Introduction
Scripting of Avizo is done by writing text les in the Tcl1 language. Tcl is known as an easy to learn language with not enough restrictions to be useful for large projects (and its to slow for complex computations as well). A language with more restrictions is useful to reduce the number of error you can make and to allow an ecient bug nding. The magic cookies capabilities of Avizo rely on a special header that has to be included in all script les
# Amira-Script-Object V3.0

Once this line is added to a le Avizo will recognize it as a script object and after loading it it will be added to the object pool with a blue icon. If you encounter any problems at this point, like Avizo being not able to load a text le with this header line, make sure that the coding type of the text is correct, that is if the line has the correct line ending character(-s). Script objects are only one way of adding functionality to Avizo. There is also a startupinit le which Avizo executes each time it is loaded. Adding Tcl-command to this le you can extend Avizo by new behavior like key-bindings. The startup le is called Avizo.init and it is located in Avizos share/resources/ directory. An additional way of adding new functionality is by editing the resource les of Avizo. Those les describe for example what readers/writers are bound to which le extensions or magic cookies. It is very easy to add for example a new le extension like .jpgs2 to the reader that reads JPEG les.

3.1. Remote controlling Avizo


There are dierent ways of doing something like this in Avizo. First of all, (i) you can start Avizo with a script le as a parameter. In this le you have to write your commands to be executed. The Avizo script language is Tcl. For doing something like loading the medical/surf.hx le and displaying it you can do this manually in Avizo and save your work with FileSaveNetwork. The resulting le contains the Tcl-commands needed to re-create your network (data and display modules). This is the best way of learning Avizos Tcl language.
1

Usually there are two languages named side by side one is Tcl the other Tk. Avizo only understands Tcl. Because of this you will not be able to create your own user interface using the Tk language. Avizo provides its own interface for that. 2 This le extension was invented to describe stereo images. Basically two images side by side are saved as one JPG coded image. If you want to display two images as a single stereo image use the: viewer setBackgroundImage -stereo image1.tif image2.tif command. Series of tif images should be displayed by the MovieMaker. In order to create high quality stereo images do single snapshots while using viewer rotate 5 u.

21

3. Introduction You can add to this le the command to save an image3 in, for example, ti format by adding the line
viewer 0 snapshot [-stereo][-offscreen x y][-alpha][-tiled x y] "c:/myimage.tif"

The viewer contains other features as well like the ability to save the current scene in an .iv or .wrl le
viewer 0 saveScene [-binary] [-root] file.iv viewer 0 saveSceneAsVRML file.wrl [1|2]

or to count the current frame rate (in frames per second)


viewer performance [-n]

In later versions of Avizo you can also call


viewer showFramesPerSecond 1

to show the current frame rate in the upper right corner of the screen. (ii) Another way of remote controlling Avizo is by using the Avizo build in socket connection. You have to start an Avizo process and enter in the console window app -listen. Avizo will answer that it is listening to port 7171 (default). You can now call Avizo by executing: Avizo -cmd echo hello -host your.host.na.me -port 7171 and the command in cmd will be executed in the other Avizo. Writing a short script le
#!/bin/sh Avizo -cmd "$*" -host your.host.na.me -port 7171

you can send arbitrary commands this way to Avizo. If you want to control Avizo from you own programs you can send commands directly to the socket. Each command has to be preceeded by a unsigned char dening the length of the overall command. Note that messages send this way have a maximum length of 255 characters. You cannot use the -cmd line to provide a tcl command at the startup of your local Avizo only the remote machines will see your command. In case you just want to provide some tcl command line argument that is executed at the startup of Avizo you can use the following trick. Dene a command line argument using -tclargs. This will generate a global tcl variable called argv (and argc), but it will not execute them automatically. In order to toggle the execution of the argument you need to change the Avizo.init le. Add the following to the end of the le:
3

A screenshot can also be done by the keyboard shortcut Ctrl-C or Ctrl-Insert. The image will be copied into the clipboard thus can be read into any other program. Please note that images imported in this way will be not compressed. Other keyboard shortcuts include the cursor keys which shift the displayed image up/down or left/right and the spacebar which is equivalent to the ViewAll viewer button.

22

3.1. Remote controlling Avizo

if { [info exists argv] } { if { [string length $argv] > 0 } { eval $argv } }

Now if you start Avizo with Avizomain.exe -tclargs echo HI whatever tcl command is between the double-quotes will be executed. (iii) There is also a way to start Avizo without its graphical user interface. This is interesting if you want to use Avizo for computations only. You can start Avizo with the command line option -no gui and a script le and it will execute that script. But this way you will not get image written by the snapshot command because this uses a screen-grab mechanism which is not available if Avizo is started without its gui. Please note that you have to be careful in starting a lot of Avizo processes this way like from a batch le. The call to Avizo will return instantly because a new process is spawned. A lot of running Avizo processes will depleed your machines memory soon. One way to wait for the current Avizo to nish is to rst put a quit command in the executed script le and second to wait till the Avizo process is nished. Using Windows you can use a shell script with
START /w Avizomain.exe job1.hx

this will execute Avizo in foreground mode and so wait till Avizo is nished. Using a linux environment like cywin you could also do:
# start the first Avizo process ... while [ ps -al | grep Avizomain | wc -w -ne 0 ]; do sleep 1; done # start the second Avizo process _after_ the first ...

1 2 3 4 5

(iv) In order to get a remotely running Avizo to display its windows on a local machine you can use a program like vnc. Set some environment variables like OIV REMOTERENDERER=ON and OIV REMOTERENDER DISPLAY=:0.0. Make sure that the vnc window is using a 24-bit color display (start its server with -depth 24). (v) Opening a socket connection from another program one can also remotely enter Tcl commands. Here an example using Tcl (in Avizo use app listen 3000 to open the port)
> % % % tclsh 1 proc send { c } { set s [socket localhost 3000]; puts $s $c; flush $s; close $s } 2 send "echo \"First Command\"" 3 send "echo \"Second Command\"" 4

23

3. Introduction

3.2. Displaying your logos


Another example how to use the Inventor le description is if you want to insert a logo in your animations. This here is the standard le that is loaded if we want to display the Avizo and VSG logo during our presentations:
#Inventor V2.1 ascii Annotation { OrthographicCamera { viewportMapping LEAVE_ALONE } Translation { translation -1 -1 0 } Image { filename "Avizo.gif" } Translation { translation 2 0 0 } Image { filename "logo.gif" horAlignment RIGHT } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

If you want to display your logos you should be interested that they do not disappear if you load another network, or more specically that it does not get removed after a call to remove all. This can be achieved by dening a variable
$this setNoRemoveAll 1

in the constructer of the script object. A similar setting in C++ would set a HxObject property called noRemoveAll.

3.3. Enable the reload button


For the development of script objects the re-write test cycle can be very time consuming. To speed up the reload of the changed script there is a special port which can be displayed for each Avizo script object. To enable the port you have to add the following line to your Avizo script object (preferable at the beginning of the le after the header line):
$this script show

The standard behavior of this feature was changed and it is now switched o by default. You should remember to switch it o once the script is nished developing in order to decrease the amount of ports displayed by the le.

24

4. Compute modules
4.1. A standard compute
There are some parts that appear several times if you are working with script objects. One is that you want to do something only if the user was hitting the DoIt button, another that you would like to have a connected data set. Here is an example how this initial script would look like:
# Amira-Script-Object V3.0 $this proc constructor {} { $this newPortButtonList doit 1 $this doit setLabel 0 "DoIt" } $this proc compute {} { if { ![$this doit wasHit 0] } { return } set inputData [$this data source] if { $inputData == "" } { echo "Please connect to a data object" return; } ...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

There is a variable which indicates in which directory on disk the current script was loaded from SCRIPTDIR. Note that this is a tcl global variable and it is by default hidden in any procedure. You will have to add a global SCRIPTDIR to the beginning of the procedure in order to make it known to tcl before you can use it.

4.2. A real example


Lets write a script which is working like a compute module. It will have a input data set and it will produce an output after a set of operations. We will assume that we have a data set which was read in as short values while in fact the data is represented as unsigned short. The conversion will use HxArithmetic and CastField to do the transformation. Here is the whole script

25

4. Compute modules

# Amira-Script-Object V3.0 # uncomment this line for debugging # $this script show # create the user interface (a single doit button) $this proc constructor {} { $this newPortButtonList doit 1 $this doit setLabel 0 "DoIt" } # now the computation $this proc compute {} { # first check if the user did in fact hit DoIt if { ![$this doit wasHit 0] } { return } # now check if there is a input to this script set inputData [$this data source] if { $inputData == "" } { echo "no input connected" return } # all went well so now create an Avizo module CastField set cf [create HxCastField] # set its label to CastField set cf [$cf setLabel "CastField"] # connect our input data to its data port $cf data connect $inputData # set values CastFields parameter section (unsigned short) $cf outputType setValue 2 # now simulate a user button click on CastFields doit $cf action touch $cf action hit $cf fire # get the output from CastField (assume that it worked) set output [lindex [lindex [$cf downStreamConnections] 0] 0] # create an Arithmetic module and connect both signed and unsigned data set ar [create HxArithmetic] set ar [$ar setLabel "Arithmetic"] $ar inputA connect $output

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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

26

4.2. A real example


$ar inputB connect $inputData # the expression uses the sign of the signed dataset to swap bytes $ar expr0 setValue \"a*(b>0) + (65535+b)*(b<0)" # now simulate a button click on Arithmetic $ar doIt touch $ar doIt hit $ar fire # clear up afterwards, leave only output of Arithmetic on screen remove $cf remove $output remove $ar }
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

In order to make this script accessible in the Avizo interface we need to write a .rc le which goes together with the script. Usually the above script is saved with extension .srco in Avizos share/script-objects/ directory. Also the .rc le should be saved in this directory. The .rc le will tell Avizo the name of our module, its input data type and the category where the script appears in the Avizo user interface after righ-mouse-click on our input data object. We named our two test les ConvertSignedToUnsigned.scro (the script above) and ConvertSignedToUnsigned.rc (the resource le below).
module -name "ConvertSignedToUnsigned" \ 1 -package "hxscripobj" \ 2 -primary "HxSpatialData" \ 3 -category "Compute" \ 4 -proc { 5 set mod [[create HxScriptObject] setLabel ConvertSignedToUnsigned] 6 $mod script setValue $AVIZO_ROOT/share/script-objects/ConvertSignedToUnsigned.scro 7 $mod fire 8 $mod data connect $PRIMARY 9 } 10

Additionally we could also specify that our module only works with a specic primitive input type. This is done with the check option. Here an example which species that our module only works with a input which is of type oat
-check { [$PRIMARY primType] == 3 } \

Another example is to customize an existing module. Here a short example that will add a new CameraRotate module with a time slider that starts at 0 and ends at 360 (instead of 1).
dataClass -name "CameraRotate360" \ -class "HxCircularCameraPath" \

27

4. Compute modules
-category "Main" \ -proc { $this time setMinMax 0 360 } -dso "libhxcamera.so"

Most .rc les will just reference an existing .scro script le. But in general rc-les are as powerful as scro scripts. Here is an example of an .rc le that provides a compute module. The module will generate a label eld for the given input dataset with predened materials and colors. After the label eld is created the segmentation editor is opened.
module -name "MyVeryOwnLabel" \ -package "hxscriptobj" \ -category "Compute" \ -primary HxUniformScalarField3 \ -proc { if { [exists $PRIMARY] } { set labelData [create HxUniformLabelField3 labelField] $labelData ImageData connect $PRIMARY $labelData fire $labelData parameters newBundle Materials # now add your materials, there is always Exterior $labelData parameters Materials newBundle Exterior $labelData parameters Materials Exterior setValue Color "0 0 0" # add another material with a color $labelData parameters Materials newBundle Brain $labelData parameters Materials Brain setValue Color "0.5 0.5 0.5" # etc.. # start Segmentation Editor set gi [create HxGiEditor] $gi attach $labelData }; }

4.3. Multiple output objects of a script


There is currently not a way to dene multiple output objects using the setResult function. But one can dene input ports and use them as a placeholder for output objects. Being connected to an output object will help the script to overwrite its output if a new computation is requested instead of creating new output objects.
# Amira-Script-Object v3.0 # Example program for multiple outputs of a script object # Hauke Bartsch, Visage Imaging 2008 $this script show $this proc constructor {} { $this newPortDoIt action
1 2 3 4 5 6 7 8

28

4.3. Multiple output objects of a script


$this action setLabel "Apply" $this newPortConnection output2 HxUniformScalarField3 } $this proc compute {} { if { ![$this action wasHit] } { return; # print the information for the two output fields } set result1 [$this createADataSet] set result2 [$this createADataSet] $this setResult $result1 # The above is equivalent with the following: #$result1 master connect $this # Any other resulting field has to be exported # like this: $this output2 connect $result2 } $this proc createADataSet {} { # create an empty field filled with zeros set field [create HxAnnaScalarField3] set arith [create HxArithmetic] $arith inputA connect $field $arith fire $arith expr setValue 0 $arith resolution setValue 0 100 $arith resolution setValue 1 100 $arith resolution setValue 2 100 $arith doIt touch; $arith doIt hit; $arith fire; set result [$arith getResult] $result master disconnect remove $field return $result } # print the information for the two output fields $this proc update {} { set res0 [$this getResult] # if there is no result, the above call will return nothing # and res0 does not get created. In this case we have to find # out first if res0 exists before looking for its value. if { [info exists res0] && $res0 == "" } { echo "no first input" } else {
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58

29

4. Compute modules
echo "first output: [$res0 getLabel] of type [$res0 getTypeId]" } # for every other output set res1 [$this output2 source] if { $res1 == "" } { echo "no second input" } else { echo "second output: [$res1 getLabel] of type [$res1 getTypeId]" } }
59 60 61 62 63 64 65 66 67 68 69

4.4. Iterating over objects in the workpool


You can iterate over objects in the workpool using the all command. The following code will get you all the currently loaded HxIvObject les
foreach obj [all HxIvObject] { echo $obj }

The all command all can have several options like -selected, -visible, -hidden, -classes, -packages, and -classesof. Each one giving back a sub-section of the currently available classes. To query the type name (like HxIvObject) for a loaded object you can use the following call
lobus.am getTypeId

where lobus.am is the name of the icon.

4.5. Annotations in 3D
You can use the Measurement tool for setting labels in 3D. Another way is to use a .iv le and put the description of the text inside the le. The le has to look like this:
#Inventor V2.1 ascii Separator { Separator { BaseColor { rgb 0.004 0.247 0.537 } Font {
1 2 3 4 5 6 7 8

30

4.5. Annotations in 3D
name "HelveticaBold" size 50 } Transform { translation 10 10 20 scaleFactor 1 1 1 } Text3 { string "Hauke Bartsch" parts ALL } } }
9 10 11 12 13 14 15 16 17 18 19 20 21

You can read in this le and the text will be displayed at the location 10, 10, 20. A script object which implements a single Annotation per module would be
# Amira-Script-Object V3.0 #$this script show $this proc constructor {} { $this newPortText label $this label setValue "label1" $this newPortFloatTextN position 3 $this newPortFloatTextN color 3 $this color setMinMax 0 1 $this color setValue 0 0.004 $this color setValue 1 0.247 $this color setValue 2 0.537 $this newPortIntTextN fontSize 1 $this fontSize setMinMax 1 100 $this fontSize setValue 50 $this setVar Annotation3DDisplay "empty" $this setVar text "empty" }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

This will produce the interface of the script object. color is a 3 eld text port and fontSize a single entry text port. Whenever we have a change in the interface the compute module will recompute the display thus we do not need a doit button in this example. Here is the source code for the compute, generating the appropriate .iv le on disc and reading it in:
$this proc compute {} { remove [$this getVar Annotation3DDisplay] [$this getVar text] set fp [open "annotation.iv" w] puts $fp "#Inventor V2.1 ascii"
1 2 3 4 5

31

4. Compute modules
puts $fp "Separator {\n\tSeparator {\n\tBaseColor {\n\t\trgb " puts $fp "[$this color getValue 0] [$this color getValue 1] " puts $fp "[$this color getValue 2] \n\t}" puts $fp "\tFont {\n\t\t name \"helveticaBold\"\n\t\tsize " puts $fp "[$this fontSize getValue]\n\t}" puts $fp "Transform {\n\t\ttranslation [$this position getValue 0] " puts $fp "[$this position getValue 1] [$this position getValue 2]" puts $fp "\t\tscaleFactor 1 1 1\n\t}" puts $fp "\tText3 {\n\t\tstring \"[$this label getValue]\"" puts $fp "\n\t\tparts ALL\n\t}\n\t}\n}" close $fp $this setVar text [load "annotation.iv"] set text [$this getVar text] $text fire $text hideIcon $this setVar Annotation3DDisplay [create HxIvDisplay] set Annotation3DDisplay [$this getVar Annotation3DDisplay] $Annotation3DDisplay $Annotation3DDisplay $Annotation3DDisplay $Annotation3DDisplay $Annotation3DDisplay $Annotation3DDisplay hideIcon data connect $text fire drawStyle setValue 0 0 fire setViewerMask 65535
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 34

} $this proc destructor {} { remove [$this getVar Annotation3DDisplay] [$this getVar text] }

We saved the additional variables of the names of the connected (and by the script generated) objects by calls to setVar which will be specic for the actual script object. Another instance of Annotation3DDisplay will have its own memory.

4.6. Transformations on Data Files


Transformations can be set in a very general way using the command <objectName> setTransform Mik in the console window, where Mik is a 4 x 4 matrix which is given in the following convention: M (0, 0)M (1, 0)M (2, 0)...M (1, 3)M (2, 3)M (3, 3) The upper left 3 3 sub-matrix denes the directional vectors and are in the orthogonal case 100, 010 and 001 (see sketch below). M (0, 0) M (0, 1) M (0, 2) M (0, 3) M (1, 0) M (1, 1) M (1, 2) M (1, 3) M (2, 0) M (2, 1) M (2, 2) M (2, 3) M (3, 0) M (3, 1) M (3, 2) M (3, 3)

Set those vectors to any oblique direction results in a shearing.

32

4.7. Renement of surfaces If you are using the PSI le format you can compute a rigid transformation (given two sets of points) by reading in the point sets and using combineLandmarks to generate a single landmark set with the two PSI data clouds as sets inside. Use the following command in the Avizo console window to obtain the 4 4 transformation matrix representing the least squares solution to the rigid transformation of the rst to the second point set:
landmarkdata computeRigidTransform

4.7. Renement of surfaces


Surfaces are collections of points and triangles. A good quality surface will have the least number of triangles possible to code for its structure. Also the points that constitute the surface vertices should be shared between the dierent triangles. Only then the surface is recognized by Avizo as connected (no holes). If vertices duplicates exist prior to any other operation these duplicates should be removed. The tcl command
surface.surf removeDuplicatePoints <tolerance>

remove duplicate points where the maximum distance between two points is given by the tolerance. The tolerance value is in units of the bounding box size and should be in general a very small value like 1e-6. Large value will remove some triangles very small values will miss duplicate points. Surface simplication is a process which requires multiple steps starting with removing of duplicate points and degenerate triangles to re-meshing triangles with bad aspect ratio. Even if the last goal is not to reduce the number of triangles an improvement of the triangle structure is a prominent post processing step for surfaces. The following script object is automating the process of simplifying a given surface without reducing the number of triangles. We start with setting up the interface, a single button starting the process of simplication once the user connects the script to a surface object.
# Amira-Script-Object V3.0 $this proc constructor {} { $this newPortButtonList Action 1 $this Action setLabel Action: $this Action setLabel 0 CleanSurface }
1 2 3 4 5 6 7

Now we actually perform the computations. We use some methods that all surface objects inherit like removeDuplicatePoints
$this proc compute {} { if [$this Action isNew] { if {![$this checkData]} {return}
1 2 3

33

4. Compute modules
4

set cleanedSurf [$this data source] set nBefore [ $cleanedSurf getNumTriangles ] # echo Number of triangles after welding = $nBefore set bb [ $cleanedSurf getBoundingBox ] set edgeLength [ expr ([lindex $bb 1] - [lindex $bb 0])/15.0 ] echo Edge length = $edgeLength # Weld duplicated vertices... $cleanedSurf removeDuplicatePoints [ expr 0.005] $cleanedSurf removeDegenerateTriangles

5 6 7 8 9 10 11 12 13

Attaching a simplier object we can make several runs improving the surface.
create HxSimplifier Simplifier Simplifier attach $cleanedSurf $cleanedSurf select # Number of triangles Simplifier simplifyParameters setValue 0 1 # Maximum edge length Simplifier simplifyParameters setValue 1 $edgeLength # Minimum edge length Simplifier simplifyParameters setValue 2 [expr $edgeLength/20.] # Fast option (less intersection testing) Simplifier simplifyOptions setValue 1 1 # Dont preserve slice structure Simplifier simplifyOptions setValue 0 0 # Scaling factor edge length part of error Simplifier setFactError [ expr 0.05 ] Simplifier fire # Flip edges of bad triangles (no crease angle check yet) Simplifier setRadiusRatio 250 Simplifier simplifyAction setIndex 2 Simplifier fire Simplifier simplifyAction setIndex 2 Simplifier fire Simplifier simplifyAction setIndex 2 Simplifier fire Simplifier simplifyAction setIndex 2 Simplifier fire # Simplify the refined surface again... Simplifier simplifyAction setIndex 0 # Simplifier fire # Contract short edges Simplifier simplifyAction setIndex 3 Simplifier fire
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 34 35 36 37

34

4.8. Simplify a surface


Simplifier detach $cleanedSurf set nAfter [ $cleanedSurf getNumTriangles ] set removedTriangles [ expr ($nBefore - $nAfter) ] echo Number of bad triangles removed : $removedTriangles } } # check if a suitable data object is connected to the module $this proc checkData {} { # check if a suitable object is connected set obj [$this data source] if {$obj == ""} { echo "$this: no data object connected." $this time stop return 0 } if {![$obj hasInterface HxSurface]} { echo "$this: $obj is not a suface object." $this data disconnect return 0 } return 1 }
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61

4.8. Simplify a surface


set mySimplifier [create HxSimplifier] $mySimplifier attach $mySurf $mySimplifier simplifyParameters setValue 0 10000 $mySimplifier simplifyAction setValue 0 1 $mySimplifier simplifyAction send $mySimplifier detach
1 2 3 4 5 6

4.9. Apply image lter


Image lters can be attached to loaded data objects. Here is a lengthly example on how you can perform images lters on a lot of les one after the other. The module scans the image lter object for all dened lters and gives the user the choice which algorithm to perform.
# Amira-Script-Object V3.0 # this script allows to apply digital filters to single # files or a series of files (all loaded from disc) # internal file list
1 2 3 4 5 6

35

4. Compute modules
set files {} set filterObject [create HxImageVisionEditor] set firstTime 0 $this proc constructor {} { $this newPortInfo text $this text setValue "Please select files from disc" $this newPortFilename fileselection $this fileselection setLabel "Input Files" # go in multiple file selection mode $this fileselection setMode 2 $this newPortText "Output Directory" $this newPortButtonList filterButton 1 $this filterButton setLabel "" $this filterButton setLabel 0 "DoIt" } $this proc destructor {} {} $this proc global global global compute {} { files filterObject firstTime
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

# get the files selected 31 set files [$this fileselection getFilename] 32 if { [llength $files] != 0 } { 33 $this text setValue "Select a filter to be applied" 34 if { $firstTime == 0 } { 35 $this add_filter 36 } 37 if { [$this filterSelection isNew] } { 38 $this update_params 39 } 40 } 41 if { [$this filterButton wasHit] } { 42 echo "now apply the current filter to all the files" 43 workArea startWorking "start processing files" 44 for {set i 0} {$i < [llength $files]} {incr i} { 45 set progress [expr 1.0*$i / [expr [llength $files]]] 46 workArea setProgressValue $progress 47 workArea setProgressInfo "processing [expr $progress*100]%% \"[lindex $files $i]\"" 48 # echo "apply current filter to \"[lindex $files $i]\"" 49 $this apply_filter [lindex $files $i] 50 } 51 workArea stopWorking 52 } 53 } 54
55

$this proc apply_filter { filename } {

56

36

4.9. Apply image lter


global PRIMARY # see if we have an output directory if { ![file isdirectory [$this "Output Directory" getValue]] } { echo "please supply an valid output directory" return } if { ![file isfile $filename] } { echo "Error: \"$filename\" is not a file" return } # load the file set hideNewModules 0 set data [load $filename] set filterObject [create HxImageVisionEditor] $filterObject attach $data $filterObject fire # this is Brightness if { [$this filterSelection getValue] == $filterObject filter setValue 0 $filterObject fire #echo [$filterObject allPorts] $filterObject "orientation" setValue $filterObject param0 setValue [$this } # this is Median 2D if { [$this filterSelection getValue] == $filterObject filter setValue 1 $filterObject "orientation" setValue $filterObject param0 setValue [$this } # this is Minimum 2D if { [$this filterSelection getValue] == $filterObject filter setValue 2 $filterObject "orientation" setValue $filterObject param0 setValue [$this } # this is Maximum 2D if { [$this filterSelection getValue] == $filterObject filter setValue 3 $filterObject "orientation" setValue $filterObject param0 setValue [$this } # this is Unsharp Masking if { [$this filterSelection getValue] == $filterObject filter setValue 4
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

0 } {

79 80 81 82

[$this "Orientation" getValue] "Scale" getValue]

83 84 85 86

1 } { [$this "Orientation" getValue] "Kernel Size" getValue]

87 88 89 90 91 92

2 } { [$this "Orientation" getValue] "Size" getValue]

93 94 95 96 97 98

3 } { [$this "Orientation" getValue] "Size" getValue]

99 100 101 102 103 104

4 } {

105 106

37

4. Compute modules
$filterObject "orientation" setValue [$this "Orientation" getValue] $filterObject param0 setValue 0 [$this "Kernel Size" getValue 0] $filterObject param0 setValue 1 [$this "Kernel Size" getValue 1] } # this is Histogram 2D if { [$this filterSelection getValue] == 5 } { $filterObject filter setValue 5 $filterObject "orientation" setValue [$this "Orientation" getValue] $filterObject param0 setValue [$this "Clip Limit" getValue] } # this is Laplacian Zero Crossing if { [$this filterSelection getValue] == 6 } { $filterObject filter setValue 6 $filterObject "orientation" setValue [$this "Orientation" getValue] } # this is 3D Gauss Filter if { [$this filterSelection getValue] == 7 } { $filterObject filter setValue 7 $filterObject param0 setValue 0 [$this "Sigma x" getValue 0] $filterObject param0 setValue 1 [$this "Sigma x" getValue 1] $filterObject param1 setValue 0 [$this "Sigma y" getValue 0] $filterObject param1 setValue 1 [$this "Sigma y" getValue 1] $filterObject param2 setValue 0 [$this "Sigma z" getValue 0] $filterObject param2 setValue 1 [$this "Sigma z" getValue 1] } # this is 3D Lanczos Filter, Phase 0 if { [$this filterSelection getValue] == 8 } { $filterObject filter setValue 8 $filterObject param0 setValue 0 [$this "Width x" getValue 0] $filterObject param0 setValue 1 [$this "Width x" getValue 1] $filterObject param1 setValue 0 [$this "Width y" getValue 0] $filterObject param1 setValue 1 [$this "Width y" getValue 1] $filterObject param2 setValue 0 [$this "Width z" getValue 0] $filterObject param2 setValue 1 [$this "Width z" getValue 1] } # this is Histogram 3D if { [$this filterSelection getValue] == 9 } { $filterObject filter setValue 9 $filterObject param0 setValue [$this "Clip Limit" getValue] } # this is Sobel 3D if { [$this filterSelection getValue] == 10 } { $filterObject filter setValue 10 ; } # this is Median 3D if { [$this filterSelection getValue] == 11 } { $filterObject filter setValue 11 $filterObject param0 setValue [$this "Kernel Size" getValue] }
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156

38

4.9. Apply image lter


# execute the filter $filterObject fire # now save the new data set fn [lindex [split $filename "/"] end] $data save "AmiraMesh" "[$this "Output Directory" getValue]/$fn" # clean the data object $filterObject detach remove $data } $this proc global global global global add_filter { } { filterObject firstTime files oldports
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174

# remove any old filter selection box if { $firstTime == 1 } { $this deletePort filterSelection } set firstTime 1 # add a new filterSelection $this newPortMultiMenu filterSelection 1 $this filterSelection setValue "Filter" # load the first object in the list set hideNewModules 0 set data [load [lindex $files 0]] set filterObject [create HxImageVisionEditor] $filterObject attach $data $filterObject fire # attach the first object in list to the filter # in order to get all the filters set i 0 $filterObject filter setValue $i # cycly through all filter objects while { $i == [$filterObject filter getValue] } { $this filterSelection setNum [expr $i + 1] $this filterSelection setLabel 0 $i [$filterObject filter getLabel 0 $i] incr i $filterObject filter setValue $i $filterObject fire } $filterObject filter setValue 0 $filterObject fire

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206

39

4. Compute modules
207

# clean the data object $filterObject detach remove $data } $this proc global global global update_params {} { filterObject files oldports

208 209 210 211 212 213 214 215 216 217

# load the first object in the list set hideNewModules 0 set data [load [lindex $files 0]] set filterObject [create HxImageVisionEditor] $filterObject attach $data $filterObject fire $this remove_old_ports # this is Brightness if { [$this filterSelection getValue] == 0 } { $this newPortMultiMenu "Orientation" 1 lappend oldports "Orientation" $this "Orientation" setNum 3 $this "Orientation" setLabel 0 0 "YZ planes" $this "Orientation" setLabel 0 1 "XZ planes" $this "Orientation" setLabel 0 2 "XY planes" $this newPortFloatTextN Scale 1 lappend oldports "Scale" $this Scale setValue 1 } # this is Median 2D if { [$this filterSelection getValue] == 1 } { $this newPortMultiMenu "Orientation" 1 lappend oldports "Orientation" $this "Orientation" setNum 3 $this "Orientation" setLabel 0 0 "YZ planes" $this "Orientation" setLabel 0 1 "XZ planes" $this "Orientation" setLabel 0 2 "XY planes" $this newPortFloatTextN "Kernel Size" 1 lappend oldports "Kernel Size" $this "Kernel Size" setValue 3 } # this is Minimum 2D if { [$this filterSelection getValue] == 2 } { $this newPortMultiMenu "Orientation" 1 lappend oldports "Orientation" $this "Orientation" setNum 3 $this "Orientation" setLabel 0 0 "YZ planes"

218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256

40

4.9. Apply image lter


$this "Orientation" setLabel 0 1 "XZ planes" $this "Orientation" setLabel 0 2 "XY planes" $this newPortFloatTextN "Size" 1 lappend oldports "Size" $this "Size" setValue 3 } # this is Maximum 2D if { [$this filterSelection getValue] == $this newPortMultiMenu "Orientation" lappend oldports "Orientation" $this "Orientation" setNum 3 $this "Orientation" setLabel 0 0 "YZ $this "Orientation" setLabel 0 1 "XZ $this "Orientation" setLabel 0 2 "XY $this newPortFloatTextN "Size" 1 lappend oldports "Size" $this "Size" setValue 3 }
257 258 259 260 261 262 263 264

3 } { 1

265 266 267 268

planes" planes" planes"

269 270 271 272 273 274 275 276

# this is Unsharp Masking if { [$this filterSelection getValue] == 4 } { $this newPortMultiMenu "Orientation" 1 lappend oldports "Orientation" $this "Orientation" setNum 3 $this "Orientation" setLabel 0 0 "YZ planes" $this "Orientation" setLabel 0 1 "XZ planes" $this "Orientation" setLabel 0 2 "XY planes" $this newPortFloatTextN "Kernel Size" 2 $this "Kernel Size" setLabel 1 "Sharpness" $this "Kernel Size" setValue 1 0.5 lappend oldports "Kernel Size" $this "Kernel Size" setValue 0 3 } # this is Histogram 2D if { [$this filterSelection getValue] == $this newPortMultiMenu "Orientation" lappend oldports "Orientation" $this "Orientation" setNum 3 $this "Orientation" setLabel 0 0 "YZ $this "Orientation" setLabel 0 1 "XZ $this "Orientation" setLabel 0 2 "XY $this newPortFloatTextN "Clip Limit" lappend oldports "Clip Limit" $this "Clip Limit" setValue 7 } # this is 2D Laplacian Zero Crossing if { [$this filterSelection getValue] == $this newPortMultiMenu "Orientation"

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292

5 } { 1

293 294 295 296

planes" planes" planes" 1

297 298 299 300 301 302 303 304

6 } { 1

305 306

41

4. Compute modules
lappend oldports "Orientation" $this "Orientation" setNum 3 $this "Orientation" setLabel 0 0 "YZ planes" $this "Orientation" setLabel 0 1 "XZ planes" $this "Orientation" setLabel 0 2 "XY planes" } # this is 3D Gauss Filter if { [$this filterSelection getValue] == 7 } { $this newPortFloatTextN "Sigma x" 2 lappend oldports "Sigma x" $this "Sigma x" setLabel 1 "Kernel size x" $this "Sigma x" setValue 0 1 $this "Sigma x" setValue 1 3 $this newPortFloatTextN "Sigma y" 2 lappend oldports "Sigma y" $this "Sigma y" setLabel 1 "Kernel size y" $this "Sigma y" setValue 0 1 $this "Sigma y" setValue 1 3 $this newPortFloatTextN "Sigma z" 2 lappend oldports "Sigma z" $this "Sigma z" setLabel 1 "Kernel size z" $this "Sigma z" setValue 0 1 $this "Sigma z" setValue 1 3 } # this is 3D Lanczos Filter, Phase 0 if { [$this filterSelection getValue] == 8 } { $this newPortFloatTextN "Width x" 2 lappend oldports "Width x" $this "Width x" setLabel 1 "Kernel size x" $this "Width x" setValue 0 0.43 $this "Width x" setValue 1 3 $this newPortFloatTextN "Width y" 2 lappend oldports "Width y" $this "Width y" setLabel 1 "Kernel size y" $this "Width y" setValue 0 0.43 $this "Width y" setValue 1 3 $this newPortFloatTextN "Width z" 2 lappend oldports "Width z" $this "Width z" setLabel 1 "Kernel size z" $this "Width z" setValue 0 0.43 $this "Width z" setValue 1 3 } # this is Histogram 3D if { [$this filterSelection getValue] == 9 } { $this newPortFloatTextN "Clip Limit" 1 lappend oldports "Clip Limit" $this "Clip Limit" setValue 7 } # this is Sobel 3D if { [$this filterSelection getValue] == 10 } {
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356

42

4.9. Apply image lter


; } # this is Median 3D if { [$this filterSelection getValue] == 11 } { $this newPortFloatTextN "Kernel Size" 1 lappend oldports "Kernel Size" $this "Kernel Size" setValue 3 } # clean the data object $filterObject detach remove $data } $this proc remove_old_ports {} { global oldports if { ![info exists oldports] } { return } for {set i 0} {$i < [llength $oldports]} {incr i} { # echo "remove old port [lindex $oldports $i]" $this deletePort [lindex $oldports $i] } set oldports {} }
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382

43

4. Compute modules

44

5. File load and save


There is a similar problem involved for the save as for the load. Each data type is one its own responsible for appropriate le save and load functions. Therefore each le type (for example a surface) has its own save formats. Usually AmiraMesh will work as a generic le type but for example Surfaces generated by the MolSurface module will not know about AmiraMesh. In this case you should once try to save the le by hand and have a look into the format selection box in the save le dialog. It lists all the allowed le types. For the molecular surface for example you will nd a HxMolSurface entry. All the listen strings can be used as valid le types.

5.1. Write your own data sets


Here an example on how to write the le format. Every line will contain the voxels i, j and k coordinates and the value at the end. Please note that saving data this way is producing very large les which contain mostly redundant information. If you ever try to use this le format for real I would advice you to rst calculate the size of the le that will be needed to store even a small volume this way.
# Amira-Script-Object V3.0 # enable this line to see the reload button port #$this script show $this proc constructor {} { $this newPortFilename filename $this newPortDoIt doit $this doit setLabel 0 "DoIt" } $this proc update {} { set inputData [$this data source] if { $inputData == "" } { return } } $this proc compute {} { if { ![$this doit wasHit 0] } { return; } set inputData [$this data source]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

45

5. File load and save


if { $inputData == "" } { echo "Please connect a data object to the script." return } #echo "NOTE: This will take a lot of time" set outfile [open [$this filename getValue] w]
25 26 27 28 29 30 31 32

set dims [$inputData getDims] 33 workArea startWorking 34 workArea setProgressInfo "NOTE: This may be time consuming." 35 for {set sliceNumber 0} {$sliceNumber < [lindex $dims 2]} {incr sliceNumber} { 36 set a 0; 37 workArea setProgressValue [expr $sliceNumber/(1.0*[lindex $dims 2])] 38 if { [workArea wasInterrupted] != 0 } { break; } 39 for {set i 0} {$i < [lindex $dims 0]} {incr i} { 40 for {set j 0} {$j < [lindex $dims 1]} {incr j} { 41 puts $outfile "$i $j $sliceNumber [$inputData getValue $i $j $sliceNumber]" 42 } 43 } 44 } 45 workArea stopWorking 46 }
47

5.1.1. Write a SpreadSheet object


Informations derived from data objects can be saved as spreadsheet objects. Here an example which needs to be connected to a lineset object and it will report the length of each line found in the table.
# Amira-Script-Object v3.0 $this script show $this proc constructor {} { $this newPortButtonList doit 1 $this doit setLabel 0 DoIt $this newPortIntSlider slider $this slider setLabel whichLine $this newPortConnection lineset HxLineSet } $this proc compute {} { if { ![$this doit wasHit 0] } { return } set inputData [$this lineset source] if {$inputData == "" } { echo error: no lineset connected
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

46

5.2. Read your own data set


return } # how many lines? set numlines [$inputData getNumLines] # create a new Table object set sp [create HxSpreadSheet] $sp addColumn line float $sp addColumn length float for { set i 0 } { $i < $numlines } { incr i } { set numPoints [$inputData getNumPoints $i] set length 0 set oldpoint [$inputData getLineVertex $i 0] for { set j 1 } { $j < $numPoints } { incr j } { set currentPoint [$inputData getLineVertex $i $j] set pc1 [$inputData getPoint $oldpoint] set pc2 [$inputData getPoint $currentPoint] # add the length set length [expr $length +\ sqrt( ([lindex $pc2 0]-[lindex $pc1 0])*([lindex $pc2 0]-[lindex $pc1 0]) +\ ([lindex $pc2 1]-[lindex $pc1 1])*([lindex $pc2 1]-[lindex $pc1 1]) +\ ([lindex $pc2 2]-[lindex $pc1 2])*([lindex $pc2 2]-[lindex $pc1 2]) ) ] set oldpoint $currentPoint } # set the line number $sp setValue 0 $i $i # set the line length $sp setValue 1 $i $length } }
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

5.2. Read your own data set


Similar to the example above we now show how to read in data which is organized as x-, y-, z-coordinate followed by a value per line:
# Amira-Script-Object v3.0 # $this script show $this proc constructor {} { $this newPortFilename filename $this newPortButtonList doit 1 $this doit setLabel 0 "Apply" }
1 2 3 4 5 6 7 8 9 10 11

47

5. File load and save


$this proc compute {} { if { ![$this doit wasHit 0] } { return } set inputData [$this filename getValue] if { $inputData == "" } { echo "Please enter a valid filename with \"x y z value\" per line" return } if { [file readable $inputData] == 0 } { echo "Error: unable to read file \"$inputData\"" return } # first we find out the dimensions of the data, # we have to read everything echo "now counting volume size..." set fp [open $inputData "r"] set thisSize [gets $fp line] set maxx 0 set maxy 0 set maxz 0 set linenumber 0 while { $thisSize >= 0 } { set linenumber [expr $linenumber + 1] if { [scan $line "%d %d %d %g" x y z value] == 4 } { if { $maxx < $x } { set maxx $x } if { $maxy < $y } { set maxy $y } if { $maxz < $z } { set maxz $z } } else { echo "warning: line $linenumber does not match format" } set thisSize [gets $fp line] } close $fp echo "generated volume will be from 0..$maxx 0..$maxy 0..$maxz" # now we know the size of our maximum dimensions of the file # generate a matching volume and fill the data in (read again) echo "now generating output and populate..." set field [create HxAnnaScalarField3] set arith [create HxArithmetic] set arith [$arith setLabel Arithmetic]
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61

48

5.2. Read your own data set


$arith inputA connect $field $arith fire # the data will be initialized with the value 0 $arith expr setValue 0 # the dimensions have to be filled in with larger # values because the resolution denotes the number # of voxels not the index $arith resolution setValue 0 [expr $maxx + 1] $arith resolution setValue 1 [expr $maxy + 1] $arith resolution setValue 2 [expr $maxz + 1] $arith doIt touch; $arith doIt hit; $arith fire; # we can fill in the value now into the output # of Arithmetic, it will have the correct dimensions set result [$arith getResult] set fp [open $inputData "r"] set thisSize [gets $fp line] set linenumber 0 while { $thisSize >= 0 } { set linenumber [expr $linenumber + 1] if { [scan $line "%d %d %d %g" x y z value] == 4 } { $result setValue $x $y $z $value } else { echo "warning: line $linenumber does not match format" } set thisSize [gets $fp line] } close $fp # update the data range of the output $result touchMinMax # clear up afterwards and leave only the resulting data object $result master disconnect remove $field # set its name to the input data name without extension $result setLabel [file rootname [file tail $inputData]] }
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103

5.2.1. Read a Surface from Trajectories


Some data formats specify a surface as a collection of parallel lines. The lines themself are drawn in space and might have data values attached. This format is dierent from the native Avizo format for surfaces which consists out of a set of triangles. The easiest way to import this type of data is to utilize the Avizo quad-mesh data struc-

49

5. File load and save tures. A quad-mesh is a rectangular grid where each point lives somewhere in 3D space. A column (or row) of this quad-mesh can represent a trajectory and a row (or column) represent the points along each trajectory. This implies that the number of points for each trajectory has to be the same. Here is a script that uses the Hx2DMesh object that provides a simple tcl interface to specify quad-mesh structures. The input to the script is a OOGL le (starts with a CNMESH) and it contains a rst line that species the number of points and the number of trajectories:
CNMESH 3400 26 6000.0 1900 -1110 0.06 0.003 0.99 0.004 0.003 0.002 0.004 ...

On the following lines it lists rst the coordinates of all the points for the rst trajectory followed by the normal at this points (is ignored in the reader below) and the color (r,g,b,alpha) at this point.
# Amira-Script-Object v3.0 $this script hide $this proc constructor {} { $this newPortFilename filename $this newPortMultiMenu color 1 $this newPortDoIt action } $this proc update {} { set data [$this filename getValue] if { $data == "" } { return } if { [$this filename isNew] } { set fp [open $data "r"] set thisSize [gets $fp line] if { [string compare $line "CNMESH"] != 0 } { echo "Error: first line in the file has to be \"CNMESH\"" return } set thisSize [gets $fp line] # look for the size if { [scan $line "%d %d" numPoints numTraj] != 2 } { echo "Error: second line should contain number of points" echo " and number of trajectories (but it does not)" return } echo "Found $numPoints number of points with"
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

50

5.2. Read your own data set


echo " $numTraj trajectories in the file" set thisSize [gets $fp line] # how many elements do we have in this list? # (remove the point coordinates and their normal) set line [string trim $line " "] regsub -all (\ )+ $line " " line set numElements [expr [llength [split $line]] - 6] $this color setNum $numElements for { set i 0 } { $i < $numElements } { incr i } { $this color setLabel 0 $i $i } close $fp } } $this proc compute {} { if { ![$this action wasHit] } { return } set data [$this filename getValue] if { $data == "" } { echo "Error: no file to read in" return } # now open the file and read in the number # of quads that we will need set fp [open $data "r"] if { $fp == "" } { echo "Error: could not open file" return } set filename [lindex [file split $data] end] set thisSize [gets $fp line] if { [string compare $line "CNMESH"] != 0 } { echo "Error: first line in the file has to be \"CNMESH\"" return } set thisSize [gets $fp line] # look for the size if { [scan $line "%d %d" numPoints numTraj] != 2 } { echo "Error: second line should contain number of points" echo " and number of trajectories (but it does not)" return } # now create a mesh object dso open hxtensor
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81

51

5. File load and save


set filename [[create Hx2DMesh] setLabel $filename] $filename fire $filename Dimension setValue 0 $numPoints $filename Dimension setValue 1 $numTraj $filename fire set currentPoint 0 set currentTraj 0 set thisSize [gets $fp line] while {$thisSize >= 0} { if { [scan $line "%f %f %f" x y z] == 3 } { # get the value set line [string trim $line " "] regsub -all (\ )+ $line " " line2 set value [lindex [split $line2] [expr 6+[$this color getValue]]] $filename setPoint $currentPoint $currentTraj $x $y $z $value set currentPoint [expr $currentPoint + 1] if { $currentPoint >= $numPoints } { set currentPoint 0 set currentTraj [expr $currentTraj + 1] } } set thisSize [gets $fp line] } $filename drawStyle setValue 0 $filename select $filename fire $filename drawStyle setValue 1 $filename fire $filename colormap show $filename fire close $fp }
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115

5.3. Plot some 1d curves


Avizo contains a module pzplot which allows us to print one dimensional curves on screen. In order to use a plot window we have to borrow one from a tool which is using the plot modules functionality. Here for example we will use the Histogram module.
set hi [create HxHistogram] # we need some data to connect histogram to $hi data connect $currentData $hi fire # we do not want to have histogram on screen so hide the icon $hi hideIcon $hi action touch

52

5.4. Load data objects


$hi action hit $hi fire # after this we have a new global variable: thePlot global thePlot # it is a histogram plot so we have a histogram, which we hide $thePlot Histogram setActive 0 $thePlot update # we like the legend so we activate it $thePlot legend setActive 1 $thePlot update # now add as many curves as we have materials (plotted over time) # here result is an array like: set result(0) {10 11 12} # and the x-axis is defined by: set teatime {1 2 3} foreach u [array names result] { set fname curve$u $thePlot createObj -type PzCurve -name $fname $thePlot appendObj # please note the usage of list here to make # a copy of teatime for each run of the foreach loop $thePlot $fname setXYValues [list $teatime] $result($u) } $thePlot update

5.4. Load data objects


The load command is responsible for the le input/output. Its basically a wrapper with a very small own functionality but a list of specialized read-in-modules. This allows Avizo to be extended with new reader and writer capabilities by adding the appropriate functions as external libraries. The load command itself only knows a single command
load -browse

opens up the load dialog to allow the user to enter values. For all other functions Avizo has to guess the correct reader from an internal list of le formats. Using the above command will not automatically show the le format list dialog. Usually this dialog will pop up if the le format of the highlighted le is unknown. The dialog usually defaults to reading in the le as raw le. In order to get this dialog one can use
theMain openData

Here an example which will ask the user to read in a le and immediately start a le save using the DICOM le format.

53

5. File load and save

# Avizo Script # what files where loaded before this script? set allData [all] # now load a file from somewhere (dialog will open up) theMain openData # what files are loaded now set allAfterData [all] # find out which files are new and save them all foreach u $allAfterData { set found 0 foreach k $allData { if { $k == $u } { set found 1 break; } } # see if there is new data if { $found == 0 } { # now save this item as DICOM set outputDir "c:/" # look for the input files directory and use # a DICOM/ sub-directory to save set loadFrom [lindex [split [$u parameters LoadCmd getValue] " "] 2] set loadPathPieces [split $loadFrom "/"] set loadPath [join [lrange $loadPathPieces 0 [expr \ [llength $loadPathPieces]-2]] "/"] if [catch { file mkdir ${loadPath}/DICOM } bla] { echo "could not write files to $loadPath/DICOM," echo "we will use $outputDir instead" set loadPath $outputDir } # now save the files in the sub-directory # save will enumerate them on its own so output # files will be called slice.0000, slice.0001, ... $u save DICOM ${loadPath}/DICOM/slice. } }

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 34 35 36 37 38 39

Note that the DICOM reader will open a dialog for DICOM le sorting. As a work-around to prevent this dialog from beeing opened an environment variable can be dened prior to the load command.
set env(AMIRA_MHT_STEREOTAXIS) 1
1

Should prevent the DICOM dialog to be opened.

54

5.4. Load data objects An example of a le reader is the raw-le-reader. Usually invoked by a separate dialog this reader can also be used by command line options from within Tcl. Here an example:
load -raw filename.raw big zfastest double 3 \ 512 512 512 0 100 0 120 0 130 -header 1000

This loads the le lename.raw as big-endian data with z as the fastest running index where each data item is a three element long double array. We have 5123 voxels and the bounding box is with its lower left corner at 0, 0, 0 and its diagonal corner is at 100, 120, 130. At the beginning of the le we skip 1000 bytes as header. Some of the options like big are switches, e.g., can be removed from the command line to invoke the standard behavior, i.e., little endian and xfastest. Avizo can also read in urls to get its data from a remote machine as from a ftp-server.
load ftp://ftp.tgs.com/private/hauke/lobus.am

this opens a dialog in which the progress of the transfer is displayed. You can also interrupt the transfer there. Here an example of a script object which is reading in a raw data le from disk. The information about the number of voxel and the voxel size is stored in a second data le which contains this information as key - value pairs. The script will extract this information, nd the raw le and read it in with the correct settings. All key-value pairs of the header le will be added to the data objects parameter section. At the end the data object is ipped in z-direction and translated.
# Amira-Script-Object v3.0 $this script show # a set set set set set set set set list of variables with default values numberOfDimensions 1 xdim 1 ydim 1 zdim 1 wdim 1 pixelSize 1 bedOffset 1 axialPlaneSize 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14

# we need to enter a filename and an Apply button $this proc constructor {} { $this newPortFilename filename $this newPortButtonList doit 1 $this doit setLabel "Load" $this doit setLabel 0 "Apply" }

15 16 17 18 19 20 21 22

55

5. File load and save


# this function will read the header file and finally the raw file $this proc compute {} { global bedOffset xdim ydim zdim wdim pixelSize axialPlaneSize if { ![$this doit wasHit 0] } { return } set filename [$this filename getValue] if { $filename == "" } { echo "please supply a filename" return } # now scan in the file $this parse_pet_header $filename set petImg [file rootname $filename].img if { [file readable $petImg] == 0 } { set petImg [file rootname $filename] if { [file readable $petImg] == 0 } { echo " error: the file \"$petImg\" could not be read" return } } set cmd "load -raw \"$petImg\" little xfastest float 1 \ $xdim $ydim $zdim $bedOffset [expr \ $bedOffset+($xdim-1)*$pixelSize] 0 [expr \ ($ydim-1)*$pixelSize] 0 [expr ($zdim-1)*$axialPlaneSize]" set dataIcon [eval $cmd] # now add the header information to the parameter section $this add_header $dataIcon [$this get_header $filename] "HEADER" $ do some transformations on the file (flip z direction and translate) $dataIcon flip 2 $dataIcon setTransform 0.883376 0 0.468665 0 0 1 0 0 -0.468665 \ 0 0.883376 0 17.903 2.27994 6.53894 1 } # this function parses a simple text header file for key-value pairs $this proc parse_pet_header { filename } { global numberOfDimensions xdim ydim zdim wdim pixelSize bedOffset set petFilename {} set fp [open $filename "r"] set thisSize [gets $fp line] while { $thisSize >=0 } { # remove any comments if { [string match "\\#" $line] == 1 } { set thisSize [gets $fp line] continue } if { [scan $line "number_of_dimensions %d" tmp] == 1 } {
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72

56

5.4. Load data objects


set numberOfDimensions $tmp echo "found $numberOfDimensions dimensions" } if { [scan $line "x_dimension %d" tmp] == 1 } { set xdim $tmp echo "$xdim voxel in x direction" } if { [scan $line "y_dimension %d" tmp] == 1 } { set ydim $tmp echo "$xdim voxel in y direction" } if { [scan $line "z_dimension %d" tmp] == 1 } { set zdim $tmp echo "$xdim voxel in z direction" } if { [scan $line "w_dimension %d" tmp] == 1 } { set wdim $tmp echo "$wdim time steps" } if { [scan $line "pixel_size %f" tmp] == 1 } { set pixelSize $tmp echo "pixel size: $pixelSize" } if { [scan $line "axial_plane_size %f" tmp] == 1 } { set axialPlaneSize $tmp echo "slice distance: $axialPlaneSize" } set thisSize [gets $fp line] } close $fp } # this function adds key - values pairs as parameters $this proc add_header { object parameterpairs str } { $object parameters newBundle $str for {set i 0} {$i < [llength $parameterpairs]} {incr i} { set parval [lindex $parameterpairs $i] set par [string trim [lindex $parval 0]] set val [string trim [lindex $parval 1]] $object parameters $str setValue $par $val } } # this function reads in all key-value pairs and returns them $this proc get_header { filename } { set fp [open $filename "r"] set thisSize [gets $fp line] set header "" while { $thisSize >= 0 } {
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122

57

5. File load and save


# first of all remove the comments (start with #) if { [string match "\\#*" $line] == 1 } { # echo "found comment \"$line\"" # get the next line and start again set thisSize [gets $fp line] continue } if { [regexp {(.*)[\ ]*(.*)$} $line match tmp1 tmp2] == 1 } { # echo "add now $tmp1 $tmp2" set header [lappend header [list $tmp1 $tmp2]] } # obtain the next line and repeat set thisSize [gets $fp line] } close $fp return $header }
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139

A special case is the reading in of a series of data objects that describes some time varying data. Usually this is done by the Load as TimeSeries menu entry, but this can also be done by the tcl-interface.
# get some files names that have a different # ending than a given file, but are in the same directory set dynFileNames [glob -nocomplain [file rootname $filename]_Ph*] # assume that the above list is not empty and create a control set TimeSeriesControl [create HxDynamicFileSeriesCtrl {TimeSeriesControl}] set cmd "$TimeSeriesControl init -loadCmd \{load -raw \"\$FILENAME\" \ little xfastest float 1 255 255 95 -50.8 50.8 -50.8 50.8 -56.4 56.4 } \ \} [lindex [join $dynFileNames] 0]" # and execute the above command set f2 [eval $cmd]
1 2 3 4 5 6 7 8 9 10 11 12

In case the les need to be loaded from a temporary directory one can query the systems TMP path from the environment variable TMP using:
set TMPDIR [file normalize $env(TMP)]

The normalization is required to make sure that characters are converted in case the operating system recognizes them erronously as quotations.

5.4.1. Create a cluster object


Data can also be created inside of Avizos tcl. Here an example on how to create a cluster object (unlike loading it from a PSI le).

58

5.4. Load data objects

dso open hxcluster # otherwise we do not know hxcluster set Object [create HxCluster] # create an empty object $Object $Object $Object $Object $Object $Object $Object $Object $Object $Object $Object $Object $Object setNumDataColumns 1 # there may be data attached per point addPoint 0.0 0.0 0.0 # just add some points addPoint 1.0 1.0 1.0 addPoint 1.0 2.5 3.0 addPoint 3.0 3.0 3.0 addPoint 2.0 4.0 1.0 setDataColumnName 0 mydata # the datas column name setDataValue 0 0 1.0 # and now attach some data per point setDataValue 0 1 0.8 setDataValue 0 2 0.7 setDataValue 0 3 0.6 setDataValue 0 4 0.5 computeBounds # compute the BoundingBox of the cluster

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

5.4.2. Create a label eld


In order to create a LabelField by tcl you need to attach the Labeleld to a data eld and call re. This will allocate the memory for the Labeleld given the memory requirements of the input data.

set labelData [$this data source] if { $labelData == "" } { # if we do not have a label field yet, create one set labelData [create HxUniformLabelField3 labelfield] $labelData ImageData connect $inputData $labelData fire # and add the parameters for some materials # remove the old bundle first $labelData parameters newBundle Materials # now add new value-key pairs $labelData parameters Materials newBundle Exterior $labelData parameters Materials Exterior setValue Color "0 0 0" $labelData $labelData $labelData $labelData

1 2 3 4 5 6 7 8 9 10 11 12 13

parameters Materials newBundle holes 14 parameters Materials holes setValue Id 5 15 parameters Materials holes setValue Color "0.8 0.407686 0.156863" 16 fire 17
18 19

} $labelData fire

59

5. File load and save

5.4.3. Remove a slice from a loaded volume


Removing single slices from a stack with uniform slice distance will need to change the position of every slice which comes after the current one. This can seldom be tollerated as the geometry is changed and measurements may result in wrong distances and volumes. Nevertheless here is a script that removes a slice from a dataset. The resource le should allow us to attach the le to any spatial data object like a UniformScalarField.
module -name "RemoveAxialSlice" \ -package "hxscriptobj" \ -category "Compute" \ -primary HxSpatialData \ -proc { set mod [[create HxScriptObject] setLabel RemoveSlice] $mod script setValue $AVIZO_ROOT/share/script-objects/RemoveSlice.scro $mod fire $mod data connect $PRIMARY }
1 2 3 4 5 6 7 8 9 10

The script that performs the removal uses a Tcl command that allows us to exchange a slice with another. Starting from the position of the slice to be removed the exchange happens with the next slice followed by another exchange of the next slice with its next slice. This way the slice that we want to remove travels down the volume until it reaches the end. If the slice that we want to remove is the last one of the volume we use crop to remove this slice. This operation will make our volume one slice smaller every time we remove a slice.
# Amira-Script-Object v3.0 $this script hide $this proc constructor {} { $this newPortInfo info $this info setValue "Set the slider to the slice you want to remove, " \ "press Apply. There is no undo!" $this newPortIntSlider AxialSlice $this newPortDoIt action } # set the slider to the number of slices in z-direction $this proc update {} { if { [$this data isNew] } { set u [$this data source] if { $u == "" } { return } set numSlices [lindex [$u getDims] 2]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

60

5.4. Load data objects


$this AxialSlice setMinMax 0 $numSlices } } $this proc compute {} { set slice [$this AxialSlice getValue] # if the user does not press Apply do nothing if { [$this action wasHit] == 0 } { return } # we need to be connected to a data file set u [$this data source] if { $u == "" } { echo "No dataset connected" return } # TODO: we should test here if we have the correct file type... for { set i $slice } { $i < [expr [lindex [$u getDims] 2]-1] } { incr i } { $u exchangeSlices $i [expr $i + 1]; } $u crop 0 [expr [lindex [$u getDims] 0]-1] \ 0 [expr [lindex [$u getDims] 1]-1] \ 0 [expr [lindex [$u getDims] 2]-2]; $u touch $u fire set numSlices [lindex [$u getDims] 2] $this AxialSlice setMinMax 0 $numSlices }
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

The crop function in the Crop editor can also do an auto-crop (removing the background). In order to use this functionaly in Tcl one needs to call the crop option with a threshold. For a label eld the threshold for the background is 1. Everything below one can be cropped away. This call results in a list of coordinates that can be used during the actual crop operation. Here an example:
eval "lobus.labels.am crop [lobus.labels.am crop -auto 1]"

5.4.4. Create a scalar eld


To create a scalar eld of an arbitrary size you can use the HxAnnaScalarField3 module which can be reached in the CreateDataScalareld menu entry. The following tcl code generates a eld of size 64, 63, 62 and lls it with a 3D sphere with a value of 42 inside and 0 outside.

61

5. File load and save

set field [create HxAnnaScalarField3] set arith [create HxArithmetic] set arith [$arith setLabel Arithmetic] $arith inputA connect $field $arith fire $arith expr setValue 0 $arith resolution setValue 0 64 $arith resolution setValue 1 63 $arith resolution setValue 2 62 $arith doIt touch; $arith doIt hit; $arith fire; # we can set now values in set result [$arith getResult] for { set i 0} {$i < 64} { incr i } { for { set j 0} {$j < 63} { incr j } { for { set k 0} {$k < 62} { incr k } { set r [expr sqrt(($i-32)*($i-32) + ($j-32)*($j-32) + ($k-32)*($k-32))] if { $r < 25 } { $result setValue $i $j $k 42 } } } }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

In order to test the above scripts you should copy them to a text le and add the following header line: # AmiraScript. Loading this text le Avizo will recognize it as a tcl script and execute its code.

5.4.5. Create a line set


Line sets are sets of connected points in space. Each point can have one or more data values attached to it. In order to add the data values one has to be careful and rst one needs to add all the points. Only after that one can add the data values.
set lineset [create HxLineSet] # first add all the points set p1 [$lineset addPoint 1 2 3] set p2 [$lineset addPoint 3 2 1] set p3 [$lineset addPoint 3 3 2] # second add all the lines (lists of points) $lineset addLine $p1 $p2 $lineset addLine $p2 $p3 # only now add the data values (1 per point per line) $lineset setNumDataValues 1 # now set for the line number 0 the first and second point to values 42 and 45
1 2 3 4 5 6 7 8 9 10 11 12 13 14

62

5.4. Load data objects


$lineset setData 0 0 42 0 $lineset setData 0 1 45 0 # now set for the line number 1 the first and second point to values 42 and 40 $lineset setData 1 0 42 0 $lineset setData 1 1 40 0
15 16 17 18 19

Here is a script which is creating a line set from a given camera path. The camera path is traversed and the current position of the viewer is entered into the line set as a position.
# Amira-Script-Object V3.0 $this script show $this proc constructor {} { $this newPortInfo info $this info setValue "connect a Campath module" $this newPortConnection Campath HxKeyframeCameraPath $this newPortButtonList doit 1 $this doit setLabel 0 "DoIt" $this data hide } $this proc compute {} { set campath [$this Campath source] if { $campath == "" } { echo "Please connect a Camerapath object" return } if { ![$this doit wasHit 0] } { return }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

# now export a Lineset 24 set lineset [create HxLineSet] 25 set cminmax [$campath time getMinMax] 26 set cincr [$campath time getIncrement] 27 $lineset setNumPoints [expr ([lindex $cminmax 1] - [lindex $cminmax 0])/$cincr] 28 $lineset addLine 0 29 set numPoints [$lineset getNumPoints] 30
31

workArea startWorking "compute lineset..." 32 for { set i 0 } { $i < $numPoints } { incr i } { 33 workArea setProgressValue [expr 1.0 * $i / $numPoints] 34 echo $i 35 # add each camera position by moving the camera according to the campath 36 $campath time setValue [expr [lindex $cminmax 0]+$i*(([lindex $cminmax 1] \ 37 - [lindex $cminmax 0])/(1.0*$numPoints))] 38 $campath time touch 39 $campath fire 40

63

5. File load and save


viewer 0 redraw set pos [viewer 0 getCameraPosition] $lineset setPoint $i [lindex $pos 0] [lindex $pos 1] [lindex $pos 2] $lineset addLineVertex 0 $i } workArea stopWorking }
41 42 43 44 45 46 47

In the next example several lines are created based on the geometrical mean of each material in an attached labeleld.
# Amira-Script-Object v3.0 #$this script show # # # # # # #
1 2 3 4 5

This module will compute the geometric center of each material in each slice. Materials are identified by their material id only. No detection of multiple regions is performed. (connect to a label field for example)

6 7 8 9 10 11 12

$this proc constructor {} { $this newPortDoIt action } $this proc compute {} { if { ![$this action wasHit 0] } { return } set data [$this data source] if { $data == "" } { echo "Error: no data connected" return } set dims [$data getDims] set bb [$data getBoundingBox] set numPoints 0 # for every slice workArea startWorking "Computing..." for { set i 0 } { $i < [lindex $dims 2] } { incr i } { workArea setProgressValue [expr 1.0 * $i / [lindex $dims 2]] if { [workArea wasInterrupted] != 0 } { break; } # for every pixel # an associative array of coordinates

13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

64

5.4. Load data objects


set sum(0) [list 0 0 0] set num(0) 0 for { set j 0 } { $j < [lindex $dims 1] } { incr j } { for { set k 0 } { $k < [lindex $dims 0] } { incr k } { set pv [expr int([$data getValue $k $j $i])] if { ![info exists sum($pv)] } { set sum($pv) [list $k $j $i] set num($pv) 0 } else { set sum($pv) [list [expr [lindex $sum($pv) 0] + $k] \ [expr [lindex $sum($pv) 1] + $j] \ [expr [lindex $sum($pv) 2] + $i] ] set num($pv) [expr $num($pv) + 1] } } } # print out info echo "Slice: $i (Material ID - center index, center voxel)" foreach u [array names sum] { set co $sum($u) set p(0) [expr [lindex $co 0] / $num($u)] set p(1) [expr [lindex $co 1] / $num($u)] set p(2) [expr [lindex $co 2] / $num($u)] set bv(0) [expr ($p(0) / [expr 1.0*[lindex $dims 0]-1]) * \ ([lindex $bb 1] - [lindex $bb 0]) + [lindex $bb 0]] set bv(1) [expr ($p(1) / [expr 1.0*[lindex $dims 1]-1]) * \ ([lindex $bb 3] - [lindex $bb 2]) + [lindex $bb 2]] set bv(2) [expr ($p(2) / [expr 1.0*[lindex $dims 2]-1]) * \ ([lindex $bb 5] - [lindex $bb 4]) + [lindex $bb 4]] echo $u - $p(0) $p(1) $p(2) $bv(0) $bv(1) $bv(2) # store lineset points first if { ![info exists pointsPerMaterial($u)] } { set pointsPerMaterial($u) [list $bv(0) $bv(1) $bv(2)] } else { lappend pointsPerMaterial($u) $bv(0) lappend pointsPerMaterial($u) $bv(1) lappend pointsPerMaterial($u) $bv(2) } set numPoints [expr $numPoints + 1] # delete array again unset sum($u) } } workArea stopWorking # create a lineset as output set lineset [create HxLineSet]
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89

65

5. File load and save


$lineset setNumPoints $numPoints # store all points set pCounter 0 set lCounter 0 foreach u [array names pointsPerMaterial] { set points $pointsPerMaterial($u) $lineset addLine $lCounter for { set pC 0 } { $pC < [llength $points] } { set pC [expr $pC + 3] } { set pointx [lindex $points $pC] set pointy [lindex $points [expr $pC + 1]] set pointz [lindex $points [expr $pC + 2]] $lineset setPoint $pCounter $pointx $pointy $pointz if { $pC > 0 } { $lineset addLine [expr $pCounter - 1] $pCounter } # p counts the point indices we assume its 0 1 etc. set pCounter [expr $pCounter + 1] } set lCounter [expr $lCounter + 1] } }
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110

5.4.6. Write a lineset out to the console window


# Amira-Script-Object V3.0 $this script show $this proc constructor {} { $this newPortButtonList doit 1 $this doit setLabel 0 "Apply" } $this proc compute {} { if { ![$this doit wasHit 0] } { return } set input [$this data source] if { $input == "" } { echo "Please connect to a Lineset object" return } else { if { [$input getTypeId] != "HxNeuronTree" } { echo "input is not a Lineset" return } }
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

66

5.4. Load data objects


echo "List contains" 26 echo "Line number|Point number|x-coordinate|y-coordinate|z-coordinate|data" 27 set numLines [$input getNumLines] 28 set numDataValues [$input getNumDataValues] 29 for { set i 0 } { $i < $numLines } { incr i } { 30 set numPoints [$input getLineLength $i] 31 for { set j 0 } { $j < $numPoints } { incr j } { 32 set lineVertex [$input getLineVertex $i $j] 33 set point [$input getPoint $lineVertex] 34 echo "$i $j [lindex $point 0] [lindex $point 1] [lindex $point 2] [$input 35 getData $i $j]" } 36 } 37
38

# print out a table which is listing the length of each line for { set i 0 } { $i < $numLines } { incr i } { set numPoints [$input getLineLength $i] set linelength 0 for { set j 1 } { $j < $numPoints } { incr j } { set lineVertex [$input getLineVertex $i $j] set point [$input getPoint $lineVertex] set lineVertex2 [$input getLineVertex $i [expr $j - 1] ] set point2 [$input getPoint $lineVertex2] set l1x [lindex $point 0] set l1y [lindex $point 1] set l1z [lindex $point 2] set l2x [lindex $point2 0] set l2y [lindex $point2 1] set l2z [lindex $point2 2] set linelength [expr $linelength + sqrt( ( ($l1x-$l2x)*($l1x-$l2x) } echo "line $i has length $linelength" } }

39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

) * ( 54($l1y-$l2y)*($l1y
55 56 57 58

5.4.7. Add a data value to each point of a line set


In this example we will add the curvature at each point to an existing line set.
# Amira-Script-Object V3.0 $this script show $this proc constructor {} { $this newPortButtonList doit 1 $this doit setLabel 0 "Apply" } $this proc compute {} { if { ![$this doit wasHit 0] } { return
1 2 3 4 5 6 7 8 9 10 11

67

5. File load and save


} set input [$this data source] if { $input == "" } { echo "Please connect to a Lineset object" return } else { if { [$input getTypeId] != "HxNeuronTree" && [$input getTypeId] != "HxLineSet" } { echo "input is not a Lineset" return } } set numLines [$input getNumLines] set numDataValues [$input getNumDataValues]
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

# create the output line set with one more output (the curvature at this point) 27 set nls [$input duplicate] 28 # using duplicate will save us later to copy over all coordinates and existing 29 # data, remember to do separate loops for adding points, adding lines and data 30
31

# increase the number of data points $nls setNumDataValues [expr $numDataValues + 1] for { set i 0 } { $i < $numLines } { incr i } { set numPoints [$input getLineLength $i] for { set j 0 } { $j < $numPoints } { incr j } { set lineVertex [$input getLineVertex $i $j] set point [$input getPoint $lineVertex]

32 33 34 35 36 37 38 39 40

# compute the curvature at this point 41 set curvature 0 42 if { $j == 0 } { 43 set curvature 0 44 } else { 45 if { [expr $j+1] < $numPoints } { 46 set p1 [$input getPoint [$input getLineVertex $i [expr $j - 1]]] 47 set p2 $point 48 set p3 [$input getPoint [$input getLineVertex $i [expr $j + 1]]] 49
50

# curvature is defined as the magnitude of the change # of unit tangent vector set t1 [list [expr [lindex $p1 0]-[lindex $p2 0]] \ [expr [lindex $p1 1]-[lindex $p2 1]] \ [expr [lindex $p1 2]-[lindex $p2 2]]] set lt1 [expr sqrt([lindex $t1 0]*[lindex $t1 0] + \ [lindex $t1 1]*[lindex $t1 1] + \ [lindex $t1 2]*[lindex $t1 2] )] set t1 [list [expr [lindex $t1 0]/$lt1] \ [expr [lindex $t1 1]/$lt1] \ [expr [lindex $t1 2]/$lt1]]

51 52 53 54 55 56 57 58 59 60 61

68

5.5. Save data objects


set t2 [list [expr [lindex $p2 [expr [lindex $p2 [expr [lindex $p2 set lt2 [expr sqrt([lindex $t2 [lindex $t2 1]*[lindex [lindex $t2 2]*[lindex set t2 [list [expr [lindex $t2 [expr [lindex $t2 [expr [lindex $t2 0]-[lindex 1]-[lindex 2]-[lindex 0]*[lindex $t2 1] + \ $t2 2] )] 0]/$lt1] \ 1]/$lt1] \ 2]/$lt1]] $p3 $p3 $p3 $t2 0]] \ 1]] \ 2]]] 0] + \
62 63 64 65 66 67 68 69 70 71

set c [list [expr [lindex $t1 0]-[lindex $t2 0]] \ 72 [expr [lindex $t1 1]-[lindex $t2 1]] \ 73 [expr [lindex $t1 2]-[lindex $t2 2]]] 74 set curvature [expr sqrt([lindex $c 0]*[lindex $c 0] + \ 75 [lindex $c 1]*[lindex $c 1] + \ 76 [lindex $c 2]*[lindex $c 2] ) / \ 77 ($lt1 + $lt2)] 78 } else { 79 # there is only two points for this line, or its an end point 80 set curvature 0 81 } 82 } for { set d 0 } { $d < $numDataValues } { incr d } { $nls setData $i $j [$input getData $i $j $d] $d } $nls setData $i $j $curvature $numDataValues } } }
83 84 85 86 87 88 89 90

5.5. Save data objects


If you want to save some data you can use the datas own save method. In order to save a le as AmiraMesh you can call save with the appropriate format string and lename.
global PRIMARY set output_dir "/data" set filename "/tmp/bla.am" set fn [lindex [split $filename "/"] end] $data save "AmiraMesh" "$output_dir/$fn"
1 2 3 4 5

The global statement is only needed if you execute the save line in a procedure. This is because of a bug in Avizo 3.0 and should not be necessary in Avizo 3.1 or higher. The next natural question is, what le types are known to Avizo? One way to nd out is to look at the directory Avizo-x.x/share/resources. It is lled with *.rc les that specify what modules are known and in which library Avizo can nd the module. File reader and writer are modules as well so they are listed in rc les.

69

5. File load and save For instance share/resources/hxmio.rc contains this entry:
dataFile -name "2D Tiff" -ext tif \ -multisave "writeTIF" \ -type "HxRegScalarField3" \ -type "HxRegColorField3" \ -check { [$PRIMARY coordType]<3 } \ -dso "libhximio.so"
1 2 3 4 5 6

You can therefore use the argument of -name as format name :


<data> save "2D Tiff" <filename>

In order to nd the correct string to specify a le type you can also look into the Format menu that appears after using the right-mouse on the name of any le in the le load dialog.

70

6. General Information
6.1. Memory consumption of data and display
In order to compute the memory consumption of a given data set one can use a simple formula. Compute the number of voxels and multiply them by the size of a single voxel. For CT data most data sets are 16 bit which requires 2 bytes for storage. The size in mega bytes of a 512 512 200 CT data set is therefore 512 512 200 2/1024.0/1024.0 = 100mb. You can query the actual memory used by Avizo with the command
mem {-n}

where the -n command only returns two values, the total size and the resident size in memory (resident memory cannot be swapped to disk). If you want to know how much memory Avizo can maximally use on your machine you can call
app maxmalloc

Typed into the Avizo console it will start to allocate memory blocks. It will allocate the maximum size of memory recursevly reporting its success as chunks. So the chunks represent the continuous free memory on your system. Because the memory is usually fragmented there will be a lot of small chunks. The importance of the chunks is that every data set in Avizo has to t into a single chunk.

6.2. Set the voxel size and the bounding box


For density data we can set the bounding box by the command setBoundingBox. This will set automatically also the voxel size of the data because the data dimension is not changed getDims. Here an example procedure which takes an object name (icon name) and tries to set its voxel size by calling the bounding box command
# call this procedure with data 0 0 0 and it # the current voxel size only proc setVoxelSize { data x y z } { #print out the current voxel size set dims [$data getDims] set bb [$data getBoundingBox] echo "X [expr ([lindex $bb 1]-[lindex $bb echo "Y [expr ([lindex $bb 3]-[lindex $bb echo "Z [expr ([lindex $bb 5]-[lindex $bb will return
1 2 3 4 5 6

0])/([lindex $dims 0]-1)]" 2])/([lindex $dims 1]-1)]" 4])/([lindex $dims 2]-1)]"

7 8 9

71

6. General Information
if { x == 0 || y == 0 || z == 0 } { return; } echo "now set the new voxel size" $data setBoundingBox \ [lindex $bb 0] [expr $x*([lindex $dims 0]-1) + [lindex $bb 0]] \ [lindex $bb 2] [expr $y*([lindex $dims 1]-1) + [lindex $bb 2]] \ [lindex $bb 4] [expr $z*([lindex $dims 2]-1) + [lindex $bb 4]] setVoxelSize $data 0 0 0 }
10 11 12 13 14 15 16 17

6.3. Querying environment variables in Avizo


This is really something which relates only to tcl but here is the answer.
array names env

typed into the Avizo console window gives a list of all known environment variables. A following call to
echo $env(HOME)

prints out the value of one variable (in this case the HOME).

6.4. Start external processes


With its scripting capabilities Avizo can call external applications. The simplest form of this is to start an executable with a set of parameters. For Windows this might look like:
set cmd "start \"I do something\" /B \"storescu.exe\" -aet ME" system $cmd

6.5. Display a progress bar


In Tcl-Avizo you can also use the progress bar to display the current position of your computation on a time line. This also enables the user to interrupt a running process. Here is an example:
workArea startWorking "start doing some work" for {set y 0 } { $y < $size } { incr y } { # first a simple update of the progress bar workArea setProgressValue [expr 1.0 * $y / $size] # now we change the string workArea setProgressInfo "still working ($y of $size)"
1 2 3 4 5 6

72

6.6. Generate numbered le names


# do we interrupt the computation? if { [workArea wasInterrupted] != 0} { break; } # now do something usefull } # always call stopWorking, otherwise we leave Avizo undefined workArea stopWorking
7 8 9 10 11 12

6.6. Generate numbered le names


This is realy Tcl specic and should not appear in a document about Avizo. But it is useful. In Tcl you can use the C-like printf replacement called format. To generate 101 lenames f ile0000, f ile0001, f ile0002, . . . , f ile0100 accordingly execute the following
for {set i 0} {$i < 101} {incr i} { set filename [format "file%.4d" $i] # now use the $filename }

You can also do a similar thing with the bash shell. Although it does not know about oating point numbers you can generate the above le list by:
i=0; while [ $i -le 100 ] do let "i += 1"; fn=$(printf "lynx -dump -raw -source \ http://www.something.edu/~someuser/file%03d.jpg > file%03d.jpg\n" $i $i); echo $fn; done;
1 2 3 4 5 6 7 8

In this example we used the terminal based http reader lynx to download some les from a distance http directory. Another useful tool for downloading a large number of les is wget.

6.7. DICOM units


Avizo versions prior to 5.x use cm as default unit for DICOM volumes. As DICOM only contains values but not their units this assumption was only correct for a very limited number of volumes. With Avizo version 5.0 the default unit is now mm which better reects the usual units stored in medical images. Due to this change DICOM volumes loaded with a newer Avizo will have a voxel size which is 10 times larger than before. One can change the default unit back to cm by changing the settings once in the DICOM read dialog and by using its Save Settings menu entry. This will set a registry value (DicomUnits) which is either 0.01 for centimeters or 0.001 for millimeters.

73

6. General Information

6.8. DICOM scaling - HU values and how they are represented


DICOM data contain a native scaled data portion and two values that map these data by a linear mapping in the HU value range. For each slice the slope and intercept are applied to the raw data during load of the data. Lets imagine that for some of the DICOM images the slope value is larger than 1. Here an example for the slope values of a couple of the images (each line is one DICOM slope value): (0028,1053) (0028,1053) (0028,1053) (0028,1053) (0028,1053) (0028,1053) (0028,1053) (0028,1053) (0028,1053) (0028,1053) (0028,1053) (0028,1053) (0028,1053) (0028,1053) (0028,1053) (0028,1053) (0028,1053) DS DS DS DS DS DS DS DS DS DS DS DS DS DS DS DS DS [1.000000] [1.000000] [1.000000] [1.000000] [1.000000] [1.078758] [2.876696] [6.141895] [8.581682] [8.892249] [7.231891] [4.301316] [1.969185] [1.000000] [1.000000] [1.000000] [1.000000]

This part of the data could represent a high intensity region like the bladder and all the image data in that region have larger than 1 slope values. When Avizo imports the data is has to scale the raw data of each slice by the slope value (printed above). As the raw pixel data could have values close to 40, 000 a slope value of 8.9 would scale the data to 8.9 40, 000 which is a value that cannot be represented anymore by an unsigned short (16bit) value. This causes Avizo to open a dialog box with three choices Adjust scaling Ignore scaling and Clamp data. The user can select one of them which inuences how the data is represented in the software. Adjust scaling : For each slice the slope value is scaled so that the maximum value for all slices is mapped to the maximum intensity that can be represented by the data type. This produces a correctly looking result, but with data values that are scaled down. Ignore scaling : This ignores the scaling of each slice. The result is a stack in which only the raw DICOM data is displayed. For your data this results in some slices with too low intensities, those are the once that would using the scaling be displayed correctly. Clamp data : This applies the scaling of each slice. The slices with too high values will clamp at the highest intensities. This produces a correct looking images with correct scaling,

74

6.9. Reading in DICOM directories without DICOMDIR but the details in the high intensity region are lost. In this case that s in the bladder, which should not be of great interest. There are two good options in this case. Selecting the Clamp data option is easiest as the data is read in with correct scaling for all the slices where the intensities are not too high. The second option involves a little bit more work. The problem is caused by using a 16bit data format for image data which has a scaling that cannot be represented by 16bit. Avizo can convert the data to 32bit but this has to be done by hand. The data would be read in using Ignore scaling and each slice would be written out as a raw data le. The corresponding slope value needs to be extracted from the DICOM data and each slice needs to be converted to a 32bit type with the correct scaling. Saved back to disk these images can be loaded a second time, as 32bit with correct scaling. A process like this could be automated using an Avizo script, but that would be non-trivial and time consuming.

6.9. Reading in DICOM directories without DICOMDIR


DICOM les can be distributed in many sub-directories. In this case it can be useful to collect them all in a at structure. The following script is using the dcmdump (from the dcmtk package) to nd out the series instance uid of each le found in the current directory. It will generate a directory with the series instance uid as name and copy the les into these directories.
#!/bin/bash # This script will sort a directory tree for DICOM files and copy # them into a new flat(-ter) structure with directories named after # the series instance uid of each file. # This script is using dcmdump from the dcmtk package. count=0 for u in find . -xtype f do # find out the files SIUID dirname=dcmdump $u | grep "(0020,000e)" | cut -d[ -f 2 | cut -d] -f 1 if [ ! -d $dirname ]; then mkdir $dirname fi count=expr $count + 1 echo $count cp $u ${dirname}/${count}.dcm done
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

6.10. Force a re-computation


In a script sometimes a computation has to be performed in a loop. In order to invoke the same actions on a module by scripting compared to the appropriate mouse-clicks in its

75

6. General Information user interface (DoIt button etc.) you will have to do the following two calls additionally to setting the value of the port
for {set i 22} {$i < 245} {incr i} { Isosurface threshold setValue $i Isosurface doIt touch Isosurface fire }
1 2 3 4 5

The touch command simulates the mouse click and the fire command will execute this action. If you need to work with ports that expose multiple buttons you can select the appropriate button by setValue <whichButton> 1 where whichButton is a number starting from 0 for the rst button in order. Here an example
Avizo XScreen action setValue 2 1 Avizo XScreen fire

which will activate the third button (2) in the action port of an Avizo XScreenmodule.

6.11. Add information to data les


The parameter editor allows to view and add parameters to any Avizo data object, i.e. a green icon. Those parameters are saved together with the data in AmiraMesh les and can therefore be used to annotate data les. Parameters can be organized by a hierarchical structure. Directories are called bundles. So you can generate a new directory by using the newBundle method.
data.am parameters newBundle DICOM data.am parameters DICOM setValue "Patient name" "anonymous"

Here we added also a parameter value pair into this directory. If the parameters are of a specic type you have to state that explicitly while generating the new parameter. For example on ca add a parameter G0018-11A0 and assign to it the following character string as its value: body part thickness [UL]: 65. If we now add a parameter G0018-1531 with the value detector secondary angle [DS]: -21.5542 both will be recognised as either unsigned long (UL) or decimal string (DS). If now value representation is given, and the tag is not included in Avizos list of known DICOM tags (AvizoBase/dicom/dicom.h), the value representation defaults to OB (other byte). In order to print all the parameters available for a data le a recursive function like the following can be used:
# Avizo Script # assume that the data file is called lobus.Labels
1 2 3

76

6.11. Add information to data les


set dataset lobus.Labels # # This function will recursively print out all parameters. # Arguments are the name of the data set (dataset) the path # to a group of arguments (path) and an initial level (level). # proc printAllBelow { dataset path level } { # get some spaces infront of the lines to represent level set spaces [format "%[expr ${level}*3]s" " "] # how many entries for this parameter section? set cmd "$dataset $path size" set numEntries [eval $cmd] # for each of the entries now print out all sub-entries for { set i 0 } { $i < $numEntries } { incr i } { # get the name of the is entry set cmd "$dataset $path index $i" set thisName [eval $cmd] # test if this entry is a bundle or a simple key set cmd "$dataset $path $thisName isBundle" # if its a key print it, else recurse again if { ![eval $cmd] } { set cmd "$dataset $path $thisName getValue" set thisValue [eval $cmd] echo "$spaces $thisName = $thisValue" } else { echo "$spaces Bundle $thisName:" # go one level deeper printAllBelow $dataset "$path $thisName" [expr $level+1] } } } # this is the initial call to the function # which will print out every key/value under parameters printAllBelow $dataset " parameters " 0
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 34 35 36 37 38

Here is another function that will print out the commands needed to copy a parameter tree from one object A to another object B. The part of the tree to be copied can be specied as well as the destination. This function is very similar to the one above. Instead of performing the move it will create a list of commands that can be executed to perform the copy operation.
set createCMD "" # # Collect the build commands to move parameters pathA in object A # to pathB in object B. # After calling this function createCMD contains the commands # needed to perform the copy operation (use eval to perform the copy # operation).
1 2 3 4 5 6 7

77

6. General Information
proc collectParameters { A pathA B pathB } { global createCMD set cmd "$A $pathA size" set numEntries [eval $cmd] for { set i 0 } { $i < $numEntries } { incr i } { set cmd "$A $pathA index $i" set thisName [eval $cmd] set cmd "$A $pathA $thisName isBundle" if { ![eval $cmd] } { set cmd "$A $pathA $thisName getValue" set thisValue [eval $cmd] set createCMD "${createCMD}\n$B $pathB setValue $thisName \"$thisValue\"" } else { set createCMD "${createCMD}\n$B $pathB newBundle \"$thisName\"" collectParameters $A "$pathA $thisName" $B "$pathB $thisName" } } } # call the function with two data objects (lobus.Labels and artischoke) # copy all parameters from A to B collectParameters lobus.Labels " parameters " artischoke " parameters " echo $createCMD # eval $createCMD
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

For an OrthoSlice this can be used to store contrast and brightness variables specic for this data le. If one uses the ContrastControl function on the OrthoSlice one can come up with a setting for the minimum and maximum value of the transfer function setup (the data window elds). These values can then be stored in the data le because they dene the brightness and the contrast used to display the image. Save the values in a variable called DataWindow. Add as values the two number you read out from the Data Window port with a space seperating them. The next time you attach an OrthoSlice module to this data object the corresponding colormap will be initially set to the two provided values. Here is how you add this in a tcl script.
data.am parameters setName dataWindow data.am parameters setValue dataWindow "22 254"

6.12. Add a button next to Load Data


Buttons next to the Load Data button are called macrobuttons and they can be dened by the user. Here is an example which implements a macro button for loading an input data le, attaching a CastField module to it, and converting the data to an unsigned short. One has to edit two les:

78

6.12. Add a button next to Load Data

<Avizo_install_dir>\share\resources\Avizo.init <Avizo_install_dir>\share\resources\macrobuttons.rc

At the bottom of Avizo.init add the following Tcl code:


# The funtion that is executed after pressing the button proc loadConvert16bit {} { set script [[create HxScriptObject] setLabel "foo"] $script newPortFilename filename $script filename setMode 1 $script select $script filename exec if [ catch { load [$script filename getFilename] } datafile ] { remove $script return } if { $datafile != "" } { set cast [create HxCastField] $cast data connect $datafile $cast select $cast fire # # Specify outputType here: # # unsigned char (byte) = 0 # signed short (16-bit) = 1 # unsigned short (16-bit) = 2 # signed int (32-bit) = 3 # float = 4 # double = 5 # LabelField = 6 # $cast outputType setValue 2 $cast fire $cast action setValue 0 $cast fire set newData [lindex [lindex [$cast downStreamConnections] 0] 0] if { $newData == "" } { return } $newData fire $newData select $newData fire $newData master disconnect $newData fire remove $cast remove $datafile $newData deselect }
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 34 35 36 37 38 39 40 41 42 43 44

79

6. General Information
remove $script }
45 46

In the macrobuttons.rc le, add the following line at the end:


macroButton -add "Load 16-bit..." -color mediumaquamarine -proc "loadConvert16bit"

With these changes, when starting up Avizo one should get another button labelled Load 16-bit... next to the standard Open Data... button. Clicking on it, the Avizo Load le dialog will be displayed, allowing to select an input le. The le being loaded, a CastField module being attached, and an output le being generated. At the end, only the new output le is added to the Pool.

6.13. Add function keys


Function keys can be added by dening tcl procedures that have a specic name. Here an example:
proc onKeyF1 {} { echo "hello" }

Allowed are only the F-keys but you can use any of the three Shift, Ctrl, or Alt keys additionally. Here a more complex example which denes two functions that increase or decrease the eye balance for stereo (OpenGL raw stereo):
set balance 0.7 set offset 0.7 proc onKeyF8 {} { global balance offset set balance [expr $balance echo balance is $balance viewer 0 setStereo -m 0 -b } proc onKeyShiftF8 {} { global balance offset set balance [expr $balance echo balance is $balance viewer 0 setStereo -m 0 -b }
1 2 3 4

+ 0.1] $balance $offset

5 6 7 8 9 10

- 0.1] $balance $offset

11 12 13 14

There is another special case for dening keys in Avizo. This is for the 2D plot windows. In this case a denition looks like this:

80

6.14. Announce new modules with .rc les

proc onPWKeyF4 {} { global thePlot # works only if there is a plot window if { ![ info exists thePlot ] } { echo "There is no plot window" return } ...

1 2 3 4 5 6 7 8 9

A more complex example of the use of onPWKey can be found in the hxdataprobe.rc les which is in the share/resources directory of Avizo.

6.14. Announce new modules with .rc les


This example .rc les add a new entry to the context menu of Ortho- and ObliqueSlice in order to set the brightness and contrast of the data. After saving the data le these values will always be used later on. This tcl-code has to be copied to the Avizo-x.x/share/resource/ directory in order to be available after the next start of Avizo.
############################################################# # .rc provides a popup menu entry for setting the data window ############################################################# # register a menu entry that will set the colormap min/max values # as the DataWindow in the dataset connected to the module module -name "Set DataWindow" \ -primary "HxObject" \ -check { [__providesDataRange $PRIMARY] } \ -category "Main" \ -proc { set range [__getDataRange $PRIMARY] if [llength $range] { set data [__getFirstData $PRIMARY] $data parameters setValue "dataWindow" $range set x "data window for $data has been set\nto range" append x "\[[lindex $range 0]:[lindex $range 1]\]" theMsg warning $x $data touch } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

Here are some additional functions that are used in the above code.
# check if a module has a (visible) port of a certain type, and return its name proc __firstPortOfType {type module} {
1 2

81

6. General Information
foreach port [$module allPorts] { if {[$module $port getTypeId] == $type} { if [$module $port isVisible] { return $port } } } return "" } # return first data object connected to a module, or show message proc __getFirstData {module} { set dataport [__firstPortOfType HxConnection $module] if {$dataport == ""} { theMsg error "no connection port found $module" "OK" } else { set data [$module $dataport source] if {$data == ""} { theMsg error "no data connected to $module" "OK" } else { return $data } } return "" } # query the data range from a given module proc __getDataRange {module} { set type [$module getTypeId] if { ($type == "HxOrthoSlice") || ($type == "HxObliqueSlice") } { # which one is active: linear range or colormap? set mapping [$module mappingType getValue] if {$mapping == 0} { # linear range is active set min [$module linearRange getValue 0] set max [$module linearRange getValue 1] } elseif {$mapping == 1} { # histogram mode theMsg error "cannot determine data range in histogram mode" "OK" return "" } else { # colormap mode set min [$module colormap getMinValue] set max [$module colormap getMaxValue] } } else { # any other module: look for the first colormap port set port [__firstPortOfType HxPortColormap $module] if {$port == ""} {
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

82

6.14. Announce new modules with .rc les


theMsg error "found no colormap port in $module" "OK" set min [$module $port getMinValue] set max [$module $port getMaxValue] } } return [list $min $max] } # check if a module is suited for data range definition proc __providesDataRange {module} { set type [$module getTypeId] if { ($type == "HxOrthoSlice") || ($type == "HxObliqueSlice") } { return 1 } set port [__firstPortOfType HxPortColormap $PRIMARY] if {$port != ""} { return 1 } echo "nix is" return 0 } echo "method setDataWindow registered."
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

New modules can also be created by customizing existing modules. This is also done in the *.rc les. Here an example that denes a new SurfaceView1 module. Using the existing module we dene new initial setting as for example a dierent transparency mode:
module -name "HaukesSurfaceView" \ -primary "HxSurface HxSurfaceScalarField" \ -class "HxDisplaySurface" \ -category "Main Display" \ -proc {$this drawStyle setValue 4; $this baseTrans setValue 0.8} \ -dso "libhxsurftools.so"
1 2 3 4 5 6

The key point is to extend the standard rc-denition of a module by a -proc section that can be lled with tcl-calls. Another example is the CreateLandmarks (2 sets) which is using the same approach to instanciate a Landmark object which contains two sets of points. As you may have noticed in the category section you can dene where your module should appear in the menu initially. The default is that modules are generated by rightclicking in the user interface on the modules that they use as input. This object oriented appearence can be broken by using the special category Data. Modules with this setting will be presented in the CreateData menu of the main control window. This makes sense
1

A good thing to know about the SurfaceView module is the fact that there is a tcl-command line option called selectVisibleOnly which can be used to change the mode of selection. Using Draw one can punch holes in a surface without removing the triangles in patches underneed.

83

6. General Information for objects that do not require any input data for their operation as for example a module which denes an analytical vector eld.

6.15. The current time


The current time can be printed out using:
clock format [clock seconds]

84

7. Editors
7.1. Invoke the Crop Editor
The crop editor can be attached to a data object with
set CE [create HxImageCrop] $CE attach $image

Some commands of the crop editor are available directly on the data sets. This includes the ip, swap and the crop options.
# flips the data around the x-Axis (1 is y-, 2 is z-Axis) lobus.am flip 0 # swap dimensions will re-arrange the order 0,1,2 into the given one lobus swapDims 1 0 2 # crop the background of a volume with a threshold lobus.am crop -auto 54 # crop the image by given values (imin imax jmin jmax kmin kmax) lobus.am crop 20 99 20 99 20 40 # if the new dimensions are bigger specify a value for filling lobus.am crop -10 500 -10 500 -5 200 42

7.2. Invoke the Segmentation Editor


In Avizo versions prior to 5.2 you can invoke the Segmentation Editor automatically for a label-eld by executing:
create HxGiEditor gi attach mydata-labels

(where mydata-labels is a prior loaded label set) in the Avizo console window (or script). HxGiEditor is the name of the class which is the Segmentation Editor. Its object name in the Avizo object pool is gi. By attach we assign the Segmentation Editor to be working on the mydata-labels labeleld. Since Avizo 5.2 one has to use the more consistent way of assigning the editor to a variable before using it:
set gi [create HxGiEditor] $gi attach mydata-labels

85

7. Editors In the following sections we will assume that gi is replaced with $gi if Avizo 5.2 or later is used. There are some options that can be invoked in the Segmentation Editor using the gi Tcl-interface. Smoothing for example is done by:
gi setSlice N gi smooth 3

(smoothes the slice number N with a Gaussian kernel of size 3) or


gi smoothAll

which smoothes all slices. There are some more commands available like llBones and removeIslands3d. You get a list of them by executing
> gi allPorts attach blowShrink clipGeom detach experimentalBrushMode experimentalLassoMode fillBone fillBoneAll fire getControllingData getLabel getProjectionBox getTypeId getVar getViewerMask hasVar help numParam paste proc redo removeIslands removeIslands3d render selectMaterial set1ViewerMode set4ViewerMode setControllingData setLabel setMaxAlpha setOrientation setSlice setTool setVar setViewerMask smooth smoothAll unclipGeom undo unsetVar >
1 2 3 4 5 6 7 8 9

in the Avizo console window. Please note, that in order to work some functions like the orientation test will require a material called Exterior.

7.2.1. Automatic erosion and dilation


The Segmentation Editor allows us to use some of its build-in functionaly from Tcl. One example is the opening and closing of structures. This involves the repeated application of erosion and dilation on a specic material of a label eld. Here is an example:
# assume lobus.labels.am is loaded create HxGiEditor gi attach lobus.labels.am # select the first non-background material (Exterior is 0) gi selectMaterial2 1 # two times dilation gi growSelection3D gi growSelection3D # one time erosion gi shrinkSelection3D # now assign the new selection to material 1

86

7.3. Invoke the Parameter Editor


gi add 1 # remove the module from display gi detach

7.3. Invoke the Parameter Editor


The following two lines will create a Parameter Editor and show the parameters of an input object whose name is stored in the variable data.
set pe [create HxParameterEditor] $pe attach $data

7.4. Invoke the Transform Editor


In a similar way than the Segmentation Editor also the Transform Editor is represented by an (hidden) object in the Avizo workspace. It needs to be generated and attached to a data object. The transform editor is generated by:
create HxTransformEditor xe xe attach mydata.am xe fire

. The current settings for the transformation of a data set can also be printed out by using the console command
datafile.am getTransform

which will result in a list of 16 numbers that represent the usual 4 4 matrix for transformations containing rotations, shifts and scalings. In order to copy a transformation from one object to another you can use this construct.
otherdatafile.am setTransform 1 0 0 ...

If you want to reset the transformation and the Transform Editor is still open you can get access to the buttons by
<datafilename> transformEditorReset hit <nB> <datafilename> transformEditorReset touch <nB> <datafilename> transformEditorReset send <nB> 1

where nB is the number of the button, e.g. All would be 0, Translation is 1, Rotation is 2 and Scale is 3. The action buttons are working similar. In this case you use transformEditorAction instead of transformEditorReset.

87

7. Editors

7.5. Invoke the Camera Path Editor


Similar to the examples above you can create a the camera path editor and attach it to a camera path by
# create the camera path first set cp [create HxKeyframeCameraPath] # now create the editor set cpe [create HxKeyframeCameraPathEditor] $cpe attach $cp $cp fire # now we can set the current camera position with viewer 0 setCameraPosition 0 0 0 viewer 0 setViewDirection 1 0 0 # and save this location in the camera path $cp portKCPEButtons setValue 0 1 $cp portKCPEButtons send $cp fire
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

7.6. Invoke the Surface Editor


create HxSurfEdit surfEdit attach lobus.surf

7.7. Invoke the slice aligner


To perform an automatic slice alignment use the following framework:
set set $la $la $la $la $la $la data YourDataObjectIconName la [create HxAlignSlices] data connect $data select action setValue 0 1 fire setAlignMode 0 alignAll

Now you can perform a resampling of the data and save them as a new 2D image series
$la action setValue 1 1 $la fire set newData [lindex [lindex [$la downStreamConnections] 0 ] 0] $newData save "Raw Data 2D" c:/newFileName####.raw

88

7.8. Invoke the Landmark Editor

7.8. Invoke the Landmark Editor


Given a landmark set you can create a landmark editor object with
set le [create HxLandmarkEditor] $le setNumSets 2 $le attach Landmarks Landmarks fire

Landmarks can be added using appendLandmark (adds a landmark for each set at the specied coordinates) and moved using setPoint (<which set> <which landmark> <new coordinates>). Before moving the landmark the editor has to switch to the correct mode using Landmarks landmarkEditorMode setValue 2 (for moving points).

89

7. Editors

90

Part III. C++ Code examples

91

8. General Information
8.1. First steps
In order to start a project in Avizo you should always start with the Development Wizard. It will generate the directory structures needed, a rst templated version of your project and the make les for your project for both Unix and Windows based systems. Sometimes you will need to link your project against a new library. This happens if you, for example, want to export a spreadsheet object as additional output. Spreadsheets are dened in the hxspreadsheet library so you need to link your project against this library. Add the new library to the Package le in the LIBS section. The le is generated by the Development Wizard in your project directory. The build system option in the Development Wizard will generate new versions of the build les which contain the library.

8.2. Compiling a package


On Linux by default (as well as on the other systems) a debug version of your module will be generated. In order to produce the nal version of the module without the debug information you need to call (for the unix systems) gmake MAKE CFG=Optimize on Windows systems you can recompile after selecting the Release solution conguration. In order to generate a 64bit version of your library you need to set the environment variable AVIZO64 to 1. You can check if the library was correctly build for 64bit by using the command line tool file. There is also a variable dened with this name in the start script that you need to set to 1 so that the 64bit version of the libraries are called. In order to check if a library contains a specic function one can use the linux tools nm and c++lt. In the following call we look for a specic function:
nm /root/Avizo-plugins/lib/arch-LinuxAMD64-Debug/libexample.so | \ c++filt | grep myfunction

More information about a dll is obtained by the objdump command. It will list the type of the executable (32 or 64 bit) and also all dependent libraries (objdump -x somelib.dll | less). In order to nd out where Avizo is looking for your module you can type into the console of Avizo (before you start your module)
dso verbose 1

Now Avizo will display some more information about the directories it is looking for additional libraries like your the library which contains your package.

93

8. General Information In order to have your debug-mode module visible in Avizo you need to start Avizo with the command line option -debug.

8.3. Display a progress bar


The progress bar can be used to display the progress of a computation and to allow the user to break out of a loop using to much computational time.
#include <Amira/HxWorkArea.h> ... theWorkArea->startWorking("Doing a lot..."); for(int i=0;i<10000;i++){ theWorkArea->setProgressValue(i*1.0/10000.0); theWorkArea->setProgressInfo("still working..."); if(theWorkArea->wasInterrupted()){ theWorkArea->stopWorking(); return; } # place your code here ... } theWorkArea->stopWorking();
1 2 3 4 5 6 7 8 9 10 11 12 13 14

8.4. Accessing environment variables and the registry


Environment variables can be queried as follows
const char* AvizoDir = getenv("AVIZO_LOCAL"); if (getenv("AVIZO_USE_ADVANCED_ALGORITHMS")) magic = true; else magic = false;

In order to read a global variables in the Avizo workspace (as dened by the tcl set command) use
const char *value = theInterpreter->getVar("someVariableName");

. You can also generate and query local variables which belong to an existing module. This way modules of the same type can have dierent values for this variable. Dene such a variable belonging to an object moduleName by for example moduleName setVar myKernelSize 42 and query them inside another module with:

94

8.5. Display the le dialog

McString res; if(getTclMemberVar("myKernelSize", res)) kernelSize = atof(res); // kernelSize has now the value of the variable

If a module needs to store settings across Avizo sesions one can use the registry of the operating system. Old versions of Avizo used a mechanism in HxResource.
#include <Amira/HxResource.h> ... // store values in the registry char str[256]; sprintf(str, "%d", portPosition.getValue(0)); HxResource::regSetValue("MyModule_positionX", str); // read values from the registry McString str; if(HxResource::regGetValue("MyModule_positionX", str)) { portPosition.setValue(0,atoi(str)); } else { // use the default value portPosition.setValue(0,42); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14

With newer versions of Avizo the settings manager should be used instead.
#include <../HxSettingsMgr.h> ... // store values in the registry char str[256]; sprintf(str, "%d", portPosition.getValue(0)); theSettingsMgr->setCustom("MyModule_positionX", str); // read values from the registry McString str; if(theSettingsMgr->getCustom("MyModule_positionX", str)) { portPosition.setValue(0,atoi(str)); } else { // use the default value portPosition.setValue(0,42); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14

8.5. Display the le dialog


The normal way to get a le dialog in your program is to use the corresponding port HxPortFilename. It provides a button and a text eld. You can also call the load/save dialog on your own.

95

8. General Information

#include <Amira/HxFileDialog.h> ... McString file; if (!filename) { HxFileDialog fn; fn.setDialogUsage (HxFileDialog::LoadFile); fn.setTitle ("Load a file on disc"); fn.postAndWait (); McFilename f (fn.getFileName ()); if (f.doesExist ()) { theMsg->printf("error: could not create file"); } }

1 2 3 4 5 6 7 8 9 10 11 12 13

There is also a global object theFileDialog which represents the le dialog. Use this one if your dialog should keep the values for the directory from the last visit. The example above also shows the usage of McFilename which contains methods for testing if a le exists, is readable by the current user or methods to seperate the basename from the root and the extension of the lename. In order to read in a number of les automatically we can also use the McFilenameCollector class. Especially the static function collect is useful to get a list of lenames from a given directory. Here an example that looks into the share/models/ directory of Avizo and tries to nd les ending with .iv. Found les are entered into a HxPortMultiMenu.
McString r = HxResource::getRootDir(); r += "/share/models/"; // which files in here are .iv? McDArray<McString> fileNames; fileNames.resize(0); /// if there would be some leftover filenames McFilenameCollector::collect(fileNames, "*.iv", r); // extend the first drop down menu and add the basenames portMenu.setNum(0,fileNames.size()); for(int i = 0;i<fileNames.size(); i++){ McFilename *fn = new McFilename(fileNames[i]); portMenu.setLabel(0,i,fn->basename()); }
1 2 3 4 5 6 7 8 9 10 11 12

But now back to the Filename dialog which is the topic of our section.
theFileDialog->setDialogUsage(HxFileDialog::LoadFileList); if (theFileDialog->postAndWait() == 0) { McString buf; // will contain a list of filenames for (int i=0; i<theFileDialog->getFileCount(); i++) { buf += "; buf += theFileDialog->getFileName(i); buf += "\" ";
1 2 3 4 5 6 7

96

8.6. Accessing other modules


} }
8 9

8.6. Accessing other modules


If you want to control other modules currently loaded in Avizo you can get a list of them by using theObjectPool. Here an example that collects all the oat sliders in all the modules currently loaded and prints out their value. First lets iterate over all objects in the workspace and see if any of them has a HxPortFloatSlider:
#include <Amira/HxObjectPool.h> ... for (int i=0; i<theObjectPool->nodeList.size(); i++){ HxObject *obj = theObjectPool->nodeList[i]; for (int j=0; j<obj->getNumPorts(); j++){ HxPort *port = obj->getPort(i); if(!port->isVisible()) // do not use them continue; HxPortFloatSlider *slider = dynamic_cast<HxPortFloatSlider*>(port); if(!slider) // its not a float slider continue; // now do something with the port theMsg->printf("the current value is: %g", ((HxPortFloatSlider *)port)->getValue()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

Note that you get the list of all visible objects by calling instead of nodeList theObjectPoolvisibleList. Another way to get only the visible objects is to call theObjectPoolnodeList[i].iconVisible(). Oddly enough the value returned by calling getLabel() on a port is a char *. For all modules it is a McString object which needs to be converted into a character pointer:
theMsg->printf("%s: %s", theObjectPool->nodeList[i].getLabel().toString(), theObjectPool->nodeList[i].getPort(0).getLabel());

8.6.1. Timing modules


Sometimes it is useful to know how long a specic call took to execute. For this purpose the following construct measures time in seconds:
#include <time.h> ...
1 2

97

8. General Information
clock_t t1 = clock(); // Start work dosomething(); clock_t t2 = clock(); float time = (float)(t2-t1)/CLOCKS_PER_SEC; theMsg->printf("Total time it took: %6.2f sec", time);
3 4 5 6 7 8 9 10

8.6.2. Create an existing module


All the Avizo objects can be created by any other module. Here an example on how to create an OrthoSlice module from a compute module.
#include <hxlattice/HxOrthoSlice.h> ... HxOrthoSlice *os = (HxOrthoSlice *)HxResource::createObject("HxOrthoSlice"); if(os) { os->setLabel("OrthoSlice"); theObjectPool->addObject(os); os->portData.connect(portData.source()); // connect to my own input os->fire(); }
1 2 3 4 5 6 7 8 9

8.6.3. Working with ports


The name ports summarizes the interface ports and the connections of a module. The order in which the ports are dened in the header le of the C++ class denes the order in which the ports will be listed in the interface. Whereas the names of objects and ports can be queried by getLabel() the classTypeId needs to be queried with getName(). Here some examples on how to get informations out of the ports of a given object:
for (int j=0; j< object->getNumPorts(); j++) { HxPort* port = object->getPort(j); if (!port->isVisible()) // ignore all invisble ports continue; theMsg->printf("%s has port %s of type %s", object->getLabel().getString(), port.getLabel(), port.getClassTypeId().getName());
1 2 3 4 5 6 7

Here now some examples of querying the values of ports. This always depends on the type of port.
if(port->isOfType(HxPortFloatSlider::getClassTypeId()){ HxPortFloatSlider *p = (HxPortFloatSlider *)port;
1 2

98

8.6. Accessing other modules


theMsg->printf(" min: %g, max: %g, current value: %g", p->getMinValue(), p->getMaxValue(), p->getValue()); } if(port->isOfType(HxPortIntSlider::getClassTypeId())){ HxPortIntSlider *p = (HxPortIntSlider *)port; theMsg->printf(" min: %d, max: %d, current value: %d", p->getMinValue(), p->getMaxValue(), p->getValue()); } if(port->isOfType(HxPortColormap::getClassTypeId())){ HxPortColormap *p = (HxPortColormap *)port; float min,max; min = max = 0; p->setLocalMinMax(min,max); theMsg->printf(" local range min: %d, max: %d", min, max); } if(port->isOfType(HxPortText::getClassTypeId())){ HxPortText *p = (HxPortText *)port; theMsg->printf(" current value: %s", p->getValue()); } if(port->isOfType(HxPortFilename::getClassTypeId())){ HxPortFilename *p = (HxPortFilename *)port; theMsg->printf(" current value: %s", p->getValue()); } if(port->isOfType(HxPortRadioBox::getClassTypeId())){ HxPortRadioBox *p = (HxPortRadioBox *)port; for(int k=0;k<p->getNum();k++){ theMsg->printf(" toggle%d: %s", k, p->getLabel(k)); } theMsg->printf(" currently highligted: %s (%d)", p->getLabel(p->getValue()),p->getValue()); } if(port->isOfType(HxPortToggleList::getClassTypeId())){ HxPortToggleList *p = (HxPortToggleList *)port; for(int k=0;k<p->getNum();k++){ theMsg->printf(" toggle%d: %s (%s)", k, p->getLabel(k), p->getValue(k)==1?"on":"off"); } } if(port->isOfType(HxPortIntTextN::getClassTypeId())){ HxPortIntTextN *p = (HxPortIntTextN *)port;
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

99

8. General Information
for(int k=0;k<p->getNum();k++){ theMsg->printf(" intText%d: %s %d [%d..%d]", k, p->getLabel(k), p->getValue(k), p->getMinValue(),p->getMaxValue()); } } if(port->isOfType(HxPortFloatTextN::getClassTypeId())){ HxPortFloatTextN *p = (HxPortFloatTextN *)port; for(int k=0;k<p->getNum();k++){ theMsg->printf(" floatText%d: %s %g [%g..%g]", k, p->getLabel(k), p->getValue(k), p->getMinValue(),p->getMaxValue()); } } if(port->isOfType(HxPortMultiMenu::getClassTypeId())){ // now some specific values for that port HxPortMultiMenu *p = (HxPortMultiMenu *)port; for(int k=0;k<p->getNumMenus();k++){ McString label = ""; for(int o=0;o<p->getNum(k);o++){ label += " \""; label += p->getLabel(k,o); label += "\""; } theMsg->printf(" MultiMenu \"%s\" (%d): %s", p->getLabel(k), k, label.getString()); theMsg->printf(" active: %d(%s)", p->getIndex(k), p->getLabel(k,p->getIndex(k))); } } }
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

Note, if you try to access the interface of a module which was just created it may be that its ports are not yet dened. This happens for example in a reader which may generate some display modules attached to the data. In this case you can force the interface of the module to be created if you select the object:
object->select(); // now interface with the module like setting a pin object->deselect();

Another case is when you like to nd a specic module in the workspace in order to attach yourself to it. This is used to automate the setup of modules that require more than one input. Usually the inputs share a common part of their lename, like starting with the same name but ending with -labels.am instead of .am. Here is a piece of code that is best put into an update()/indexupdate module.

100

8.7. Textual input and output

if(portData.isNew()){ // if we get connected // guess the name of the other module McString name; setResultName(name, portData.source()->getName(), "_EVALS"); if(theObjectPool->findObject(name)){ // include Amira/HxObjectPool.h portEVALS.connect(theObjectPool->findObject(name)); } }

1 2 3 4 5 6 7 8

8.7. Textual input and output


To produce textual output from inside Avizo modules you have to use the
theMsg->printf("bla %d", 100)

functionality known from the C language. You can also use the C++ equivalent which looks like this
theMsg->stream() << "using bla" << endl;

8.8. Debug messages (enable/disable)


A simple way to create debug messages that can be switched of in your program is to use a construct like the following:
// when DEBUG_MSG is not defined debugMsg() lines are removed completely #undef DEBUG_MSG #ifdef DEBUG_MSG # ifndef HX_MESSAGE_H # include <Amira/HxMessage.h> # endif # define debugMsg(x) {theMsg->printf x;} #else # define debugMsg(x) #endif
1 2 3 4 5 6 7 8 9 10 11

In general you will need to add the option -debug to your Avizo call if you want Avizo to use the debug version of your module. This is placed in a dierent directory than the one with the optimized code. On a Windows 32 bits system this may be Avizo-x.x/bin/arch-win32-debug/ instead of Avizo-x.x/bin/arch-win32-optimize.

101

8. General Information

8.9. Executing Tcl commands


Sometimes you will have to use the tcl interface of Avizo even if your modules are written in C++. This happens if you want to get access to a module which is not contained in the Avizo XPand include libraries. A prominent example is the surface generation programm SurfaceGen. To access it from within C++ you will have to rst generation some tcl-code like the following:
create HxGMC {SurfaceGen} SurfaceGen data connect lobus.labels.am SurfaceGen fire SurfaceGen smoothing setValue 0 1 ... # some more settings SurfaceGen select [ {SurfaceGen} create {lobus.surf.am} ] setLabel {lobus.surf.am} lobus.surf.am master connect SurfaceGen
1 2 3 4 5 6 7 8

SurfaceGen is doing a smoothing on the label eld beforehand. This is implemented by a Gaussian smoothing with a kernel size of 5. You can change this default by
SurfaceGen setVar SmoothKernelSize 13

To execute these tcl commands from inside C++ you can use the HxInterpreter class. It provides also a way to set or query variables.
#include <Amira/HxInterpreter.h> ... const char* someStr = theInterpreter->eval("echo \"tcl command\""); theMsg->printf("Value returned by eval: %s", someStr); ... Tcl_Interp* inter = theInterpreter->getTclInterp(); // set a tcl variable char t[256]; sprintf(t, "%d", 30 + 12); theInterpreter->setVar("bla", t, TCL_GLOBAL_ONLY); // ask the variable for its value char* ampl = (char*)theInterpreter->getVar("bla"); theMsg->printf("Value of tcl variable bla is: %s", ampl);
1 2 3 4 5 6 7 8 9 10 11 12

or to remove the variable again


Tcl_UnsetVar(inter,"bla",TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);

8.10. Change the voxel size


The voxel size is dened by bounding box that a number of voxel occupy. So we can change the voxel size by changing the datas bounding box (and leaving the dimensions the same). So lets ask the eld for the values of the bounding box and its dimension.

102

8.11. The camera position

const int *dims = field->lattice.dims(); float bb[6]; field->getBoundingBox(bb);

The voxel size (for uniform scalar elds) is given as:


/*voxelsize for */ x= ((bb[1]-bb[0])/(dims[0]-1); /*voxelsize for */ y= ((bb[3]-bb[2])/(dims[1]-1); /*voxelsize for */ z= ((bb[5]-bb[4])/(dims[2]-1);

In order to change the voxel size to xn, yn, zn we have to have a new bounding box which is:
float nbb[6]; nbb[0] = bb[0]; nbb[2] = bb[2]; nbb[4] = bb[4]; nbb[1] = (dims[0]-1)*xn + bb[0]; nbb[3] = (dims[1]-1)*yn + bb[2]; nbb[5] = (dims[2]-1)*zn + bb[4];

and now the new bounding box is in nbb.


field->coords()->setBoundingBox(nbb);

Please note that this will let the lower right corner of the bounding box be xed. Your data will therefore grow or shrink in a specic direction rather than keep its mean position.

8.11. The camera position


The camera is an object of viewer. Here an example:
#include <Amira/HxController.h> #include <Amira/HxViewer.h> #include <Inventor/nodes/SoCamera.h> ... SoCamera *camera = theController->viewer(0)->getCamera(); float rad; SbVec3f axis; SbVec3f pos = camera->position.getValue(); camera->orientation.getValue().getValue(axis, rad); float focalDistance = camera->focalDistance.getValue(); float nearDistance = camera->nearDistance.getValue();
1 2 3 4 5 6 7 8 9 10 11

Also if there are some values to be saved/changed if one is using a perspective or some other values if one is using an orthogonal camera. Using an SoPerspectiveCamera one has to ask for its heightAngle. The same value from an SoOrtographicCamera is called height (see Inventor/nodes/SoPerspectiveCamera.h and Inventor/nodes/SoOrthographicCamera.h).

103

8. General Information

8.12. Updating the viewer


In order to give the user an impression of the status of the current computation it is useful to update the display during the computation every once in a while. This might be done by calling the viewers render function and telling Avizo that the data has been changed. In this case Avizo will send an update command to all connected modules in the work area which in turn will update their computations and visualizations.
#include <Amira/HxWorkArea.h> #include <Amira/HxController.h> #include <Amira/HxViewer.h> ... // field could be lobus.am HxUniformScalarField3 *field = (HxUniformScalarField3 *)portData.source(); if(!field){ theMsg->printf("error: no data object connected"); return; } // we will update this viewer HxViewer *viewer = theController->getCurrentViewer(); const int *dims = field->lattice.dims(); // get the dimensions of lobus.am float value[1]; // we will assume scalar values // loop and update the data theWorkArea->startWorking("Computing..."); for(int i=0;i<10000;i++){ theWorkArea->setProgressValue((1.0*i)/10000.0); value[0] = (rand()/(pow(2,15)-1.0)) * 255.0; // generate a value // and pic an arbitrary position in the field to update with value[0] field->lattice.set( (int)(rand()/(pow(2,15)-1) * (dims[0]-2)), (int)(rand()/(pow(2,15)-1) * (dims[1]-2)), (int)(rand()/(pow(2,15)-1) * (dims[2]-2)), &value[0]); if((i % 10) == 0){ // update only every 10th iteration field->touch(); // tell Avizo that the data has changed field->fire(); viewer->render(); // now update the first viewer } } theWorkArea->stopWorking();
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

This solution will update the viewer window during the computation but it will not allow the user to interact. In order to perform a computation in the background you need to read on to the next section in which we introduce a multi-threaded approach.

8.13. Multi-threading
Threads are used in Avizo in the following way:

104

8.13. Multi-threading

#include <Amira/HxThread.h> ... myModule::MyThread::MyThread(myModule *cp, int param){ me = cp; // remember the calling object something = param; // remember some parameters } myModule::MyThread::~MyThead(){ } void myModule::MyThead::run(){ me->DoSomething(something); }

1 2 3 4 5 6 7 8 9 10 11

A thread can be called from this code using


MyThread *mt1 = new MyThread(this,1); MyThread *mt2 = new MyThread(this,2); mt1->start(); mt2->start(); while(!mt1->wait(300)); while(!mt2->wait(300));
1 2 3 4 5

Here is the denition of the MyThread class. You can copy something like this into your module and call it similar to the code above.
class MyThread : public HxThread { private: MyThread(){}; public: MyThread( myModule *me, int direction ); ~MyThread(); virtual void run(); // abstract from base class
1 2 3 4 5 6 7

int something; // my player object hxConePlot * coneplot; };

8 9 10 11

There is more to threading than just starting and stopping some background jobs. We also need to communicate between the threads. There is a whole lot of stu that could be told here but for the moment just look up things like mutex.lock, mutex.unlock, and wait.wakeOne. Especially the lock and unlock commands should be used if you write some global variables in the threads. Here is now a full example program doing three things, one is it it using three threads to generate data, do computation of the data (a smoothing) and it saves the data as LargeDiskData.
///////////////////////////////////////////////////////////////// /*
1 2

105

8. General Information
* Avizo example source to run three seperate threads for * computation, display and online data aquisition. (Hauke) */ ///////////////////////////////////////////////////////////////// #include <Amira/HxMessage.h> #include <hxfield/HxUniformScalarField3.h> #include <Amira/HxObjectPool.h> #include <hxMajo/hxMajoTest2.h> // those are for the compute part (convolution) #include <mclib/McTypedPointer.h> #include <mclib/McTypedData3D.h> #include <hximproc/ImGaussFilter3D.h> #include <Inventor/SoDB.h> // a timer event in the background doing nothing so far #include <Inventor/nodes/SoCallback.h> HX_INIT_CLASS(hxMajoTest2,HxCompModule) hxMajoTest2::MyThread::MyThread(hxMajoTest2 *cp, int val){ SoDB::threadInit(); // Inventor scene graph ready, // start Avizo with -mt in order to make it thread save onlineData = cp; type = val; } #define #define #define #define #define lockthread threadmutex->lock();//SoDB::writelock(); unlockthread threadmutex->unlock();//SoDB::writeunlock(); sleepread 1000 sleepcompute 1000 sleepupdate 1000
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 34 35

hxMajoTest2::MyThread::~MyThread(){} void hxMajoTest2::MyThread::run(){ switch(type) { case MyThread::READ_THREAD: onlineData->readThread(); break; case MyThread::COMPUTE_THREAD: onlineData->computeThread(); break; case MyThread::DISPLAY_THREAD: onlineData->displayThread(); break; default: theMsg->printf("error: unknown job type"); } } // its actually not reading by generating data here void hxMajoTest2::readThread(){

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

106

8.13. Multi-threading
// reading data from disk theMsg->printf("thread reading..."); while(!output) HxThread::msleep(500); theMsg->printf("ok, we got the output"); // now fill it every so often with some new data while(!0){ HxThread::msleep(sleepread); const int *dims = output->lattice.dims(); // pick a slice to fill int whichSlice = (int)(rand()/((float)pow(2.0,15.0)-1.0)* (dims[2]-1.0)); // use the mutex to lock an area in the code which // we have to handle alone lockthread; int k = whichSlice; for(int j=0;j<dims[1]; j++){ for(int i=0;i<dims[0]; i++){ output->set(i,j,k,((float)rand())/((float)(pow(2.0,15.0)-1.0))); } } unlockthread; theMsg->printf("updated the data..."); } } // do a smoothing on the data every some seconds void hxMajoTest2::computeThread(){ // wait till we get the output data object while(!output) HxThread::msleep(5000); // do some computation on the data... like convolve with a filter while(!0){ HxThread::msleep(sleepcompute); smooth(output); // convolution with a Gaussian theMsg->printf("data smoothed..."); } } // very simple just update the display at certain intervals void hxMajoTest2::displayThread(){ // update the display modules theMsg->printf("thread display..."); while(!output){ HxThread::msleep(500); } theMsg->printf("ok, we got the output"); while(!0){
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102

107

8. General Information
HxThread::msleep(sleepupdate); lockthread; output->touch(); // update display modules output->fire(); theMsg->printf("updated display..."); unlockthread; } } // do some advanced computation void hxMajoTest2::smooth(HxUniformScalarField3 *data){ const int *dims = data->lattice.dims(); unsigned char *imgBuffer1 = (unsigned char *)malloc( dims[0]*dims[1]*dims[2]*sizeof(double)); McTypedPointer *i1Ptr = new McTypedPointer(imgBuffer1, McPrimType::mc_double); McTypedData3D *i1 = new McTypedData3D(dims[0],dims[1],dims[2],*i1Ptr); // copy data into field lockthread; memcpy((double *)i1Ptr->data, data->lattice.dataPtr(), dims[0]*dims[1]*dims[2]*sizeof(double)); unlockthread; ImGaussFilter3D *filter = new ImGaussFilter3D(); int kernelsize[] = {3,3,3}; float sigma[] = {0.6,0.6,0.6}; filter->setParams(kernelsize,sigma); filter->apply3D(i1,NULL); lockthread; memcpy(data->lattice.dataPtr(), (double *)i1Ptr->data, dims[0]*dims[1]*dims[2]*sizeof(double)); unlockthread; free(imgBuffer1); } void oneShot(void * data, SoSensor* sensor){ hxMajoTest2* onlineData = (hxMajoTest2*)data; theMsg->printf("time step in oneShotCB %ld", ((hxMajoTest2 *)data)->counter++); } void hxMajoTest2::startThreads(void *data){ MyThread * mt1 = new MyThread( this, MyThread::READ_THREAD ); MyThread * mt2 = new MyThread( this, MyThread::COMPUTE_THREAD ); MyThread * mt3 = new MyThread( this, MyThread::DISPLAY_THREAD ); mt3->start(); mt2->start(); mt1->start(); theMsg->printf("threads are startet..."); } hxMajoTest2::hxMajoTest2() :
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152

108

8.13. Multi-threading
HxCompModule(HxUniformScalarField3::getClassTypeId()), portSize(this, "size", 3), portAction(this,"action") { portSize.setLabel(0,"x"); portSize.setLabel(1,"y"); portSize.setLabel(2,"z"); portSize.setValue(0,100); portSize.setValue(1,100); portSize.setValue(2,100); portAction.setLabel(0,"DoIt"); oneShotCallback = new SoTimerSensor(oneShot, this); oneShotCallback->setBaseTime(0.0); oneShotCallback->setInterval(2.0); oneShotCallback->schedule(); counter = 0; output = NULL; // init, threads will wait for this one // we will use this Mu_tually Ex_clusive object to handle the single // threads work on the output data object threadmutex = new HxMutex(); } hxMajoTest2::~hxMajoTest2() { } void hxMajoTest2::compute() { if (!portAction.wasHit()) { return; } startThreads(this); const int dims[] = {100,100,100}; output = dynamic_cast<HxUniformScalarField3 *>(getResult()); if( output && !output->isOfType(HxUniformScalarField3::getClassTypeId()) ) { output = 0; } if( !output ){ output = new HxUniformScalarField3(dims,MC_DOUBLE); } output->setLabel("output"); setResult(output);
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201

109

8. General Information
}
202

8.14. LargeDiskdata
Here is an example on how to write data as a LargeDiskData to disk and how to read such data back in.
friend class LatticeManager; void hxOnlineData::compute() { if (!portAction.wasHit()) return; float bbox[6]; const int *dims; int nDataVar = 1; // the data volume on disk that we will use to store the data char *parameter = "c:/bla.data"; HxRawAsExternalData *newlda = NULL; if(!HxResource::loadData(parameter, "AmiraMesh as LargeDiskData")){ theMsg->printf("error: could not find the file \"%s\"", parameter); return; } newlda = dynamic_cast<HxRawAsExternalData *>(theObjectPool->nodeList.last()); if(!newlda){ theMsg->printf("error: last object is not RawAsExternalData in object pool"); return; } newlda->getBoundingBox(bbox); dims = newlda->dims(); // remember that we load this as large disk data McString loadCommand; loadCommand.printf("load -amLDD %s", parameter); newlda->setLoadCmd(loadCommand, 1); // now to read the memory int numSlices = 1; HxExternalData::BlockRequest request(newlda); // loop over all the slices for(int slices=0; slices < dims[2]; slices += numSlices){ int num = numSlices; if(slices + num > dims[2]){ // last block problem num = dims[2] - slices; }
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 34 35 36 37 38 39 40 41

110

8.15. Generating warning messages and simple dialogs


request.setSize(dims[0],dims[1], num); request.setOrigin(0,0,slices); if(!newlda->getBlock(&request)){ theMsg->printf("error: could not get data block"); } for(int i=0;i<dims[0]*dims[1]*numSlices;i++){ theMsg->printf("data is %g", newlda[i]); } } // now an example how to write from memory to disk const int blockSize[3] = {64, 64, 64}; // lets write a standard block void* block = mcmalloc (sizeof(char) * nDataVar * blockSize[0] * blockSize[1] * blockSize[2]); McRawData::memset (McTypedPointer (block, McPrimType::mc_uint8), 0, blockSize[0] * blockSize[1] * blockSize[2]); int p[3]; for (p[2] = 0; p[2] < dims[2]; p[2] += blockSize[2]) { for (p[1] = 0; p[1] < dims[1]; p[1] += blockSize[1]) { for (p[0] = 0; p[0] < dims[0]; p[0] += blockSize[0]) { int subSize[3]; for (int i = 0; i < 3; i++) { subSize[i] = dims[i] - p[i]; if (blockSize[i] < subSize[i]) { subSize[i] = blockSize[i]; } } request.setBlockOrigin(subSize); request.setDataOrigin(subSize); request.data = block; request.setBlockSize(blockSize); request.setDataSize(blockSize); if( newlda->putBlock(&request) < 0 ) { theMsg->printf("error set external data"); } } } } }
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

8.15. Generating warning messages and simple dialogs


#include <Amira/HxMessage.h> ... if(HxMessage::question("Do you realy want to quit?", "ok", "stop that", 0, 1, 1) == 1) { ...
1 2 3 4

111

8. General Information
5

if (HxMessage::warning ("You selected to display more than 5000 spheres.\n" "Avizo may get very slow.\n" "Do you want to continue?", "Yes", "No", 0, 1) == 1) {...

6 7 8

8.16. Adding additional information to elds


The AmiraMesh le format allows to attach any number of parameters to the data in a le. Those parameters are used for example to hold DICOM information or parameters that where used to generate the data icon. In general you will want to document what was done to the data. Here is an example:
HxParameter* fftParam = new HxParameter(paramHalfComplex,realSize); fftParam->setFlag(HxParamBase::NO_SETVALUE, 1); fftParam->setFlag(HxParamBase::NO_RENAME, 1); fftParam->setFlag(HxParamBase::NO_DELETE, 1); result->parameters.insert(fftParam);

See also chapter 11 on page 149 for a more complex example involving sub-groups of parameter settings. Parameters can be copied from one le to the next.
output->parameters.copy(lineset->parameters);

In order to nd a specic parameter bundle you can call it by its name. The following code fragment adds a bundle called LaplacianEigenmaps at the end of the already existing set LineSetDataValues.
HxParamBundle* descr = output->parameters.bundle("LineSetDataValues", 1); if(!descr) return; // something is wrong HxParamBundle* val = descr->bundle ("LaplacianEigenmaps", 1); val->set ("LaplacianEigenmaps", descr->nBundles());

Or you can ask for values from a bundle by findString, findColor, findNum, findReal.
const char *str = 0; val->findString("LaplacianEigenmaps", str); // str now contains the value of the key LaplacianEigenmaps

8.17. Utility functions


Most utility functions are stored in mclib. This package contains some classes that are frequently used in Avizo. A prominent example is the dynamic array class McDArray.

112

8.18. Utilizing Analytical Expressions Additionally to the usual append and insert functions the array can also be sorted. Assume that you have a structure called Component and an array of such structures that you want to sort. The sorting should be done based on a specic eld in each component called inputValue. Dene rst a static compare function that we can use to sort our array.
int compare(HxShapeAnalysis::Component const& t1, HxShapeAnalysis::Component const& t2) { if(t1.inputValue < t2.inputValue) return -1; if(t2.inputValue < t1.inputValue) return 1; return 0; } ... McDArray<Components> bla; ... bla.sort(compare); // now the array is sorted

8.18. Utilizing Analytical Expressions


Expressions are used in Avizo mainly inside the Arithmetic module. It is used to perform (nearly) arbitrary local computations on the values of elds. You can use it for example to calculate a gamma correction on an image (1/exp(gamma)) or to do a exposure limitation (1 exp(x 0.5)) or in the case of LabelFields to extract one or more volume parts based on their material index. Its main limitation is that you can only use it for operations locally to a single voxel. With other interfaces like tcl or MATLAB (CalculusMatlab) you could also do computations like nite dierences between neighboring voxels. In order to utilize the analytical expressions you will have to dene variables and set their value. After that you can interprete a string which will result in its value. Arithmetic uses this in order to calculate a new eld given the current values as variables in the expression to be evaluated.
#include <anna/Anna.h> #include <anna/AnVarnode.h> #include <anna/AnConverter.h> ... McDArray<AnVarnode*> var; // in .h Anna *epxr; ... var = 0; // in constructor expr = 0; var.append(new AnVarnode("u", 0)); var.append(new AnVarnode("v", 0));
1 2 3 4 5 6 7 8 9 10 11

In a compute module we can evalutate an expression by:

113

8. General Information

Anna* oldExpr = expr; // now use the field to set a value const char *expressionString = portText.getValue(); // a HxPortText expr = AnConverter::strToAnna(expressionString, var.size(), var); if (!expr) { expr = oldExpr; theMsg->printf("HxAnnaScalarField3: failed\n"); } else if (oldExpr) delete oldExpr; // now set the variables to their actual values var[0]->setValue(42.0); // u var[1]->setValue(10.0); // v if(expr) // and evaluate the expression value = expr->value();

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

8.19. Documentation
Once written you can add a documentation for your Avizo module. For a loaded module in Avizo you can call the command createDocFile in the Avizo console window. This will create a .doc le (not Microsoft Word but docygen) and png-image les for each port. Move the les to your ${AVIZO LOCAL}/src/mypackage/doc directory. Now edit the .doc le and ll in the information for all the ports, the input connections and the general description. Now you can generate the documentation in its nal html format by executing at the command prompt (not the Avizo console)
doc2html -a

The executable can be found in your Avizo bin directory. If you move the resulting les to the ${AVIZO ROOT}/share/doc/usersguide/ directory your help will be called and displayed in the Avizo help window if you hit the question sign in the Avizo work window. In the nal doc (doxygen) le you can also put in links to other documents a
\link{HxArbitraryCut}{a link to the cutting module}

will produce such a link. One of the great points about the doxygen is that you can actually put in latex commands to describe math formulas. Avizo contains its own http-server which handels the help pages. Because of this sometimes you will see network activity of Avizo. The server can be started or stopped by
httpd {start|quit}

114

9. Compute modules
Avizo compute modules are used to perform computations on one or more input objects. The result is usually saved in an output object created by the module and re-used if accessible. Here is the general framework
void hxSomeClass::compute() { if(!portAction.wasHit()) // continue only if the user presses doit return; HxUniformColorField3* field = (HxUniformColorField3*) portData.source(); if(!field) { theMsg->printf("Error: not connected to a color field."); return; } float bb[6]; field->getBoundingBox(bb); /// < physical size const int *dims = field->lattice.dims(); /// < number of voxel
1 2 3 4 5 6 7 8 9 10 11 12

Once the input is accessible we can create the output object our re-use a priviously conneced module:
// int dims[3] = { 100, 100, 100 }; 1 HxUniformScalarField3 *output = dynamic_cast<HxUniformScalarField3 *>(getResult()); 2
3

if( output && !output->isOfType(HxUniformScalarField3::getClassTypeId()) ) { output = 0; } if( output ){ const int* outdims = output->lattice.dims(); if( dims[0] != outdims[0] || dims[1] != outdims[1] || dims[2] != outdims[2] || field->primType() != output->primType() ){ output = 0; } } if( !output ){ output = new HxUniformScalarField3(dims,MC_DOUBLE); } // now copy the bounding box from the input to the result output->lattice.coords().setBoundingBox(bb);

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

If the dimension of the output changes we will create a new data object. The old data object will be still in the workspace. You can try to remove it but in this case all the

115

9. Compute modules attached display modules will be removed from the workspace as well (which is not good style). Nevertheless here is the code:
if(!output || tmpdims[0] != outdims[0] || tmpdims[1] != outdims[1] || tmpdims[2] != outdims[2]){ removeFormerResult = 1; ... // after the generation of the data if(removeFormerResult){ HxObject* obj = getResult(); if(obj){ theObjectPool->removeObject(obj); } } setResult(output); output->setLabel("OutputObject");
1 2 3 4 5 6 7 8 9 10 11 12 13

In modules derived from HxCompModule you can use the setResult call to register the data in the object pool. A probably sequence looks like this
output->composeLabel(inputData->getName(),"_modified"); setResult(0,output);

For multiple outputs increase the 0 to 1 and so on. If you use several result objects special care has to be taken to allow a correct saving and restoring of network les. If for example the output can be re-created from the module then the module should state so in the canCreateData function. Here an example which guesses the output object port based on the icon name:
/** this is a virtual function */ int HxExtractEigenvalues::canCreateData(HxData* data, McString& createCmd) { McString dn(data->getName()); bool res = false; if(dn.matches("*_evec1")){ // only for this one we will create the outputs createCmd.printf("{%s} action hit; {%s} fire; {%s} getResult 0\n", getName(), getName(), getName()); res = (HxCompModule::getTouchTime(0) == data->getTouchTime()); } if(dn.matches("*_evec2")){ // here we assume that the output exists already createCmd.printf("{%s} getResult 1\n", getName()); res = (HxCompModule::getTouchTime(1) == data->getTouchTime()); } if(dn.matches("*_evec3")){ createCmd.printf("{%s} getResult 2\n", getName()); res = (HxCompModule::getTouchTime(2) == data->getTouchTime()); } if(dn.matches("*_evals")){
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

116

9.1. Adding another eld as input port


createCmd.printf("{%s} getResult 3\n", getName()); res = (HxCompModule::getTouchTime(3) == data->getTouchTime()); } return res; }
19 20 21 22 23

If you do not derive your object from HxCompModule and you still want to generate an output object you will have to use
theObjectPool->addObject(output);

. The object will be placed in the workspace but no blue line will indicate that your module generated the data.

9.1. Adding another eld as input port


The special input port called portData can be taught to accept multiple types of input data. So your program could connect at one time to a lattice at another time to a surface or cluster. The list of classes that a port can be connected to is specied two times. In the .rc le of your module you need to add the classes to the -primary value (a list of C++ class names separated by spaces). In your class denition le you can add the new type to the constructor as in:
portData.addType(HxCluster::getClassTypeId()); // add a cluster as pot. input

Now it is important to decide at one point in your program if the input is one or the other type. This can be done with a construct like the following:
HxUniformScalarField3* field = static_cast<HxUniformScalarField3*> (portData.source(HxUniformScalarField3::getClassTypeId ())); HxCluster *cluster = static_cast<HxCluster *> (portData.source(HxCluster::getClassTypeId ())); if(field){ // we have a scalar field as input ... } else { // we have a cluster as input ... }

Another way to extend the module is to use dedicated ports for dierent tasks. This example shows how to add more inputs to a compute module. The module in fact has several inputs that appear as ports (somethimes with and sometimes without a user interface). Usually in compute modules you will get the input data by a call to portData.source(). To add additional input ports you can use the HxConnection class.

117

9. Compute modules

// in the .h file HxConnection portColorField; // in the .cpp files class constructor someClass::someClass() :HxCompModule(HxField3::getClassTypeId()), portColorField (this,"Colorfield",HxField3::getClassTypeId()), { ...

1 2 3 4 5 6 7

This module can be connected to a HxField3 object (general lattice) because its derived from the HxCompModule and therefore has a build-in port called portData. The data can be obtained by calling (HxField3*)portData.source(). Because it has dened a HxConnection it can also be connected to a ColorField (usually to provide additional color values per voxel).
HxField3 *colorfield = (HxField3 *)portColorField.source( HxField3::getClassTypeId()); HxLocation3 *locColorField = 0; int locnDataVar = 0; if(!colorfield) return; locColorField = ((HxField3 *) portColorField.source())->createLocation(); locnDataVar = colorfield->nDataVar(); float *return = (float *)malloc(sizeof(float)*locnDataVar); locColorField->set(1,1,1); colorfield->eval(locColorField,result); for(int i=0;i<locnDataVar; i++) theMsg->printf("Value in Colorfield at point 1,1,1,(%d) is %g\n", i, result[0]);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

We added here also an example how to query data from the connected eld. Because we used a more general class like HxField3 we may encounter dierent kinds of grid-types attached to our module. The use of HxLocation3 allows us to abstract from all those dierent cases. Instead we re-sample the data and can obtain values for any point we like (HxLocation3 uses a by default a tri-linear interpolation method to return appropriate values). The resource le for this project looks like this:
module -name "someClass" \ -primary "HxField3" \ -class "someClass" \ -category "Display" \ -package "hxsomeClass"
1 2 3 4 5

118

9.1. Adding another eld as input port The category section reects the context menu after right-mouse-click on the data item. If there is no primary section, i.e., the module will not be connected to an input data object, category will be the section in the create menu.

9.1.1. Correct behaviour for saving networks


Your module needs to tell Avizo what output it needs to save during a network save process. This will reduce the number of modules that your network needs to store if data can be created on the y instead of beeing copied to disc. This is done by the method savePorts. Usually the method savePorts of the parent class should be called in order to save all standard ports of the interface. But, in case that you have dened some tcl-commands and would like to save by them status information of your module you will need to add to the function.
void HxMyModule::savePorts(FILE* fp){ fprintf(fp, "{%s} setHiddenVariable %d\n", getName(), 42); fprintf(fp, "{%s} fire\n", getName()); HxCompModule::savePorts(fp); // saves all standard ports }
1 2 3 4 5

In order to tell the Avizo runtime what outputs can be created and which outputs needs to be save seperately use a command like:
int MyModule::canCreateData(HxData* data, McString& createCmd) { createCmd.printf("{%s} compute; {%s} getResult\n", getName(), getName()); return (resultTouchTime == data->getTouchTime()); }
1 2 3 4 5

The createCmd denition will be used to create the modules output, thus should return the name of the connected output object.

9.1.2. Adding an almost innite number of ports


Of course you cannot add an innite number of input ports, because it would be a waste of resources. But we can add 1 more that is actually used - at every point in time. Here is an example:
int freeportthere = 0; for(int i=0;i<connectionPorts.size();i++){ if(connectionPorts[i]->source() == NULL){ freeportthere = 1; break; } else { connectionPorts[i]->rename("closedPort"); } }
1 2 3 4 5 6 7 8

119

9. Compute modules
if(!freeportthere){ // this memory will never get freed new HxConnection(this,"freePort",HxObject::getClassTypeId()); }
9 10 11

As you can see this code has the slight problem that the new connection object will only get freeed if we close our application but not if we close the module. Also we have to be careful now to allow the module to be saved as a network le. The network le lists the connections present at the time that the network was saved which includes the connection generated in, for example the update() method. During reading of the network le update() will not be called after each data set is connected, thus some ports might not exist (not yet created) during the network load. In order to allow the module to behave correctly we can introduce another tcl command that species the number of ports the module should expose. We will call this function before we connect the data items. Here is the denition of the tcl-command in the parse() function of our module.
} else if ( CMD2("createMorePorts", "createMorePorts") ) { 1 ASSERTARG(3); 2 int howManyMore = atoi(argv[2]); 3 if(howManyMore < 0) 4 howManyMore = 0; 5 char str[256], str2[256]; 6 for(int i = 0; i < howManyMore; i++){ 7 // first the HxConnection 8 sprintf(str, "S%d", (int)connectionPorts.size()); 9 HxConnection *t = new HxConnection(this,str,HxLattice3::getClassTypeId()); 10 t->addType(HxStackedScalarField3::getClassTypeId()); 11 portConnectionList.append(t); 12
13

// now the interface (three scalars) sprintf(str2, "G%d", (int)connectionPorts.size()-1); HxPortFloatTextN *tt = new HxPortFloatTextN(this, str2, 3); tt->setValue(0,1.0); // others are by default 0 portGradientList.append(tt); tt->hide(); // enable this port if there is a connected object }

14 15 16 17 18 19 20

Now this function needs to be called when we save a network. We can overwrite the savePorts function:
void HxComputeTensor::savePorts(FILE* fp) 1 { 2 fprintf(fp, "{%s} createMorePorts %d\n", getName(), connectionPorts.size()-8); 3 fprintf(fp, "{%s} fire\n", getName()); 4
5

HxCompModule::savePorts(fp); // do your usual work }

6 7

120

9.2. Adding a region of interest input

9.2. Adding a region of interest input


Regions of interest give the user more control over your module. To extend a given project to the use of regions of interest is very easy.
// in the .h file #include <Amira/HxRoiInterface.h> ... HxRoiInterface.h ... HxConnection portROI; // in the constructor someClass::someClass(): HxCompModule(HxUniformScalarField3::getClassTypeId()), portROI (this,"ROI",HxRoiInterface::getClassTypeId()), ...
1 2 3 4 5 6 7 8 9 10 11 12

The values from the region of interest (portROI) can be accessed like this (in compute()):
float bb[6]; field->getBoundingBox(bb); HxRoiInterface* roi = (HxRoiInterface *) portROI.source(HxRoiInterface::getClassTypeId()); if (roi) { // if we found a region of interest, use its bounding box roi->getRoi(bb); // instead of the original bounding box } // now we have to restrict our computation to the bb
1 2 3 4 5 6

The main point here is to show that the compute module has to be changed only minimally to add the required functionality in case a region of interest if present.

9.3. User dened tcl commands the parse function


The parse function denes additional tcl-commands to Avizo modules. It is executed whenever the user calls the function on the command line in the console window.
int hxLua::parse( Tcl_Interp * t, int argc, char ** argv ){ char *cmd = argv[1]; if (CMD("exec")){ ASSERTARG(3); // automatic error message if no 2 arguments are given theMsg->printf("execute this: \"%s\"",argv[2]); } else HxCompModule::parse(t,argc,argv); // everything else is // handeled by the global compute module return TCL_OK; }
1 2 3 4 5 6 7 8 9 10 11 12

121

9. Compute modules Please note that the theMsg call will not result in proper values returned in the Avizo console window (tcl). In order to return tcl values you will have to use one of the following functions:
Tcl_VaSetResult(t,"%d",splineOrder);

or for a list of return values:


for(int i=0;i<5;i++) Tcl_VaAppendElement(t,"%d",i);

In a good style program you would ask for the number of arguments given to the function, in case no arguments are provided print an (useful) error message. This might be done by the macro CMD2
if (CMD2("exec", "exec <number1> <number2>")){ ASSERTARG(3); // automatic error message if no 2 arguments are given dosomething(); ... }
1 2 3 4 5

Of equal importance is that the program will remember its values if the user saves a module as a network. Additional tcl-commands need to be saved in the network le by overwriting the function savePorts. Here an example which saves the current settings of three tcl commands. If a network is read in later those commands will be executed and restore the internal status of the module.
void HxMyModule::savePorts(FILE* fp) { HxModule::savePorts(fp); fprintf(fp,"%s setFontSize %d\n",getLabel().getString(),pointSize); fprintf(fp,"%s setColor %g %g %g\n",getLabel().getString(), textColor[0],textColor[1],textColor[2]); fprintf(fp,"%s setBackgroundColor %g %g %g\n",getLabel().getString(), bgColor[0],bgColor[1],bgColor[2]); }
1 2 3 4 5 6 7 8 9

9.4. Save a network le


McFilename dataDir; McFilename::dirname(m_networkPath, dataDir); int saveFlags = 0; bool forceAutoSave = true;
1 2 3 4 5

122

9.4. Save a network le


6

if (HxResource::getPreferenceBit(Pref::IncludeUnusedDataObjects)) saveFlags |= HxObjectPool::INCLUDE_INVISIBLE; if (HxResource::getPreferenceBit(Pref::OverwriteInAutoSave)) saveFlags |= HxObjectPool::OVERWRITE_EXISTING; if (forceAutoSave) saveFlags |= HxObjectPool::FORCE_AUTO_SAVE;

7 8 9 10 11 12 13

if(theFileDialog) { 14 theFileDialog->registerFileType(); 15 theFileDialog->registerFileType("Avizo script", ".hx"); 16 theFileDialog->registerFileType("Avizo script and data files (pack & go)", ".hx"); 17 const char* format = theFileDialog->getFileType(); /// < this crashes! 18 if (format && strstr(format,"data files")) // pack & go 19 saveFlags |= HxObjectPool::SAVE_LOADABLE; 20 } 21 theObjectPool->saveState( m_networkPath, dataDir, saveFlags ); 22

123

9. Compute modules

124

10. Data import and export


10.1. AmiraMesh as a general purpose le format
AmiraMesh can save an arbitrary number of elds into a single le. It will keep all the bounding box information and additional parameter sections in the les as well.
AmiraMesh am; /* lets save the following two fields into the AmiraMesh file McDArray<float> mean; // this is a simple linear array McDArray<McDArray<float> > weights; // this is 2D */
1 2 3 4 5

// a location will keep the information about the number of voxels 6 AmiraMesh::Location *loc1 = new AmiraMesh::Location("meanD", mean.size()); 7 am.insert(loc1); 8 // each location has a data section attached 9 AmiraMesh::Data* d1 = new AmiraMesh::Data("mean", loc1, 10 McPrimType::mc_float, 1, mean.dataPtr()); // note that 1 here refers to 11 // scalar data in contrast to 12 // 3 which would be vector data 13 am.insert(d1); 14
15

int weightDim[2]; // the size of the 2D array 16 weightDim[0] = weights.size(); weightDim[1] = weights[0].size(); 17 AmiraMesh::Location *loc4 = new AmiraMesh::Location("weightD", 2, weightDim); 18 am.insert(loc4); 19 // for the data we need a linear memory area so we have to copy the data over 20 float *d4linear = (float *)malloc(sizeof(float)*weightDim[0]*weightDim[1]); 21 for(int i=0;i<weightDim[0];i++){ // copy over the data 22 memcpy(&d3linear[i*weightDim[1]], weights[i].dataPtr(), 23 sizeof(float)*weightDim[1]); 24 } 25 AmiraMesh::Data* d4 = new AmiraMesh::Data("weights", loc4, 26 McPrimType::mc_float, 1, d4linear); 27 am.insert(d4); 28 am.write(filename); 29 free(d3linear); 30

Note that there is a severe memory problems with the solution above. We have to copy our data into a linear array to use the write() method. In order to change this you can provide a callback to the AmiraMesh library that returns the correct memory location of non-linear arrays in memory. Here is now how we can read the data back into our program:

125

10. Data import and export

AmiraMesh *am = AmiraMesh::read(filename); // read in the mean array AmiraMesh::Location* loc1 = am->findLocation("meanD"); if(loc1 && loc1->nDim() != 1){ theMsg->printf("error reading meanD: meanD is not there or not 1D"); return; } const int* meanDims = loc1->dims(); AmiraMesh::Data* meanData = am->findData("meanD", HxFLOAT, 1, "mean"); if(!meanData){ theMsg->printf("error: could not read mean array"); return; } mean.resize(0); // remove the old stuff mean.append(meanDims[0],(float *)(meanData->dataPtr()));

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// read in the weights array 18 AmiraMesh::Location* loc4 = am->findLocation("weightD"); 19 if(loc4 && loc4->nDim() != 2){ 20 theMsg->printf("error reading weightD: weightD is not there or not 2D"); 21 return; 22 } 23 const int* weightsDims = loc4->dims(); 24 AmiraMesh::Data* weightsData = am->findData("weightD", HxFLOAT, 1, "weights"); 25 if(!weightsData){ 26 theMsg->printf("error: could not read weights array"); 27 return; 28 } 29 weights.resize(weightsDims[0]); // resize to new size 30 for(int i=0;i<weightsDims[0];i++){ 31 weights[i].resize(weightsDims[1]); 32 memcpy(weights[i].dataPtr(), &((float *)weightsData->dataPtr())[i*weightsDims[1]], 33 sizeof(float)*weightsDims[1]); 34 } 35 delete am; 36

10.2. Read data sets


10.2.1. Formating your own data
Here is a common question regarding reading in of density data:
> I have a data file that contains [ix iy iz scalar] in sequence, > ( (ix=1,ngrid_x), iy=1,ngrid_y), iz=1,ngrid_z) > E.g. > ix iy iz scalar
1 2 3 4

126

10.2. Read data sets


> 1 1 1 .32772E-12 > 2 1 1 .72553E-05 > 3 1 1 .30395E-04 > ....... > > Id like to load it and visualize it in 3D in Avizo.
5 6 7 8 9 10

Perhaps the simplest way to read in this kind of data is to add an Avizo header to the le. As we can see the data is sorted so that the x-index is the fastest running. This is also the standard format for Avizos internal data structure. So we can remove the rst three columns from the le (the ones containing the coordinates) and Avizo will assume the correct locations for our data. This leaves only the data values in their order. Now we add the following header:
# AmiraMesh 3D ASCII 2.0 define Lattice ngrid_x ngrid_y ngrid_z Parameters { BoundingBox -1 1 -1 1 -1 1 CoordType "uniform" } Lattice { float Data } @1 @1 .32772E-12 .72553E-05 ...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

You will have to change the names ngrid x,y, and z in this example into the actual values for the number of points in x, y, and z direction in order be able to load the le. We could also have used binary data which takes up less space. In this case only the header signature needs to be changed to
# AmiraMesh 3D BINARY 2.0

Conversion between ASCII and BINARY data can be performed in Avizo by the build-in program aminfo. For example type in the Avizo console window aminfo -a outputfile.am inputfile.am to save the input le in ascii format. To read in a vector eld header we need to change the header into the following way:
# AmiraMesh ASCII 1.0 define Lattice 30 30 30

127

10. Data import and export


Lattice { float[3] Coordinates } = @1 Parameters { CoordType "uniform", BoundingBox 0 1 0 1 0 1 } @1 1 1 1 2 1 1 ...

10.2.2. Convert binary to ascii data


Included in Avizo is a program which is called aminfo. Its also available from the home page of Avizo. On the command line it can be called by
Usage: aminfo [-a outfile|-b outfile] <AmiraMesh-File> Prints ascii header of an AmiraMesh file. Convert AmiraMesh to ASCII/BINARY format. -a writes whole file in ascii format. -b writes whole file in binary format.

It is important to do (i) the order of the arguments correct AND (ii) to be in the correct directory. Alternatively one can also use explicit path names. So assume you have a le called Result.am in the current working directory (use the pwd command in the Avizo console window to make sure you where you are, otherwise use cd to go where the data resides on disk). Now:
aminfo -a ASCIIResult.am Result.am

converts the le Result.am into an equivalent ascii coded le ASCIIResult.am. This may take a while because of the usually very large le size of ascii les. Please note that if you switch the last two arguments nothing will happen (also no error message will be produced). If you want to convert a series of les, like all les in the current working directory you can use a tcl loop like the following which converts all les with the extension .am into their ascii le equivalent.
foreach u [glob *.am] {aminfo -a ASCII$u $u }

10.2.3. Read in Curvilinear Fields


Curvilinear elds are specied as lists of data plus lists of coordinates of the data. Here is an example le for a vector eld dened on a curvilinear grid:
# AmiraMesh 3D ASCII 2.0 define Lattice 2 2 2
1 2 3

128

10.2. Read data sets


4

Parameters { CoordType "curvilinear" } Lattice { float[3] Data } @1 Lattice { float[3] Coordinates } @2 # Data section follows @1 -1 1 1 -1 -1 1 1 1 1 1 -1 1 -1 1 -1 -1 -1 -1 1 1 -1 1 -1 -1 @2 1 2 1 3 0 2 0 3 1 2 1 3 0 2 0 3

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

0 0 0 0 1 1 1 1

24 25 26 27 28 29 30 31

In this le we dene a cube of size 2 2 2 with grid positions x, y, z where x [0..1], y [2..3], and z [0..1]. The vectors attached to each grid position are pointing into the center of the cube. Each entry in the list represents either a coordinate or a data entry for a single point in space. The two lists are sorted, i.e., the rst three elements in the data section are the vector components at the coordiante specied by the rst three numbers in the coordiante section. Basically you can use the HxRegVectorField3 class and specify the coordinates as HxCurvilinearCoord3. Here is an example source code, you will have to add the extra code for the actual read-in of the values from the le. This example shows only how to ll the internal Avizo structures.
#include #include #include #include #include <readCurvelinearField/readCurvelinearFieldAPI.h> <Amira/HxData.h> <Amira/HxMessage.h> <hxfield/HxRegVectorField3.h> <hxfield/HxCurvilinearCoord3.h>
1 2 3 4 5 6

READCURVELINEARFIELD_API int readCurvelinearField(const char* filename) {

7 8

129

10. Data import and export


FILE* fp = fopen(filename,"rb"); if (!fp) { theMsg->ioError(filename); return 0; } int dims[3]; dims[0]=42; dims[1]=42; dims[2]=42; int npoints = dims[0]*dims[1]*dims[2]; // number of points // space for the xyz coordinates (!add check for memory) float* xyz = (float *) malloc(3*npoints*sizeof(float)); // fill the coordinate values int count = 0; for(int k=0;k<dims[2];k++) for(int j=0;j<dims[1];j++) for(int i=0;i<dims[0];i++){ xyz[count*3+0]=i; xyz[count*3+1]=j; xyz[count*3+2]=k; count++; } // create the data object HxRegVectorField3* vector = HxRegVectorField3::create( dims, McPrimType::mc_float, c_curvilinear); // copy the coordinates into the lattice memcpy(((HxCurvilinearCoord3 *)vector->lattice.coords())->coords(), xyz, 3*npoints*sizeof(float)); // read in now the values per node from fp float *tmp = (float *)vector->lattice.dataPtr(); for(int i=0;i<npoints;i++){ tmp[i*3+0]=42.0; tmp[i*3+1]=42.0; tmp[i*3+2]=42.0; } fclose(fp); if (vector) HxData::registerData(vector, filename); return 1; }
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 34 35 36 37 38 39 40 41 42 43 44 45

10.2.4. Work with oine data


Oine data are data objects that may never be read in completely into memory. One reason to use this concept is that the data can be bigger than the actual size of the memory of the executing machine. Another reason is that the data is still updated or generated during the visualization. There are currently two avours of oine data. One is the Large Disk Data the other the approach used by VolumeViz to store the data. Before presenting an example for each of them here rst the ocial way on how to ask the user for what method he would like to prefer. This is done for example by the AmiraMesh reader but it can also be integrated into

130

10.2. Read data sets any other custom le reader. First we have to look for the slider setting in the Preference Dialog. We will present the dialog only if the le size (here the integer volSize) is larger than the limit specied by the slider.
#include <Amira/QxPreferenceDialog.h> #include <hxexternaldata/HxRawAsExternalData.h> #include <Amira/QxDataConvertDialog.h> #include <hxvolumeviz/HxVolumeDataObject.h> #include <Amira/HxAmiraExtensionManager.h> ... if (volSize/1024.0/1024.0 > QxPreferenceDialog::memoryLimitMB){ // ask for the dialog about how to read the data QxDataConvertDialog convertDlg; McFilename fname(filename); McString dirPath; fname.dirname(dirPath); convertDlg.setDestFileDirectory(dirPath.getString()); bool failed = false; if(convertDlg.exec() == QDialog::Rejected) { return 0; } howToRead = convertDlg.getChoice(); if(howToRead == 0){ // VolumeViz sprintf(out, "%s", convertDlg.getDestFilePath().ascii()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

VolumeViz: The following example shows how to read in a le from disk which consists out of some header part and the raw data.
McString *convertRawDataWithHeaderToLDM(const char *filename, int *dims, float *bbox, McPrimType type, int isLittleEndian, int header) { McFilename fn(filename); McString inBaseName(fn.basename()); McString lstFilenameStr; lstFilenameStr += fn.getString(); int dotPos = inBaseName.index(".", 0); int i; char* ibn = inBaseName.getString(); for (i = 0; i < dotPos; i++) lstFilenameStr += ibn[i]; lstFilenameStr += ".lst"; McFilename outFilenameStr(filename); outFilenameStr.replaceSubStr(outFilenameStr.extension(), ".lda"); char *out = outFilenameStr.getString();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

131

10. Data import and export


19

FILE* fp = fopen(lstFilenameStr.getString(), "w"); if (!fp) { theMsg->printf("Could not create list file in %s", lstFilenameStr.getString()); return NULL; // error! } HxVolumeDataObject volDataObj; bool isDataTypeOK = true; int dataType; switch (type) { case McPrimType::mc_uint8 : dataType = 0; break; case McPrimType::mc_int16 : dataType = 5; break; case McPrimType::mc_uint16 : dataType = 1; break; case McPrimType::mc_int32 : dataType = 6; break; case McPrimType::mc_float : dataType = 10; break; default : isDataTypeOK = false; break; } if(isDataTypeOK == false){ theMsg->printf("error: could not recognize the data type"); return NULL; } int ret = 0; ret = volDataObj.convertToLDA(filename, out, bbox[0], bbox[2], bbox[4], bbox[1], bbox[3] , bbox[5], dataType, dims[0], dims[1], dims[2], header); if(!ret) return NULL; // error else return new McString(out); }

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

Please note that there is also a second function convertToLDA() which gets a control le from disk. This le contains the information needed to do the conversion, bounding box information etc. This version of convertToLDA() apparently only works with series of image

132

10.2. Read data sets les (known to the VolumeViz extension). So for loading single raw data les the above mentioned way is working. In order to detect if the current machine is using little or big endian one can use this handy two liner
int isLittleEndian = 1; isLittleEndian = *((char *)&isLittleEndian);

LargeDiskData: Here an example which is reading in blocks of data from disk. The data is mapped to a memory area where it can be changed or visualized.
float bbox[6]; const int *dims; int nDataVar = 1; // the data volume on disk that we will use to store the data char *parameter = "c:/bla.data"; HxRawAsExternalData *newlda = NULL; if(!HxResource::loadData(parameter, "AmiraMesh as LargeDiskData")){ theMsg->printf("error: could not find the file \"%s\"",parameter); return; } newlda = dynamic_cast<HxRawAsExternalData *> (theObjectPool->nodeList.last()); if(!newlda){ theMsg->printf("error: last object is not RawAsExternalData"); return; } newlda->getBoundingBox(bbox); dims = newlda->dims(); // remember that we load this as large disk data McString loadCommand; loadCommand.printf("load -amLDD %s", parameter); newlda->setLoadCmd(loadCommand, 1); // now to get the data into memory int numSlices = 1; HxExternalData::BlockRequest request(newlda); // loop over all the slices for(int slices=0; slices < dims[2]; slices += numSlices){ int num = numSlices; if(slices + num > dims[2]){ // last block problem num = dims[2] - slices; } request.setSize(dims[0],dims[1], num); request.setOrigin(0,0,slices); if(!newlda->getBlock(&request)){
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 34 35 36 37 38

133

10. Data import and export


theMsg->printf("error: could not get data block"); } for(int i=0;i<dims[0]*dims[1]*numSlices;i++){ theMsg->printf("data is %g", newlda[i]); } } // now an example how to write from memory to disk const int blockSize[3] = {64, 64, 64}; // lets write a standard block void* block = mcmalloc (sizeof(char) * nDataVar * blockSize[0] * blockSize[1] * blockSize[2]); McRawData::memset (McTypedPointer (block, McPrimType::mc_uint8), 0, blockSize[0] * blockSize[1] * blockSize[2]); int p[3]; for (p[2] = 0; p[2] < dims[2]; p[2] += blockSize[2]) { for (p[1] = 0; p[1] < dims[1]; p[1] += blockSize[1]) { for (p[0] = 0; p[0] < dims[0]; p[0] += blockSize[0]) { int subSize[3]; for (int i = 0; i < 3; i++) { subSize[i] = dims[i] - p[i]; if (blockSize[i] < subSize[i]) { subSize[i] = blockSize[i]; } } request.setBlockOrigin(subSize); request.setDataOrigin(subSize); // combined as setOrigin request.data = block; request.setBlockSize(blockSize); request.setDataSize(blockSize); if( newlda->putBlock(&request) < 0 ) { theMsg->printf("error in setting external data"); } } } }
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

10.2.5. Read in time dependent data


Time dependent data is handled in Avizo by the TimeSeriesCtrl module.
// generate the control and add it to the workspace HxDynamicSeriesCtrl* ctrl = new HxDynamicSeriesCtrl; McDArray<HxDynamicSeriesCtrl::TimeStep> timeSteps(numOfVolumes); char ctrlfn[1024]; sprintf(ctrlfn, "%s-Ctrl", McFilename::basename(filename)); ctrl->setLabel(ctrlfn); timeSteps.resize(0); theObjectPool->addObject(ctrl);
1 2 3 4 5 6 7 8

134

10.2. Read data sets


// now interate over all the data files for(int numVols = 0; numVols < numOfVolumes; numVols++){ long skip = numVols * (dims[0]*dims[1]*dims[2]*sizeOfNf); // read in a single volume (magic function) HxLattice3 *lattice = readData(qualifiedFn, dataformatstr, "xfastest", typestr, 1, dims[0], dims[1], dims[2], bbox[0], bbox[1], bbox[2], bbox[3], bbox[4], bbox[5], skip); if(!lattice){ // a fail-save mechanism that adds the current directory McFilename *fn = new McFilename(files[i]); fn->dirname(qualifiedFn); qualifiedFn += filename; lattice = readData(qualifiedFn, dataformatstr, "xfastest", typestr, 1, dims[0], dims[1], dims[2], bbox[0], bbox[1], bbox[2], bbox[3], bbox[4], bbox[5], skip); } if(!lattice){ theMsg->printf("error: could not read \"%s\"", qualifiedFn.getString()); return 0; } HxUniformScalarField3 *obj = new HxUniformScalarField3(lattice); // now lets append all the parameters and their values to the file // we have build before a dynamic array valuePairs that // contains all the information HxParamBundle* bundle = obj->parameters.bundle("INTERFILE", 1); char linestr1[1024]; char linestr2[1024]; for(int pa=0; pa<valuePairs.size(); pa+=2){ sprintf(linestr1, "line%4d", pa); sprintf(linestr2, "%s := %s", valuePairs[pa].getString(), valuePairs[pa+1].getString()); bundle->insert(new HxParameter(linestr1,linestr2)); } McString loadCommand; loadCommand.printf("load -interfile %s", qualifiedFn.getString()); obj->setLoadCmd(loadCommand,1); timeSteps.appendSpace(1); char fnn[1024]; sprintf(fnn, "%s%04d", McFilename::basename(filename), numVols); obj->setLabel(fnn); timeSteps.last().objects.append(obj); } ctrl->init(timeSteps); // this is important to let the ctrl know whats // going on
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

Here is another example that is generating a class DynamicScalarField which helps in creating the appropriate load command for the time series.
#include <RajaM/RajaMAPI.h>
1 2

135

10. Data import and export


#include #include #include #include #include #include #include #include <McFilename.h> <Amira/HxMessage.h> <Amira/HxController.h> <hxsurface/HxSurface.h> <hxsurface/HxSurfaceScalarField.h> <hxtime/HxDynamicSeriesCtrl.h> <Amira/HxObjectPool.h> <RajaM/RajaHxSurface.h>
3 4 5 6 7 8 9 10 11

#define MAX_LENGTH 4096 #define MAX_FIELDS 1024 HX_INIT_CLASS(RajaHxSurface,HxSurface) class HxDynamicScalarField : public HxDynamicSeriesCtrl { public: HxDynamicScalarField() { } /* This method should write a Tcl command which creates the object. In this case the data file is read. The reader creates this object, the surface, and the surface fields. */ virtual int saveCreation(FILE* fp, const char*, int) { if (fp) { fprintf(fp, "load -m %s\n", filename.dataPtr()); } return 0; // Indicates, that no autosaving is needed } // The name of the data file is stored here McString filename; }; RAJAM_API int RajaRead(int n, const char** files){ int i,j,k; for (i=0; i<n; i++) { FILE* fp = fopen(files[i],"rb"); if (!fp) { theMsg->ioError(files[i]); return 0; } char buffer[MAX_LENGTH]; fgets(buffer,MAX_LENGTH,fp); // Skip first line (Title) fgets(buffer,MAX_LENGTH,fp); // Get field infos int start = 23; // We just skip the begining of this line... int end; int nFields = 0; char fieldName[MAX_FIELDS][128]; while (start < strlen(buffer)) {

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

136

10.2. Read data sets


end = start+1; while (buffer[end] != , && end < strlen(buffer)) end++; strncpy(fieldName[nFields],&buffer[start+2],end-start-3); fieldName[nFields][end-start-3] = 0; start = end; nFields++; } fieldName[nFields-1][strlen(fieldName[nFields-1])] = 0; theMsg->printf("#Fields = %d\n",nFields); int nPoints=0, nTriangles=0; // Get number of points and triangles fgets(buffer,MAX_LENGTH,fp); sscanf(buffer,"ZONE T=\"TRIANGLES\", N=%d, E=%d, F=FEPOINT, ET=TRIANGLE",&nPoints, &nTriangles); theMsg->printf("#Points %d #triangles %d\n",nPoints,nTriangles); RajaHxSurface* surface = new RajaHxSurface; // create new surface surface->addMaterial("Inside",0); // add some materials surface->addMaterial("Outside",1); HxSurface::Patch* patch = new HxSurface::Patch; surface->patches.append(patch); // add patch to surface patch->innerRegion = 0; patch->outerRegion = 1; surface->points.resize(nPoints); surface->triangles.resize(nTriangles); McDArray<HxDynamicSeriesCtrl::TimeStep> timeSteps(nFields); McDArray<float*> data(nFields); for (j = 0; j < nFields; j++) { HxSurfaceScalarField* field = (HxSurfaceScalarField*) HxSurfaceScalarField::create(surface, HxSurfaceScalarField::OnNodes, 1); timeSteps[j].objects.append(surface); timeSteps[j].objects.append(field); field->setLabel(fieldName[j]); data[j] = field->dataPtr(); } for (j=0; j<nPoints; j++) { // read point coordinates McVec3f& p = surface->points[j]; fscanf(fp, "%f %f %f", &p[0], &p[1], &p[2]); for (k = 0; k < nFields; k++) { fscanf(fp,"%f",&data[k][j]); } }
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102

137

10. Data import and export


fgets(buffer,MAX_LENGTH,fp); for (j=0; j<nTriangles; j++) { // read triangles int idx[3]; fgets(buffer,MAX_LENGTH,fp); sscanf(buffer, "%d %d %d", &idx[0], &idx[1], &idx[2]); Surface::Triangle& tri = surface->triangles[j]; tri.points[0] = idx[0]-1; // indices should start at zero tri.points[1] = idx[1]-1; tri.points[2] = idx[2]-1; tri.patch = 0; } // Add all triangles to the patch patch->triangles.resize(nTriangles); for (j=0; j<nTriangles; j++) patch->triangles[j] = j; surface->setLabel(McFilename::basename(files[i])); fclose(fp); HxDynamicScalarField *dynScalar = new HxDynamicScalarField; // Let the module know the filename dynScalar->filename = files[i]; dynScalar->setLabel("DynData"); dynScalar->init(timeSteps); theObjectPool->addObject(dynScalar); surface->setFields(&data); } return 1; }
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136

Sometimes it is very inecient to use HxDynamicSeriesCtrl. Especially if the number of time steps is very large operations on the object pool become very slow. In this case it is better to derive directly from the data object and extend its capabilities. Note that using this approach the automatic memory handling (caching) of HxDynamicSeriesCtrl is not available so data needs to t into memory.
class HxMySurfaceScalarField : public HxSurfaceScalarField { public:
1 2 3 4

HxMySurfaceScalarField(HxSurface * surface, Encoding encoding, void *data) : 5 HxSurfaceScalarField(surface, encoding, data), 6 portTime(this, "Time") 7 { 8

138

10.2. Read data sets


portTime.setMinMax(0,1); portTime.setIncrement(1); myTimePoints.resize(0); numPoints = 0; }
9 10 11 12 13 14

// makes a copy of the data, use can free data afterwards 15 bool addTimePoint(float *data, int nums, double time){ 16 float *tmp = (float *)malloc(sizeof(float)*nums); 17 if(!tmp){ 18 theMsg->printf("error: not enough memory"); 19 return false; 20 } 21 if(numPoints > 0 && nums != numPoints){ 22 theMsg->printf("error: trying to add %d points, but had 23 points previous %d nums, numPoints); 24 free(tmp); 25 return false; 26 } 27 numPoints = nums; 28 memcpy(tmp, data, sizeof(float)*nums); 29 myTimePoints.append(tmp); 30 myTimeTimes.append(time); 31 portTime.setMinMax(0,myTimePoints.size()-1); 32 return true; 33 } 34
35

void update() { portTime.setMinMax(0,myTimePoints.size()-1); // set the pointer for the actual data int step = portTime.getValue(); // where we should look for the closest data item in time to the current time // (myTimeTimes as lookup into myTimePoints) left as an exercise to the user if(step < 0 || step >= myTimePoints.size()){ theMsg->printf("step %d does not exist (%d..%d)", step, 0, myTimePoints.size()-1); return; } // now copy the current data over memcpy(this->dataPtr(), myTimePoints[step], numPoints); touch(); } HxPortTime portTime; protected: McDArray<float *> myTimePoints; McDArray<double> myTimeTimes; mclong numPoints;

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58

139

10. Data import and export


};
59

Using the above class we will end up with a eld that contains scalar values per triangle and exposes a time slider to the user that switches the data between the dierent points in time.

float *data = (float *)malloc(sizeof(float)*surface->triangles.size()); 1 if(!data){ 2 theMsg->printf("Error: not enough memory"); 3 return 0; 4 } 5 fread(v_array, nfloat, totalPoints, fp); 6 for (int i = 0; i < totalPoints; i++) { 7 data[2*i+1] = data[2*i] = v_array[i]; 8 } 9 HxMySurfaceScalarField* ssf = new HxMySurfaceScalarField(surface,HxSurfaceScalarField::On 10 ssf->addTimePoint(data,totalPoints*2,0); // add time point 0 11 ssf->setLabel(McFilename::basename(filefield)); 12
13

theObjectPool->addObject(ssf);

14 15

for (int j = 1; j < nTimes; j++) { 16 theWorkArea->setProgressValue((float)j/(float)nTimes); 17 if(theWorkArea->wasInterrupted()){ 18 theWorkArea->stopWorking(); 19 break; 20 } 21 // read the time variable 22 // read the data values from a file 23 fread(v_array, nfloat, totalPoints, fp); 24 for (int i = 0; i < totalPoints; i++) { // add the data for two triangles 25 ssf->dataPtr()[2*i+1] = ssf->dataPtr()[2*i] = v_array[i]; 26 } 27 ssf->addTimePoint(ssf->dataPtr(), totalPoints*2, time); 28 // add the data to the array 29 } 30 ssf->update(); 31 if(v_array) 32 free(v_array); 33

10.3. Multi-channel elds


Multi-Channel elds are data objects that dene a set of (single channel) data objects as being connected to each other. Display modules will recognize this connection and allow you do switch on and o channels or to use a specic color encoding each channel.
#include <Amira/HxObjectPool.h> #include <hxfield/HxUniformScalarField3.h>
1 2

140

10.4. Surface data


#include <hxmultichannel/HxMultiChannelField3.h> ... HxUniformScalarField3 *field = (HxUniformScalarField3 *)portData.source(); if(!field){ theMsg->printf("error: no data connected"); return; } // generate a multi-channel object and make appear in the object pool HxMultiChannelField3 *mcf = new HxMultiChannelField3(); mcf->setLabel("MultiChannelFieldObject"); theObjectPool->addObject(mcf); // now add three channels HxUniformScalarField3 *field1 = new HxUniformScalarField3(&field->lattice); HxSpatialData::registerData(field1,"field1"); field1->parameters.setColor(&McColor(1,0,0)[0]); field1->setLabel("field1"); field1->portMaster.connect(mcf); HxUniformScalarField3 *field2 = new HxUniformScalarField3(&field->lattice); HxSpatialData::registerData(field2,"field2"); field2->parameters.setColor(&McColor(0,1,0)[0]); field2->setLabel("field2"); field2->portMaster.connect(mcf); // this connects the first channel HxUniformScalarField3 *field3 = new HxUniformScalarField3(&field->lattice); HxSpatialData::registerData(field3,"field3"); field3->parameters.setColor(&McColor(0,0,1)[0]); field3->setLabel("field3"); field3->portMaster.connect(mcf); // to get access to a multi-channel field (its lattice) HxLattice3 *lat1 = &mcf->getChannel(0)->lattice;
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 34 35

10.4. Surface data


Surfaces are described minimally as two lists, one list of 3D coordinates - the coordinates of each vertex - and one list of triangles each described by the three indexes of its vertexes. Here is an example how such a le might look like on disk:
# HyperSurface 0.1 ASCII Parameters { Materials { Exterior { Id 1 } Green {
1 2 3 4 5 6 7 8

141

10. Data import and export


Color 0.21622 0.8 0.16 } } BoundaryIds { name "BoundaryConditions" } Filename "C:/data/trivialSurface.surf" } Vertices 8 1.000000 0.666667 0.666667 0.500000 1.000000 0.500000 0.500000 1.000000 0.000000 0.000000 0.000000 1.000000 0.500000 0.000000 0.523810 0.523809 NBranchingPoints 0 NVerticesOnCurves 0 BoundaryCurves 0 Patches 1 { InnerRegion Green OuterRegion Yellow BoundaryID 0 BranchingPoints 0 Triangles 7 3 1 8 4 3 8 6 4 8 5 6 8 7 5 8 2 7 8 1 2 8 }
9 10 11 12 13 14 15 16 17 18

0.500000 1.000000 0.000000 0.000000 0.500000 0.500000 1.000000 0.500000

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

It might be useful to be able to attach data to each of the surface vertices. These data could represent the curvature at each vertex or the vertex normals (see the compute modules GetCurvature and SurfaceNormals). Here is an example on such a le that contains vectors at each node. If both les are loaded and the number of nodes in both les are the same Avizo will connect them with a black line indicating that the data values can be visualized on the locations stored in the surface object.
# AmiraMesh 3D ASCII 2.0 nNodes 8
1 2 3 4

142

10.4. Surface data


Parameters { ContentType "SurfaceField", Encoding "OnNodes" } NodeData { float[3] values } @1 # Data section follows @1 0.28616 -0.953862 0.0908864 0.672649 -0.704687 -0.225741 -0.206371 -0.966829 -0.150505 -0.577045 -0.60496 -0.548674 0.325057 -0.325058 -0.888074 -0.316176 -0.347793 -0.882651 0.772042 -0.456859 -0.441849 0.182139 -0.805094 -0.56449
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

10.4.1. Write a surface


In order to write out a surface you only need to construct a writer class. Here an example for writing out an Avizo surface to Matlab.
#include #include #include #include <saveSurfaceToMatlab/saveSurfaceToMatlabAPI.h> <Amira/HxData.h> <Amira/HxMessage.h> <hxsurface/HxSurface.h>
1 2 3 4 5

SAVESURFACETOMATLAB_API int saveSurfaceToMatlab(HxSurface* data, const char* filename) { FILE* fp = fopen(filename,"wb"); if (!fp) { theMsg->ioError(filename); return 0; } /* Write data into file ... */ fprintf(fp, "%% this is a matlab file which can be loaded by\n"); fprintf(fp, "%% load \"%s\"\n\n", filename); fprintf(fp, "surface = struct(vertices, ["); int np = data->getNumPoints(); int nt = data->triangles.size(); McVec3f* c = data->getCoords(); for (int i=0 ; i<np ; i++) fprintf(fp,"%g %g %g; ...\n",c[i].x,c[i].y,c[i].z); fprintf(fp,"], faces, ["); for ( i=0 ; i<nt ; i++) { int* p = data->triangles[i].points;

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

143

10. Data import and export


fprintf(fp,"%d %d %d; ...\n",p[0],p[1],p[2]); } /* we currently have no data assigned to the vertices fprintf(fp, "], facevertexcdata, [ "); for (i=0 ; i<np ; i++) fprintf(fp,"%g; ...\n",0.0); */ fprintf(fp, "]);\np = patch(surface);"); fclose(fp); return 1; // success }
27 28 29 30 31 32 33 34 35 36 37 38

Generate surface data Either a surface is dened as a HxSurface object or a function like SoSphere is used to describe a surface. In the later case we can get the coordinates and triangles of the surface by using an Inventor callback.
#include <Inventor/nodes/SoSphere.h> #include <Inventor/nodes/SoIndexedFaceSet.h> #include <Inventor/nodes/SoShapeHints.h> #include <Inventor/SoPrimitiveVertex.h> #include <Inventor/actions/SoCallbackAction.h> ... // data structure needed for the quadratic ellipsoid structure static SoCoordinate3 *coord3 = NULL; static SoIndexedFaceSet *ifs = NULL; static int coord3idx = 0; static void triangle_cb(void *userdata, SoCallbackAction *action, const SoPrimitiveVertex *v1, const SoPrimitiveVertex *v2, const SoPrimitiveVertex *v3){ const SbVec3f vtx[] = { v1->getPoint(), v2->getPoint(), v3->getPoint() } ; const SbMatrix mm = action->getModelMatrix(); SbVec3f vx[3]; for(int j = 0; j < 3; j++) { mm.multVecMatrix(vtx[j],vx[j]); } // see if the point is already in the coord3 list int cc[3]; cc[0] = -1; cc[1] = -1; cc[2] = -1;

144

10.4. Surface data


bool newPoints[] = { true, true, true }; // initially all points are allowed for(int j = 0; j < coord3->point.getNum(); j++){ // now look if some points have to be removed if(coord3->point[j][0] == vx[0][0] && coord3->point[j][1] == vx[0][1] && coord3->point[j][2] == vx[0][2] ){ newPoints[0] = false; cc[0] = j; } if(coord3->point[j][0] == vx[1][0] && coord3->point[j][1] == vx[1][1] && coord3->point[j][2] == vx[1][2] ){ newPoints[1] = false; cc[1] = j; } if(coord3->point[j][0] == vx[2][0] && coord3->point[j][1] == vx[2][1] && coord3->point[j][2] == vx[2][2] ){ newPoints[2] = false; cc[2] = j; } } // for each point that is new we have to add an index if(cc[0] == -1) cc[0] = coord3idx++; if(cc[1] == -1) cc[1] = coord3idx++; if(cc[2] == -1) cc[2] = coord3idx++; // only add the points that are new! if(newPoints[0] || newPoints[1] || newPoints[2]) { for(int j=0;j<3;j++) { if(newPoints[j] == true) { coord3->point.setNum(coord3->point.getNum() + 1); coord3->point.setValues(cc[j], 1, &vx[j]); } } } int32_t indices[] = { cc[0], cc[1], cc[2], -1 }; int oldsize = ifs->coordIndex.getNum(); ifs->coordIndex.setNum(oldsize + 4); ifs->coordIndex.setValues(oldsize, 4, indices); } ... // generate a geometry (SoSphere) SoSeparator *root = new SoSeparator; SoComplexity *complexity = new SoComplexity; complexity->value.setValue(portComplexity.getValue()); root->addChild(complexity);

145

10. Data import and export


root->addChild(new SoSphere); coord3 = new SoCoordinate3; coord3->point.setNum(0); ifs = new SoIndexedFaceSet; ifs->coordIndex.setNum(0); coord3idx = 0; root->ref(); // add a callback that will call the triangle_cb function SoCallbackAction ca; ca.addTriangleCallback(SoShape::getClassTypeId(), triangle_cb, NULL); ca.apply(root); root->unref(); // after this the coord3 and ifs contain the vertex and triangle informations

Enable two-sided lightning Sometimes it is important to enable the two-sided lightning for surfaces. In this case add the following node to your surface part of the scene graph:
SoShapeHints* shapehints = new SoShapeHints; shapehints->shapeType.setValue(SoShapeHints::UNKNOWN_SHAPE_TYPE); shapehints->vertexOrdering.setValue(SoShapeHints::COUNTERCLOCKWISE); group->addChild(shapehints);

Based on the coordinates of your surface sometimes you will have to use CLOCKWISE instead of COUNTERCLOCKWISE. Inspect the currently used scene graph Since Avizo version 4.1 a new tool is available that is able to display and also change the currently used scene graph. The tool is called ivTune and is started with the keyboard shortcut Shift-F11. Save surface data for PovRay Here is another example that is writing out the triangles of a surface object per patch. The example is building a le suitable for the ray-tracing program PovRay. First lets clear up a connected surface object.
surface->removeDuplicatePoints(1e-4); surface->removeDegenerateTriangles(); surface->removeEmptyPatches(); //surface->recompute(); // will introduce new patches for all non-connected surfaces surface->computeNormalsPerTriangle(); surface->touch();

146

10.4. Surface data Now we can generate some materials. This is only interesting for the later use in the triangle lists.
// generate the Materials for(int patch = 0; patch < surface->patches.size(); patch++){ fprintf(fp, "\n#declare Mat%04d = texture {\n\tpigment {\n\t\tcolor rgb <%f,%f,%f>" "\n\t}\n\tfinish { surffinish }\n\tnormal { surfnormal }\n}", patch, (1.0*patch+1)/(surface->patches.size()), (1.0*patch+1)/(surface->patches.size()), (1.0*patch+1)/(surface->patches.size())); }
1 2 3 4 5 6 7 8 9 10

Now save all the triangles for each patch and assign them their material.
theWorkArea->startWorking("building pov-ray file..."); for(int patch = 0; patch < surface->patches.size(); patch++){ theWorkArea->setProgressValue((1.0*patch)/(surface->patches.size()-1)); fprintf(fp, "\nmesh {\n"); for(int triangles = 0; triangles < surface->patches[patch]->triangles.size(); triangles++){ int currenttriangle = surface->patches[patch]->triangles[triangles]; int p0 = surface->triangles[currenttriangle].points[0]; int p1 = surface->triangles[currenttriangle].points[1]; int p2 = surface->triangles[currenttriangle].points[2]; fprintf(fp,"\n\tsmooth_triangle { <%f,%f,%f> <%f,%f,%f> " "<%f,%f,%f> <%f,%f,%f> <%f,%f,%f> <%f,%f,%f>\n\ttexture{ Mat%04d }\n\t}", surface->points[p0].x, surface->points[p0].y, surface->points[p0].z, surface->normals[p0].x, surface->normals[p0].y, surface->normals[p0].z, surface->points[p1].x, surface->points[p1].y, surface->points[p1].z, surface->normals[p1].x, surface->normals[p1].y, surface->normals[p1].z, surface->points[p2].x, surface->points[p2].y, surface->points[p2].z, surface->normals[p2].x, surface->normals[p2].y, surface->normals[p2].z, patch
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

147

10. Data import and export


); } fprintf(fp, "\n}\n"); // end of mesh{ } theWorkArea->stopWorking();
34 35 36 37 38

148

11. LabelFields
Labelelds are the result of a segmentation process. They can be used to either visualize the labels by Surfaces (SurfaceGen1 ) or used to get quantitative measurements (MaterialStatistics ). To generate a LabelField and sets its size we can use the following construct
HxUniformLabelField3 *output = dynamic_cast<HxUniformLabelField3*>(getResult()); 1 if(output && !output->isOfType(HxUniformLabelField3::getClassTypeId())) 2 output = 0; 3 if(!output){ 4 output = new HxUniformLabelField3(); 5 output->resize(dims[0],dims[1],dims[2]); 6 } 7 // init and generate the labels 8 memset(output->lattice.dataPtr(),0,dims[0]*dims[1]*dims[2]); 9 output->coords()->setBoundingBox(bb); 10 output->composeLabel(field->getName(),"-labels"); 11

In a compute module this will either generate a new LabelField or re-use the outcome of a previous computation (an attached result object). We are using here the bounding box and the name of an attached eld. This is likeliy the case if you want to write a module for automatic segmentation. We now have to generate the names and colors for each material. Those values are saved as HxParameters with a specic number of entries. We have to provide an Id and a color for each material. As a speciality we will allow the user of the module to enter labels for each material in a text window (HxPortText portLabels). Using a string tokenizer we can query for the labels or use instead some that are automatically generated.
HxParamBundle& materials = *output->parameters.materials(); materials.removeAll(); // we should add a default Exterior material first materials.bundle("Exterior", 1); McString *labelname = new McString(portLabels.getValue()); labelname->unpad(); // remove starting and trailing blanks McStringTokenizer *tokenizer = new McStringTokenizer(*labelname); for(int i=0; i<colors.size(); i++){
1 1 2 3 4 5 6 7 8 9 10 11

SurfaceGen has a hidden port that allows one to select a single material for which a surface is generated. Enable this port by calling SurfaceGen showMaterialList.

149

11. LabelFields
HxParamBundle* bundle = materials.bundle(i+1); if(!bundle){ char str[256]; // bad habit if(tokenizer->hasMoreTokens()){ sprintf(str, "%s",(tokenizer->nextToken()).getString()); } else { sprintf(str, "Tissue%d",i); } bundle = materials.bundle(str,1); } float tc[3]; (colors[i])->getValue(tc[0],tc[1],tc[2]); bundle->insert(new HxParameter("Color",3,tc)); bundle->insert(new HxParameter("Id",i)); }
12 13 14 15 16 17 18 19 20 21 22 23 24 25

colors contains a list of SbColor (Inventor/SbColor.h) values that we will use as the colors for the dierent labels in the eld. Given the labels we can now ll the structure by
unsigned char *outputdata = (unsigned char *)output->lattice.dataPtr(); ... outputdata[pos] = id; ... setResult(output);

11.1. Exporting LabelFields


You can save the label eld as series of tif, bmp, png etc images. This will output the whole stack as a numbered sequence of monochrome bitmap images. If you need the colors that were dened in the segmentation editor being preserved you need to rst issue the command
image.labels makeColormap

in the console window (assuming image being the name of your stack). Then you attach a ComputeColorCombine module to the labeleld, right click the colormap window in ColorCombine, select entry image.colors (if image has been the name of your stack) and click DoIt. The newly created Combination can be saved as tif or bmp. The Segmentation Editor can also display labels as lled regions ontop of the raw data. In order to change the default (outline mode) to the lled mode you need to right click on each material in the material list and select the new draw-style option.

150

12. LineSets
LineSets are used to store connected points in space.
HxLineSet* lineSet = new HxLineSet; if (lineSet) { lineSet->lines.resize(1); lineSet->points.resize(0); lineSet->lines[0].points.resize(0); lineSet->data.resize(1); // data per point for (int i = 0; i < sampleLocs.size(); i++) { lineSet->points.append(McVec3f(*(sampleLocs[i]))); lineSet->data[0].append(42.0); lineSet->lines[0].points.append(i); } lineSet->setLabel("LineSet"); theObjectPool->addObject(lineSet); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

This example will be quite slow because of the potentially many append calls which take some time. A faster way to do the same is here:
HxLineSet *myLineset = new HxLineSet(); McVec3f *vertex = new McVec3f[VERTEXNUM]; int *Idx = new int[VERTEXNUM]; for(int i=0;i<VERTEXNUM;i++) { float x = 0.2f*i; float y = 0.5f*i; float z = x*sin(i/30.f)+y*cos(i/30.f); vertex[i].setValue(x,y,z); Idx[i] = i; } myLineset->addPoints(vertex,VERTEXNUM); delete vertex; myLineset->addLine(VERTEXNUM,Idx); delete Idx; myLineset->setNumDataValues(1); // only correct if the number of // points is already known! float *val = new float[VERTEXNUM]; for(int i=0;i<VERTEXNUM;i++) { val[i] = i; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

151

12. LineSets
memcpy(myLineset->getData(0),val,VERTEXNUM*sizeof(float)); delete val; myLineset->touchMinMax(); // resets the min/max values
22 23 24

12.1. Reading a line set


Here an example that lls a label eld from the point that are in a line set. Note that this will not trace the lines directly but only show how to map a point found in a line set to an index in a eld (in this case a label eld).

// output is a label field 1 HxUniformLabelfield *labelField = dynamic_cast<HxUniformLabelField *>(getResult()); 2 // here check if labelField is not empty, generate it etc. 3
4

// ask for the bounding box of the data (xmin, xmax, ymin, ymax, zmin, zmax) float bbox[6]; labelField->getBoundingBox(bbox); // ask for the dimensions const int *dims = labelField->lattice.dims(); // ask for a pointer to the data unsigned char *data = labelField->lattice.dataPtr(); // input is a line set HxLineSet *lineSet = (HxLineSet *)portData.source(); if(!lineSet) return; for (int i =0; i < lineSet->lines.size(); i++) { for(int j=0; j < lineSet->lines[i].points.size(); j++) { McVec3f *pos = &lineSet->points[lineSet->lines[i].points[j]]); theMsg->printf("point (%d) of line (%d) is at %g %g %g", j, i, (*pos)[0], (*pos)[1], (*pos)[2]); // find the voxel in the labelField if( (*pos)[0] < bbox[0] || (*pos)[0] > bbox[1] || (*pos)[1] < bbox[2] || (*pos)[1] > bbox[3] || (*pos)[2] < bbox[4] || (*pos)[2] > bbox[5]) continue; // data is outside the volume int x = ((*pos)[0]-bbox[0]) / ( (bbox[1]-bbox[0])-1.0 ) * dims[0]; int y = ((*pos)[1]-bbox[2]) / ( (bbox[3]-bbox[2])-1.0 ) * dims[1]; int z = ((*pos)[2]-bbox[4]) / ( (bbox[5]-bbox[4])-1.0 ) * dims[2]; // now set the value in the label field to 1 (first material) data[ z * (dims[0]*dims[1]) + y * dims[0] + x] = 1; } }

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

152

12.2. Drawing a line set

12.2. Drawing a line set


LineSets can also be drawn directly into the Avizo viewer. This is done by the correspondig Inventor nodes. Here an example which is displaying three lines (a local coordinate system).
#include <Inventor/nodes/SoCoordinate3.h> #include <Inventor/nodes/SoLineSet.h> ... SoSeparator *lines = new SoSeparator; SoLineSet *lineset = new SoLineSet; SoCoordinate3 *coords = new SoCoordinate3; scene->addChild(lines); lines->addChild(coords); lines->addChild(lineset); // now fill the coordinates with three lines each int nPoints = 0; for(int i=0;i<3;i++) { // three lines in the set McVec3f a = pos; // the start position for each line coords->point.set1Value(nPoints++,a.x,a.y,a.z); // start point coords->point.set1Value(nPoints++,a.x+scale*(evals[mo[i]]*m(0,i)), a.y+scale*(evals[mo[i]]*m(1,i)), a.z+scale*(evals[mo[i]]*m(2,i))); // end point lineset->numVertices.set1Value(i,2); // connect the last two points }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

Please note that the numVertices command always needs at least two points per call. For a line which is longer than 2 points you would rst add all the points and naly use a single call to numVertices.set1Value to add all vertices for n points. Similar to SoLineSet there is also a SoPointSet.
SoSeparator *points = new SoSeparator; SoPointSet *ps = new SoPointSet; SoCoordinate3 *pcoords = new SoCoordinate3; SoDrawStyle *drawStyle = new SoDrawStyle; drawStyle->pointSize = 4; // a fixed point size scene->addChild(points); points->addChild(drawStyle); points->addChild(pcoords); points->addChild(ps); int nP = 0; // seedpoints is a McDArray<SbVec3f> and // contains our point locations pcoords->point.setNum(seedpoints.size()); for(int i = 0;i<seedpoints.size(); i++){ pcoords->point.set1Value(nP++,seedpoints[i]); } ps->numPoints = nP;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

153

12. LineSets In order to draw a nicer looking set of points you could use of course real spheres in an inventor scene graph. But sometimes the amount of geometry generated by this approach would slow down the display greatly. There is a middle way which is to use screen aligned textures instead of the real geometry. Texture draw is very fast on all modern hardware so the framerate with this approach is very good (see HxClusterViews plate mode for drawing spheres). Here is an example on how to render plates in Avizo. Lets assume that the coordinates of the spheres are in an SbVec3f array called seedpoints.
#include <hxvertexset/SoSphereSet.h> ... SoSeparator *scene = new SoSeparator; // scene->removeAllChildren(); SoSphereSet *ps = new SoSphereSet; ps->renderFunction = SoSphereSet::PLATES; ps->setComplexity(0.2); ps->setNumSpheres(seedpoints.size()); ps->coords.init(seedpoints.size(), seedpoints); ps->newCoords(); ps->nSelected = seedpoints.size(); ps->selected.resize(seedpoints.size()); SbColor col(1.0,0,0); // one color for all for(int k=0;k<seedpoints.size();k++) { ps->setRadius(k, 0.05); ps->setColor(k,col); ps->selectSphere(k,1); } ps->update(); ps->touch(); scene->addChild(ps); showGeom(scene);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

154

13. Field access


There are several ways in which you can read the values from, lets say, a uniform vector eld. This works the same even if you work with scalar- complex- or colorelds. Only the number of values per location in space is changing.

13.1. Access stored values


The following code is returning the stored eld values. Later we will see an example which can evaluate the eld on arbitrary position.
HxUniformVectorField3 *bla = (HxUniformVectorField3 *)portData.source(); const int *dims = bla->lattice.dims(); // now dims[0], dims[1], and dims[2] contain the number of voxels float *data = (float *)bla->lattice.dataPtr(); // data now points to the data, you can use memcpy etc. data[0] = 42.0; data[1] = 42.0; data[2] = 42.0; // sets the first vector at position 0, 0, 0 to 42, 42, 42
1 2 3 4 5 6 7

An important class to use is HxLattice3. It provides functions that are working with all data types by implicitely casting the parameters and the results into oating point values thus we do not have to provide interfaces to all the dierent data types.
HxLattice3 *lattice = bla->lattice; float result[3]; result[0] = 42; result[1] = 42; result[2] = 42; lattice->set(i,j,k,result);
1 2 3

The above code is setting the value in the eld at the index tripel i, j, k to the vector dened in result. There is also an eval(i,j,k,&result 0 ) method that will return the value at index i, j, k. Please note that the index position is in general not directly connected to a position in space. The connected data object bla may be dened on a curvilinear eld and therefore there may be arbitrary distances between points in space.

13.2. Access interpolated positions


The second way is to use the location class and get an interpolation for arbitrary locations in the eld. The locations in this case are not dened in terms of voxel indexes but by a oat location in terms of origin of the bounding box and the voxel size.

155

13. Field access

HxUniformVectorField3 *bla = (HxUniformVectorField3 *)portData.source(); if(bla == NULL){ theMsg->printf("error, no data connected"); return; } float bb[6]; bla->getBoundingBox(bb); HxLocation3 *loc = bla->createLocation(); int nDataVar = bla->nDataVar(); // nDataVar should be 3 now float *result = (float*)malloc(sizeof(float)*nDataVar); // space for the values loc->set(42.0, 42.0, 42.0); bla->eval(loc,result); // now we have the vector from location 42,42,42 in result[0..2]

1 2 3 4 5 6 7 8 9 10 11 12

The above example is working but will be slower than needed. Basically the problem is that the set function will take some time in order to calculate the required value. It is more ecient to ask the location class to re-use some part of the computation. This is done by
int ret = 0; if(ret) ret = loc->move(42.0, 42.0, 42.0); else ret = loc->set(42.0, 42.0, 42.0); if(ret) bla->eval(loc,result); else theMsg->printf("error: could not read values");
1 2 3 4 5 6 7 8 9

13.3. Access coordinates


There is a build-in coordinate system in each data object. Mostly the coordinates are implicit thus information about the origin and the voxel size are sucient to calculate for each voxel its position in space in real world coordinates. But in case of curvilinear elds the information about the position of each voxel is stored in the eld itself. Here is an example which is looking for the type of the attached object and in case its not curvilinear yet it is converting it to a curvilinear eld.
#include <hxfield/HxCurvilinearCoord3.h> #include <hxfield/HxCoordType.h> ... char *str = "lobus.am"; // HxObject is a basis class of all data objects, so this always works HxObject *obj = theObjectPool->findObject(str); if(!obj){ theMsg->printf("error: unknown data object \"%s\"",str); return 0;
1 2 3 4 5 6 7 8 9

156

13.3. Access coordinates


} int kval = int jval = int ival = float xval float yval float zval
10

1; // voxel at this index 1; 1; = 42.0; // will be at this position = 42.0; = 42.0;

11 12 13 14 15 16 17

if(obj->isOfType(HxRegScalarField3::getClassTypeId())){ 18 HxRegScalarField3 *data = dynamic_cast<HxRegScalarField3*>(obj); 19 HxRegScalarField3 *data2 = NULL; 20 // new data in case that we have to generate a curvilinear field 21 const int *dims = data->lattice.dims(); 22 // if we do not have already curvilinear fields we need to create one 23 // we will try to keep its name and remove the former data object 24 if(data->lattice.coords()->coordType() != c_curvilinear){ // we need to create a data 25 // object which is c_curvilinear now 26 int npoints = dims[0]*dims[1]*dims[2]; // number of voxel 27 // space for the xyz coordinates 28 float* xyz = (float *) malloc(3*npoints*sizeof(float)); 29 if(!xyz){ 30 theMsg->printf("error: could not allocate %.2fMB", 31 3*npoints*sizeof(float)/1024.0/1024.0); 32 return 0; 33 } 34 // fill the coordinate values, use defaults 35 int count = 0; 36 for(int kk=0;kk<dims[2];kk++) 37 for(int jj=0;jj<dims[1];jj++) 38 for(int ii=0;ii<dims[0];ii++){ 39 xyz[count*3+0]=ii; xyz[count*3+1]=jj; 40 xyz[count*3+2]=kk; count++; 41 } 42 data2 = HxRegScalarField3::create(dims, McPrimType::mc_float, c_curvilinear); 43 if(!data2){ 44 theMsg->printf("error: could not allocate %gMB", 45 dims[0]*dims[1]*dims[2]*sizeof(float)/1024.0/1024.0); 46 return 0; 47 } 48 // copy the coordinates into the lattice 49 memcpy(((HxCurvilinearCoord3 *)data2->lattice.coords())->coords(), 50 xyz, 3*npoints*sizeof(float)); 51 free(xyz); 52 // read in now the values per node 53 int nDataVar = data->lattice.nDataVar(); // number of values per 54 node float *tmp = (float *)data2->lattice.dataPtr(); 55 // copy the contents of the data into data2 56 memcpy(tmp,data->lattice.dataPtr(),sizeof(float)*npoints*nDataVar); 57 char filename[1024]; sprintf(filename,"%s",data->getName()); 58 if (data2){ 59

157

13. Field access


theObjectPool->removeObject(data); HxData::registerData(data2, filename); } else return 0; // should never happen } // now change the coordinate because it will be curvilinear! if(data2){ // data 2 contains the data object ((HxCurvilinearCoord3 *)data2->lattice.coords())->coords() [3*((kval*dims[0]*dims[1])+(jval*dims[0])+ival)+0] = ((HxCurvilinearCoord3 *)data2->lattice.coords())->coords() [3*((kval*dims[0]*dims[1])+(jval*dims[0])+ival)+1] = ((HxCurvilinearCoord3 *)data2->lattice.coords())->coords() [3*((kval*dims[0]*dims[1])+(jval*dims[0])+ival)+2] = } else { // the data was already curvilinear ((HxCurvilinearCoord3 *)data->lattice.coords())->coords() [3*((kval*dims[0]*dims[1])+(jval*dims[0])+ival)+0] = ((HxCurvilinearCoord3 *)data->lattice.coords())->coords() [3*((kval*dims[0]*dims[1])+(jval*dims[0])+ival)+1] = ((HxCurvilinearCoord3 *)data->lattice.coords())->coords() [3*((kval*dims[0]*dims[1])+(jval*dims[0])+ival)+2] = } return 0; }
60 61 62 63 64 65 66 67

xval; yval; zval;

68 69 70 71 72 73 74

xval; yval; zval;

75 76 77 78 79 80 81 82

158

14. Data Clusters


14.1. Reading in a cluster object
Data cluster are collections of points in space. There may be any number of data corresponding to each single point in the cluster. The usuall le format to store this type of data understood by Avizo is PSI.
#include <hxcluster/HxCluster.h> HxCluster* cluster = (HxCluster*) portData.source(); if (cluster && portData.isNew()) { int n = cluster->getNumDataColumns(); portVariable.setNum(1+n); # Amira/HxPortMultiMenu.h portVariable.setLabel(0,"Id"); for (int i=0; i<n; i++) { HxCluster::DataColumn& dc = cluster->dataColumns[i]; portVariable.setLabel(i+1,dc.name.dataPtr()); }
1 2 3 4 5 6 7 8 9 10 11

Query the point coordinates:


n = cluster->getNumPoints(); float b[6]; cluster->getBoundingBox(b); McVec3f* points = cluster->points.dataPtr(); int* src = cluster->ids.dataPtr(); # how many points? # point coordinates # point ids

Query data per point:


HxCluster::DataColumn* dc = &cluster->dataColumns[0]; McPrimType primType = dc->primType; # something like McPrimType::mc_int32 float* src = (float*) dc->data; # value per point

14.2. Generating a new cluster object


Modules that are known to Avizo are kept in an internal structure most of which can be accessed by the HxResource module. For example a HxResource::getExecPath() will return the path of the Avizo executable which is below the HxResource::getRootDir() the installation directory of Avizo. HxResource::getDataDir() returns the value of the global variable AVIZO DATA.

159

14. Data Clusters

#include #include #include #include #include #include

<Amira/HxMessage.h> <hxcluster/HxCluster.h> <Amira/HxResource.h> <Amira/HxObjectPool.h> <Amira/HxInterpreter.h> <Pat_Amira/simpleCluster.h>

1 2 3 4 5 6 7

HX_INIT_CLASS(simpleCluster,HxModule) simpleCluster::simpleCluster() : HxModule(){} simpleCluster::~simpleCluster(){} void simpleCluster::compute(){ HxCluster* cluster = new HxCluster(); cluster->setNumDataColumns(1); cluster->resize(2); McVec3f* coords = cluster->getCoords(); coords[0].setValue(0,0,0); coords[1].setValue(10,0,0); float* data = (float*) cluster->dataColumns[0].data; data[0] = 0; data[1] = 1; cluster->computeBounds(); cluster->setLabel("Cluster"); theObjectPool->addObject(cluster); } void simpleCluster2::compute(){ HxCluster* cluster = new HxCluster(); cluster->setNumDataColumns(1); cluster->resize(2); cluster->points[0] = McVec3f(0,0,0); cluster->ids[0] = 0; cluster->points[1] = McVec3f(10,0,0); cluster->ids[1] = 1; float* data = (float*) cluster->dataColumns[0].data; data[0] = 0; data[1] = 1; cluster->computeBounds(); cluster->setLabel("Cluster"); theObjectPool->addObject(cluster); }

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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

160

14.3. Adding a clusterView and a vertexView


50

14.3. Adding a clusterView and a vertexView


In order to create standard Avizo objects you have to use the HxResource tool. It allows you to specify the object by its name as printed out by the console command iconname getTypeId. Here are two examples for ClusterView and DisplayVertices.
HxModule *clusterView = (HxModule*)HxResource::createObject("HxClusterView"); clusterView->composeLabel(field->getName(),"_ClusterView"); theObjectPool->addObject(clusterView); clusterView->portData.connect(cluster); clusterView->fire(); HxModule *vertexView = (HxModule*)HxResource::createObject("HxDisplayVertices"); vertexView->composeLabel(field->getName(),"_VertexView"); theObjectPool->addObject(vertexView); vertexView->portData.connect(cluster); vertexView->fire(); }
1 2 3 4 5 6 7 8 9 10 11 12

161

14. Data Clusters

162

15. Surfaces
15.1. A simple surface
Surfaces can contain any number of patches (and contours and edges). This example copies data from a structure cutTriangulated to the patch.
// if we already constructed a surface re-use it HxSurface *surface = dynamic_cast<HxSurface *>(getResult(0)); if(!surface){ surface = new HxSurface(); surface->setLabel("CuttingPlane"); // the icon name } surface->clear(); // remove everything from before surface->points.resize(cutTriangulated->numberofpoints); surface->pointType.resize(cutTriangulated->numberofpoints); // copy the points to the surface for (k = 0; k < cutTriangulated->numberofpoints ; k++){ surface->points[k].setValue( cutTriangulated->pointlist[2*k], cutTriangulated->pointlist[2*k+1], myLineSet->getAverageCoord().z); surface->pointType[k] = HxSurface::INNER; } HxSurface::Patch* patch = new HxSurface::Patch; patch->outerRegion = surface->addMaterial("Exterior",0); patch->innerRegion = surface->addMaterial("Interior",1); surface->triangles.resize(cutTriangulated->numberoftriangles); patch->triangles.resize(cutTriangulated->numberoftriangles); for (i = 0; i < cutTriangulated->numberoftriangles; i++){ surface->triangles[i].points[0] = cutTriangulated->trianglelist[i * 3 ]; surface->triangles[i].points[1] = cutTriangulated->trianglelist[i * 3 + 1]; surface->triangles[i].points[2] = cutTriangulated->trianglelist[i * 3 + 2]; surface->triangles[i].patch = 0; patch->triangles[i] = i; } surface->patches.append(patch); // add patch to surface setResult(0,surface);
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 34 35

Please take the above example as a minimally needed framework. You should not leave out

163

15. Surfaces something from the above mentioned steps as for example adding materials. If the data structures are not set correctly the most likely result of working with a surface will be a segmentation fault.

15.2. Quadro-lateral surfaces


Quadro-laterial surfaces are surfaces which consist out of quadrats instead of the usual triangles. Internally also these surfaces are rendered by two triangles per quadrat. An Avizo module using this type of surface is HxHeightField.
... Hx2DMesh::Hx2DMesh():HxQuadBase(HxRegScalarField3::getClassTypeId()), ... hideGeom(soRoot); int dim[3] = { 0, 10, 20 }; soQuadSurface->coords.resize(dim[1]*dim[2]); soQuadSurface->quads.resize( (dim[1]-1)*(dim[2]-1) ); soQuadSurface->colorBinding = SoSurface::VERTEX_COLORS; soQuadSurface->colors.resize(dim[1]*dim[2]); float coords1[3]; // will be the 3d coordinates float value = 0.0; // will be the color value for(j=0;j<dim[1];j++){ int offs=j*dim[2]; for(i=0;i<dim[2];i++){ double xval = gridData[(i*currentDimX+j)*4]; double yval = gridData[(i*currentDimX+j)*4+1]; double zval = gridData[(i*currentDimX+j)*4+2]; double value = gridData[(i*currentDimX+j)*4+3]; soQuadSurface->coords[offs+i] = McVec3f(xval,yval,zval); soQuadSurface->colors[offs+i] = portColormap.getPackedColor(value); } } for(j=0;j<dim[1]-1;j++){ int offs=j*dim[2]; for(i=0;i<dim[2]-1;i++){ soQuadSurface->quads[j*(dim[2]-1)+i] = McVec4i(offs+i, offs+i+1, offs+dim[2]+i+1, offs+dim[2]+i); } } soQuadSurface->computeVertexNormals(); handleDrawStyle(); showGeom(soRoot);
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 34 35 36 37 38

164

15.3. Vector elds attached to a surface You will have to override the update() function in order to get the colormap port displayed correctly.

15.3. Vector elds attached to a surface


In Avizo the vertices of surfaces can have attached data. Scalar data may be visualized as color only the surface but also surface vector elds are used in order to show ow arround some objects for example. Here is an example on how to dene such a data object:
HxSurface *surface = new HxSurface(); HxSurface::Patch* patch = new HxSurface::Patch; surface->patches.append(patch); // add patch to surface // this is the default Exterior == 0 patch->outerRegion = surface->addMaterial("Exterior",0); patch->innerRegion = surface->addMaterial("Interior",1); surface->points.resize(3); surface->triangles.resize(1); surface->points[0].setValue(0,0,0); surface->points[1].setValue(10,0,0); surface->points[2].setValue(10,10,0); surface->triangles[0].points[0] = 0; surface->triangles[0].points[1] = 1; surface->triangles[0].points[2] = 2; surface->triangles[0].patch = 0; patch->triangles.resize(1); patch->triangles[0] = 0; HxSurfaceVectorField *vecField = new HxSurfaceVectorField(surface,HxSurfaceField::OnNodes); float *vectors = vecField->dataPtr(); vectors[0] = 0; vectors[1] = 0; vectors[2] = 10; vectors[3] = 0; vectors[4] = 0; vectors[5] = 20; vectors[6] = 0; vectors[7] = 0; vectors[8] = 5; HxData::registerData(surface,"HuiSurface"); HxData::registerData(vecField,"HuiSurfaceVector");
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 34 35 36 37 38

165

15. Surfaces

166

16. Image lter


16.1. Using a pre-dened lter
Lets assume we want to use a Gaussian lter on a uniform eld. Image lters work on typed objects in the Avizo framework. That is we need to construct an appropriate data object. Here is an example which uses a random eld:
#include <malloc.h> #define srand48(x) srand(x) #define drand48() ((double)rand()/RAND_MAX) ... imgBuffer1 = (unsigned char *)malloc(dims[0]*dims[1]*dims[2]*sizeof(double)); i1Ptr = new McTypedPointer(imgBuffer1,McPrimType::mc_double); i1 = new McTypedData3D(dims[0],dims[1],dims[2],*i1Ptr); for(int i=0;i<outdims[0]*outdims[1]*outdims[2];i++) ((double *)i1Ptr->data)[i] = drand48() -0.5;
1 2 3 4 5 6 7 8 9 10

Given this data set we can convole it with a Gaussian function:


ImGaussFilter3D *filter = new ImGaussFilter3D(); int kernelsize[3]; float sigma[3]; kernelsize[0] = 3; kernelsize[1] = 3; kernelsize[2] = 3; sigma[0] = 1.6; sigma[1] = 1.6; sigma[2] = 1.6; filter->setParams(kernelsize,sigma); filter->apply3D(i1,NULL);
1 2 3 4 5 6

Because we did not supplied a second argument in apply3D the input data will be overwritten. Note, that in case you want to get the data out of an McTypedData2D you will have to use a construct like this:
McTypedData2D *image = new McTypedData2D(dims[0],dims[1], McPrimType::mc_double); ImGaussFilter2D *filter = new ImGaussFilter2D(); filter->setKernelSize(12,8); filter->setSigma(8,4); filter->calculateKernel(); filter->apply2D(image,NULL); for(int j=0;j<dims[1];j++){ for(int i=0;i<dims[0];i++){
1 2 3 4 5 6 7 8 9

167

16. Image lter


theMsg->printf("value at location %d %d is %g\n", x,y,((double *)image->address(0,0))[y*dims[0]+x]); } }
10 11 12 13

Please note the dierence in accessing the address.

16.2. Image lters implemented by shaders


In order to use shaders you can use the mcgl package. It provides the classes for accessing the OpenGL shaders (example below is in GLSL). Here is an example of a module that is using two images to perform a trivial computation between them. One of them could represent a mask the other the image data or both of them could be data that needs to be combined on a single pixel base. First here are the variables generated in the header le of the module.
protected: int unsigned int GLhandleARB GLhandleARB GLint GLint
1

iWidth, iHeight; _iTexture;

// The dimensions of our array // The texture used as a data array

2 3 4 5

_programObject; // the program used to update _fragmentShader, _vertexShader; _texUnit; _coordUnit; // a parameter to the fragment program // another texture to the fragment program

6 7 8 9 10 11

int make_tex(SHORT *pBuf, int texSize); // texture unit 1 int make_ftex(FLOAT *pBuf, int texSize); // texture unit 0 int CompilerLog(void); // prints error message from comiling the shader

12 13 14

Here are the two shaders, on vertex shader that is doing nothing at the moment and one pixel shader. Both are dened by strings in the program and we will compile them and send them to the graphics board.
// Here is the texture vertex shader static const char *cart2polVertSource = { "void main(void)" "{" " gl_TexCoord[0].x = gl_MultiTexCoord0.x;" " gl_TexCoord[0].y = gl_MultiTexCoord0.y;" " gl_Position = ftransform();" "}" }; // Here is my texture fragment shader. At the moment its doing nothing but
1 2 3 4 5 6 7 8 9 10 11

168

16.2. Image lters implemented by shaders


// copy the input to the output. Sadly enough not even that is // working. static const char *cart2polFragSource = { "uniform sampler2D texUnit;" // the current texture "uniform sampler2D coordUnit;" // the mask "uniform float minRadius;" "uniform float maxRadius;" "void main(void)" "{" " const float offset = 1.0 / 512.0;" " vec4 c1 = texture2D(texUnit, gl_TexCoord[0].st);" " vec4 c2 = texture2D(coordUnit, gl_TexCoord[0].st);" " gl_FragColor = c1+c2;" "}" };
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

Now we will dene the constructor. There we compile and load the shaders and dene some variables that we can have access to in the shader itself.
HxCorrelateImages::HxCorrelateImages() : HxCompModule(HxUniformScalarField3::getClassTypeId()), portField2( this, "field2", HxUniformScalarField3::getClassTypeId()), portAction( this, "action"){ portAction.setLabel(0, "DoIt"); iWidth = iHeight = 512; _programObject = glCreateProgramObjectARB(); // Create the edge detection fragment program _vertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); _fragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); glShaderSourceARB(_vertexShader, 1, &cart2polVertSource, NULL); glShaderSourceARB(_fragmentShader, 1, &cart2polFragSource, NULL); glCompileShaderARB(_vertexShader); glCompileShaderARB(_fragmentShader); glAttachObjectARB(_programObject, _vertexShader); glAttachObjectARB(_programObject, _fragmentShader); // Link the shader into a complete GLSL program. glLinkProgramARB(_programObject); GLint progLinkSuccess; glGetObjectParameterivARB(_programObject, GL_OBJECT_LINK_STATUS_ARB, &progLinkSuccess); if (!progLinkSuccess){ theMsg->printf("Filter shader could not be linked\n"); CompilerLog(); // print out the error message from the compiler
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

169

16. Image lter


return; } glUseProgramObjectARB(_programObject); // here or below? // Get location of the sampler uniform _texUnit = glGetUniformLocationARB(_programObject, if(_texUnit == -1) theMsg->printf("error: could not get uniform _coordUnit = glGetUniformLocationARB(_programObject, if(_coordUnit == -1) theMsg->printf("error: could not get uniform
32 33 34 35 36

"texUnit"); named (texture)."); "coordUnit"); named (coordinates).");

37 38 39 40 41 42 43

// now transmit two variables to the shader, the minimum and maximum radius // for the polar variable r GLint loc1, loc2, loc3; float minRadius = 5; float maxRadius = 200; loc1 = glGetUniformLocationARB(_programObject, "minRadius"); GLERRCHECK; glUniform1fARB(loc1,minRadius); loc2 = glGetUniformLocationARB(_programObject, "maxRadius"); GLERRCHECK; glUniform1fARB(loc2,maxRadius); // disable the shader again glUseProgramObjectARB(0); GLint maxSamplers; glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS_ARB,&maxSamplers); theMsg->printf("Number of texture image units found: %d", maxSamplers); }

44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

After doing this here is now the compute module. Presented is only the part where we transmit textures to the GPU and start doing the work but not the parts where we generate the data. Here is out of compute() the setup of the screen. We will draw a quad later on that should ll the whole screen (which is of the size of the textures used).
clock_t t1 = clock();
1 2

// generate and transmit all templates 3 GLuint *IDS = (GLuint *)malloc(sizeof(GLuint)*dims[2]); 4 int nChan = 4; // we will use bilinear filtering 5 // we need square images 6 int texSize = dims[0]; 7 if(dims[0] != dims[1]){ 8 theMsg->printf("error: image size has to be square (%dx%d)", dims[0], dims[1]); 9 free(IDS); 10 return; 11 } 12 iWidth = iHeight = dims[0]; // should be 256.. but maybe others are working as well 13
14

170

16.2. Image lters implemented by shaders


// first setup the display to cover the texture 1 to 1 int vp[4]; glGetIntegerv(GL_VIEWPORT, vp); glViewport(0,0,iWidth,iHeight); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); // set the projection matrix to orthogonal for pixel to texel 1 to 1 mapping glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(-1, 1, -1, 1); // gluPerspective(45,1.0*w/h,1,1000); glMatrixMode(GL_MODELVIEW); glLoadIdentity();
15 16 17 18 19 20 21 22 23 24 25

Now I can generate a texture that will be send to the board by


float *pVB = (float *)malloc(sizeof(float)*512*512*3); for(int j = 0; j < 512; j++) { for (int i = 0; i < 512; i++) { pVB[3*(j*512+i)+0] = i/512.0; pVB[3*(j*512+i)+1] = j/512.0; pVB[3*(j*512+i)+2] = j/512.0; } } // now I would like to transfer these coordinates to the CPU GLuint COORD_ID = make_ftex(pVB, texSize); free(pVB);
1 2 3 4 5 6 7 8 9 10 11

The important part is make ftex and this function sets the texture into a texture unit. All textures that we want to use later on in shaders at the same time have to be in dierent units. So make ftex is using one unit and make tex is using another. Also one of them is dening a oat the other a short texture.
int HxCorrelateImages::make_tex(SHORT *pBuf, int texSize) { GLuint ID; glGenTextures(1, &ID); glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_2D, ID); GLERRCHECK; GLERRCHECK;
1 2 3 4 5 6

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 7 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 8 glTexImage2D( GL_TEXTURE_2D, 0, 4, texSize, texSize, 0, GL_RGBA, GL_SHORT, pBuf); 9 GLERRCHECK; 10
11

return ID; } int HxCorrelateImages::make_ftex(FLOAT *pBuf, int texSize) { GLuint ID; glGenTextures(1, &ID); glActiveTexture(GL_TEXTURE0 + 0); GLERRCHECK;

12 13 14 15 16 17

171

16. Image lter


glBindTexture(GL_TEXTURE_2D, ID); 18 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 19 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 20 glTexImage2D( GL_TEXTURE_2D, 0, 3, texSize, texSize, 0, GL_RGB, GL_FLOAT, pBuf); 21
22

return ID; }

23 24

We will use make tex now to put in several textures (RGBA textures derived from the input data eld). They all end up in the same texture unit because we will work on them one at a time.
SHORT *pBuf = new SHORT[texSize*texSize*nChan]; for(int i=0;i<dims[2];i++){ // this sets data ready for bilinear interpolation for (int j=0;j<dims[1];j++){ for( int k=0;k<dims[0];k++){ pBuf[4*(j*dims[0]+k)+0] = data1[(dims[0]*dims[1]*i)+ ((j%texSize)*dims[0]+(k%texSize))]; pBuf[4*(j*dims[0]+k)+1] = data1[(dims[0]*dims[1]*i)+ ((j%texSize)*dims[0]+((k+1)%texSize))]; pBuf[4*(j*dims[0]+k)+2] = data1[(dims[0]*dims[1]*i)+ (((j+1)%texSize)*dims[0]+(k%texSize))]; pBuf[4*(j*dims[0]+k)+3] = data1[(dims[0]*dims[1]*i)+ (((j+1)%texSize)*dims[0]+((k+1)%texSize))]; } } IDS[i] = make_tex(pBuf, texSize); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

Now lets do the actual work. Here we will draw a quad and, after the shader did his work we will ask it to transfer the changed texture back as our result image. As result only a single channel is transferred back.
for(int i=0;i<dims[2];i++){ // we have to activate the current texture glBindTexture(GL_TEXTURE_2D, IDS[i]);
1 2

GLERRCHECK;

3 4

// now call the fragment shader by render into a quad glUseProgramObjectARB(_programObject); glUniform1iARB(_coordUnit, 0); // select the texture unit glUniform1iARB(_texUnit, 1); glBegin(GL_QUADS); { glTexCoord2f(0, 0); glVertex3f(-1, -1, -0.5f); glTexCoord2f(1, 0); glVertex3f( 1, -1, -0.5f); glTexCoord2f(1, 1); glVertex3f( 1, 1, -0.5f); glTexCoord2f(0, 1); glVertex3f(-1, 1, -0.5f); }

5 6 7 8 9 10 11 12 13 14 15

172

16.2. Image lters implemented by shaders


glEnd(); glUseProgramObjectARB(0); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, iWidth, iHeight); glGetTexImage(GL_TEXTURE_2D, 0, GL_RED, GL_SHORT, &outdata[i*(texSize*texSize)]); GLERRCHECK; } clock_t t2 = clock(); theMsg->printf("time: %6.2f sec", (float)(t2-t1)/CLOCKS_PER_SEC);
16 17 18 19 20 21 22 23 24

As a utility function to see if there are compile errors for the shader we can use this function:
int HxCorrelateImages::CompilerLog(void){ int blen = 0; int slen = 0; char *compiler_log; if (_programObject==0) return 0; // not a valid program object glGetObjectParameterivARB(_programObject, GL_OBJECT_INFO_LOG_LENGTH_ARB , &blen); if (blen > 1){ if ((compiler_log = (GLcharARB*)malloc(blen)) == NULL) { return -3; // out of memory! } glGetInfoLogARB(_programObject, blen, &slen, compiler_log); if (compiler_log!=0) theMsg->printf("shader compiler error: \n%s\n", compiler_log); } free(compiler_log); return 0; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

Here is also a construct that is used to print out opengl errors that might have occoured.
#ifdef _DEBUG static GLenum glerr = GL_NO_ERROR; #define GLERRCHECK \ { glerr = glGetError();\ if (glerr != GL_NO_ERROR) {\ /* theMsg->printf("*** GL Error 0x%x\n", glerr ); */ \ if(glerr == GL_INVALID_ENUM) \ theMsg->printf("GL_INVALIDE_ENUM line:%d\n", __LINE__ ); \ if(glerr == GL_INVALID_VALUE) \ theMsg->printf("GL_INVALIDE_VALUE line:%d\n", __LINE__ ); \ if(glerr == GL_INVALID_OPERATION) \ theMsg->printf("GL_INVALID_OPERATION line:%d\n", __LINE__ ); \ if(glerr == GL_STACK_OVERFLOW) \
1 2 3 4 5 6 7 8 9 10 11 12 13

173

16. Image lter


theMsg->printf("GL_STACK_OVERFLOW line:%d\n", __LINE__ ); \ if(glerr == GL_STACK_UNDERFLOW) \ theMsg->printf("GL_STACK_UNDERFLOW line:%d\n", __LINE__ ); \ if(glerr == GL_OUT_OF_MEMORY) \ theMsg->printf("GL_OUT_OF_MEMORY line:%d\n", __LINE__ ); }\ while(glGetError() != GL_NO_ERROR);\ } #else #define GLERRCHECK #endif
14 15 16 17 18 19 20 21 22 23

174

17. Colormaps
Colormaps are objects in the workspace of Avizo and can be used by more than one module at a time. To dene a colormap you can use the following code
float colormap[768] = { 0.8905, 0.1429, 0, 0.8905, 0.1524, 0, ... 0.8762, 0, 0.1524}; HxColormap256 *cm = new HxColormap256(250); for(int i=0;i<250;i++){ cm->setRGBA(i,colormap[i*3+0],colormap[i*3+1], colormap[i*3+2],0.5); } cm->setLabel(name); cm->setMinMax(-3.1415927, 3.1415927); theObjectPool->addObject(cm);
1 2 3 4 5 6 7 8 9 10 11 12 13

175

17. Colormaps

176

18. SpreadSheets
Generate the spreadsheet object:
#include <hxstatistics/HxSpreadSheet.h> ... HxSpreadSheet *ss = new HxSpreadSheet(); ss->composeLabel(data->getLabel().getString(),"PowerStatistics"); ss->addColumn("nmode",HxSpreadSheet::Column::FLOAT); ss->addColumn("power",HxSpreadSheet::Column::FLOAT);
1 2 3 4 5 6

Fill its rows:


ss->setNumRows(npow); for(int i=0; i<npow; i++) { ss->columns[0].setValue(i,(double)pow[i]/nmode[i]); ss->columns[1].setValue(i,(double)nmode[i]); }
1 2 3 4 5

There are only two data types available for the column in a table, one is the HxSpreadSheet::Column::FLOAT the other one is HxSpreadSheet::Column::STRING. To go through all the columns in a table:
for(int i = 0; i < ss->nRows(); i++){ 1 for(int j = 0; j < ss->columns.size(); j++){ 2 if(ss->columns[j].type == HxSpreadSheet::Column::STRING) 3 theMsg->printf("Row %d, value \"%s\"", i, ss->columns[j].stringValue(i)); 4 else 5 theMsg->printf("Row %d, value %g", i, ss->columns[j].floatValue(i)); 6 } 7 } 8

177

18. SpreadSheets

178

19. Graphical interfaces


19.1. Screen aligned text output
To present some text into the window in a sort of head-up-display you can use the OpenGL underlying all draw commands in Avizo. The following code will do the drawing in screen positioned mode so the text will not be attached to an 3D object. But rst an small example:
1

SbVec4f region(0,0,1,1); HxViewer *viewer = theController->getCurrentViewer(); const SbViewportRegion& viewport = viewer->getViewportRegion(); SbVec2s windwoSize = viewport.getWindowSize(); windowSize[0] = (short)(windowSize[0]/(region[2]-region[0])+0.5); windowSize[1] = (short)(windowSize[1]/(region[3]-region[1])+0.5); float orgx, orgy; orgx = 40; orgy = 40; if(orgx<0) orgx = -(windowSize[0]+orgx); if(orgy<0) orgy = -(windowSize[1]+orgy);

2 3 4 5 6 7 8 9 10 11 12

Note that this will not work correctly for tiled displays. Here a more complex example:
#include <Inventor/actions/SoCallbackAction.h> // in constructor callbackR = new SoCallback; callbackR->setCallback(renderCallback, this); SoSeparator *root = (SoSeparator *)theController->getSceneGraph(0); root->insertChild(eventCB,0); root->insertChild(callbackR,0); memcpy(currentText, "some text displayed in Avizo viewer", 1024); fontSize = 18; // in destructor SoSeparator *root = (SoSeparator *)theController->getSceneGraph(0); root->removeChild(eventCB); root->removeChild(callbackR);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

Now we have to add the following two functions to our code. They get called any time we render the image thus can add something to the scene. In our case we re-position the some text to a xed position on screen.

179

19. Graphical interfaces

// render function void askForValue::render(SoGLRenderAction* renderAction) { #ifdef HX_TILED_RENDERING SoState* state = renderAction->getState(); SbVec4f region = SoModifyViewVolumeElement::get(state); #else SbVec4f region(0,0,1,1); #endif const SbViewportRegion& viewport = renderAction->getViewportRegion(); SbVec2s windowSize = viewport.getWindowSize(); windowSize[0] = (short)(windowSize[0]/(region[2]-region[0])+0.5); windowSize[1] = (short)(windowSize[1]/(region[3]-region[1])+0.5); SoCacheElement::invalidate(renderAction->getState()); float orgx, orgy; orgx = 40; orgy = 40; if (orgx<0) orgx = -(windowSize[0]+orgx); if (orgy<0) orgy = -(windowSize[1]+orgy); glPushAttrib(GL_ALL_ATTRIB_BITS); glDisable(GL_LIGHTING); glDisable(GL_BLEND); // Dont use glDisable(GL_DEPTH_TEST). Then no z-values will be written! glDepthFunc(GL_ALWAYS); glFrontFace(GL_CCW); glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(region[0]*windowSize[0], region[2]*windowSize[0], region[1]*windowSize[1], region[3]*windowSize[1], -1000, 1000); float x=region[0]*windowSize[0]+1; float y=region[1]*windowSize[1]+1; renderText(orgx,orgy,x,y); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); }

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 34 35 36 37 38 39 40 41

And now the function renderText in which we will set a standard text font and the fontSize dened before.
void askForValue::renderText(float x, float y, float refX, float refY){ char* text = currentText; if (!text || !*text)
1 2 3

180

19.2. Repeated events in time


return; int halign = -1; if (x<0) { x = fabs(x); halign = 1; } int valign = -1; if (y<0) { y = fabs(y); valign = 1; } const float* fg = &textColor[0]; HxController::glTextSetFont(fontSize,boldFlag,italicFlag,family); HxController::glTextSetForeground(fg[0],fg[1],fg[2],1); if(1){ // :-) HxController::glTextSetBackground(); } else { float* bg = &bgColor[0]; HxController::glTextSetBackground(bg[0],bg[1],bg[2],1); } HxController::glTextSetAlignment(halign,valign); HxController::glTextSetPixelOffset( (int)floor(x-refX+0.5), (int)floor(y-refY+0.5)); HxController::glText(refX,refY,999,text); HxController::glTextSetPixelOffset(); }
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

19.2. Repeated events in time


The OpenInventor framework allows to schedule a various number of dierent events to be handled. Here is an example of a callback that can be used to call a function repeatately every some seconds:
// .h #include <Inventor/elements/SoElements.h> #include <Inventor/sensors/SoTimerSensor.h> class SoGLRenderAction; ... McHandle<SoCallback> callback; SoTimerSensor *oneShotCallback; long int counter; // .cpp // the function to be called every some seconds void oneShotCB(void * data, SoSensor* sensor) { hxMyModule* onelineData = (hxMyModule*)data; theMsg->printf("in oneShotCB %ld", ((hxMyModule *)data)->counter++); } ...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

181

19. Graphical interfaces


// in constructor oneShotCallback = new SoTimerSensor(oneShotCB, this); oneShotCallback->setBaseTime(0.0); oneShotCallback->setInterval(2.0); // every two seconds oneShotCallback->schedule(); counter = 0;
19 20 21 22 23 24

19.3. Generate geometries by Open Inventor


Open Inventor can read in geometries as .iv les similar to wrml les. You can use this feature to generate Inventor objects (like manipulators) on-the-y. Here an example for generating a plane (used by GeometryCutter in Avizo):
static char* translateGeom = { "Separator {" " ShapeHints { vertexOrdering COUNTERCLOCKWISE }" " Material { diffuseColor 0 0.15 0.3 }" " PickStyle { style UNPICKABLE }" " Coordinate3 { point [ 0 -1 -1, 0 -1 1, 0 1 1, 0 1 -1 ] }" " Normal { vector -1 0 0 }" " NormalBinding { value OVERALL }" " FaceSet { }" " PickStyle { style SHAPE }" " DrawStyle { style INVISIBLE }" " Coordinate3 { point [ 0 -0.8 -0.8, 0 -0.8 0.8, 0 0.8 0.8, 0 0.8 -0.8 ] }" " FaceSet { }" "}" }; ... SoInput in; SoNode (node; in.setBuffer(geometry,strlen(geometry)); SoDB::read(&in,node); tabPlane->setPart("translator",node);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

Here tabPlane is a SoTabPlaneDragger, but an addChild in the scenegraph will do nicely (with showGeom).

19.4. Plot textures in 3D


Textures plotted in 3D are useful for many display modules. One example is the use of textures instead of geometry for drawing spheres (see plates). (Drawing textures can be much faster than drawing triangles on current hardware.) The following example will arrange squares lled with textures on a surface. You can learn here how to create a texture and how to set its position in 3D space.

182

19.4. Plot textures in 3D Note that before being able to reach the surface normals one has to compute them for the surface. A good strategy for this is to rst remove duplicate points
surface->removeDuplicatePoints(1e-4); surface->computeNormalsPerVertexIndexed( );

After this operation on the surface one can start generating the textures.
#include <Inventor/nodes/SoTexture2.h> #include <Inventor/nodes/SoCoordinate3.h> #include <Inventor/nodes/SoNormal.h> #include <Inventor/nodes/SoFaceSet.h> #include <Inventor/nodes/SoTextureCoordinate2.h> #include <Inventor/nodes/SoTextureCoordinateBinding.h> ... HxViewer *viewer = theController->getCurrentViewer(); if(scene) { // scene is SoSeparator hideGeom(scene); scene->removeAllChildren(); } else { scene = new SoSeparator; } scene->ref(); // for each image in field for(int i = 0; i < dims[2]; i++) { int max = ...; // some index of a point of the surface // so image i matches best to surface point max McVec3f point = surface->points[max]; McVec3f normal = -surface->normals[max]; // we use the inverted normals because we have one sided // lighting // What are the coordinates for each image (offset vectors)? // They are normal to the normal of the surface at this position. McVec3f xdir(1,0,0); McVec3f ydir(0,1,0); McVec3f inplane1, inplane2; normal.normalize(); if(normal.dot(xdir)-1.0 < 1e-6) inplane1 = normal.cross(ydir); else inplane1 = normal.cross(xdir); inplane1.normalize(); inplane2 = inplane1.cross(normal); inplane2.normalize(); // create four points that are the corners of the texture McVec3f p0 = -inplane1/2.0 - inplane2/2.0 + point; McVec3f p1 = inplane1/2.0 - inplane2/2.0 + point; McVec3f p2 = inplane1/2.0 + inplane2/2.0 + point;
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 34 35 36 37 38 39 40 41

183

19. Graphical interfaces


McVec3f p3 = -inplane1/2.0 + inplane2/2.0 + point; // now add a separator for this texture to scene SoSeparator *sep = new SoSeparator; scene->addChild(sep);
42 43 44 45 46 47

// ok now show a texture at that coordinate 48 SoTexture2 *slice = new SoTexture2; 49 sep->addChild(slice); 50 // attention this only works for byte data in field! 51 slice->image.setValue( SbVec2s(dims[1],dims[0]), 52 1, 53 &((const unsigned char *)(field->lattice.dataPtr()))[i*(dims[0]*dims[1])] ); 54
55

SoCoordinate3* coord = new SoCoordinate3; sep->addChild(coord); coord->point.set1Value(0, SbVec3f( p0[0], coord->point.set1Value(1, SbVec3f( p1[0], coord->point.set1Value(2, SbVec3f( p2[0], coord->point.set1Value(3, SbVec3f( p3[0],

56 57

p0[1], p1[1], p2[1], p3[1],

p0[2])); p1[2])); p2[2])); p3[2]));

58 59 60 61 62

SoNormal *normalV = new SoNormal; sep->addChild(normalV); normalV->vector.set1Value(0, SbVec3f(normal[0], normalV->vector.set1Value(1, SbVec3f(normal[0], normalV->vector.set1Value(2, SbVec3f(normal[0], normalV->vector.set1Value(3, SbVec3f(normal[0],

63 64

normal[1], normal[1], normal[1], normal[1],

normal[2])); normal[2])); normal[2])); normal[2]));

65 66 67 68 69

SoTextureCoordinate2 *texCoord = new SoTextureCoordinate2; sep->addChild(texCoord); texCoord->point.set1Value(0, SbVec2f( 0 , 0) ); texCoord->point.set1Value(1, SbVec2f( 1 , 0) ); texCoord->point.set1Value(2, SbVec2f( 1 , 1) ); texCoord->point.set1Value(3, SbVec2f( 0 , 1) ); SoTextureCoordinateBinding *tBind = new SoTextureCoordinateBinding; sep->addChild(tBind); tBind->value.setValue(SoTextureCoordinateBinding::PER_VERTEX); SoFaceSet *myFaceSet = new SoFaceSet; sep->addChild(myFaceSet); myFaceSet->numVertices.set1Value(0, 4); } showGeom(scene); viewer->render();

70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86

184

19.5. Get the actual mouse position

19.5. Get the actual mouse position


This example is out of a module which displays additional informations for selected points in 3D.
#include <Inventor/SoPickedPoint.h> // Inventor classes are known 1 #include <Inventor/events/SoMouseButtonEvent.h> 2 #include <Inventor/nodes/SoSeparator.h> 3 #include <Inventor/nodes/SoEventCallback.h> 4 #include <Inventor/actions/SoCallbackAction.h> 5 ... 6 eventCB = new SoEventCallback; // add in the constructor 7 eventCB->addEventCallback(SoMouseButtonEvent::getClassTypeId(), 8 mouseClickCB, this); 9 SoSeparator *root = (SoSeparator *)theController->getSceneGraph(0); 10 root->insertChild(eventCB,0); 11 ... 12 SoSeparator *root = (SoSeparator *)theController->getSceneGraph(0); // add in destructor 13 root->removeChild(eventCB); 14 ... 15 void mouseClickCB(void *p, SoEventCallback *eventCallback){ // not in a class 16 SoEventCallback *eventCB = eventCallback; 17 const SoMouseButtonEvent *event = (SoMouseButtonEvent *) 18 eventCallback->getEvent(); 19 if (SO_MOUSE_PRESS_EVENT(event, BUTTON1)) { 20 const SoPickedPoint * pp = eventCallback->getPickedPoint(); 21 SbVec3f sp = pp->getObjectPoint(); 22 if(event->wasShiftDown()){ 23 ((askForValue*)p)->reportValue(sp[0], sp[1], sp[2]); 24 } else { 25 ((askForValue*)p)->reportValue(sp[0], sp[1], sp[2]); 26 } 27 } 28 eventCB = 0; 29 } 30

19.6. Specify the type of connection of a module to some data


A module can refuse to be connected to some data. The autoConnect function has to return a non-zero value in this case. Here an example of a module that likes only to be connected to a HxSplineProbe:
int someModule::autoConnect(HxObject* primary){ if(primary->isOfType(HxSplineProbe::getClassTypeId())){ portModule.connect(primary); return 1; }
1 2 3 4 5

185

19. Graphical interfaces


return 0; }
6 7

19.7. Writing Interfaces


The interface of Avizo is written in Qt. You can use Qt to generate interfaces used by your modules. The simplest case is if your reader or writer class needs some input options to be set by the user. Here an example:
#include #include #include #include #include #include "myReaderQtPanel.h" <qpushbutton.h> <qcheckbox.h> <qframe.h> <qlayout.h> <qgroupbox.h>
1 2 3 4 5 6 7

myReaderQtPanel::myReaderQtPanel() : QDialog(NULL,"myReaderQtPanel",true){ flag = 0; setCaption("MyReader Dialog"); resize(200,100); QVBoxLayout *layout = new QVBoxLayout(this,10,10); QCheckBox *checkBox = new QCheckBox("check this out!",this); layout->addWidget(checkBox); QPushButton *buttonLoad = new QPushButton("Load", this); layout->addWidget(buttonLoad); connect(buttonLoad, SIGNAL( clicked() ), SLOT( accept())); connect(checkBox, SIGNAL( clicked() ), SLOT( toggleFlag())); } void myReaderQtPanel::toggleFlag() {flag = 1 - flag;} int myReaderQtPanel::getFlag() {return flag;}

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

Here is the corresponding .h le:


#ifndef TECPLOTREADERQTPANEL_H #define TECPLOTREADERQTPANEL_H #include <qdialog.h> class myReaderQtPanel : public QDialog { Q_OBJECT
1 2 3 4 5 6 7 8

186

19.7. Writing Interfaces


public: myReaderQtPanel(); int getFlag(); protected slots: void toggleFlag(); private: int flag; }; #endif
9 10 11 12 13 14 15 16 17 18 19

This Qt class implements a simple panel to setup a variable (ag). In an Avizo module you just instantiate it and then call its exec() method. This will stop for a modal dialog until you validate or close it.
myReaderQtPanel panel; panel->exec(); if(panel->getFlat()) { ...

It is much easier to generate user interfaces with the QtDesigner application. It will generate a XML description of the layout (.ui) which can be used to generate the corresponding C code:
${QTDIR}\bin\uic.exe myForm.ui -o myForm.h ${QTDIR}\bin\uic.exe myForm.ui -impl myForm.h -o myForm.cpp ${QTDIR}\bin\moc.exe myForm.h -o moc_myForm.cpp

187

19. Graphical interfaces

188

20. Use of Avizo build-in libraries


20.1. mclib
20.1.1. Dynamic Arrays
An implementation for dynamic arrays is McDArray. You can dene the array and change its size:
McDArray<float> someData(1); theMsg->printf("size is: %d", someData.size()); someData.resizeKeep(100);

and McDMatrix. Using these structures Avizo will take care of removing the memory after the module allocating the memory is removed from the Avizo workspace.

20.1.2. The ood-ll algorithm


#include <mclib/McFloodFill3D.h> #include <mclib/McVoxelChecker.h> #include <mclib/McData3D.h>

We need to do the ood ll dependent on the input data type. Therefore we will rst ask our input (eld) about its type. Here is the example for elds that are of type unsigned char.
if(field->primType() == McPrimType::mc_uint8){ 1 McData3D<unsigned char>* data = new McData3D<unsigned char>(dims[0],dims[1],dims[2]); 2 data->clear(); 3 memcpy(data->dataPtr(), field->lattice.dataPtr(), 4 dims[0]*dims[1]*dims[2]*sizeof(unsigned char)); 5
6

McRangeChecker<unsigned char> checker; checker.init(0,127,0); McBitfield out; out.resize(dims[0]*dims[1]*dims[2]); out.clearAll();

7 8 9 10 11 12

clock_t t1 = clock(); // lets also count the time we need (include <time.h> also) 13 mcFloodFill3D26(*data, checker, portPosition.getValue(0), 14 portPosition.getValue(1), portPosition.getValue(2), out, 0); 15 clock_t t2 = clock(); 16

189

20. Use of Avizo build-in libraries


float time = (float)(t2-t1)/CLOCKS_PER_SEC; theMsg->printf("Total time: %6.2f sec", time); for(int i=0;i<dims[0]*dims[1]*dims[2];i++){ if(out[i]) ((unsigned char *)output->lattice.dataPtr())[i] = 1; else ((unsigned char *)output->lattice.dataPtr())[i] = 0; }
17 18 19 20 21 22 23 24 25

20.1.3. Line integration in vector elds


In order to demonstrate a line integration in vector elds we will use here a module to distribute seed points in a vector eld similar to what the HxDisplayISL module is using. The line integration itself will be performed with a third order Runge-Kutte method.
#include <hxisl/FieldLineSet.h> #include <hxisl/ParticleSet.h> #include <hxisl/Particle.h> ... fieldLineSet = new FieldLineSet(); particles = new ParticleSet(); fieldLineSet->defaultLengthForward = 30; fieldLineSet->defaultLengthBackward = 30; // distribute the particles particles->init(vectorField,0,0,30); particles->setBoundingBox(bb); particles->scalarField = (HxScalarField3*)portDistField.source(); particles->dims.setValue(portResolution.getValue(0), portResolution.getValue(1), portResolution.getValue(2)); particles->resize(portOptions.getValue(0)); particles->setDistMode(portDistribute.getOptValue(), 1); // now copy the particles to the fieldLineSet fieldLineSet->seedPoints.resize(particles->size()); for(int j=0;j<particles->size();j++){ fieldLineSet->seedPoints[j] = (*particles)[j]->seedPoint; } // perform the integration (using ODE3) fieldLineSet->computeFieldLines(vectorField, 1E-9); // we got back this many traced points long numPoints = fieldLineSet->points.size(); // here a simple loop which is looking for each time step, // for each line set, for the position and tangent information
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

190

20.1. mclib
theWorkArea->startWorking("compute..."); for(int t=0;t<(tmax-tmin)+1;t++) { // for each time step theWorkArea->setProgressValue(t/(1.0f*(tmax-tmin))); for(int i=0;i<fieldLineSet->traceStart.size();i++){ // for each line if(t > fieldLineSet->traceLength[i]-1) continue; // finish this trace // now the current point is SbVec3f point = fieldLineSet->points[fieldLineSet->traceStart[i]+t]; // and its tangent is SbVec3f tangent = fieldLineSet->tangents[fieldLineSet->traceStart[i]+t]; // now do what you like with this information ... } } theWorkArea->stopWorking();
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

Instead of using the FieldLineSet code one can also use the McODE classes directly which allows a better control of the interpolation component of the algorithm. The McODE3 class can be initialized with the following code
McODE3 ode; ode.setHSampling(<value>); ode.setHMinMax(<value>, <value>); ode.setTolerance(<value>); ode.setRightHandSide(<function>, this); ode.setOrientation(<value>); ode.setNormalization(<value>); ode.setMethod(McODE::RK34); ode.init(Point);
1 2 3 4 5 6 7 8 9

This sets up the integration using the Runge-Kutta method. The vector eld would enter this code through the function argument of setRightHandSide. Here is an example:
int function(const McVec3f& r, McVec3f& f, void* userData) { // the calling functions clas type is assumed to be HxMe HxMe* me = (HxMe*) userData; HxVectorField3* field = me->field; HxLocation3* loc = me->loc; if (!loc->move(r.x, r.y, r.z)) { return 1; } // add code here to check for NaN or to map the vectors to a sub-space. field->eval(loc, &f.x);
1 2 3 4 5 6 7 8 9 10 11 12 13 14

191

20. Use of Avizo build-in libraries


return 0; }
15 16

The vector eld is evaluated at the location r and the result is stored in f. In order to run this code one has to call
Ode.next(r);

which will return the next streamline value in r.

192

21. Use of external libraries


21.1. Working with Numerical Recipes
Here an example how to create a module which uses the Numerical Recipies functions f3tensor, matrix, rlft3, free f3tensor, free matrix, to produce two output images (imaginary and real part of the discrete fourier decomposition). In turn it can also reconstruct based on the two output images its input (backward pass). This example also generates a SpreadSheet object and lls it with the power spectrum of the data.
#include #include #include #include #include <Amira/HxMessage.h> <Amira/HxWorkArea.h> <hxstatistics/HxSpreadSheet.h> <hxfield/HxUniformScalarField3.h> <fft/fft.h>
1 2 3 4 5 6

HX_INIT_CLASS(fft,HxCompModule) fft::fft() : HxCompModule(HxUniformScalarField3::getClassTypeId()), portForwardBackward(this,"Direction",2), portOutput(this,"Output",3), portInfo(this,"Info"), portAction(this,"action") { portInfo.setValue("Please use only powers of 2"); portForwardBackward.setLabel(0,"forward"); portForwardBackward.setLabel(1,"backward"); portOutput.setLabel(0,"combined"); portOutput.setLabel(1,"phase+amplitude"); portOutput.setLabel(2,"1D power spectrum"); portAction.setLabel(0,"DoIt"); new HxConnection(this,"phase", HxUniformScalarField3::getClassTypeId()); new HxConnection(this,"ampli", HxUniformScalarField3::getClassTypeId()); }

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

Here is the compute module which will test its input/output connections and call the Numerical Recipes functions copying the result back into the Avizo workspace.
#define SQR(a) ((a)*(a)) void fft::compute(){
1 2

193

21. Use of external libraries


int k; HxUniformScalarField3 *output; HxUniformScalarField3 *output1; HxUniformScalarField3 *output2; int outdims[3]; HxUniformScalarField3 *data; double *pow; int *nmode; int dims[3]; HxSpreadSheet *ss; int npow = 50; // resolution of the spreadsheet if (!portAction.wasHit() || connectionPorts.size() == 0) { return; } // process the inputs (it may be either combined or seperately // phase and amplitude if(connectionPorts[0]->source() != NULL){ data = (HxUniformScalarField3 *)connectionPorts[0]->source(); data->lattice.getSize(dims[0],dims[1],dims[2]); } else if(connectionPorts[1]->source() != NULL && connectionPorts[2]->source() != NULL){ HxUniformScalarField3 *phase = (HxUniformScalarField3 *) connectionPorts[1]->source(); HxUniformScalarField3 *amplitude = (HxUniformScalarField3 *) connectionPorts[2]->source(); const int* pdims = phase->lattice.dims(); const int* adims = amplitude->lattice.dims(); if(pdims[0]!=adims[0] || pdims[1]!=adims[1] || pdims[2]!=adims[2]){ theMsg->printf("ERROR: phase and ampli have different dim."); return; } dims[0] = adims[0]; dims[1] = adims[1]; dims[2] = adims[2]; data = new HxUniformScalarField3(dims,MC_DOUBLE); for(int k=1;k<=adims[2]/2;k++){ for(int j=1;j<=adims[1];j++){ for(int i=1;i<=adims[0];i++){ if(k==1){ data->set(i-1,j-1,2*k-1-1 , amplitude->evalReg(i-1,j-1,k-1)*cos(phase->evalReg(i-1,j-1,k-1))); data->set(i-1,j-1,2*k-1, amplitude->evalReg(i-1,j-1,k-1)*sin(phase->evalReg(i-1,j-1,k-1))); } else if(k<=adims[2]/2){ data->set(i-1,j-1,2*k-1-1, amplitude->evalReg(i-1,j-1,k-1)*cos(phase->evalReg(i-1,j-1,k-1))); data->set(i-1,j-1,2*k-1, amplitude->evalReg(i-1,j-1,k-1)*sin(phase->evalReg(i-1,j-1,k-1))); data->set(i-1,j-1,dims[2]+1-k-1, amplitude->evalReg(i-1,j-1,k-1)*cos(phase->evalReg(i-1,j-1,k-1))); data->set(i-1,j-1,dims[2]+1-k-1,
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

194

21.1. Working with Numerical Recipes


amplitude->evalReg(i-1,j-1,k-1)*sin(phase->evalReg(i-1,j-1,k-1))); } else{ theMsg->printf("should never happen! (fft)"); } } } } //bb {0,dims[0],0,dims[1],0,dims[2]}; float bb[6]; phase->getBoundingBox(bb); data->coords()->setBoundingBox(bb); // field->bbox()); data->lattice.getSize(dims[0],dims[1],dims[2]); McString info = phase->getLabel(); data->setLabel(info); } else // no correct input connected return; if(!checkpower2(dims[0],dims[1],dims[2])){ theMsg->printf("Error: Dim. is not power2 (use resample)."); return; } // generate the output objects if(portOutput.getValue() == 0){ // save both values in one field // (sufficient for inverse fourier) output = dynamic_cast<HxUniformScalarField3 *>(getResult()); if(output && !output->isOfType(HxUniformScalarField3::getClassTypeId())){ output = 0; } if(output){ const int* outdims = output->lattice.dims(); if(dims[0] != outdims[0] || dims[1] != outdims[1] || dims[2] != outdims[2]){ output = 0; } } if(!output) output = new HxUniformScalarField3(dims,MC_DOUBLE); float bb[6]; data->getBoundingBox(bb); output->coords()->setBoundingBox(bb); // field->bbox()); } else if(portOutput.getValue() == 1){ // save the phase and amplitude in separate output objects output1 = dynamic_cast<HxUniformScalarField3 *>(getResult(0)); output2 = dynamic_cast<HxUniformScalarField3 *>(getResult(1)); if(output1 && !output1->isOfType(HxUniformScalarField3::getClassTypeId())){ output1 = 0; }
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102

195

21. Use of external libraries


if(output2 && !output2->isOfType(HxUniformScalarField3::getClassTypeId())){ output2 = 0; } if(output1){ const int* out1dims = output1->lattice.dims(); if(dims[0] != out1dims[0] || dims[1] != out1dims[1] || dims[2] != out1dims[2]){ output1 = 0; } } if(output2){ const int* out2dims = output2->lattice.dims(); if(dims[0] != out2dims[0] || dims[1] != out2dims[1] || dims[2] != out2dims[2]){ output2 = 0; } } outdims[0] = dims[0]; outdims[1] = dims[1]; outdims[2] = dims[2]; if(!output1){ output1 = new HxUniformScalarField3(outdims,MC_DOUBLE); } if(!output2){ output2 = new HxUniformScalarField3(outdims,MC_DOUBLE); } float bb[6]; data->getBoundingBox(bb); // = {0,dims[0],0,dims[1],0,dims[2]}; output1->coords()->setBoundingBox(bb); // field->bbox()); output2->coords()->setBoundingBox(bb); // field->bbox()); } else { // portOutput.getValue() == 2, spreadsheet with power spectrum ss = new HxSpreadSheet(); ss->composeLabel(data->getLabel().getString(),"PowerStatistics"); ss->addColumn("nmode",HxSpreadSheet::Column::FLOAT); ss->addColumn("power",HxSpreadSheet::Column::FLOAT); } float ***data1 = f3tensor(1,dims[0], 1,dims[1], 1,dims[2]); float **speq1 = matrix (1,dims[0], 1,dims[1]<<1); // copy the data to data1 (we do fft in this array) for(k=0;k<dims[2];k++){ for(int j=0;j<dims[1];j++){ for(int i=0;i<dims[0];i++){ data1[i+1][j+1][k+1] = data->evalReg(i,j,k); } } } int direction = 1; if(portForwardBackward.getValue() == 1){ theMsg->printf("backward fft"); direction = -1;
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152

196

21.1. Working with Numerical Recipes


} theWorkArea->startWorking("fft..."); rlft3(data1,speq1,dims[0],dims[1],dims[2],direction); theWorkArea->stopWorking(); if(portOutput.getValue() == 0){ // save combined data for(k=0;k<dims[2];k++){ for(int j=0;j<dims[1];j++){ for(int i=0;i<dims[0];i++){ output->set(i,j,k,(double)data1[i+1][j+1][k+1]); } } } // save the phase and the amplitude seperately } else if(portOutput.getValue() == 1){ // complicated because of the wrap around order in rlft3 double phase, amplitude; for(k=1;k<=dims[2]/2+1;k++){ for(int j=1;j<=dims[1];j++){ for(int i=1;i<=dims[0];i++){ if(k==1){ double ak = data1[i][j][2*k-1]; double bk = data1[i][j][2*k]; phase = atan2(bk,ak); amplitude = sqrt(SQR(ak)+SQR(bk)); } else if(k<=dims[2]/2){ double ak = data1[i][j][2*k-1]; double bk = data1[i][j][2*k]; phase = atan2(bk,ak); amplitude = sqrt(SQR(ak)+SQR(bk)); output1->set(i-1,j-1,dims[2]+1-k,phase); output2->set(i-1,j-1,dims[2]+1-k,amplitude); } else if (k == dims[2]/2+1){ double ak = speq1[i][2*j-1]; double bk = speq1[i][2*j]; phase = atan2(bk,ak); amplitude = sqrt(SQR(ak)+SQR(bk)); } else { theMsg->printf("THERE IS SOMETHING MORE????"); } output1->set(i-1,j-1,k-1,phase); output2->set(i-1,j-1,k-1,amplitude); } } } } else { // generating the spreadsheet data //double amplitude, phase; pow = (double *)calloc(sizeof(double),npow); nmode = (int *) calloc(sizeof(int),npow); double kwi = 2.0*M_PI/dims[0]; double kwj = 2.0*M_PI/dims[1]; double kwk = 2.0*M_PI/dims[2];
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202

197

21. Use of external libraries


double dk = sqrt(3.0)*M_PI/npow; double fbox = (dims[0]*dims[1]*dims[2]); for(k=1;k<=dims[2]/2+1;k++){ for(int j=1;j<=dims[1];j++){ for(int i=1;i<=dims[0];i++){ int ie = i-1; if(ie>dims[0]/2) ie = ie-dims[0]; int kxq = kwi*ie; int je = j-1; if(je>dims[1]/2) je = je-dims[1]; int kyq = kwj*je; int ke = k-1; if(ke>dims[2]/2) ke = ke-dims[2]; int kzq = kwk*ke; double ktot = sqrt((double)(SQR(kxq)+SQR(kyq)+SQR(kzq))); int nbin = ktot/dk; if(k==1){ double ak = data1[i][j][2*k-1]; double bk = data1[i][j][2*k]; pow[nbin] +=(SQR(ak)+SQR(bk)); nmode[nbin]+=1; //phase = atan2(bk,ak); amplitude = sqrt(SQR(ak)+SQR(bk)); } else if(k<=dims[2]/2){ double ak = data1[i][j][2*k-1]; double bk = data1[i][j][2*k]; pow[nbin] +=2.0*(SQR(ak)+SQR(bk)); nmode[nbin]+=2; //phase = atan2(bk,ak); amplitude = sqrt(SQR(ak)+SQR(bk)); //output1->set(i-1,j-1,dims[2]+1-k,phase); //output2->set(i-1,j-1,dims[2]+1-k,amplitude); } else if (k == dims[2]/2+1){ double ak = speq1[i][2*j-1]; double bk = speq1[i][2*j]; pow[nbin] +=(SQR(ak)+SQR(bk)); nmode[nbin]+=1; //phase = atan2(bk,ak); amplitude = sqrt(SQR(ak)+SQR(bk)); } else { theMsg->printf("THERE IS SOMETHING MORE????"); } } } } ss->setNumRows(npow); for(int i=0;i<npow;i++){ pow[i] = pow[i]/nmode[i]; ss->columns[0].setValue(i,(double)pow[i]/nmode[i]); ss->columns[1].setValue(i,(double)nmode[i]); } free(nmode); free(pow); } free_matrix(speq1,1,dims[0],1,dims[1]<<1); free_f3tensor(data1, 1,dims[0], 1,dims[1], 1,dims[2]); if(portOutput.getValue() == 0)
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252

198

21.2. Matlab stu


setResult(0,output); else if(portOutput.getValue() == 1){ McString info = data->getLabel(); setResult(0,output1);output1->composeLabel(info,"phase"); setResult(1,output2);output2->composeLabel(info,"amplitude"); } else { // construced the spreadsheet so lets save that setResult(0,ss); } } bool fft::checkpower2(int x, int y, int z){ bool ret = true; if( floor(log((double)x)/log((double)2)) != log((double)x)/log((double)2)) ret = false; if( floor(log((double)y)/log((double)2)) != log((double)y)/log((double)2)) ret = false; if( floor(log((double)z)/log((double)2)) != log((double)z)/log((double)2)) ret = false; return ret; }
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273

21.2. Matlab stu


Here an example Matlab le that will write out a surface object as AmiraMesh. Also it will write a second eld that contains data per nodes. This could be either scalar data or vector data.
function saveAsAvizoSurface(filename,vertices,triangles,nodeData) % saveAsAvizoSurfaces will save a surface with attached data as % an AmiraMesh data object. % Here an example: % numPoints = 8; % vertices = randn(3,numPoints); % triangles = [randperm(numPoints); randperm(numPoints); randperm(numPoints)]; % nodeData = randn(3,8); % saveAsAvizoSurface(c:/bla,vertices,triangles(:,1:7),nodeData); if size(vertices,1) ~= 3, echo Error: vertices should have size 3xN return end; if size(triangles,1) ~= 3, echo Error: triangles should have size 3xN return end; if size(vertices,2) ~= size(nodeData,2), echo Error: number of vertices and nodes has to be the same
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

199

21. Use of external libraries


return end % now save the surface as an AmiraMesh object fp1 = fopen(strcat(filename,_Surface.am),w); fprintf(fp1,# HyperSurface 0.1 ASCII\n\n); fprintf(fp1,Parameters {\nMaterials {\nExterior { Id 1 }\n); fprintf(fp1,Yellow { Color 0.9 0.9 0 }\nGreen { Color 0.21 0.8 0.16 }\n}); fprintf(fp1,\nBoundaryIds { name \"BoundaryConditions\" }\nFilename); fprintf(fp1, \"%s\"\n}\n,strcat(filename,_Surface.am)); fprintf(fp1,Vertices %d\n, size(vertices,2)); for i = 1:size(vertices,2), fprintf(fp1, %f %f %f\n, vertices(1,i), vertices(2,i), vertices(3,i)); end fprintf(fp1,\nPatches 1\n{\nInnerRegion Green\nOuterRegion Yello\n); fprintf(fp1,Triangles %d\n, size(triangles,2)); for i = 1:size(triangles,2), fprintf(fp1, %d %d %d\n, triangles(1,i), triangles(2,i), triangles(3,i)); end fprintf(fp1, }\n); fclose(fp1); % now save the nodes as an AmiraMesh object fp2 = fopen(strcat(filename,_SurfaceNodes.am),w); fprintf(fp2,# AmiraMesh 3D ASCII 2.0\n\n); fprintf(fp2,nNodes %d\n\nParameters {\n ContentType \"SurfaceField\",); fprintf(fp2,\n Encoding \"OnNodes\"\n}\n,size(nodeData,2)); fprintf(fp2,NodeData { float[%d] values } @1\n\n, size(nodeData,1)); fprintf(fp2,# Data section follows\n@1\n); for i = 1:size(nodeData,2), for j = 1:size(nodeData,1), fprintf(fp2, %f, nodeData(j,i)); end fprintf(fp2,\n); end fclose(fp2);
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56

21.3. Working with TinyXML


TinyXML consists of a couple of source les that are easy to add to an existing project. Please note that Avizo contains already a mcxerces library which is supposed to be more powerful. The exmple assumes that we have an XML le that contains color information for a segmented data set. The two functions dened below can be called after the le reader and writer. They create or parse the XML structures and read or add them to the data object (usually a label eld).
#include <tinyxml.h>
1

200

21.3. Working with TinyXML


... 2 // this will read in the following xml file structure 3 /* 4 <?xml version="1.0" encoding="UTF-8"?> 5 <Atlas> 6 <Area abbrev="H2" name="anterior olfactory nucleus" id="H 1" value="34"> 7 <color b="0" g="0" r="255" /> 8 <Surface>bla.off</Surface> 9 </Area> 10 <Area abbrev="H3" name="superior olfactory nucleus" id="H 2" value="35"> 11 <color b="0" g="128" r="255" /> 12 <Surface>bla2.off</Surface> 13 </Area> 14 ... 15 </Atlas> 16 */ 17 void loadXMLColorInformation(char xmlFileName[1024], HxField3 *lf) { 18 Vsg::TiXmlDocument xmlDoc(xmlFileName); 19 bool loadOK = xmlDoc.LoadFile(); 20 if(!loadOK) 21 return; 22 HxParamBundle *materials = lf->parameters.materials(1); // create it if it does not exist 23 materials->bundle("Exterior", 1); // create this first! 24
25

int mat = 0; Vsg::TiXmlElement *handle = xmlDoc.FirstChildElement("Atlas"); if(handle){ Vsg::TiXmlNode *area = handle->FirstChild("Area"); while(area != 0) { theMsg->printf("found an area node in the xml file"); McString name(""); McString id(""); int value = -1; McString abbrev(""); McString r(""); McString g(""); McString b(""); McString surfaceName(""); Vsg::TiXmlElement *aElem = area->ToElement(); Vsg::TiXmlAttribute *att = aElem->FirstAttribute(); while(att) { theMsg->printf("attrib: %s = %s", att->Name(), att->Value()); if(strcmp(att->Name(),"name")== 0){ name = att->Value(); name.replaceSubStr(" ", "_"); } if(strcmp(att->Name(),"id")==0) id = att->Value(); if(strcmp(att->Name(),"abbrev")==0)

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

201

21. Use of external libraries

abbrev = att->Value(); 52 if(strcmp(att->Name(),"value")==0) 53 if (att->QueryIntValue(&value)!=Vsg::TIXML_SUCCESS) 54 theMsg->printf("Error: value of material \"%s\" is not an 55 integer", name. att = att->Next(); 56

} 57 // now we need the color and surface name 58 Vsg::TiXmlNode *color = area->FirstChild("color"); 59 if(color){ 60 aElem = color->ToElement(); 61 att = aElem->FirstAttribute(); 62 while(att){ 63 if(strcmp(att->Name(), "r")==0) 64 r = att->Value(); 65 if(strcmp(att->Name(), "g")==0) 66 g = att->Value(); 67 if(strcmp(att->Name(), "b")==0) 68 b = att->Value(); 69 att = att->Next(); 70 } 71 bool ok; theMsg->printf("found color with %d %d %d", r.toInt(ok), 72 g.toInt(ok), b. } 73 Vsg::TiXmlNode *surface = area->FirstChild("Surface"); 74 if(surface){ 75 aElem = surface->ToElement(); 76 surfaceName = aElem->GetText(); 77 // theMsg->printf("found a surface with name: %s", text); 78 // surfaceName = aElem->ToText(); 79 } 80 // now we have all the information together to add the material to the parameter sect 81 HxParamBundle *bundle = materials->bundle(mat+1); 82 if(!bundle) 83 bundle = materials->bundle(name.getString(), 1); 84 float tc[3]; bool ok; 85 tc[0] = r.toInt(ok)/255.0; 86 if(!ok) 87 tc[0] = 0; 88 tc[1] = g.toInt(ok)/255.0; 89 if(!ok) 90 tc[1] = 0; 91 tc[2] = b.toInt(ok)/255.0; 92 if(!ok) 93 tc[2] = 0; 94 bundle->insert(new HxParameter("Color", 3, tc)); 95 bundle->insert(new HxParameter("Id", value)); // trust that this is working 96 bundle->insert(new HxParameter("abbrev", abbrev.getString())); 97 bundle->insert(new HxParameter("Surface", surfaceName.getString())); 98
99

area = area->NextSiblingElement("Area"); mat++; }

100 101

202

21.3. Working with TinyXML


} 102 } 103 void saveXMLColorInformation(HxField3 *data, char xmlFileName[1024], HxUniformLabelField3 *lf) { 104 if(lf){ 105 // if we have a label field we can save the color information for this field as well 106 strcat(xmlFileName, ".atlas.xml"); 107 Vsg::TiXmlDocument xmlDoc; 108 Vsg::TiXmlDeclaration *decl = new Vsg::TiXmlDeclaration( "1.0", "", "" ); 109 xmlDoc.LinkEndChild( decl ); 110
111

Vsg::TiXmlElement *hl = new Vsg::TiXmlElement( "Atlas" ); xmlDoc.LinkEndChild( hl ); Vsg::TiXmlComment *comment = new Vsg::TiXmlComment(); comment->SetValue(" Created with Avizo www.vsg3d.com[] "); hl->LinkEndChild( comment ); HxParamBundle *par = data->parameters.materials(); int numMaterials = par->nBundles(); for(int i = 0; i < numMaterials; i++) { // for each material define an entry in the export document HxParamBundle *currentMat = par->bundle(i); if(!currentMat) continue; const char *strName = currentMat->name(); int id; if(!currentMat->findNum("Id", id)) id = i; float col[4]; if(!currentMat->findColor(col)) col[0] = col[1] = col[2] = col[3] = 0; else { // save the colors from 0..255 col[0] *= 255; col[1] *= 255; col[2] *= 255; } Vsg::TiXmlElement *element = new Vsg::TiXmlElement( "Area" ); element->SetAttribute("abbrev", strName); element->SetAttribute("id", id); element->SetAttribute("name", strName); element->SetAttribute("value", id); hl->LinkEndChild( element ); Vsg::TiXmlElement *element2 = new Vsg::TiXmlElement( "color" ); element2->SetDoubleAttribute("r", col[0]); element2->SetDoubleAttribute("g", col[1]); element2->SetDoubleAttribute("b", col[2]); element->LinkEndChild( element2 ); Vsg::TiXmlElement *msgs = new Vsg::TiXmlElement( "Surface" ); msgs->LinkEndChild( new Vsg::TiXmlText(strName) ); element->LinkEndChild( msgs );

112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151

203

21. Use of external libraries


} xmlDoc.SaveFile(xmlFileName); } }
152 153 154 155 156

204

Index
AvizoMesh, 69, 114 2D plot, 80 64bit, 95 aminfo, 129 convert binary to ascii, 130 Arithmetic, 115 auto spin animation, 11 big endian, 55 BoundingBox, 104 Cluster create, 58 command line -debug, 96 -no gui, 23 console command all, 30 Create Scalareld by tcl, 61 Create Menu, 83 createDocFile, 116 crop, 85 cursor color, 11 debugging, 95 Development Wizard, 95 Dimension, 104 DynamicTimeSeriesCtrl, 136 Editor crop, 85 segmentation, 85 Surface simplication, 33 transform, 87 endian, 135 Environment variables C++, 96 predened, 11 scriping, 72 tcl, 96 eld curvilinear, 157 le open save/load dialog, 97 le format PSI, 33, 58, 161 le type AvizoMesh, 45 ip, 85 heighteld, 166 http-server, 116 HxAnnaScalarField3, 61 HxCompModule, 118 HxField3, 120 HxLocation3, 120 HxParameters, 151 HxResource Avizo executable, 161 create modules, 163 data directory, 161 load data, 111 Image lters tcl, 35 install extensions, 9 on unix, 9 on windows, 9 Inventor, 24 iv le, 30 Keys Shift,Ctrl,Alt, 80

205

Index label eld C++, 151 LabelField, 115 create, 59 Labeleld create by tcl, 59 LabelSet, 152 LargeDiskData, 111 license le, 10 lightning two-sided, 148 little endian, 55 logo, 24 lua, 115 magic cookies, 21 Matlab Surface write, 145 McFilename, 97 McFilenameCollector reading les, 98 networks, 124 neuron tracing, 86 opengl, 181 OrthoSlice contrast and brightness, 78 png image les, 116 Port HxPortColormap, 100 HxPortFilename, 100 HxPortFloatSlider, 100 HxPortFloatTextN, 100 HxPortIntSlider, 100 HxPortIntTextN, 100 HxPortMultiMenu, 100 HxPortRadioBox, 100 HxPortText, 100 HxPortToggleList, 100 progress bar, 72 registry store and read, 97 remote les, 55 resource le .rc, 69, 81, 83, 120 savePorts, 121, 124 SbColor, 152 ScalarField create, 61 screenshot, 22 shaders, 170 SpreadSheet, 195 startup le, 21 stereo, 21, 80 String tokenizer, 151 surface simplication, 33 swap, 85 text font, 182 threads, 106 update viewer, 105 workspace, 98

206

You might also like