Making your own software within OpendTect is in principle pretty easy. You could change the software by modifying existing classes and functions, and adding your own stuff to the libs. The advantage is total control. The problem with this approach, however, is that you have to keep the OpendTect sources in sync with new releases. Furthermore, if you cannot convince the opendtect.org people to also make those changes, OpendTect users may not be happy with your work.
An easy way to overcome this is to make your own plugins. Plugins make use of all the facilities of OpendTect but are loaded at run-time and can therefore be developed in a completely independent way. If you then find things that can't be done without modifying the OpendTect environment, it should be much easier to convince the opendtect.org people to take over or even implement those things themselves.
One thing you cannot do, is use another compiler than gcc/g++. OpendTect is built with it, if you want to use another compiler (why?) you'll have to make all libs and supporting libs (Qt, COIN, fftw) yourself. The make itself should be pretty easy to get started, but there will probably be some porting to do, too.
All modern Operating systems nowadays have ways to dynamically load libraries into a running program. The basic idea is:
As an example, a 'hello world' program could conceptually look like this:
dynlib = OpenDynamicLib( "libc.so" );
PrintfTypeFunction fn = (PrintfTypeFunction)GetDynLibFn( dynlib, "printf" );
fn( "Hello world\n" );
In this very simple case calling the function has no other effect than printing a string. In OpendTect, you'd want to add an attribute, create a menu item in the Opendtect menu, start horizon tracking, that sort of thing.
In OpendTect, all of the dynamic lib querying etc. is already programmed. A plugin just needs to contain a few standard functions that will be called automatically when the dynamic library is loaded. There are three functions, of which only one is really required. Let's say the name of the plugin is MyMod, these functions will be:
GetMyModPluginType
GetMyModPluginInfo
InitMyModPlugin
Only the last one is required. The first one,
Get...PluginType determines whether the plugin can be
used for the auto-load, and if so, when. 'When'
means: before or after the program has created the OpendTect GUI
objects (and always after the static objects are initialised). The
second function simply provides info for the users of your plugin.
Note that the standard plugin functions must be declared 'extern "C"', as you can see in the first example plugin "Hello":
#include <iostream>
extern "C" const char* InitHelloPlugin( int, char** )
{
std::cout << "Hello world" << std::endl;
return 0; // All OK - no error messages
}
You'll find this example plugin in the plugins directory of your work environment. Those 6 lines are enough to constitute a plugin for OpendTect. The Makefile to build the plugin looks like this:
SRC.cc := hellopi.cc
PLUGIN := yes
include make.od.Defaults
include make.Targets
First try make -n (make sure you have Pmake initialised!). Notice that Pmake makes an
awful lot of commands with a small specification. This is because Pmake includes loads of make-code.
An important remark here is that Pmake supports plugin generation fully when the subdirectory name is the same as the plugin (base-)name. Thus, subdirectory MyPlugin generates libMyPlugin.so (or libMyPlugin.dylib or MyPlugin.dll) which should contain an InitMyPlugin() function.
Anyway, after typing
make the plugin should be generated. The new plugin can
be loaded from within OpendTect (Menu Utilities-Plugins; press the
'Load new' button). At that point in time, the message 'Hello world'
should appear on stdout (on Windows, stdout messages appear on the
OpendTect console window).
To make this a UI - based program we'll need to use functionality
from the uiBase module. In this case, we use the
uiMSG() utility:
#include "uimsg.h"
extern "C" const char* InituiHelloPlugin( int*, char** )
{
uiMSG().message( "Hello world" );
return 0; // All OK - no error messages
}
Again 6 lines, but now, when you load it, you'll get a popup message with an OK button.
In this case we actually use stuff from OpendTect libraries, so the Makefile needs to change. We need to add the line:
MODDEP := uiBase
Notice that this is added before the make
inclusions: It's a directive to establish all kinds of variables in
the make includes. By typing make -n again you'll see
that Pmake added some directives for the compiler.
If you really want to see this simple thing working, make sure you add:
OWNC++FLAGS := -DPLAN_A
to the make file (or specify make OWNC++FLAGS=-DPLAN_A on the command line).
Adding menu items popping up your own dialogs is pretty easy once you get the hang of it. Although we fear that you might be scared by this code when you first see it, we hope that after the explanations below you will see some beauty in it and start to like it.
Look in uihellopi.cc - the else of the PLAN_A part. First of all, we nicely define the other standard plugin functions:
extern "C" int GetuiHelloPluginType()
{
return PI_AUTO_INIT_LATE;
}
extern "C" PluginInfo* GetuiHelloPluginInfo()
{
static PluginInfo retpi = {
"uiHello plugin - plan B",
"Bert",
"1.1.1",
"This is the more extensive variant of the uiHello example.\n"
"See the plugin manual for details." };
return &retpi;
}
The plugin will now auto-load when added to the auto-loaded plugins.
The idea is to add a menu item 'Display Hello Message ...' to the
'Utilities' menu of OpendTect. The Makefile and the example code are
already prepared for this.
The
MODDEP := uiODMain tells Pmake that the
uiODMain headers, and the headers uiODMain
depends on will be used. That is much more than the original
uiBase dependencies. uiODMain is the top level of
OpendTect.
If "make" does not compile anything
(because a uiHello.so already exists), you can try "make
uihellopi.rmlib" first.
The real problem behind the first is not so much how to make the
item itself, but how to get hold of the parent menu it must be
inserted into. This is a threshold that needs to be conquered for
every new system: you'll need an entry point, a seed to get you going.
In OpendTect, the entry point is the global instance of the uiODMain
class that can be accessed through the uiODMain*
ODMainWin() global function. Once you have that, you can reach
any object you need.
To see where things start, go to the InituiHelloPlugin function at the bottom of uihellopi.cc to find:
extern "C" const char* InituiHelloPlugin( int, char** )
{
(void)new uiHelloMgr( ODMainWin() );
return 0; // All OK - no error messages
}
As you can see, the ODMainWin() instance is passed to a new
instance of uiHelloMgr which is programmed in the lines
above it. This Manager object is needed as you will find that most
non-trivial GUI plugin will need some kind of top-level 'application
management' object to drive it.
uiHelloMgr An important part of any GUI system is callback handling. In
OpendTect, callbacks are basic objects used throughout the system, not
only in the User Interface. In any case, because callbacks can only be
sent to objects that are CallBackers, we have made
uiHelloMgr a descendent of CallBacker, so it
can receive the callback from the menu. When the uiHelloMgr is
constructed, it will add a menu item to the OpendTect menu:
uiMenuItem* newitem = new uiMenuItem( "&Diplay Hello Message ...",
mCB(this,uiHelloMgr,dispMsg) );
appl.menuMgr().utilMnu()->insertItem( newitem );
The new menu item is defined by the display text (the '&'
makes the 'D' the shortcut key) and the CallBack that needs to be
called when the item is activated (by the user). Note the
mCB which is a macro that makes the rather difficult C++
notation easy to understand. See callback.h in the Basic
include directory if you're really interested.
The next line inserts the item into the 'Utilities' menu. You'll
find a bunch of methods in the uiODMenuMgr class to
access all menus and toolbars.
uiHelloMsgBringer Most dialog windows in OpendTect are objects of type
uiDialog. uiDialog inherits from uiMainWin, the base class without Ok and Cancel buttons, a window title,
and so forth (all of those are optional BTW).
The uiHelloMsgBringer class is such a uiDialog.
The class contains a field for user input of the string
to display as message. To make the example a bit lively, we added a
field whether the message should be, eh, just a message, or a warning.
You can see the uiGenInput class will serve both input
fields nicely, you just need to define another InpSpec
instance:
txtfld = new uiGenInput( this, "Hello message",
StringInpSpec("Hello world") );
typfld = new uiGenInput( this, "Message type",
BoolInpSpec(true,"Info","Warning") );
typfld->attach( alignedBelow, txtfld );
Note that "Hello world" is the inital string displayed in
txtfld. Another feature of the OpendTect UI is the
automatic layout: we program the positions of the various fields by
attaching them in certain ways - most commonly
alignedBelow.
The actual message is displayed when the user presses Ok on the dialog that pops up. When nothing is filled in, we give the user the finger with the "Please type a text" message, and we do not accept the Ok, i.e. the dialog remains on the screen and the user can either be a good girl (or boy) and fill in something or press cancel.
if ( ! *typedtxt )
{
uiMSG().error( "Please type a message text" );
return false;
}
When a message is present, we do as we're told:
if ( typfld->getBoolValue() )
uiMSG().message( typedtxt );
else
uiMSG().warning( typedtxt );
return true;
and the dialog disappears immediately after the message appears on the screen and the user clicks OK.
Now that we've seen some basic plugin stuff, and some basic GUI stuff, we can move on to a real plugin that looks like something that could actually be useful. For this purpose, we have created the Tutorial plugins. As is common in OpendTect, there is a plugin 'Tut' for non-ui, real-work stuff, and the 'uiTut' for the GUI part.
The idea of the tutorial plugins is to show a variety of common things that one might want to do, rather than make something useful for end-users. For that we'll make the following tools:
uiTut pluginIn the uiHello plugin, you have already seen how to add a menu item and how to create a dialog. In uiTut, the GUI consists of two parts. One is similar to that in uiHello and deals with opening an independent dialog box via a menu item in the 'Utilities' menu. The other part gets the 'Tutorial' attribute listed in the 'Edit Attributes' dialog and creates the input fields in the same dialog box. It also sends the input parameters to Tutorial for attribute computation
Let us first have a look at the independent dialog part which in turn has two parts -- one for seismic tools and the other for horizon tools. the insertion of menu item and opening of dialog is similar to that in uiHello. The only interesting part is the uiIOObjSel class which allows you to select an item from a set -- a horizon or a seismic cube ( subclass uiSeisSel is used for seismic cube selection).
Both uiSeisTools and uiHorTools use the class uiTaskRunner, which triggers the Executor's in the Tut plugin. The class uiTaskRunner also displays a progress bar which keeps the user informed about the progress of the process.
Now we come to the attribute part. In the uitutorialattrib.cc file we see that although uiAttrDescEd is not a uiDialog like the the uiHello example, it still is a valid parent (being a uiGroup) for the various UI elements. A nice feature of OpendTect is clear from the first line in the constructor: the inpfld is a special Attribute UI class which is handled just like any pre-defined uiBase or uiTools class. This illustrates that in the OpendTect GUI system, not only pre-made GUI elements are 'first class' - new objects with different shape and behavior attached will be usable transparently by any other GUI class.
Coming to the plugin 'main' file uitutpi.cc, you will find it very similar to uihellopi.cc. Like any typical UI plugin, uiTut is a LATE plugin, which means that it will be loaded only after the rest of the UI is already in place.
extern "C" int GetuiTutPluginType()
{
return PI_AUTO_INIT_LATE;
}
Then comes the second 'special' plugin function GetxxxPluginInfo(). You may want to refer to the definition of the class PluginInfo for a better understanding of the above function. It allows the plugin manager to make this info available to the world.
extern "C" PluginInfo* GetuiTutPluginInfo()
{
static PluginInfo retpi = {
"Tutorial plugin development",
"dGB (Raman/Bert)",
"4.2",
"Shows some simple plugin basics." };
return &retpi;
}
And the last 'special' function is the one which gets things going:
The responsibility of uiTut is limited to talking to the user and getting the input parameters. The real work is done behind the scene by the non-UI
Let us first look at the direct seismic operations, that are handled by the class
Scaling and squaring are single-sample operations. But as you can see in the implementation of the function
Similar to
Please note the difference in the function
We have seen the direct seismic approach to simple operations on seismic data in
The main plugin file "tutpi.cc" makes a call to
A Steering cube, as the name suggests, works as a guiding cube. It stores the Inline dip and Crossline dip at each point, which guides the attribute engine in multi-trace computations. In case of our Tutorial attribute, we can use the steering data for horizontal smoothening. The key function is Some fundamental attribute functions are listed here: This static function initializes the attribute: sets up the parameters and
the number and type of the inputs and outputs. You can compare this to what you see in Opendtect in the attribute definition window after loading the uiTut
plugin. If you look at the parts of the implementation carefully,
( tutorialattrib.cc ) you'll see that each parameter is built up following
this example: Every parameter is required by default, to overrule this use initClass() also adds the attribute to the attribute factory.
In this case, as every attribute is a provider, the Tutorial attribute is
added to PF() (the Attrib::ProviderFactory singleton access function). This function is standard for every attribute,
here is the attribute constructor called. Will be used not only to update the parameters but also the number and type
of the outputs and to add or disable some inputs. If you look at the
implementation for the tutorial attribute, this function just allows to enable or disable
the inputs ( factor, shift and smooth ) according to the action chosen
by the user we need to define this initialization function because we have Steering. Steering always carries two outputs and we need them both.
If we are using steering data, this function prepares the steering input for use in computation. A subvolume is generated around the central trace, with the size of the subvolume specified by the stepout. This data contains the shifts in terms of number of samples for each trace in the subvolume relative to the central trace.
Before the work can be done, some input has to be given. This function is the
place where you specify how to get your input data.
For the Tutorial this is the seismic data. But it can also be Steering Data or any other attribute. You will notice from here that the calculation of the attributes is not done
on traces but using a different object, the DataHolder. The dataholder contains
a set of ValueSeries which
holds the value of every sample of the SeisTrc.
Advantage: in case of an attribute which has other attributes as inputs, data
is available in the corresponding dataholders, it thus saves a lot of time
( easier and much faster to read some floats in a ValueSeries than to get values
from a SeisTrc ). Stored data are read from cubes of seismic traces and written
the same way. The DataHolder is also carrying some specific information about the trace to
be processed, like the start sample number and the number of samples you wish
to calculate. Another important remark: calculation is made using sample numbers,
not time or depth Most of the rest of the methods are there to comply with the
When we want to look at the actual work, the place to be is the
In the computeData() method, we are faced with a number of Z ranges.
To be able to support multi-threading, computeData must be ready to only
process part of the trace. Then, also, we can have input cubes that are
larger than requested or desired, or smaller than that. This delivers a
rather nasty picture of Z indexes that we really cannot circumvent.
To make things at least clear, the indexes are all related to the the
absolute Z=0. This is where everything refers to. Then, we have different
start Z indexes for each of the input cubes and the output cube. These are
named 'z0_' in the corresponding DataHolders. Let us have a look at the Like any other commercial application, our plugin also needs a help document which a user can see by clicking on a button in the user interface. In OpendTect, the help documents are stored in $WORK/doc/User/### directories. In case of our Tutorial plugin, the directory is called tut. There are 4 key files in this directory:
The codes of these help documents as mentioned in the
From 3.3.2, OpendTect has the 'Credits' documentation, for aknowledging
helpers, sponsors, idols, ....
The structure is different from the user documentation described earlier. The base directory is If you want to tie certain windows to specific credits (as we want in OpendTect base), you can use the Help ID as reference. This should then go in the
If you want to add credits, you should at least make an Once you have made your own plugin, you probably would like it to
be loaded automatically whenever OpendTect is started. OpendTect
provides some facilities that do just that. If you want to prepare your plugin for auto-loading at startup, you
basically just have to define when it should be loaded. This is done
by implementing the GetxxxxPluginType() function, for example:
The GetxxxxPluginType() specifies when a plugin is loaded:
The auto-load tool of OpendTect looks for plugins to load in two
places: On Windows, your 'Personal directory' is located at $HOME if this
is defined. Otherwise, $USERPROFILE is used. Also see the specific
notes in the windows
documentation. Once you have located (and/or made) the directory where the plugins should be stored, you have to make a subdir 'libs' to contain the plugins and use .alo files. Auto LOad files are simple text files that tell a program which
plugins it is supposed to load from the 'libs' directory. Since
OpendTect contains multiple programs, each program has its own set of
.alo files '<program name>.*.alo', while the plugins can be
shared between multiple programs. OpendTect will scan for any file
with this naming convention. So od_main.john.alo is perfectly OK. Since there are multiple vendors and/or plugin sets, each vendor
can make his own .alo files. od_main, for example, will look at any
file named od_main.*.alo. For this example, the default plugins are
specified by od_main.base.alo, while dgb's plugins are specified by
od_main.dgb.alo. This way, each vendor can make his own .alo files,
without interfering with others. A .alo file is nothing more then a simple list of plugins, without
extensions. For example, here is part of the od_main.base.alo file:
Note that for each platform, a specific .alo file must be created.
Usually, they will be the same, but some plugins might not be relevant or
supported on all platforms. The plugins in the .alo files are loaded in the order as specified in the file. The alo files themselves are handled in alphabetical order. The publishing and distribution of OpendTect plugins is pretty
straightforward. The .alo files can be installed in the plugins/platform
( On Unix, this means that you can make a .tar.gz file containing
the plugins in a directory structure as described above, which can be
extracted into the existing OpendTect installation directory. On Windows this is also possible, but it is more common to use an
auto-extracting installer to do this. For more info on this, see the
windows documentation.
extern "C" const char* InituiTutPlugin( int, char** )
{
static uiTutMgr* mgr = 0; if ( mgr ) return 0;
mgr = new uiTutMgr( ODMainWin() );
uiTutorialAttrib::initClass();
return 0;
}
The
Tut pluginTut plugin. And that is the reason why it is of type EARLY. This particular plugin tells OpendTect's application manager that it wants to be loaded early - i.e. before any build of tables, data structures or user interfaces are made. That is typical of 'Real Work' plugins. The alternatives are NONE (which is default so in that case a GetxxxxPluginType() need not be defined) and LATE, which is typical
for UI plugins that want to start working when all objects have already been created.extern "C" int GetTutPluginType()
{
return PI_AUTO_INIT_EARLY;
}
SeisTools
SeisTools, which in turn is a subclass of class Executor. 'Real work' is done by the function nextStep() which is typical of class Executor. Here, three different operations are possible: Scaling, where you can multiply the data values by a certain factor and apply a shift; Squaring, where as the name suggests, you can take a square of the data values; and Smoothening, where you can take the arithmatic average of 3 or 5 samples depending on the filter strength. Traces are read one-by-one by a SeisTrcReader and supplied to the function handleTrace() where the actual computation is done. Then a SeisTrcWriter writes the output traces on-by-one to the output cube.int Tut::SeisTools::nextStep()
{
if ( !rdr_ )
return createReader() ? Executor::MoreToDo : Executor::ErrorOccurred;
int rv = rdr_->get( trcin_.info() );
if ( rv < 0 )
{ errmsg_ = rdr_->errMsg(); return Executor::ErrorOccurred; }
else if ( rv == 0 )
return Executor::Finished;
else if ( rv == 1 )
{
if ( !rdr_->get(trcin_) )
{ errmsg_ = rdr_->errMsg(); return Executor::ErrorOccurred; }
trcout_ = trcin_;
handleTrace();
if ( !wrr_ && !createWriter() )
return Executor::ErrorOccurred;
if ( !wrr_->put(trcout_) )
{ errmsg_ = wrr_->errMsg(); return Executor::ErrorOccurred; }
}
return Executor::MoreToDo;
}
handleTrace() below, smoothening involves multi-sample computation. It requires separate input and output traces. Otherwise, if we did the operation on the same trace, we would be taking the modified values of samples preceding the current sample. For the sake of simplicity, we make a copy of the input trace to store the output values. This is not a good practice as it results in duplication of data. But since it is a tutorial, our aim is to keep the code as simple as possible and leave the efficiency part for serious programming.void Tut::SeisTools::handleTrace()
{
switch ( action_ )
{
case Scale: {
SeisTrcPropChg stpc( trcout_ );
stpc.scale( factor_, shift_ );
} break;
case Square: {
for ( int icomp=0; icomp < trcin_.nrComponents(); icomp++ )
{
for ( int idx=0; idx < trcin_.size(); idx++ )
{
const float v = trcin_.get( idx, icomp );
trcout_.set( idx, v*v, icomp );
}
}
} break;
case Smooth: {
const int sgate = weaksmooth_ ? 3 : 5;
const int sgate2 = sgate/2;
for ( int icomp=0; icomp < trcin_.nrComponents(); icomp++ )
{
for ( int idx=0; idx < trcin_.size(); idx++ )
{
float sum = 0;
int count = 0;
for( int ismp=idx-sgate2; ismp <= idx+sgate2; ismp++)
{
const float val = trcin_.get( ismp, icomp );
if ( !mIsUdf(val) )
{
sum += val;
count++;
}
}
if ( count )
trcout_.set( idx, sum/count, icomp );
}
}
} break;
}
nrdone_++;
}
HorTool
SeisTools, HorTool performs some simple operations on horizons: thickness computation and smoothening. Each of these operations is handled by a subclass of HorTool which is a subclass of Executor and as expected the computation is performed by the function nextStep(). You may notice here that no object of class HorTool is defined anywhere. It is only used as the base class for classes ThicknessCalculator and HorSmoothener. Let us have a look at the nextStep() function in class ThicknessCalculator to see how the data values are accessed in a Horizon3D:int Tut::ThicknessCalculator::nextStep()
{
if ( !iter_->next(bid_) )
return Executor::Finished();
int nrsect = horizon1_->nrSections();
if ( horizon2_->nrSections() < nrsect ) nrsect = horizon2_->nrSections();
for ( EM::SectionID isect=0; isectdataSaver in the two classes. In ThicknessCalculator, it saves the auxilary data, whereas in HorSmoothener, it saves the geometry.The Tutorial Attribute
SeisTools. For our purpose, it suits well. But the main problem with this approach is the difficulty in multi-trace handling. Moreover, for large seismic volumes, handling each trace one-by-one may slow down the process. This brings us to another approach called Attributes. In this example, we define the Tutorial attribute to do things once done by SeisTools. As we discuss different aspects of making an attribute, we will also discuss its advantages over the direct seismic approach.Tutorial::initClass(). The class Tutorial ( tutorialattrib.h ) is defined as a subclass of Attrib::Provider class. Every attribute is a provider, each can thus be used as input for another attribute.Steering
initSteering() which makes the steering data available in the form of shifts relative to the central trace. To understand how this shift is used during computation, please refer to the horizontal smoothening section in the function computeData().
initClass()
EnumParam* action = new EnumParam( actionStr() );
action->addEnum( "Scale" );
action->addEnum( "Square" );
action->addEnum( "Smooth" );
desc->addParam( action );
setRequired(false)"createInstance()
updateDesc()
getInputOutput()
initSteering()
void Tutorial::initSteering()
{
if ( inputs[1] && inputs[1]->getDesc().isSteering() )
inputs[1]->initSteering( stepout_ );
}
getInputData()
bool Tutorial::getInputData( const BinID& relpos, int zintv )
{
if ( inpdata_.isEmpty() )
inpdata_ += 0;
const DataHolder* data = inputs[0]->getData( relpos, zintv );
if ( !data ) return false;
inpdata_.replace( 0, data);
if ( action_ ==2 && horsmooth_ )
{
steeringdata_ = inputs[1] ? inputs[1]->getData( relpos, zintv ) : 0;
const int maxlength = mMAX(stepout_.inl, stepout_.crl)*2 + 1;
while ( inpdata_.size() < maxlength * maxlength )
inpdata_ += 0;
for ( int idx=0; idxAttrib::Provider interface - see the Attrib::Provider
documentation. The basic idea is that for each sample of each
trace one or more attribute values can be calculated. The number of
attribute values (or outputs) is defined in the initClass()
function. If your input requires additional samples (timegate) or neighbouring
traces (stepout), you will have to define reqZMargin() and
reqStepout() respectively. computeData()
computeData() method. This is the place where you define the
mathematics for calculating the attribute. This function is called for each
trace of your output cube.
Tutorial::computeData function and compare it with the code in SeisTools. The algorithm for actual computation is the same in both the cases, but there is a marked difference in the manner in which seismic data is accessed in each case.
bool Tutorial::computeData( const DataHolder& output, const BinID& relpos,
int z0, int nrsamples, int threadid ) const
{
for ( int idx=0; idx < nrsamples; idx++ )
{
float outval = 0;
if ( action_==0 || action_==1 )
{
const float trcval = getInputValue( *inputdata_, dataidx_,
idx, z0 );
outval = action_==0 ? trcval * factor_ + shift_ :
trcval * trcval;
}
else if ( action_==2 && !horsmooth_ )
{
float sum = 0;
int count = 0;
for ( int isamp=sampgate_.start; isamp <= sampgate_.stop; isamp++ )
{
const float curval = getInputValue( *inpdata_[0], dataidx_,
idx + isamp, z0 );
if ( !mIsUdf(curval) )
{
sum += curval;
count ++;
}
}
outval = sum / count;
}
else if (action_ == 2 && horsmooth_ )
{
float sum = 0;
int count = 0;
for ( int posidx=0; posidx < inpdata_.size(); posidx++ )
{
if ( !inpdata_[posidx] ) continue;
const float shift = steeringdata_ ?
getInputValue( *steeringdata_,posidx, idx, z0 ) : 0;
const int sampidx = idx + ( mIsUdf(shift) ? 0 : mNINT(shift) );
if ( sampidx < 0 || sampidx >= nrsamples ) continue;
const float val = getInputValue( *inpdata_[posidx],
dataidx_, sampidx, z0 );
if ( !mIsUdf(val) )
{
sum += val;
count ++;
}
}
outval = sum / count;
}
setOutputValue( output, 0, idx, z0, outval );
}
return true;
}
Creating the Help Documention
The help system
WindowLinkTable.txt can be passed as a parameter during creation of the UI dialog. For example,
In the above code, uiTutSeisTools::uiTutSeisTools( uiParent* p )
: uiDialog( p, Setup( "Tut seismic tools",
"Specify process parameters",
"tut:105.0.1") )
"tut:105.0.1" is passed as the help id, where tut refers to the directory and 105.0.1 is the window link code of the corresponding help document.The 'credits' documentation
$WORK/doc/Credits.index.txt file. All files are opened by the browser via that key. It's easy to see, for example in Credits/tut/index.txt how this works. Note that the IDs in index.txt need no scope, the scope is already used to for the subdirectory (no scope is as usual 'base'). Then, and this is essential, you'll have to use setHaveCredits( true ); in the constructor of your dialog. This will add the 'credits' button to the dialog.
index.html. If it is present, an entry will be made in the top-level Help-Credits menu.
Installation and auto-loading
Preparing a plugin for auto-load
#include "plugins.h"
extern "C" int GetTutPluginType()
{
return PI_AUTO_INIT_EARLY;
}
#include "plugins.h" is needed for the PluginInfo
structure and the PI_AUTO_INIT_xxx defines. Installing plugins for auto-load
$DTECT_APPL/plugins/$HDIR)($HOME/.od/plugins/$HDIR on Unix)Using .alo files
Annotations
Madagascar
uiMadagascar
CmdDriver
GMT
uiGMT
Distributing your plugins:
$DTECT_APPL/plugins/$HDIR) directory, while the actual
plugins come in the 'libs' sub-directory.