DMDX Help.


StoreCoords Keyword

<StoreCoords l, t, r, b>
<sc l, t, r, b>


    Switch introduced in version 6.1.1.0 of DMDX to store the coordinates that a given frame was drawn on the screen at in the four predefined macros (in the order left, top, right and bottom as per standard windows RECT structures) in the normalized fractional form of a coordinate (that is between 0 and 1) with six significant digits (so 0.123456 for instance).  This keyword exists for eye tracker interfaces using <InputDevice tcpip> where one might want to check if the subjects gaze is upon a certain display element or not.  As with all macro definitions you can't use the macros this keyword manipulates in the same item they're defined in, or at least you can however they'll expand to whatever value they had before the item commenced being parsed -- doubly so here as the display routines won't set those macros till they're called after the item is parsed.
 
    While using this keyword is relatively straight forward for discrete frames a little creativity is required if you want to detect the location of a single word in a sentence, here you have to break the sentence up into individual words and present them with progressive X  (even though you may want the whole sentence presented simultaneously) in order to determine where any given word might be on the screen:
 
1 d2 <px .2> <dwc 255,255,255 stat> "Hickory ", "dickory ", "dock, ", "the ", <sc .l., .t., .r., .b.> "mouse ", "ran ", "up ", "the ", "clock";
 
    Here we're rendering the text in white on white because we have to determine where the target is in the item before it's presented, if not two items before and because it's nothing the subject is actually going to see rendering it as quickly as possible with a delay of two tics.  And just as a double check I also rendered it with points at the corners of the target display just to make sure it's fairly sane:
 
2 <x .2> <dwc 0,0,0 stat> "Hickory dickory dock, the moose ran up the clock", <dfm .3 stat> <xyjustification center> <xy ~.l., ~.t.> "◆", <xy ~.l., ~.b.> "◆", <xy ~.r., ~.t.> "◆", <xy ~.r., ~.b.> "◆" ; 

    Of course breaking the sentence up into words is probably superfluous and even counter productive if you want to catch POGs just before the target word and saccades past the target word and you instead want something like the following (note the space before mouse is now in the frame with mouse in it):
 
1 d2 <px .2> <dwc 255,255,255 stat> "Hickory dickory dock, the",
            <sc .l., .t., .r., .b.> " mouse ran up the clock";

 

    If you're are doing whole paragraphs with the <inst> keyword of course it's already broken up into individual frames so no big deal.
 
    I expect most people will be using <sc> with <expireif> (which is where that Hickory dickory dock example comes from) however for an example of how this keyword might be used here I'll include the original example written before <expireif> was created where we'll detect when the point of gaze from my pseudo eye tracker Debug Monitor used in the <send> example hits a target on the screen and we'll display the number field from out of the reply from the the Debug Monitor (so <ACK number="1" BPOGV="1" BPOGX="0.6655" BPOGY="0.8477"> for example) on the screen at the POG which requires the item file itself process the received TCP/IP reply (this example was also written before I added the tcpipreply functionality to <2Did>).  We're going to parse the BPOG fields out of the replies and check if the point of gaze ever hits a square on the screen and we'll also parse the number field to display it as well as to stop the script if the point of gaze never hits our target.  After requesting our 600 ACKs we're going to display our target and save the target region's coordinates in macros .l., .t., .r. and .b. (noting that macro .t. is distinct from macro T) and then we have a new subroutine at item 3000 that takes a macro and returns a fixed point five digit representation of it on account of DMDX not being able to handle floating point values so instead we represent them with (in this case) five digit integers ignoring the non-fractional component (so 0.6655 for instance would result in 66550).  We pass the saved position macros .l., .t., .r. and .b. to the item 3000 subroutine having their values converted in counters 200 through 203 that we'll use later on for hit detection.  So we've got three blocks of code (2200, 2300 and 2400, see the <send> documentation for a description of what's going on in those blocks) that parse the individual fields we want from the last reply into macros N, X and Y then we display it (because I don't have an eye tracker and need to see where the POG might be) and then we pass macros X and Y to item 3000 having their values converted in counters 100 and 101 and then we see if the coordinate c100, c101 lies within the c200, c201 to c202, c203 rectangle.  Note a real world implementation of this, at least with the Gazepoint eye trackers, would want to check the BPOGV field to make sure the BPOGX and BPOGY fields are valid:

<ep> <cr> <nfb> <safemode 1> <fd 10>
<id tcpip> <!branchdiagnostics>
<xyjustification center>
<eop>
0 "test tcpip reply parsing hit detection"
m.tcpipreply.++ <set c1=0>
m.l.+1+ m.t.+1+ m.r.+1+ m.b.+1+ <! macros for sc set to one so a syntax check won't puke on empty xy keywords as the sc code won't execute during a syntax check>;

1 <SEND "<requestack num=600 delay=1000 subsequentdelay=16>">
"send <requestack num=600 delay=1000 subsequentdelay=16>";
 
1 d2 <storecoords .l., .t., .r., .b.> <fm 6> "▯" <xy .5, .5> <! determine target coords>;
~1 mC+~.l.+ m.cntr.+200+ <call 3000> <! convert them to counters>;
~1 mC+~.t.+ m.cntr.+201+ <call 3000>;
~1 mC+~.r.+ m.cntr.+202+ <call 3000>;
~1 mC+~.b.+ m.cntr.+203+ <call 3000>;
~1 <emit 200> <emit 201> <emit 202> <emit 203>;

~1111 mS+~.tcpipreply.+ <! will be parsing this 3x so store orig in S and parse each field out of T three times> ;
 
~1 mT+~S+ ;
~2222 <macro pop T, c1> <bi 1111, c1 .eq. 0>;
~1 <bi 2222, c1 .ne. 'n'>;
~1 <macro pop T, c1> <bi 2222, c1 .ne. 'u'>;
~1 <macro pop T, c1> <bi 2222, c1 .ne. 'm'>;
~1 <macro pop T, c1> <bi 2222, c1 .ne. 'b'>;
~1 <macro pop T, c1> <bi 2222, c1 .ne. 'e'>;
~1 <macro pop T, c1> <bi 2222, c1 .ne. 'r'>;
~1 <macro pop T, c1> <bi 2222, c1 .ne. '='>;
~1 <macro pop T, c1> <bi 2222, c1 .ne. 34> <!double quote>;
~1 mR++ <! extract the field we want into R>;
~2230 <macro pop T, c1> <bi 1111, c1 .eq. 0>;
~1 <bi 2240, c1 .eq. 34> <!double quote>;
~1 <macro push R, c1> <bu 2230>;
~2240 mN++ <! reverse R into N>;
~2245 <macro pop R, c1> <bi 2250, c1 .eq. 0>;
~1 <macro push N, c1> <bu 2245>;
~2250;

~1 mT+~S+ <! and if we cared to assume a fixed order we could save ourselves some work by not going back to the source but we're not...> ;
~2322 <macro pop T, c1> <bi 1111, c1 .eq. 0>;
~1 <bi 2322, c1 .ne. 'B'>;
~1 <macro pop T, c1> <bi 2322, c1 .ne. 'P'>;
~1 <macro pop T, c1> <bi 2322, c1 .ne. 'O'>;
~1 <macro pop T, c1> <bi 2322, c1 .ne. 'G'>;
~1 <macro pop T, c1> <bi 2322, c1 .ne. 'X'>;
~1 <macro pop T, c1> <bi 2322, c1 .ne. '='>;
~1 <macro pop T, c1> <bi 2322, c1 .ne. 34> <!double quote>;
~1 mR++ <! extract the field we want into R>;
~2330 <macro pop T, c1> <bi 1111, c1 .eq. 0>;
~1 <bi 2340, c1 .eq. 34> <!double quote>;
~1 <macro push R, c1> <bu 2330>;
~2340 mX++ <! reverse R into X>;
~2345 <macro pop R, c1> <bi 2350, c1 .eq. 0>;
~1 <macro push X, c1> <bu 2345>;
~2350;

~1 mT+~S+ ;
~2422 <macro pop T, c1> <bi 1111, c1 .eq. 0>;
~1 <bi 2422, c1 .ne. 'B'>;
~1 <macro pop T, c1> <bi 2422, c1 .ne. 'P'>;
~1 <macro pop T, c1> <bi 2422, c1 .ne. 'O'>;
~1 <macro pop T, c1> <bi 2422, c1 .ne. 'G'>;
~1 <macro pop T, c1> <bi 2422, c1 .ne. 'Y'>;
~1 <macro pop T, c1> <bi 2422, c1 .ne. '='>;
~1 <macro pop T, c1> <bi 2422, c1 .ne. 34> <!double quote>;
~1 mR++ <! extract the field we want into R>;
~2430 <macro pop T, c1> <bi 1111, c1 .eq. 0>;
~1 <bi 2440, c1 .eq. 34> <!double quote>;
~1 <macro push R, c1> <bu 2430>;
~2440 mY++ <! reverse R into Y>;
~2445 <macro pop R, c1> <bi 2450, c1 .eq. 0>;
~1 <macro push Y, c1> <bu 2445>;
~2450;

1 d2 "~N" <xy ~X, ~Y>, <fm 6> "▯" <xy .5, .5>;

~1 mC+~X+ m.cntr.+100+ <call 3000>;
~1 mC+~Y+ m.cntr.+101+ <call 3000>;
~1 <emit 100> <emit 101>;

~1 <bi 0, (c100 .ge. c200) .and.
          (c100 .lt. c202) .and.
          (c101 .ge. c201) .and.
          (c101 .lt. c203)>;

~1 <bi 1111, ~N .lt. 600>;
0 "Done" L;

~3000 <macro pop C, c1> <bi 3010, c1 .eq. 0>;
~1 <bi 3000, c1 .ne. '.'> <! get to fractional component>;
~3010 <set c2 = 10000> <set c~.cntr. = 0> ;
~3020 <macro pop C, c1> <bi 3030, c1 .eq. 0>;
~1 <set c~.cntr. = c~.cntr. + (c1 - '0') * c2> <set c2 = c2 / 10> <bu 3020>;
~3030 <return> ;

    Depending on how trusting you are you could avoid parsing macro T from the start each time you extract a field from it by commenting out the lines with mT+~S+ in them and by setting macro T to ~.tcpipreply. instead of macro S in item 1111.  You save a bit of time but it all depends on how fast your machine is, on my pudgy 2017 budget laptop the time to process this script (excluding time spent displaying the POG and target) drops from 15 or so milliseconds down to 10 or so but the cost is that if the eye tracker ever reorders those fields your item file won't work any more.  Not to mention the fact that the quickest DMDX is going to be displaying anything in response to a reply is in two tics should your paradigm revolve around changing something on the screen as the point of gaze falls on it -- there you'll have to use the new <expireif> functionality (I expect that now that <expireif> exists pretty much the only reason someone might want to use this example is if they want some complex behavior based upon the gaze).

    I've used U+25AF hoping it would cover the entire region DMDX considers it's drawing region but it would appear to be quite a bit shorter and smaller in other dimensions and is probably exacerbated by oversize calculations and indeed after displaying points at the corners of the returned region I can see it's only about 2/3 of the height of the region, ho hum, if it matters to you you can always shrink it with fudgemification of the 200 series counters.  It's also possible using a graphic would be more accurate as DMDX doesn't have to rely on win32 text sizing functions or worry about stray pixels (the 23 in the graphic is from the end of a run of the above script when it included the corners):

1 d2 "~N" <xy ~X, ~Y>, <xy ~.l., ~.t.> "◆", <xy ~.l., ~.b.> "◆", <xy ~.r., ~.t.> "◆", <xy ~.r., ~.b.> "◆", <fm 6> "▯" <xy .5, .5>;




DMDX Index