Table of contents:
or here (substitute today's date for YYYYMMDD):
Optionally, if you're interested in playing around with the visual object editor program, you can also build itcl (incr tcl) and iwidgets (incr widgets), for which I also have tarballs:
Assuming you have all the tarballs sitting in a "$BUILD" directory somewhere (e.g. perhaps ~/build/), and that you'd like to install everything to $INSTALL (e.g. perhaps ~/local or /usr/local), then
### unpack all the tarballs cd $BUILD tar xfz tcl8.5a1-src.tar.gz tar xfz tk8.5a1-src.tar.gz tar xfz itcl3.2.1_src.tgz # optional tar xfz iwidgets4.0.1.tar.gz # optional tar xfz groovx_20041004.tar.gz ### configure+build tcl cd $BUILD/tcl8.5a1/unix ./configure --prefix=$INSTALL/tcl8.5a1 make make install ### configure+build tk cd $BUILD/tk8.5a1/unix ./configure --prefix=$INSTALL/tcl8.5a1 make make install ### optional: configure+build itcl cd $BUILD/itcl3.2.1 ./configure --prefix=$INSTALL/tcl8.5a1 make make install ### optional: configure+build iwidgets (this requires itcl) cd $BUILD/iwidgets4.0.1 ./configure --prefix=$INSTALL/tcl8.5a1 \ --with-itcl=$BUILD/itcl3.2.1 \ --with-itk=$BUILD/itcl3.2.1 # no need to do "make", just do "make install" make install ### now configure+build groovx itself cd $BUILD/groovx_20041004 ./configure \ --prefix=$INSTALL/groovx \ --with-tcltk=$INSTALL/tcl8.5a1 \ --disable-matlab # this will make+install everything, plus run a test suite: make
Note that any of the configure scripts will recognize a --help option (i.e. do "./configure --help"), causing the script to print a list of all the configure-time options that it accepts.
You can start the shell interactively by calling $INSTALL/groovx/bin/groovx (where $INSTALL is the install directory you chose when configuring groovx). Of course, if $INSTALL/groovx/bin is in your $PATH environment variable, then you can just call 'groovx'.
Here's the obligatory "hello, world" example for groovx. Assuming you start from a standard shell prompt ("$"), you start the groovx shell, and then issue a series of commands. The groovx prompt ends with ">>>", so everything following that prompt on a given line is what you would type. Here, I've added comments following "#" characters which you would not see or type during an actual shell session.
[sideswipe 13:26 53]$ groovx GroovX 1.0a1 (Feb 22 2005) Copyright (c) 1998-2004 California Institute of Technology Copyright (c) 2004-2007 University of Southern California GroovX is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. startup time (tcl+tk) 0.160s (user) 0.019s (sys) 0.198s (wall) startup time (GroovX) 0.151s (user) 0.062s (sys) 0.395s (wall) groovx>>> set g [new GxText] 19 # 19 is the id of the new text object groovx>>> -> $g text "Hello, World" groovx>>> see $g
Now you should see the text "Hello, World" in your groovx window.
As an aside, one way to get a list of all the classnames known to the "new" command is to call the "new" command with a bogus classname (such as the ever-popular "foobar"):
groovx>>> new foobar Obj::new: known types are: AbortTrialEvent AllowResponsesEvent Bitmap Block ClearBufferEvent CloneFace DenyResponsesEvent DrawEvent EndTrialEvent EventResponseHdlr ExptDriver Face FileWriteEvent FilledFace FinishDrawingEvent Fish FixPt Gabor GaborArray GenericEvent Gtext GxColor GxCylinder GxDisk GxDrawStyle GxEmptyNode GxFixedScaleCamera GxLighting GxLine GxMaterial GxPerspectiveCamera GxPixmap GxPointSet GxPsyphyCamera GxScaler GxSeparator GxSphere GxText GxTransform House InnerFace Jitter KbdResponseHdlr MaskHatch MorphyFace MultiEvent NextNodeEvent NullResponseHdlr NullTrialEvent output_file RenderBackEvent RenderEvent RenderFrontEvent Sound SwapBuffersEvent TimingHandler TimingHdlr Toglet TopToglet Trial UndrawEvent unknown object type 'foobar' (rutz::error)
Let's return to our "Hello, World" example and explore a little deeper. Groovx is a roughly object-oriented system (after all the acronym was originally conceived as a GRaphical Object-Oriented Visual eXperiments). So, in our example, what we have stored in the variable "g" is the unique object-id (or UID) for a GxText object. UIDs in groovx are like raw addresses in C or C++; they uniquely refer to a particular object. Groovx uses a reference-counting system behind the scenes.
The arrow syntax with the pseudo-operator "->" is just a bit of syntactic sugar. Note that the order of arguments is different than in C or C++ -- there, you might say
ptr->someFunction()
but in groovx you'd say
-> $ptr someFunction"
In fact, with Tcl's extremely simple and uniform syntax, "->" is just a command like any other, only with a strange-looking name. So, the following two commands are precisely equivalent:
-> $object command arg1 arg2 arg3 Class::command $object arg1 arg2 arg3
where "Class" is the class of the given $object. What "->" does is to look up the class of $object, and then call the command as shown in the second form above.
OK, we've seen what "->" means, now let's use it to explore the text object above.
First, let's see what its type is:
groovx>>> -> $g type GxText
Good, that makes sense since we created it by calling "new GxText".
Given that its type is GxText, we know that calling "-> $g command" is equivalent to calling "GxText::command $g". Now, when Tcl is running in interactive mode, if you type a partial command name, Tcl will try to make a complete command name out of the partial name. If there is a unique match, Tcl will call it; otherwise, it will helpfully list all possible matches. So in our case, we do the following to get a list of all possible commands in the GxText:: namespace:
groovx>>> GxText::
ambiguous command name "GxText::": ::GxText::alignmentMode
::GxText::allFields ::GxText::aspectRatio ::GxText::bbVisibility
::GxText::boundingBox ::GxText::category ::GxText::centerX
::GxText::centerY ::GxText::contains ::GxText::countAll
::GxText::decr_ref_count ::GxText::deepChildren ::GxText::delete
::GxText::fields ::GxText::findAll ::GxText::font
::GxText::height ::GxText::heightFactor ::GxText::incr_ref_count
::GxText::is ::GxText::loadASW ::GxText::loadGVX
::GxText::maxDimension ::GxText::new ::GxText::newarr
::GxText::readASW ::GxText::readLGX ::GxText::realType
::GxText::refCount ::GxText::removeAll ::GxText::renderMode
::GxText::retrieveASW ::GxText::saveASW ::GxText::saveGVX
::GxText::savePS ::GxText::scalingMode ::GxText::strokeWidth
::GxText::text ::GxText::traceOff ::GxText::traceOn
::GxText::traceStatus ::GxText::traceToggle ::GxText::type
::GxText::width ::GxText::widthFactor ::GxText::writeASW
::GxText::writeGVX ::GxText::writeLGX ::GxText::xml_debug
So this shows us all possible commands that we can call on a GxText object. You'll notice that among them is GxText::type, the command that we previously used to get the type of the object.
Groovx has a mostly-working self-documentation system. For all groovx commands, you can use "?" to get information about how to use the command. For example, for GxText::text, you can do the following:
groovx>>> ? GxText::text GxText::text resolves to ::GxText::text ::GxText::text objref(s) ::GxText::text objref(s) new_val(s)
What that result tells you is that there are two syntaxes for calling the GxText::text command: in the first, you just call it with an objref (or object-id), in which case it returns the current 'text' value for that object; in the second, you call it with an objref plus a new 'text' value (that's how we specified the "Hello, World" value previously).
One thing to note is that with the way that "inheritance" currently works in groovx (by using Tcl's namespace importing mechanism), inherited commands will appear under their base name when looked up with "?". Thus:
groovx>>> ? GxText::type GxText::type resolves to ::Obj::type ::Obj::type objref(s) (defined at src/io/objtcl.cc:205)
shows that GxType::type is an inherited command, with the original command being Obj::type.
Similarly with GxText::loadGVX, which is inherited from IO::loadGVX:
groovx>>> ? GxText::loadGVX GxText::loadGVX resolves to ::IO::loadGVX ::IO::loadGVX filename (defined at src/io/iotcl.cc:69)
Finally, you can even use the "?" command to look up itself:
groovx 1>>> ? ? ? resolves to ::? ::? cmd_name (defined at src/tcl/misctcl.cc:148)
A useful tool for inspecting objects involves using the persistence layer built into the C++ framework. This is a generic serialization API that allows objects to serialize and deserialize independent of the "backend" -- that is, independent of the ultimate storage medium and formatting.
One of the available formats is an XML format (called the GVX format, using filenames with a .gvx extension). There are commands to read/write GVX format from strings in the groovx shell, as well as to read/write GVX format to files in the filesystem.
To dump an object's contents to a string in the groovx shell, do the following (assuming you still have the $g object with "Hello, World" still sitting around):
groovx>>> -> $g writeGVX <?xml version="1.0"?> <!-- GroovX XML 1 --> <object type="GxText" id="1" name="root" version="3"> <string name="text">Hello, World</string> <int name="strokeWidth" value="2"/> <baseclass type="GxShapeKit" id="2" name="GxShapeKit" version="3"> <int name="category" value="-1"/> <int name="renderMode" value="1"/> <bool name="bbVisibility" value="0"/> <int name="scalingMode" value="2"/> <double name="widthFactor" value="2.46548"/> <double name="heightFactor" value="2.46548"/> <int name="alignmentMode" value="2"/> <double name="centerX" value="0"/> <double name="centerY" value="0"/> </baseclass> </object>
This is a useful way to garner some insight into the internals of the toolkit. You'll see that GxText has "GxShapeKit" as a subclass. You'll also see that the text message "Hello, World" is displayed as well. To capture these GVX contents in a string, do the following (reminder: square brackets [] in Tcl are used to evaluate the expression inside, and return its result):
groovx>>> set s [-> $g writeGVX]
Similarly, GVX output can be written to a file using saveGVX, like this:
groovx>>> -> $g saveGVX foo.gvx
You can verify the contents of the file "foo.gvx" by doing "cat foo.gvx" at the groovx shell prompt (in interactive mode, if Tcl sees a command it doesn't recognize it will first look to see if that command is an executable in the user's $PATH, which should be the case for "cat").
Now, to restore the contents of the foo.gvx file at a later time, use the IO::loadGVX command:
groovx>>> set g2 [IO::loadGVX foo.gvx]
40
You can verify that $g2 now holds the same values as $g by doing "-> $g2 writeGVX".
Also note that groovx can transparently handle compressed files in most cases. For saving files, groovx will apply gzip compression if you give it a filename ending in .gz, like this:
groovx>>> -> $g saveGVX foo.gvx.gz
Verify that compression actually took place using Tcl's "file size" command to get the size of each file in bytes:
groovx>>> file size foo.gvx 630 groovx>>> file size foo.gvx.gz 301
For loading files, compression is handled completely transparently:
groovx>>> set g3 [IO::loadGVX foo.gvx] groovx>>> set g4 [IO::loadGVX foo.gvx.gz]
#!/usr/bin/env groovx
for the shbang line. Alternatively, you could use that same shbang form if you have created a symbolic link in some directory that IS in your $PATH, pointing to groovx's real location, e.g.:
ln -s $INSTALL/groovx/bin /usr/local/bin/groovx
Otherwise, you can specify the full path to the groovx executable in the shbang line:
#!/path/to/installation/groovx/bin/groovx
$INSTALL/groovx/bin/sample_expt.tcl
to get a sample version of my free-viewing psychophysics experiment. The original source for that script would be found in $BUILD/groovx/share/scripts/sample_expt.tcl if you want to read the source, experiment with changing it, etc.
When you run sample_expt.tcl, it will go through 10 free-viewing trials with some sample images I've included with groovx (you have to actually respond on the response screens by pressing '1' or '2' to keep the experiment moving along, thought). Once the experiment quits, you'll be left with three files like the following:
The .gvx file is an XML file that contains the entire object tree of your experiment, and should begin something like the following:
<?xml version="1.0"?> <!-- GroovX XML 1 --> <object type="ExptDriver" id="1" name="root" version="6"> <string name="hostname">localhost.localdomain</string> <string name="subject">/home/rjpeters/tmp/groovx_20041004</string> <string name="beginDate">Mon Oct 04 19:27:10 PDT 2004</string> <string name="endDate">Mon Oct 04 19:28:11 PDT 2004</string> <string name="autosaveFile">__autosave_file</string> <int name="autosavePeriod" value="10"/> <string name="infoLog">@Mon Oct 04 19:27:09 PDT 2004 groovx /home/rjpeters/local/groovx_20041004/bin/sample_expt.tcl @Mon Oct 04 19:27:10 PDT 2004 Beginning experiment. @Mon Oct 04 19:27:46 PDT 2004 Experiment saved. @Mon Oct 04 19:28:11 PDT 2004 Experiment complete. </string> <ownedobj type="tcl::ProcWrapper" id="2" name="doWhenComplete" version="0"> <string name="args"/> <string name="body">exit</string> </ownedobj> ...
The .log file contains a verbose log of everything that happened during the experiment, and may be useful for debugging as well as for checking how the exact timing of things played out during the experiment. Each line of the log file shows the current time (in milliseconds) relative to various reference points; e.g. the following line
ExptDriver(19) @ 3549.279 | Trial(286) @ 44.388 | RenderBackEvent(462) @ 0.362 | entering RenderBackEvent(462)
was written at 3549ms after the start of the experiment (which was object number 19), and at 44ms after the start of Trial (object number 286), and 0.36ms after the start of the RenderBackEvent.
Lines that may be of particular interest:
ExptDriver(19) @ 8057.190 | Trial(286) @ 4552.299 | event_info: 1
ExptDriver(19) @ 8057.439 | Trial(286) @ 4552.548 | response val: 1
Here's what an excerpt of the log file contents showing a single trial.
ExptDriver(19) @ 3517.003 | EndTrialEvent(599) @ 13.400 | Trial(286) @ 12.112 | entering Trial(286) ExptDriver(19) @ 3517.237 | EndTrialEvent(599) @ 13.634 | Trial(286) @ 12.346 | image /home/rjpeters/local/groovx_20041004/bin/../share/images/nga02.jpg ExptDriver(19) @ 3517.398 | EndTrialEvent(599) @ 13.795 | Trial(286) @ 12.507 | binding to ::__ERHPrivate::handler1 ExptDriver(19) @ 3517.709 | EndTrialEvent(599) @ 14.106 | Trial(286) @ 12.818 | scheduled @ 1: DenyResponsesEvent(458) ExptDriver(19) @ 3517.876 | EndTrialEvent(599) @ 14.273 | Trial(286) @ 12.985 | scheduled @ 25: RenderBackEvent(462) ExptDriver(19) @ 3518.013 | EndTrialEvent(599) @ 14.410 | Trial(286) @ 13.122 | scheduled @ 50: RenderEvent(466) ExptDriver(19) @ 3518.168 | EndTrialEvent(599) @ 14.565 | Trial(286) @ 13.277 | scheduled @ 100: MultiEvent(470) ExptDriver(19) @ 3518.307 | EndTrialEvent(599) @ 14.704 | Trial(286) @ 13.416 | scheduled @ 300: NextNodeEvent(482) ExptDriver(19) @ 3518.448 | EndTrialEvent(599) @ 14.845 | Trial(286) @ 13.557 | scheduled @ 500: ClearBufferEvent(486) ExptDriver(19) @ 3518.586 | EndTrialEvent(599) @ 14.983 | Trial(286) @ 13.695 | scheduled @ 600: RenderEvent(490) ExptDriver(19) @ 3518.730 | EndTrialEvent(599) @ 15.127 | Trial(286) @ 13.839 | scheduled @ 1000: MultiEvent(494) ExptDriver(19) @ 3518.868 | EndTrialEvent(599) @ 15.265 | Trial(286) @ 13.977 | scheduled @ 1100: NextNodeEvent(506) ExptDriver(19) @ 3519.009 | EndTrialEvent(599) @ 15.406 | Trial(286) @ 14.118 | scheduled @ 1200: ClearBufferEvent(510) ExptDriver(19) @ 3519.147 | EndTrialEvent(599) @ 15.544 | Trial(286) @ 14.256 | scheduled @ 1300: RenderEvent(514) ExptDriver(19) @ 3519.291 | EndTrialEvent(599) @ 15.688 | Trial(286) @ 14.400 | scheduled @ 4000: MultiEvent(518) ExptDriver(19) @ 3519.426 | EndTrialEvent(599) @ 15.823 | Trial(286) @ 14.535 | leaving EndTrialEvent(599) ExptDriver(19) @ 3519.596 | Trial(286) @ 14.705 | DenyResponsesEvent(458) @ 0.095 | entering DenyResponsesEvent(458) ExptDriver(19) @ 3519.716 | Trial(286) @ 14.825 | DenyResponsesEvent(458) @ 0.215 | req 1 - -0 ExptDriver(19) @ 3519.835 | Trial(286) @ 14.944 | DenyResponsesEvent(458) @ 0.334 | trDenyResponses ExptDriver(19) @ 3520.016 | Trial(286) @ 15.125 | DenyResponsesEvent(458) @ 0.515 | leaving DenyResponsesEvent(458) ExptDriver(19) @ 3549.279 | Trial(286) @ 44.388 | RenderBackEvent(462) @ 0.362 | entering RenderBackEvent(462) ExptDriver(19) @ 3549.480 | Trial(286) @ 44.589 | RenderBackEvent(462) @ 0.563 | req 25 - -0 ExptDriver(19) @ 3549.681 | Trial(286) @ 44.790 | RenderBackEvent(462) @ 0.764 | leaving RenderBackEvent(462) ExptDriver(19) @ 3574.454 | Trial(286) @ 69.563 | RenderEvent(466) @ 0.161 | entering RenderEvent(466) ExptDriver(19) @ 3574.588 | Trial(286) @ 69.697 | RenderEvent(466) @ 0.295 | req 50 - -0 ExptDriver(19) @ 3574.757 | Trial(286) @ 69.866 | RenderEvent(466) @ 0.464 | current node is GxSeparator(241) ExptDriver(19) @ 3576.642 | Trial(286) @ 71.751 | RenderEvent(466) @ 2.349 | leaving RenderEvent(466) ExptDriver(19) @ 3623.771 | Trial(286) @ 118.880 | MultiEvent(470) @ 0.160 | entering MultiEvent(470) ExptDriver(19) @ 3623.906 | Trial(286) @ 119.015 | MultiEvent(470) @ 0.295 | req 100 - -0 ExptDriver(19) @ 3624.069 | Trial(286) @ 119.178 | MultiEvent(470) @ 0.458 | SwapBuffersEvent(474) @ 0.111 | entering SwapBuffersEvent(474) ExptDriver(19) @ 3624.214 | Trial(286) @ 119.323 | MultiEvent(470) @ 0.603 | SwapBuffersEvent(474) @ 0.256 | req 0 - -0 ExptDriver(19) @ 3624.410 | Trial(286) @ 119.519 | MultiEvent(470) @ 0.799 | SwapBuffersEvent(474) @ 0.452 | leaving SwapBuffersEvent(474) ExptDriver(19) @ 3624.585 | Trial(286) @ 119.694 | MultiEvent(470) @ 0.974 | FinishDrawingEvent(478) @ 0.111 | entering FinishDrawingEvent(478) ExptDriver(19) @ 3624.731 | Trial(286) @ 119.840 | MultiEvent(470) @ 1.120 | FinishDrawingEvent(478) @ 0.257 | req 0 - -0 ExptDriver(19) @ 3637.045 | Trial(286) @ 132.154 | MultiEvent(470) @ 13.434 | FinishDrawingEvent(478) @ 12.571 | leaving FinishDrawingEvent(478) ExptDriver(19) @ 3637.210 | Trial(286) @ 132.319 | MultiEvent(470) @ 13.599 | leaving MultiEvent(470) ExptDriver(19) @ 3823.634 | Trial(286) @ 318.743 | NextNodeEvent(482) @ 0.165 | entering NextNodeEvent(482) ExptDriver(19) @ 3823.769 | Trial(286) @ 318.878 | NextNodeEvent(482) @ 0.300 | req 300 - -0 ExptDriver(19) @ 3823.904 | Trial(286) @ 319.013 | NextNodeEvent(482) @ 0.435 | leaving NextNodeEvent(482) ExptDriver(19) @ 4023.610 | Trial(286) @ 518.719 | ClearBufferEvent(486) @ 0.168 | entering ClearBufferEvent(486) ExptDriver(19) @ 4023.774 | Trial(286) @ 518.883 | ClearBufferEvent(486) @ 0.332 | req 500 - -0 ExptDriver(19) @ 4023.986 | Trial(286) @ 519.095 | ClearBufferEvent(486) @ 0.544 | leaving ClearBufferEvent(486) ExptDriver(19) @ 4123.581 | Trial(286) @ 618.690 | RenderEvent(490) @ 0.162 | entering RenderEvent(490) ExptDriver(19) @ 4123.715 | Trial(286) @ 618.824 | RenderEvent(490) @ 0.296 | req 600 - -0 ExptDriver(19) @ 4123.881 | Trial(286) @ 618.990 | RenderEvent(490) @ 0.462 | current node is GxPixmap(266) ExptDriver(19) @ 4135.669 | Trial(286) @ 630.778 | RenderEvent(490) @ 12.250 | loaded image file /home/rjpeters/local/groovx_20041004/bin/../share/images/nga02.jpg ExptDriver(19) @ 4154.162 | Trial(286) @ 649.271 | RenderEvent(490) @ 30.743 | leaving RenderEvent(490) ExptDriver(19) @ 4523.635 | Trial(286) @ 1018.744 | MultiEvent(494) @ 0.162 | entering MultiEvent(494) ExptDriver(19) @ 4523.771 | Trial(286) @ 1018.880 | MultiEvent(494) @ 0.298 | req 1000 - -0 ExptDriver(19) @ 4523.935 | Trial(286) @ 1019.044 | MultiEvent(494) @ 0.462 | SwapBuffersEvent(498) @ 0.111 | entering SwapBuffersEvent(498) ExptDriver(19) @ 4524.081 | Trial(286) @ 1019.190 | MultiEvent(494) @ 0.608 | SwapBuffersEvent(498) @ 0.257 | req 0 - -0 ExptDriver(19) @ 4524.280 | Trial(286) @ 1019.389 | MultiEvent(494) @ 0.807 | SwapBuffersEvent(498) @ 0.456 | leaving SwapBuffersEvent(498) ExptDriver(19) @ 4524.456 | Trial(286) @ 1019.565 | MultiEvent(494) @ 0.983 | FinishDrawingEvent(502) @ 0.112 | entering FinishDrawingEvent(502) ExptDriver(19) @ 4524.602 | Trial(286) @ 1019.711 | MultiEvent(494) @ 1.129 | FinishDrawingEvent(502) @ 0.258 | req 0 - -0 ExptDriver(19) @ 4536.783 | Trial(286) @ 1031.892 | MultiEvent(494) @ 13.310 | FinishDrawingEvent(502) @ 12.439 | leaving FinishDrawingEvent(502) ExptDriver(19) @ 4536.938 | Trial(286) @ 1032.047 | MultiEvent(494) @ 13.465 | leaving MultiEvent(494) ExptDriver(19) @ 4623.734 | Trial(286) @ 1118.843 | NextNodeEvent(506) @ 0.162 | entering NextNodeEvent(506) ExptDriver(19) @ 4623.870 | Trial(286) @ 1118.979 | NextNodeEvent(506) @ 0.298 | req 1100 - -0 ExptDriver(19) @ 4624.003 | Trial(286) @ 1119.112 | NextNodeEvent(506) @ 0.431 | leaving NextNodeEvent(506) ExptDriver(19) @ 4721.174 | Trial(286) @ 1216.283 | ClearBufferEvent(510) @ 0.167 | entering ClearBufferEvent(510) ExptDriver(19) @ 4721.312 | Trial(286) @ 1216.421 | ClearBufferEvent(510) @ 0.305 | req 1200 - -0 ExptDriver(19) @ 4721.526 | Trial(286) @ 1216.635 | ClearBufferEvent(510) @ 0.519 | leaving ClearBufferEvent(510) ExptDriver(19) @ 4823.696 | Trial(286) @ 1318.805 | RenderEvent(514) @ 0.158 | entering RenderEvent(514) ExptDriver(19) @ 4823.832 | Trial(286) @ 1318.941 | RenderEvent(514) @ 0.294 | req 1300 - -0 ExptDriver(19) @ 4824.001 | Trial(286) @ 1319.110 | RenderEvent(514) @ 0.463 | current node is GxSeparator(101) ExptDriver(19) @ 4828.685 | Trial(286) @ 1323.794 | RenderEvent(514) @ 5.147 | leaving RenderEvent(514) ExptDriver(19) @ 7523.559 | Trial(286) @ 4018.668 | MultiEvent(518) @ 0.300 | entering MultiEvent(518) ExptDriver(19) @ 7523.743 | Trial(286) @ 4018.852 | MultiEvent(518) @ 0.484 | req 4000 - -0 ExptDriver(19) @ 7523.956 | Trial(286) @ 4019.065 | MultiEvent(518) @ 0.697 | SwapBuffersEvent(522) @ 0.157 | entering SwapBuffersEvent(522) ExptDriver(19) @ 7524.149 | Trial(286) @ 4019.258 | MultiEvent(518) @ 0.890 | SwapBuffersEvent(522) @ 0.350 | req 0 - -0 ExptDriver(19) @ 7524.399 | Trial(286) @ 4019.508 | MultiEvent(518) @ 1.140 | SwapBuffersEvent(522) @ 0.600 | leaving SwapBuffersEvent(522) ExptDriver(19) @ 7542.130 | Trial(286) @ 4037.239 | MultiEvent(518) @ 18.871 | FinishDrawingEvent(526) @ 17.662 | entering FinishDrawingEvent(526) ExptDriver(19) @ 7542.390 | Trial(286) @ 4037.499 | MultiEvent(518) @ 19.131 | FinishDrawingEvent(526) @ 17.922 | req 0 - -0 ExptDriver(19) @ 7542.593 | Trial(286) @ 4037.702 | MultiEvent(518) @ 19.334 | FinishDrawingEvent(526) @ 18.125 | leaving FinishDrawingEvent(526) ExptDriver(19) @ 7542.787 | Trial(286) @ 4037.896 | MultiEvent(518) @ 19.528 | AllowResponsesEvent(530) @ 0.117 | entering AllowResponsesEvent(530) ExptDriver(19) @ 7543.574 | Trial(286) @ 4038.683 | MultiEvent(518) @ 20.315 | AllowResponsesEvent(530) @ 0.904 | req 0 - -0 ExptDriver(19) @ 7543.778 | Trial(286) @ 4038.887 | MultiEvent(518) @ 20.519 | AllowResponsesEvent(530) @ 1.108 | trAllowResponses ExptDriver(19) @ 7543.983 | Trial(286) @ 4039.092 | MultiEvent(518) @ 20.724 | AllowResponsesEvent(530) @ 1.313 | binding to ::__ERHPrivate::handler1 ExptDriver(19) @ 7544.337 | Trial(286) @ 4039.446 | MultiEvent(518) @ 21.078 | AllowResponsesEvent(530) @ 1.667 | leaving AllowResponsesEvent(530) ExptDriver(19) @ 7546.764 | Trial(286) @ 4041.873 | MultiEvent(518) @ 23.505 | leaving MultiEvent(518) ExptDriver(19) @ 8057.190 | Trial(286) @ 4552.299 | event_info: 1 ExptDriver(19) @ 8057.439 | Trial(286) @ 4552.548 | response val: 1 ExptDriver(19) @ 8057.578 | Trial(286) @ 4552.687 | trProcessResponse ExptDriver(19) @ 8057.781 | Trial(286) @ 4552.890 | MultiEvent(534) @ 0.090 | entering MultiEvent(534) ExptDriver(19) @ 8057.900 | Trial(286) @ 4553.009 | MultiEvent(534) @ 0.209 | req 0 - -0 ExptDriver(19) @ 8058.060 | Trial(286) @ 4553.169 | MultiEvent(534) @ 0.369 | ClearBufferEvent(538) @ 0.111 | entering ClearBufferEvent(538) ExptDriver(19) @ 8058.206 | Trial(286) @ 4553.315 | MultiEvent(534) @ 0.515 | ClearBufferEvent(538) @ 0.257 | req 0 - -0 ExptDriver(19) @ 8058.437 | Trial(286) @ 4553.546 | MultiEvent(534) @ 0.746 | ClearBufferEvent(538) @ 0.488 | leaving ClearBufferEvent(538) ExptDriver(19) @ 8058.610 | Trial(286) @ 4553.719 | MultiEvent(534) @ 0.919 | SwapBuffersEvent(542) @ 0.110 | entering SwapBuffersEvent(542) ExptDriver(19) @ 8058.756 | Trial(286) @ 4553.865 | MultiEvent(534) @ 1.065 | SwapBuffersEvent(542) @ 0.256 | req 0 - -0 ExptDriver(19) @ 8069.660 | Trial(286) @ 4564.769 | MultiEvent(534) @ 11.969 | SwapBuffersEvent(542) @ 11.160 | leaving SwapBuffersEvent(542) ExptDriver(19) @ 8069.847 | Trial(286) @ 4564.956 | MultiEvent(534) @ 12.156 | FinishDrawingEvent(546) @ 0.115 | entering FinishDrawingEvent(546) ExptDriver(19) @ 8069.998 | Trial(286) @ 4565.107 | MultiEvent(534) @ 12.307 | FinishDrawingEvent(546) @ 0.266 | req 0 - -0 ExptDriver(19) @ 8094.967 | Trial(286) @ 4590.076 | MultiEvent(534) @ 37.276 | FinishDrawingEvent(546) @ 25.235 | leaving FinishDrawingEvent(546) ExptDriver(19) @ 8095.148 | Trial(286) @ 4590.257 | MultiEvent(534) @ 37.457 | leaving MultiEvent(534) ExptDriver(19) @ 8095.274 | Trial(286) @ 4590.383 | scheduled @ 0: MultiEvent(534) ExptDriver(19) @ 8095.394 | Trial(286) @ 4590.503 | scheduled @ 500: EndTrialEvent(550) ExptDriver(19) @ 8603.020 | Trial(286) @ 5098.129 | EndTrialEvent(550) @ 0.163 | entering EndTrialEvent(550) ExptDriver(19) @ 8603.157 | Trial(286) @ 5098.266 | EndTrialEvent(550) @ 0.300 | req 500 - -0 ExptDriver(19) @ 8603.275 | Trial(286) @ 5098.384 | EndTrialEvent(550) @ 0.418 | Trial::trEndTrial ExptDriver(19) @ 8603.604 | Trial(286) @ 5098.713 | EndTrialEvent(550) @ 0.747 | leaving Trial(286) ExptDriver(19) @ 8603.752 | EndTrialEvent(550) @ 0.895 | current element Trial(370), completed 2 of 11
Finally, the .resp file gives a summary of the responses across all trials, like this:
% Trial N Average msec description 265 1 2.00 4719.00 % image /home/rjpeters/local/groovx_20041004/bin/../share/images/nga01.jpg 286 1 1.00 4539.00 % image /home/rjpeters/local/groovx_20041004/bin/../share/images/nga02.jpg 307 1 1.00 6726.00 % image /home/rjpeters/local/groovx_20041004/bin/../share/images/nga03.jpg 328 1 1.00 4420.00 % image /home/rjpeters/local/groovx_20041004/bin/../share/images/nga04.jpg 349 1 2.00 6858.00 % image /home/rjpeters/local/groovx_20041004/bin/../share/images/nga05.jpg 370 1 2.00 4558.00 % image /home/rjpeters/local/groovx_20041004/bin/../share/images/nga06.jpg 391 1 2.00 4468.00 % image /home/rjpeters/local/groovx_20041004/bin/../share/images/nga07.jpg 412 1 1.00 5305.00 % image /home/rjpeters/local/groovx_20041004/bin/../share/images/nga08.jpg 433 1 1.00 5082.00 % image /home/rjpeters/local/groovx_20041004/bin/../share/images/nga09.jpg 454 1 1.00 5114.00 % image /home/rjpeters/local/groovx_20041004/bin/../share/images/nga10.jpg 603 1 0.00 2496.00 % text message 'FREE-VIEWING SEQUENCE\n\nafter each image:\n press '1' if the region labeled '1' contained the most interesting feature\n press '2' if the region labeled '2' contained the most interesting feature\n\n[press the spacebar to continue]'
It's in a format that is suitable for importing into matlab using matlab's "load", since all of the non-numeric stuff is commented out with the % characters. The "Trial" column shows the object number of each trial; "N" shows how many times each trial was run; "Average" gives the average response value across those trials; "msec" gives the average response time of each response (in milliseconds, relative to the start of the corresponding trial); "description" just gives a textual description of the trial.
There are a couple mechanisms in place to help with the discovery of these relationships, involving different ways to get referred to specific places in the C++ codebase.
First of all, any time an error is generated by the groovx C++ code, a backtrace is stored internally (assuming that groovx was configured with --enable-debug, which is the default). To retrieve this backtrace, use the "bt" command from the groovx interpreter. For example, let's try to create an unknown object type:
groovx>>> new foobar
...long error message...
Now, we can use "bt" to see where things went wrong:
groovx>>> bt [ 0] nub::obj_mgr::new_obj(const fstring&) (src/util/objmgr.cc:47) [ 1] tcl/Obj::new (src/io/objtcl.cc:206) [ 2] tcl::command_group::rawInvoke (src/tcl/tclcommandgroup.cc:285) [ 3] tcl::MainImpl::execCommand (src/tcl/tclmain.cc:397) [ 4] tcl::MainImpl::handleLine (src/tcl/tclmain.cc:345) [ 5] tcl::MainImpl::readlineLineComplete (src/tcl/tclmain.cc:298) [ 6] tcl::MainImpl::grabInput (src/tcl/tclmain.cc:318) [ 7] tcl::MainImpl::stdinProc (src/tcl/tclmain.cc:521) [ 8] tcl::MainImpl::run (src/tcl/tclmain.cc:534) [ 9] tcl::event_loop::run (src/tcl/tclmain.cc:619) [10] main (src/grsh/grshAppInit.cc:234)
Note that this "stack" is built up using various DOTRACE statements placed through the code, so it is NOT identical to the execution stack that might be reported by e.g. a debugger, since certain functions do not have DOTRACE statements and hence would not show up in the "bt" stack, while other functions that might have been inlined by the compiler would not show up in a debugger backtrace but would still be visible in "bt".
Now, looking at the backtrace above, we can see several things:
return nub::soft_ref<nub::object>(nub::obj_factory::instance().new_checked_object(type));
So, clearly to get to the real bottom of things, one would have to dig a bit deeper; nevertheless "bt" gets you close.
pkg->def( "new", "typename", &objNew, SRC_POS );
[ 3] tcl::MainImpl::execCommand (src/tcl/tclmain.cc:397) [ 4] tcl::MainImpl::handleLine (src/tcl/tclmain.cc:345) [ 5] tcl::MainImpl::readlineLineComplete (src/tcl/tclmain.cc:298) [ 6] tcl::MainImpl::grabInput (src/tcl/tclmain.cc:318) [ 7] tcl::MainImpl::stdinProc (src/tcl/tclmain.cc:521) [ 8] tcl::MainImpl::run (src/tcl/tclmain.cc:534)
reflect that tcl has found some input available on stdin (stdinProc and grabInput), has found that it has a complete line of input (readlineLineComplete), has found that this complete line is also a complete tcl command (handleLine), and has thus executed this command (execCommand).
Just one more quick example of "bt"... suppose we're trying to get the eyeHeight of the Face object stored in variable $a, but we accidentally type "Face::eyeHeight a" instead of "FaceeyeHeight $a"; then we get the following backtrace:
groovx>>> Face::eyeHeight blah Face::eyeHeight: expected long value but got "blah" (rutz::error) groovx 5>>> bt [ 0] tcl::convert_to(long*) (src/tcl/tclconvert.cc:143) [ 1] tcl::convert_to(unsigned long*) (src/tcl/tclconvert.cc:159) [ 2] tcl::VecDispatcher::dispatch (src/tcl/tclveccmd.cc:157) [ 3] tcl/Face::eyeHeight (src/visx/facetcl.cc:65) [ 4] tcl::command_group::rawInvoke (src/tcl/tclcommandgroup.cc:285) [ 5] tcl::MainImpl::execCommand (src/tcl/tclmain.cc:397) [ 6] tcl::MainImpl::handleLine (src/tcl/tclmain.cc:345) [ 7] tcl::MainImpl::readlineLineComplete (src/tcl/tclmain.cc:298) [ 8] tcl::MainImpl::grabInput (src/tcl/tclmain.cc:318) [ 9] tcl::MainImpl::stdinProc (src/tcl/tclmain.cc:521) [10] tcl::MainImpl::run (src/tcl/tclmain.cc:534) [11] tcl::event_loop::run (src/tcl/tclmain.cc:619) [12] main (src/grsh/grshAppInit.cc:234)
Again, the line starting with "tcl/" shows the current entry point from the script side back into c++ (in this case the Face::eyeHeight command), and, from the contents of the stack above that line, we can see how the problem came from trying to convert the string "a" into an unsigned long value.
So, "bt" can be used to help tracking down where errors are occuring.
The other way to get pointers to specific locations in the c++ source is by looking up the usages associated with groovx script commands (either by using "?", or by giving the wrong number of arguments on purpose in order to force a usage warning):
groovx>>> ? IO::loadGVX ::IO::loadGVX filename (defined at src/io/iotcl.cc:67)
which points us to this:
pkg->def( "loadGVX", "filename", IO::loadGVX, SRC_POS );
or another example:
groovx>>> Face::eyeHeight wrong # args: should be one of: "Face::eyeHeight objref(s)" "Face::eyeHeight objref(s) new_val(s)" (resolves to ::Face::eyeHeight, defined at src/visx/facetcl.cc:65)
which points us to this:
tcl::defFieldContainer<Face>(pkg, SRC_POS);
#include "tcl/tcllistobj.h" #include "tcl/tclpkg.h" namespace Dlist { tcl::list choose(tcl::list source_list, tcl::list index_list) { tcl::list result; for (unsigned int i = 0; i < index_list.length(); ++i) { unsigned int index = index_list.get<unsigned int>(i); result.append(source_list.at(index)); } ASSERT(result.length() == index_list.length()); return result; } }
Here we see how the C++ class tcl::list wraps the functionality of Tcl list objects, making them easily manipulable within C++. Next, we expose this choose() function as a Tcl command, [dlist::choose]:
extern "C" int Dlist_Init(Tcl_Interp* interp) { PKG_CREATE(interp, "dlist", "$Revision: 10065 $"); pkg->def( "choose", "source_list index_list", &Dlist::choose, SRC_POS ); PKG_RETURN; }