#!/bin/sh # Restart with tcl: -*- mode: tcl; tab-width: 4; -*- \ exec wish $0 ${1+"$@"} # demo7-EEG.tcl - HaJo Gurt - 2005-12-26 - https://wiki.tcl-lang.org/15146 #: Demo - Toy-EEG: draw dots on head, colored according to data-values #########1#########2#########3#########4#########5#########6#########7##### package require Tk proc ReadFile fn { #: Read + parse datafile # Format of data (no tabs!) : # #### Fz Cz Pz FP1 FP2 F7 F3 F4 F8 T3 C3 C4 T4 T5 P3 P4 T6 O1 O2 A1 A2 # "0001 a a a x x b b b b d d d d t t t t x x 0 0" global Data P0 #puts "fn: $fn" ;## set i 0 set Comment 0 set nData 0 # Read file one line at a time: set fp [open $fn r] fconfigure $fp -buffering line gets $fp Line while {$Line ne {}} { incr i 1 #puts "$i: $Line" ;## set Line [string trim $Line] switch -- [string index $Line 0] { # {incr Comment 1} default { incr nData 1 set w 0 set D {} foreach Word [split $Line { }] { if {$Word ne {}} { if {$w == 0} { set Key $Word } else { #puts "$w '$Word'" ;## set Val grey catch { set Val $::ColorTab($Word) } lappend D $Val } incr w 1 } } set Data($nData) $D #puts "#> $nData $Key: '$D'" ;## } } ;#switch gets $fp Line } ;#while close $fp puts "#EOF: $nData = [array size Data]" ;## return [array size Data] } # #: 2D-Graphics Operations : affine transforms on a canvas # set ::pi [expr {atan(1)*4}] proc translation {dx dy} {list 1 0 0 1 $dx $dy} proc reflect-y {} {list 1 0 0 -1 0 0} ;# ? proc reflect-x {} {list -1 0 0 1 0 0} ;# ? proc shear {sx sy} {list 1 $sx $sy 1 0 0} proc rotation {angle {units radians}} { global pi switch -- $units { d - de - deg - degr - degre - degree - degrees { set angle [expr {double($angle)/180*$pi}] } g - gr - gra - grad - gradi - gradie - gradien - gradient - gradients { # I think I've spelt this one right... set angle [expr {double($angle)/200*$pi}] } r - ra - rad - radi - radia - radian - radians { # Do nothing } default { return -code error "unknown angle unit \"$units\": \ must be one of degrees, gradients or radians" } } list [expr { cos($angle)}] [expr {sin($angle)}] \ [expr {-sin($angle)}] [expr {cos($angle)}] 0 0 } proc apply_affine {transform args} { if {[llength $args]==1} {set args [lindex $args 0]} set result [list] foreach {a b c d e f} $transform {break} foreach {x y} $args { lappend result [expr {$a*$x+$b*$y+$e}] [expr {$c*$x+$d*$y+$f}] } return $result } proc combine_affine {transform args} { foreach {a b c d e f} $transform {break} foreach xform $args { foreach {i j k l m n} $xform {break} # Next line does simultaneous assignment... foreach {a b c d e f} [list \ [expr {$a*$i+$c*$j}] [expr {$b*$i+$d*$j}] \ [expr {$a*$k+$c*$l}] [expr {$b*$k+$d*$l}] \ [expr {$e*$i+$f*$j+$m}] [expr {$e*$k+$f*$l+$n}]] break } list $a $b $c $d $e $f } # # Routines to draw heads and set up their dot-arrangement: # proc ReadPic {w fn} { #: Read imagefile, put image on canvas #global im1 set midX [expr { $::maxX / 2 }] set midY [expr { $::maxY / 2 }] catch {image delete $im1} set im1 [image create photo -file $fn] $w create image $midX $midY -image $im1 -tags {all img} } proc DrawDots w { #: Same arrangement of dots as for head2 set i 0 foreach {x1 y1} { 90 80 120 80 150 80 180 80 90 110 120 110 150 110 180 110 90 140 120 140 150 140 180 140 90 170 120 170 150 170 180 170 90 200 120 200 150 200 180 200 210 200} { set nr [lindex {Fz Cz Pz FP1 FP2 F7 F3 F4 F8 T3 C3 C4 T4 \ T5 P3 P4 T6 O1 O2 A1 A2} $i] set xx [expr { $x1 + 15 }] set yy [expr { $y1 + 15 }] $w create oval $x1 $y1 $xx $yy -fill white -tags [list all $nr] incr i 1 } } proc DrawHead1 w { #: Draw a round head, as viewed from above set x1 32 set y1 40 ;# 120-80=40 set x2 [expr { $x1 + 220 }] set y2 [expr { $y1 + 220 }] $w create oval $x1 $y1 $x2 $y2 -fill $::Fill -tags [list all Head1] # Nose up: $w create poly [expr { $x1 + 94 }] [expr { $y1 + 1 }] \ [expr { $x1 +111 }] [expr { $y1 - 13 }] \ [expr { $x1 +111 }] [expr { $y1 - 13 }] \ [expr { $x1 +125 }] [expr { $y1 + 1 }] \ -fill $::Fill -tags [list all Head1] $w create line [expr { $x1 + 94 }] [expr { $y1 + 1 }] \ [expr { $x1 +111 }] [expr { $y1 - 13 }] \ -tags [list all Head1] $w create line [expr { $x1 +111 }] [expr { $y1 - 13 }] \ [expr { $x1 +125 }] [expr { $y1 + 1 }] \ -tags [list all Head1] # Ears: $w create rect [expr { $x1 - 4 }] [expr { $y1 + 90 }] \ [expr { $x1 + 2 }] [expr { $y1 +122 }] \ -fill $::Fill -tags [list all Head1] $w create rect [expr { $x1 +219 }] [expr { $y1 + 90 }] \ [expr { $x1 +225 }] [expr { $y1 +122 }] \ -fill $::Fill -tags [list all Head1] # Arrangement of dots according to 1020-system: foreach {nr x1 y1} {Fz 135 95 Cz 135 142 Pz 135 192 FP1 106 60 FP2 166 60 F7 66 90 F3 100 100 F4 170 100 F8 206 90 T3 50 140 C3 90 140 C4 185 140 T4 225 140 T5 66 195 P3 95 185 P4 175 185 T6 205 195 O1 108 225 O2 165 225 A1 10 130 A2 260 130} { set x2 [expr { $x1 + 15 }] set y2 [expr { $y1 + 15 }] $w create oval $x1 $y1 $x2 $y2 -fill white -tags [ list all $nr Head1] set xT [expr { $x1 + 8 }] set yT [expr { $y1 + 21 }] $w create text $xT $yT -text $nr -tags [list all Head1] } } proc HeadPoints {} { #: Create polygon for a head in profile lappend H 63 251 65 235 66 220 62 207 50 192 \ 44 180 35 158 33 139 38 124 48 96 \ 77 62 106 52 149 47 186 58 205 70 \ 223 93 231 134 226 146 256 180 258 186 \ 240 197 241 214 229 218 243 222 239 228 \ 234 247 213 257 202 262 195 266 190 274 return $H } proc DrawHead2 w { #: Draw oval head, side view set x [expr { $::maxX / 2 }] set y [expr { $::maxY / 2 }] set HeadCoords1 [HeadPoints] # puts "HeadCoords1: $HeadCoords1" #set xform [reflect-x] set xform [combine_affine [translation -$x -$y] [reflect-x] [ translation $x $y]] set HeadCoords2 [apply_affine $xform $HeadCoords1] # puts "HeadCoords2: $HeadCoords2" $w create poly $HeadCoords2 -outline red -fill $::Fill \ -tags {all Head2} ;# -smooth true # Selected dots of 1020-system, as viewed from side: foreach {nr x1 y1} {Fz 100 50 Cz 170 45 Pz 230 85 FP1 66 106 C3 160 80 T3 146 140 F3 108 85 F7 98 126 P3 208 103 T5 202 150 O1 233 145 A1 135 200} { set x2 [expr { $x1 + 15 }] set y2 [expr { $y1 + 15 }] $w create oval $x1 $y1 $x2 $y2 -fill white -tags [ list all $nr Head2] set xT [expr { $x1 + 8 }] set yT [expr { $y1 + 20 }] $w create text $xT $yT -text $nr -tags [list all Head2] } } proc DrawHead3 w { #: Draw oval head, side view $w create poly [HeadPoints] -outline black -fill $::Fill -tags {all Head3} ;# -smooth true # selected dots of 1020-system, as viewed from side: foreach {nr x1 y1} {Fz 180 50 Cz 100 45 Pz 43 88 FP2 208 108 C4 115 85 T4 130 138 P4 66 106 F8 180 120 F4 170 85 T6 77 146 O2 45 150 A2 140 200} { set x2 [expr { $x1 + 15 }] set y2 [expr { $y1 + 15 }] $w create oval $x1 $y1 $x2 $y2 -fill white -tags [ list all $nr Head3] set xT [expr { $x1 + 8 }] set yT [expr { $y1 + 20 }] $w create text $xT $yT -text $nr -tags "all Head3" } } #########1#########2#########3#########4#########5#########6#########7##### # # Routines to change the color of dots: proc ClrCanvas {} { #: Clear all canvas foreach w {.cA .cB .cC .cD} {$w delete all} } proc ColorAllDots c { #: Set all dots to the same color c foreach {nr} {Fz Cz Pz FP1 FP2 F7 F3 F4 F8 T3 C3 C4 T4 T5 P3 P4 T6 O1 O2 A1 A2} { foreach w {.cA .cB .cC .cD} {$w itemconfig $nr -fill $c} } } proc ColorDots Colors { #: Set all dots to the colors in array Colors ColorAllDots white set i 0 foreach {nr} {Fz Cz Pz FP1 FP2 F7 F3 F4 F8 T3 C3 C4 T4 T5 P3 P4 T6 O1 O2 A1 A2} { set c [lindex $Colors $i] if {$c eq {}} {set c white} #puts "$i $nr: $c" ;## foreach w {.cA .cB .cC .cD} { $w itemconfig $nr -fill $c } incr i 1 } } proc NextSample inc { #: Show next sample from data-array, with range-check global Data P0 set P $P0 if {[catch {incr P $inc}]} { StopAnimation; bell; return 1 } set x [array get Data $P] #puts "x: $x" ;## if {$x eq {}} { StopAnimation bell return 1 ;# Error } else { ;# ok: set P0 $P ColorDots $Data($P0) return 0 } } # Repeating timer: proc every {ms body} {after $ms [namespace code [info level 0]]; try $body} proc StartAnimation {} { #: Show data from data-array as animation every 100 { NextSample +1 } } proc StopAnimation {} { #: Reset all 'every' timers foreach id [after info] {after cancel $id} } #########1#########2#########3#########4#########5#########6#########7##### proc Init {} { #: Initialize values, build GUI global maxX maxY Data P0 ColorTab Fill Color P1 P2 set maxX 290 set maxY 290 array set ColorTab { a red b yellow d green t blue 0 white x grey } set Fill {light yellow} ;# yellow / bisque / bisque2 / wheat1 / tan / gold2 set Color blue set Data(0) {} set P0 NoData # Fz Cz Pz / FP1 FP2 / F7 F3 F4 F8 / # T3 C3 C4 T4 / T5 P3 P4 T6 / O1 O2 / A1 A2 set P1 {yellow gold goldenrod blue green4 cyan SteelBlue1 green2 green SteelBlue2 SteelBlue3 aquamarine SeaGreen1 DodgerBlue3 SteelBlue4 {lime green} {medium sea green} magenta PaleGreen3 gray44 gray88 orange} set P2 {OrangeRed2 red tomato green4 blue {medium sea green} PaleGreen3 cyan SteelBlue1 {lime green} green2 SteelBlue2 SteelBlue3 aquamarine SeaGreen1 DodgerBlue3 SteelBlue4 green magenta grey black} frame .f1 frame .f2 frame .f3 frame .f4 pack .f1 .f2 .f3 .f4 #canvas .cA -width $maxX -height $maxY -bg white foreach {w} {.cA .cB .cC .cD} { canvas $w -width $maxX -height $maxY -bg white } pack .cA .cB -in .f1 -side left pack .cC .cD -in .f2 -side left # Alternative Layout: #pack .cB .cD -in .f1 -side left #pack .cA .cC -in .f2 -side left button .b1 -text Clear -command { ClrCanvas } button .b2 -text Image -command { ReadPic .cA stampr1.gif } button .b3 -text Dots -command { DrawDots .cA } button .b4 -text Heads -command { DrawHead1 .cB; DrawHead2 .cC; DrawHead3 .cD } label .- #button .b5 -text Dot1 -command { .cA itemconfig Dot1 -fill $Color } button .b5 -text AllDots -command { ColorAllDots $Color } button .b6 -text Pattern1 -command { ColorDots $P1 } button .b7 -text Pattern2 -command { ColorDots $P2 } label .nr -textvar P0 button .bF -text {Read File} -command { set P0 [ReadFile eeg2.txt] } button .b0 -text Reset -command { set P0 0; NextSample 0 } button .b- -text { - } -command { NextSample -1 } button .b+ -text { + } -command { NextSample +1 } button .bA -text Play -command { StartAnimation } button .bS -text Stop -command { StopAnimation } pack .b1 .b2 .b3 .b4 .- .b5 .b6 .b7 -in .f3 -side left -padx 2 pack .bF .b0 .b- .nr .b+ .bA .bS -in .f4 -side left -padx 2 bind . <Key-a> { ColorAllDots $Color } bind . <Key-1> { ColorDots $P1 } bind . <Key-2> { ColorDots $P2 } bind . <Key-r> { set P0 [ReadFile "eeg2.txt"] } bind . <Key-0> { set P0 0; NextSample 0 } bind . <Key-minus> { NextSample -1 } bind . <Key-KP_Subtract> { NextSample -1 } bind . <Key-plus> { NextSample +1 } bind . <Key-KP_Add> { NextSample +1 } bind . <Return> { StartAnimation } bind . <Key-space> { StopAnimation } wm title . {Toy EEG} focus -force . } # #: Main : # Init #ReadPic .cA "stampr1.gif" DrawDots .cA #ReadPic .cB "1020top.gif" #ReadPic .cC "1020left.gif" #ReadPic .cD "1020right.gif" DrawHead1 .cB ;# Nose up DrawHead2 .cC ;# Nose left DrawHead3 .cD ;# Nose right ### Debug: bind . <F1> { console show } proc int x { expr int($x) } bind .cD <Motion> {wm title . [int [%W canvasx %x]],[int [%W canvasy %y]]} bind .cA <Motion> {wm title . [.cA itemcget current -tag ] } #catch {console show} #set c a; puts "$c : $ColorTab($c)" #puts "P1: $P1" #.