Vision Analyzer Makro-Kochbuch Version 1.05 © Brain Products GmbH 1999 - 2004 Der Inhalt dieses Handbuchs ist geistiges Eigentum der Brain Products GmbH. Er kann ohne besondere Ankündigung geändert werden. Die Brain Products GmbH übernimmt keine Gewähr oder Haftung für die Richtigkeit einzelner Aussagen bzw. geht mit diesem Dokument keine Verpflichtung ein. Alle in diesem Dokument aufgeführten Warenzeichen sind geschützte Marken ihrer jeweiligen Inhaber. 2 Inhalt 1. Einleitung .......................................................................................................... 5 2. Ein simples Makro erstellen ............................................................................ 6 3. Kurzer Streifzug durch Basic .......................................................................... 7 3.1. Variablen ......................................................................................................... 7 3.2. Prozeduren und Funktionen ............................................................................ 8 3.3. Objekte ............................................................................................................ 8 3.4. Benutzer-Interaktion ...................................................................................... 10 3.5. Kommentare .................................................................................................. 10 3.6. Kontrollstrukturen........................................................................................... 10 3.7. Fehlerbehandlung .......................................................................................... 11 3.8. Optimierung der Ablaufgeschwindigkeit......................................................... 12 4. Objektmodell des Analyzers .......................................................................... 14 5. Datenmanipulation mit Makros – Grundlagen ............................................. 16 6. Beispiele aus der Praxis ................................................................................ 18 6.1. Automatisierung ............................................................................................. 18 6.1.1. Komprimieren aller History-Dateien ......................................................... 18 6.1.2. Drucker-Batchlauf .................................................................................... 18 6.1.3. Umbenennen eines History-Knotens in allen History-Dateien ................. 18 6.1.4. Grafikexport mit Report nach Winword .................................................... 21 6.2. Datenmanipulation ......................................................................................... 23 6.2.1. Marker entfernen, setzen, umbenennen .................................................. 23 6.2.2. Erzeugen neuer Daten ............................................................................. 24 6.2.3. Einlesen von Stimulatordaten aus externen Dateien ............................... 26 6.2.4. Einlesen von Kanalpositionen aus externen Dateien ............................... 28 6.2.5. Exportieren von Frequenzdaten in eine ASCII-Datei ............................... 30 6.3. Dynamische Parametrisierung ....................................................................... 32 7. Tipps für Fortgeschrittene ............................................................................. 35 7.1. Variablendeklaration ...................................................................................... 35 7.2. Eigene Dialogboxen....................................................................................... 36 Vision Analyzer Makro-Kochbuch 3 7.3. Funktionen / Prozeduren ............................................................................... 38 7.4. Unterdrückung von Dialogen in History-Vorlagen .......................................... 39 7.5. Debugging ("Entwanzen", Fehlersuche) ........................................................ 40 4 1. Einleitung Dieses kleine Makro-Kochbuch soll Ihnen ohne größeren theoretischen Ballast einen Schnelleinstieg in die Erstellung von Makros für den Vision Analyzer ermöglichen. Makros im Analyzer können Sie für (fast) alles verwenden. Sie können Verarbeitungsschritte automatisieren, eigene Verfahren und Algorithmen zur Neuberechnung von Daten implementieren. Außerdem können Sie Marker zu Datensätzen hinzufügen oder entfernen, Kanalpositionen aus den eigenwilligsten Dateien in vorhandene EEG-Datensätze importieren, Daten und Marker in eigenen Formaten exportieren, Reports erstellen und vieles mehr. Obwohl Makros in der Programmiersprache Basic geschrieben werden, müssen Sie kein Programmierer sein, um sie zu erstellen. Sie können sich im Kapitel "Beispiele aus der Praxis" weiter unten davon überzeugen, dass schon einfache Makros Ihnen einen großen Zugewinn an Funktionalität ermöglichen. Am einfachsten erstellen Sie Makros, indem Sie sich unter den Beispielen eines heraussuchen, das Ihrer Aufgabenstellung am nächsten kommt und dieses dann modifizieren. Die den Beispielen vorausgehenden Kapitel sollen Sie in der Lage versetzen, einfache Manipulationen an den vorhandenen Makros vornehmen zu können. Sie finden die Beispielmakros unterhalb des Vision-Verzeichnisses im Verzeichnis "Examples". Wenn Sie ein Beispiel verwenden oder modifizieren wollen, sollten Sie es in das Unterverzeichnis "Workfiles" des Vision-Verzeichnisses kopieren und dann auf die Kopie zurückgreifen. Vision Analyzer Makro-Kochbuch 5 2. Ein simples Makro erstellen In diesem Kapitel werden wir ein simples Makro erstellen, das alle offenen History-Dateien und die zugehörigen Fenster schließt, also aufräumt. Damit Sie diesem und allen weiteren Beispielen folgen können, sollten Sie eine funktionsfähige Version des Analyzers installiert haben. Außerdem sollten sich in Ihrem aktuellen Workspace einige History-Dateien befinden. Gehen Sie nun wie folgt vor. Starten Sie den Analyzer. Wählen Sie den Menüpunkt Macro > New. Es erscheint ein Fenster mit dem Titel "Macro1 (macro)...". In dem Fenster finden Sie zwei Zeilen "Sub Main" und "End Sub". Diese Zeilen schließen das eigentliche Makro ein, d.h. Sie positionieren Ihren Text zwischen diesen beiden Zeilen. Tippen Sie das Makro wie folgt ein: Sub Main for each hf in historyfiles hf.close next End Sub Der Editor ändert teilweise die Groß-/Kleinschreibung automatisch. Allgemein gilt, dass Groß- und Kleinschreibung ignoriert werden können. Ausnahmen gibt es nur dort, wo Texte verglichen werden sollen. Speichern Sie das Makro unter den Menüpunkt File > Save mit dem Namen "Close All" (ohne Anführungsstriche) ab. Führen Sie es mit der Taste F5 aus. Sollten Sie keinen Tippfehler gemacht haben, so sollte gar nichts passieren. Ansonsten wird das Programm Sie zu der Zeile mit dem Tippfehler führen. Öffnen Sie einige History-Dateien, indem Sie auf das (+)-Zeichen neben den Buchsymbolen drücken. Führen Sie das Makro noch einmal aus. Alle History-Dateien werden wie von Zauberhand wieder geschlossen. Sie können nun das Makrofenster schließen. Um das Makro noch einmal auszuführen, wählen Sie es unter Macro > Run aus. Alternativ können Sie auch Makros als Menüpunkte in der Makromenüleiste erscheinen lassen. Wählen Sie dazu den Menüpunkt Macro > Options. Hier können Sie bis zu zehn verschiedene Makros auswählen. Wenn Sie die Auswahl abgeschlossen haben und nun wieder das Menü Macro anwählen, finden Sie Ihr Makro als Menüpunkt vor. Die ausgewählten Makros können Sie nun auch über Tastaturbefehle erreichen (Alt-M, 1, 2 , 3...). Bitte beachten Sie, dass die betreffenden Makros sich im aktuellen Arbeitsverzeichnis befinden müssen. Das aktuelle Arbeitsverzeichnis wird im Analyzer unter Configuration > Select Folder for Workfiles festgelegt. In den folgenden Kapiteln gehen wir auf die Bedeutung der Zeilen ein, die Sie soeben eingetippt haben. 6 3. Kurzer Streifzug durch Basic Dieses Kapitel gibt Ihnen einen kurzen und damit natürlich auch unvollständigen Überblick über einige Aspekte der Makroerstellung mit der verwendeten Basic-Sprache. Für weitergehende Informationen verwenden Sie bitte die Online-Hilfe, die Ihnen während der Makro-Erstellung unter Help > Language Help zur Verfügung steht. Wenn Sie in den Beispielen Begriffe finden, die Sie nicht verstehen, können Sie auch die sogenannte kontextsensitive Hilfe beanspruchen. Setzen Sie dafür den Cursor auf einen Begriff und betätigen Sie anschließend die F1-Taste. Die Online-Hilfe wird Ihnen dann eine Erläuterung des Begriffes zeigen, sofern dieser in der Hilfedatei registriert ist. Das gilt grundsätzlich nicht für die Analyzer-Objekte, die im folgenden Kapitel erläutert werden. Der verwendete Basic-Dialekt ist kompatibel mit dem von Microsoft vertriebenen Visual Basic, so dass Sie für weitere Fragen auch auf Visual-Basic-Literatur zurückgreifen können. 3.1. Variablen Variablen sind so etwas wie kleine Behälter, in die man einen bestimmten Inhalt, z.B. Zahlen oder Texte verstauen kann, um sie dann an einer beliebigen Stelle im Makro wieder verwenden zu können. Ein Beispiel: i = 0 i = i + 1 In der ersten Zeile wurde der Variablen i der Wert 0 zugewiesen. In der zweiten Zeile wurde i um eins erhöht. Man kann Variablen in einem Makro explizit deklarieren wie im folgenden Beispiel. Dim f as Single Hier haben wir eine Variable mit dem Namen f als Behälter für Fließkommazahlen einfacher Genauigkeit deklariert. Die Deklaration einer Variablen ist optional. Sie können Variablen auch benutzen, ohne sie zu deklarieren. Solange Makros kurz sind, spielt die Deklaration keine Rolle. Werden Ihre Makros aber länger als 30 bis 50 Zeilen, so ist es ratsam, Variablen zu deklarieren, um den Überblick nicht zu verlieren. Lesen Sie dazu mehr im Kapitel "Tipps für Fortgeschrittene" am Ende dieses Buches. Wollen Sie in einer Variablen nicht einen Wert, sondern mehrere unterbringen, so sprechen wir von einem sogenannten Array (Feld). Die folgende Deklaration erzeugt ein Array mit 20 Fließkommazahlen einfacher Genauigkeit. In der zweiten Zeile wird der zweite Eintrag in diesem Array gesetzt. In der dritten Zeile wird der zweite Eintrag des Arrays der Variablen x zugewiesen. Dim fArray(1 To 20) as Single fArray(2) = 12 x = fArray(2) Vision Analyzer Makro-Kochbuch 7 Wenn Sie ein Array benötigen, dessen Größe sich während des Makrolaufs ändern kann, deklarieren Sie es wie folgt: Dim fArray() as Single Redim fArray(1 to 20) Hier wurde das Array fArray deklariert und dann dimensioniert auf zwanzig Einträge. Redim Preserve fArray(1 to 20) dient ebenso der Redimensionierung, lässt aber einen eventuell schon vorhandenen Inhalt unversehrt. 3.2. Prozeduren und Funktionen Der Basic-Interpreter verfügt über eine größere Anzahl von eingebauten Funktionen und Prozeduren, die Sie in Ihren Makros verwenden können. Funktionen führen Operationen aus und liefern ein Resultat zurück. Ein Beispiel ist die Funktion "sin". Sie liefert den Sinus einer Zahl im Bogenmaß zurück und wird wie folgt eingesetzt: x = sin(0.3) Die Variable x hat in diesem Falle den Sinus von 0,3 erhalten. Prozeduren führen ebenfalls eine oder mehrere Operationen aus, liefern aber keinen Wert zurück. Beispiel: beep Die Prozedur "Beep" erzeugt einen kurzen Ton. Eine vollständige Beschreibung aller eingebauten Funktionen und Prozeduren des BasicInterpreters finden Sie in der Online-Hilfe. Sie können in Ihren Makros eigene Prozeduren und Funktionen verwenden. Dies kann bei größeren Makros die Übersichtlichkeit erhöhen. Mehr dazu erfahren Sie im Kapitel "Tipps für Fortgeschrittene" weiter unten. 3.3. Objekte Aufgrund der inflationären Verwendung des Begriffs "Objekt" erklären wir hier das Objekt, wie es in diesem Handbuch verwendet wird. Ein Objekt ist eine Funktionseinheit, die Sie programmatisch manipulieren können. Es verfügt über Methoden und Eigenschaften. Methoden entsprechen in ihrer Form Funktionen oder Prozeduren. Eigenschaften können entweder einfache Variablen sein oder ihrerseits wieder Objekte. Das wichtigste Objekt im Analyzer heißt "Application". Es repräsentiert den Analyzer. Das folgende Makro bedient sich des "Application"-Objekts, um das Programm zu beenden. Sub Main Application.Quit End Sub 8 Hier wurde die Methode "Quit" des "Application"-Objekts aufgerufen, um die Ausführung des Analyzers zu beenden. Das "Application"-Objekt ist das sogenannte Standard-Objekt des Analyzers. Das bedeutet, dass es im Text auch weggelassen werden kann. Das folgende Macro ist mit dem vorhergehenden in der Funktion identisch. Sub Main Quit End Sub Das "Application"-Objekt verfügt seinerseits wieder über weitere Objekte, z.B. das Objekt "HistoryExplorer", das den History-Explorer repräsentiert. "HistoryExplorer" verfügt über die Eigenschaft "Visible", die den Wert 1 hat, wenn der Explorer sichtbar ist, und den Wert 0, wenn er unsichtbar ist. Die Zeile Application.HistoryExplorer.Visible = 0 oder HistoryExplorer.Visible = 0 schaltet den Explorer weg. Mehr über das Analyzer-Objektmodell erfahren Sie im nächsten Kapitel. Sie können Variablen verwenden, die auf Objekte verweisen. In diesem Falle verwenden Sie für eine Zuweisung neben dem Zuweisungszeichen "=" das Schlüsselwort "Set". Beispiel: set he = HistoryExplorer he.Visible = 0 Objekte können sogenannte Standardelemente enthalten. Diese müssen dann im Makrotext nicht mehr explizit erwähnt werden. Im folgenden Beispiel wird der Variablen "hf" ein Verweis auf die erste History-Datei im Workspace zugewiesen: set hf = HistoryFiles.Item(1) Da "Item" das Standardelement von "Application.HistoryFiles" ist, kann der Ausdruck auch wie folgt lauten: set hf = HistoryFiles(1) Ein besonderer Objekttyp ist die sogenannte Auflistung. Hierbei handelt es sich um Objekte, die ihrerseits wieder mehrere Objekte eines Typs enthalten. Ein Beispiel ist das Objekt "HistoryFiles", das mehrere Objekte des Typs "HistoryFile" enthält. Solche Auflistungsobjekte erkennt man im Analyzer und auch in meisten anderen OLE-AutomationServern daran, dass sie in der (englischen) Mehrzahl geschrieben sind, "HistoryFiles" beispielsweise enthält Objekte vom Typ "HistoryFile", "HistoryNodes" Objekte vom Type "HistoryNode" usw.. Elemente der Auflistungen können wie Arrays indiziert werden: Dim hf as HistoryFile set hf = HistoryFiles(1) Vision Analyzer Makro-Kochbuch 9 3.4. Benutzer-Interaktion Sie können während des Ablaufs eines Makros auch Meldungen an den Anwender ausgeben, oder ihn zur Eingabe von Parametern auffordern. Zur Eingabe können Sie die Funktion "InputBox" und zur Ausgabe "MsgBox" verwenden. Das folgende Makro nimmt eine Benutzereingabe an und gibt sie wieder als Meldung aus. Sub Main x = InputBox("Enter Text") MsgBox x End Sub Zur Auswahl von Dateien steht Ihnen noch die Funktion "GetFilePath" zur Verfügung. Lesen Sie dazu die Beschreibung in der Online-Hilfe. Schließlich können Sie noch eigene Dialoge entwerfen und im Makro verwenden. Lesen Sie dazu mehr im Kapitel "Tipps für Fortgeschrittene" sowie in der Online-Hilfe. 3.5. Kommentare Um Makros übersichtlicher und verständlicher zu machen, können Sie Kommentare einfügen. Kommentare beginnen mit dem Zeichen '. Der Text nach diesem Zeichen bis zum Ende der Zeile wird dann vom Basic-Interpreter ignoriert. Beispiel: ' This macro receives an user input and shows the result in a message box. Sub Main x = InputBox("Enter Text") ' Get user input and store it in x. MsgBox x ' Show user input in a message box. End Sub Sie sollten nicht an Kommentaren sparen. Speziell bei größeren Makros passiert es sonst schnell, dass Sie die Übersicht verlieren, welchem Zweck die einzelnen Anweisungen eigentlich dienen. 3.6. Kontrollstrukturen Zu jeder leistungsfähigen Makrosprache gehören auch Kontrollstrukturen, die eine bedingte Ausführung von Code zulassen, oder basierend auf eine Bedingung eine oder mehrere Operationen mehrfach wiederholen. Zur bedingten Verzweigung verwendet der Basic-Interpreter die If ... then ... else ... end ifKonstruktion. Beispiel: Sub Main Dim x as long x = InputBox("Enter a number:") if x > 20 then MsgBox "X is larger than 20." else MsgBox "X is not larger than 20." end if End Sub Wird hier eine Zahl eingegeben, die größer als 20 ist, so wird anschließend die erste Meldung ausgegeben, sonst die zweite. 10 Der else-Zweig kann auch weggelassen werden. In diesem Falle passiert einfach nichts, wenn die eingegebene Zahl kleiner oder gleich 20 ist: Sub Main Dim x as long x = InputBox("Enter a number:") if x > 20 then MsgBox "X is larger than 20." end if End Sub Das Einrücken der Zeilen erfolgt, um Ebenen im Makro-Code aufzuzeigen und ist nicht zwingend erforderlich. Bei größeren Makros erhöht es aber deutlich die Lesbarkeit. Soll eine oder mehrere Operationen wiederholt werden (Schleife), so bietet sich die for ... next-Konstruktion an. Beispiel: Dim fArray(1 to 20) as single for i = 1 to 20 fArray(i) = 2 next Es werden alle Elemente des Arrays fArray auf den Wert 2 gesetzt. Eine andere Wiederholungskonstruktion behandelt speziell Auflistungsobjekte - die for each ...in .. next-Konstruktion: Dim hf as HistoryFile for each hf in HistoryFiles hf.Close next Hier werden nacheinander alle in der "HistoryFiles"-Auflistung vorhandenen "HistoryFile"Objekte von "hf" referenziert. Innerhalb der Schleife werden dann die History-Dateien geschlossen. Es ist nun an der Zeit, auf eine sehr schöne Schleifeneigenschaft hinzuweisen, die sogenannte Endlosschleife. Programmieren Sie ein Makro wie folgt, so wird "i" intern hochgezählt und in der Schleife wieder heruntergezählt. Die Schleife wird erst verlassen, wenn i den Wert 1000 überschreitet, was hier nie der Fall ist. Sub Main For i = 1 To 1000 i = i - 1 Next End Sub Das Makro läuft ewig. Sie können es allerdings mit der Tastenkombination Strg-Unterbr, bzw. Ctrl-Break abbrechen. Informationen über weitere Kontrollstrukturen entnehmen Sie bitte der Online-Hilfe. 3.7. Fehlerbehandlung Es ist möglich, dass während der Ausführung eines Makros ein Fehler auftritt. Angenommen Ihr Workspace enthält 1000 History-Dateien, und Sie geben im Makro die Anweisung, die 1001. Datei zu öffnen: hf = HistoryFiles(1001) hf.Open Vision Analyzer Makro-Kochbuch 11 Es kommt zu einer Fehlermeldung in der Statuszeile und die weitere Ausführung des Makros wird beendet. Um die Möglichkeit zu haben, irgendwelche Aktionen durchzuführen, bevor das Makro stoppt, gibt es die On Error-Anweisung. Beispiel: Sub Main On Error Goto CheckError hf = HistoryFiles(1001) hf.Open exit sub CheckError: MsgBox "Could not open history file!" End Sub Wenn ein Fehler auftritt, wird hier die Ausführung bei der Marke "CheckError" weitergeführt. Beachten Sie bitte die Anweisung "exit sub" vor der "CheckError"-Marke. Sie beendet das Makro an dieser Stelle und verhindert so, dass der Code nach der "CheckError"-Marke ausgeführt wird, wenn kein Fehler vorliegt. Diese Konstruktion bietet für das Beispielproblem kaum Vorteile, kann aber in bestimmten Fällen nützlich sein. Weitere Informationen über Fehlerbehandlungsroutinen entnehmen Sie bitte der Online-Hilfe. 3.8. Optimierung der Ablaufgeschwindigkeit Makros können durch das Vermeiden von unnötigen Objekt-Referenzierungen erheblich beschleunigt werden. In der Praxis traten Fälle mit Beschleunigungen um den Faktor 100 auf. Der wichtigste Ort, in dem Optimierungen greifen, ist das Innere einer Schleife. Hier wird jeder programmierte Zeitverlust mit der Anzahl der Schleifendurchläufe multipliziert. Nachfolgend sehen Sie ein Beispiel für eine nicht optimierte Schleife. Der Unterstrich ("_") als letztes Zeichen einer Zeile wird verwendet, wenn eine Anweisung sich über mehrere Zeilen erstreckt. Sub Main Set nhn = New NewHistoryNode nhn.Create "New", ActiveNode For i = 1 To 100 nhn.RemoveMarker 0, hn.Dataset.Markers(i).Position, 1, _ ActiveNode.Dataset.Markers(i).Type, ActiveNode.Dataset.Markers(i).Description nhn.AddMarker 0, ActiveNode.Dataset.Markers(i).Position, 1, "Stimulus", _ ActiveNode.Dataset.Markers(i).Description + "A" Next nhn.Finish End Sub Es folgt ein Makro mit der gleichen Funktionalität, etwa 100-mal schneller und nebenbei auch noch übersichtlicher. Beachten Sie, dass die Objektkette "ActiveNode.Dataset.Markers" nur einmal außerhalb der For ...Next-Schleife referenziert wird. Sub Main Set nhn = New NewHistoryNode nhn.Create "New", ActiveNode Set Mks = ActiveNode.Dataset.Markers ' Use variable as object reference. For i = 1 To 100 Set mk = Mks(i) nhn.RemoveMarker 0, mk.Position, 1, mk.Type, mk.Description nhn.AddMarker 0, mk.Position, 1, "Stimulus", mk.Description + "A" Next nhn.Finish End Sub 12 Eine einfache Faustregel zur Beschleunigung lautet: Entfernen Sie so viele Punkte (".") wie möglich aus der Schleife, sofern diese Objektverweise beschreiben. Es kommt bei Objektverweisen oft zu komplexen Erstellung von Objekten innerhalb des Analyzers. Sie werden dann innerhalb der Schleife immer wieder gelöscht und neu erstellt, wenn Sie nicht einmalig einer Variablen zugewiesen werden, wie in Set Mks = ActiveNode.Dataset.Markers ' Use variable as object reference. geschehen. Das führt manchmal zu dramatischen Geschwindigkeitseinbußen. Einige Punkte in der Schleife betreffen Eigenschaften, die einfache Variablen im Objekt repräsentieren, z.B. "mk.Position". Hier ist der zu erwartende Geschwindigkeitsgewinn bei einer Zuweisung zu einer Variablen so gering, dass die Nachteile der größeren Unübersichtlichkeit überwiegen. Vision Analyzer Makro-Kochbuch 13 4. Objektmodell des Analyzers Hier gehen wir im Schnellgang durch das Analyzer-Objektmodell. Für Details beachten Sie bitte das ebenfalls im Lieferumfang enthaltene Ole-Automation-Referenzhandbuch. Nachfolgend finden Sie zur Orientierung die grafische Darstellung der Objekthierarchie des Analyzers. Ganz oben in der Hierarchie steht das "Application"-Objekt. Application CurrentWorkspace NewHistoryNode HistoryExplorer HistoryFiles (HistoryFile) HistoryNodes (HistoryNode) DataSet Channels (Channel) ChannelPosition Markers (Marker) HistoryNodes (HistoryNode) Segments (Segment) DataSet ... Windows (Window) ... Transformation Windows (Window) HistoryNode ... Workspaces (Workspace) Legende Objekt und Auflistung Nur Objekt Abbildung 4-1: Objekt-Hierarchie des Analyzers Von der Application-Klasse gibt es nur ein Objekt, das das Programm als ganzes repräsentiert. Es ist das Standardobjekt, was bedeutet, dass die Methoden und Eigenschaften dieses Objekts direkt ansprechbar sind, d.h. z.B. "HistoryFiles" ist gleichbedeutend mit "Application.HistoryFiles". 14 Die Objektauflistung "HistoryFiles" repräsentiert alle History-Dateien im aktuellen Workspace. Eine einzelne History-Datei ist als "HistoryFile" repräsentiert. Jede History-Datei enthält eine Auflistung "HistoryNodes", die normalerweise ein Objekt vom Typ "HistoryNode" enthält. Bei den primären History-Dateien ist das der Datenknoten mit dem Namen "Raw Data". Jedes Objekt der Objektklasse "HistoryNode" enthält seinerseits wieder eine Auflistung "HistoryNodes" mit Verweisen auf abgeleitete Datensätze. So können Sie durch eine komplette History-Datei mit all ihren Verästelungen navigieren. Das "HistoryNode"-Objekt besitzt auch ein weiteres Objekt, das "Dataset"-Objekt. Das "Dataset"-Objekt enthält eine Auflistung aller Marker im Datensatz, das "Markers"-Objekt und eine Auflistung der Kanäle, das "Channels"-Objekt. Jedes einzelne "Channel"-Objekt schließlich liefert Ihnen endlich den Zugang zu jedem einzelnen Datenpunkt. Der Zugriff auf den ersten Datenpunkt des ersten Kanals eines Historie-Knotens lässt sich also wie folgt beschreiben: Dataset.Channels(1).DataPoint(1) Da "Channels" das Standardelement von "Dataset" ist und außerdem "DataPoint" das Standardelement von "Channels" ist, lässt sich der Begriff wie folgt verkürzen: Dataset(1)(1) Weitere Aspekte von "HistoryNode"-, "Dataset"- und "Channel"-Objekten werden wir unten im Kapitel "Beispiele aus der Praxis" aufgreifen. Etwas abseits steht noch das Objekt "NewHistoryNode". Es dient der Erstellung neuer History-Knoten. Da es bei Bedarf neu erzeugt wird, wird es in der Objekthierarchie separat dargestellt. Sein Einsatz wird im nächsten Kapitel ausführlicher beschrieben. Vision Analyzer Makro-Kochbuch 15 5. Datenmanipulation mit Makros – Grundlagen Unter Datenmanipulation verstehen wir in diesem Kapitel das Ändern der eigentlichen Daten, aber auch das Entfernen und Hinzufügen von Markern bzw. das Ändern von Eigenschaften wie Kanalnamen oder –Positionen. Tatsächlich wird keine physikalische Änderung von Daten durchgeführt. Es wird stattdessen ein neuer abgeleiteter Datensatz erzeugt, wie wir das auch bei den Transformationen im Analyzer kennen. Sie können diesen Datensatz auch genauso wieder löschen, wie es mit anderen Datensätzen möglich ist. Ihrer Experimentierfreude sind also auch hier wieder keine Grenzen gesetzt. Bevor Sie Makros zur Datenmanipulation einsetzen, sollten Sie allerdings überprüfen, ob Ihre Problemstellung nicht von einem der vorhandenen Transformationsmodule gelöst werden kann. Für die Erzeugung eines neuen Datensatzes benötigen wir ein "NewHistoryNode"-Objekt. Am einfachsten erzeugen wir es mit der folgenden Zeile. Dim nhn as New NewHistoryNode Im Wesentlichen sind zwei Prozeduren für die Erstellung und Fertigstellung des Knotens zuständig: "Create" und "Finish". Zwischen diesen beiden Prozeduren können die Kanalnamen, Marker und Daten gesetzt werden. Wurde "Create" erfolgreich aufgerufen und die Daten nicht vom Parent vererbt, wird ein Datensatz mit Standardeinstellungen erstellt. Dieser kann nun manipuliert werden. "Finish" schließt den Erstellungsprozess ab. Die neu erzeugten Historieknoten können eingeschränkt auch in Historievorlagen eingesetzt werden. Dafür muss der neue Knoten als Child-Knoten der vordefinierten Variablen "ActiveNode" erzeugt werden. Diese Variable ist immer definiert, wenn der Basic-Interpreter läuft und zwar wie folgt: Dim ActiveNode As HistoryNode Der Knoten repräsentiert das aktuell offene Datenfenster. Ist kein Datenfenster geöffnet, so enthält dieser Knoten keine Daten. Um also ein vorlagenfähigen Datensatz zu erzeugen, sollten Sie alle offenen Datenfenster schließen, bis auf das Fenster, das als Parent für den neuen Child-Knoten dienen soll. Dann wird der Code des Basic-Makros ausgeführt. Bitte beachten Sie, dass die Ausführung des Makros automatisch beendet wird, wenn die NewHistoryNode.Finish()-Methode ausgeführt wird. Beim Erstellen des neuen Knotens wird der gesamte Makro-Code in den Knoten kopiert. Jetzt können Sie den Knoten wie eine gewöhnliche Transformation auf andere Knoten ziehen, um die Operation zu wiederholen. Der Knoten kann auch in eine Historie-Vorlage aufgenommen werden. Sehen Sie sich mit der rechten Maustaste am Knoten die sogenannten "Operation Infos" an, so wird zusätzlich der Code angezeigt, der zur Erstellung des Knotens geführt hat. 16 Nachfolgend finden Sie einen Beispielcode, der nur den ersten Kanal in "xxx" umbenennt, ansonsten alles unverändert lässt. Die neuen Daten werden im Knoten "BasicTest" unter dem aktuell offenen History-Knoten abgelegt. Dieser Code dient hier nur der Demonstration. Wenn Sie tatsächlich Kanäle umbenennen wollen, können Sie dies einfacher mit der Transformation "Edit Channels" durchführen. Sub Main Dim nhn as New NewHistoryNode nhn.Create "BasicTest", ActiveNode nhn.SetChannelName 1, "xxx" nhn.Finish End Sub Wie Sie sehen, erfordert es nur einen minimalen Aufwand, einen neuen Datensatz, bzw. History-Knoten anzulegen. Wir haben eben einen Datensatz erzeugt, der alle Daten seines Vorgängers übernimmt. In diesem Falle fällt für das Makro nur sehr wenig Arbeit an, so dass es sehr schnell ausgeführt wird. Auch ist der Platzbedarf des neuen Datensatzes gering, da tatsächlich nur ein Verweis auf die Daten gespeichert wird. Erzeugen Sie im Gegensatz dazu wirklich neue Daten, so werden diese in der History-Datei gespeichert. Wir empfehlen deshalb, Manipulationen an Daten nach Möglichkeit nur nach der Mittelung durchzuführen, da hier erheblich weniger Daten anfallen, als beim Roh-EEG. Das wirkt sich auch auf die Geschwindigkeit positiv aus, denn die Makrosprache ist relativ langsam. Wenn Sie also Operationen auf Rohdaten ausführen möchten, so sollten Sie überprüfen, ob ein Transformationsmodul diese Arbeit erledigen kann (z.B. "Formula Evaluator"). Auch eine Mischform aus Makros und Transformationen ist eingeschränkt möglich, wie im folgenden Kapitel, Unterkapitel "Dynamische Parametrisierung" beschrieben. Anders sieht es aus, wenn Sie nur Marker setzen, löschen oder umbenennen wollen. Da Marker intern in einer Tabelle gespeichert werden, kann ein Makro hier auch bei Roh-EEGs recht schnell agieren und der resultierende Datensatz benötigt nur wenig Platz. Dasselbe gilt für das Ändern von Eigenschaften, wie Kanalnamen oder -positionen. Die verschiedenen Arten neuer History-Knoten werden im Kapitel "Beispiele aus der Praxis" erläutert. Die genaue Syntax für die Erstellung der Knoten entnehmen Sie bitte dem OleAutomation-Referenzhandbuch im Kapitel "Objektklassen", Unterkapitel "NewHistoryNode". Vision Analyzer Makro-Kochbuch 17 6. Beispiele aus der Praxis 6.1. Automatisierung 6.1.1. Komprimieren aller History-Dateien Wenn Sie viel mit Ihren Daten experimentieren, Knoten in History-Dateien anlegen und wieder löschen, so bleiben üblicherweise mehr oder weniger große Lücken, gewissermaßen Löcher, in den Dateien übrig. Dies führt dazu, dass History-Dateien unnötig groß werden können. Das folgende Makro durchläuft alle History-Dateien der aktuellen Workspace und komprimiert sie, d.h. es entfernt alle Lücken. Datei "Compress All.vabs": ' Compress all history files Sub Main For each hf in HistoryFiles hf.Compress Next End Sub 6.1.2. Drucker-Batchlauf Das folgende Makro sucht nach dem Datensatz "Average" in allen History-Dateien. Wenn einer gefunden wird, wird er ausgedruckt. Befinden sich mehrere Datensätze mit dem gleichen Namen in der History-Datei, wird hier nur der zuerst gefundene Datensatz ausgedruckt. Datei "PrintAverages.vabs": ' Search in each history file for a node named "Average". If found, print it. Sub Main For Each hf In HistoryFiles hf.Open Set hn = hf.FindNode("Average") If Not hn Is Nothing Then ' "Average" node found? hn.Show ' When the node is shown, at least one window is attached. hn.Windows(1).Print Wait 2 End If hf.Close Next End Sub 6.1.3. Umbenennen eines History-Knotens in allen History-Dateien Angenommen, Sie haben nach zwei verschiedenen Kriterien segmentiert, haben also z.B. einen Knoten "Segmentation" und einen Knoten "Segmentation2". Danach haben Sie noch einige Operationen durchgeführt bis Sie zum Average kamen. Ihre History-Datei enthält nun zwei verschiedene Knoten mit demselben Namen "Average". Sie haben die Operation mit 162 History-Dateien gemacht und hätten nun gerne ein Grandaverage über das von "Segmentation 2" abgeleitete Average. Sie stellen fest, es geht nicht! Das Grandaverage-Modul nimmt nur einen Knotennamen an, wie z.B. "Average" und verwendet dann den ersten Knoten, den es in der History-Datei findet. Sie können nun entweder eine History-Vorlage aus einer der Dateien erstellen und das zweite "Average" in "Average 2" umbenennen und dann die Vorlage laufen 18 lassen oder in jeder vorhandenen History-Datei den zweiten "Average"-Knoten umbenennen oder das folgende Makro ausführen. Datei "RenameToAverage2.vabs": ' Search for "Average" below "Segmentation 2" and rename it to "Average 2". ' This macro assumes that there is no branch below "Segmentation 2". Sub Main Dim hf As HistoryFile Dim hn As HistoryNode For Each hf In HistoryFiles hf.Open Set hn = hf.FindNode("Segmentation 2") If Not hn Is Nothing Then ' "Segmentation 2" found? Dim nChildren As Long ' Check for children of node. Dim hn2 As HistoryNode Set hn2 = hn nChildren = hn2.HistoryNodes.Count If nChildren > 1 Then ' branch found. MsgBox "Branch found in " & hf.DisplayName & "!", "Warning" End If Do While nChildren > 0 Set hn2 = hn2.HistoryNodes(1) If StrComp(hn2.Name, "Average", 1) = 0 Then ' Case insensitive comparison. ' Rename it. hn2.Name = "Average 2" End If nChildren = hn2.HistoryNodes.Count If nChildren > 1 Then ' branch found. MsgBox "Branch found in " & hf.DisplayName & "!", "Warning" End If Loop End If hf.Close Next End Sub Das obige Makro hat einen Nachteil: es dürfen nach "Segmentation 2" keine Verzweigungen auftreten, d.h. "Segmentation 2" und jeder nachfolgende Knoten darf nur einen Child-Knoten haben. Ist das nicht der Fall, wird eine Warnung ausgegeben und eventuell ein "Average"Knoten übersehen. Um auch beliebige Verzweigungen zu berücksichtigen, können Sie das folgende Makro verwenden, das etwas komplizierter aufgebaut ist. Es verwendet eine sogenannte Rekursion, d.h. es wird eine Funktion definiert, die sich selbst aufruft. Informationen über den Aufbau von Funktionen erhalten Sie im Kapitel "Tipps für Fortgeschrittene". Die Funktion "FindSubNode" sucht nach einem Knoten mit einem vorgegebenen Namen unterhalb eines vorgegebenen Knotens. Sie kann auch für andere Anwendungen von Interesse sein. Datei "RenameToAverage2Rec.vabs": ' Search for "Average" below "Segmentation 2" and rename it to "Average 2". Sub Main Dim hf As HistoryFile Dim hn As HistoryNode For Each hf In HistoryFiles hf.Open Set hn = hf.FindNode("Segmentation 2") If Not hn Is Nothing Then ' "Segmentation 2" node found? Dim hnAverage As HistoryNode Set hnAverage = FindSubNode(hn, "Average") If Not hnAverage Is Nothing Then hnAverage.Name = "Average 2" End If End If hf.Close Next End Sub Vision Analyzer Makro-Kochbuch 19 ' This function searches recursively for a history node with the given name below the given ' node. Function FindSubNode(hn As HistoryNode, sName As String) As HistoryNode Dim hnChild As HistoryNode For Each hnChild In hn.HistoryNodes If StrComp(hnChild.Name, sName, 1) = 0 Then ' Case insensitive comparison. Set FindSubNode = hnChild Exit Function End If Set FindSubNode = FindSubNode(hnChild, sName) ' Recursive call If Not FindSubNode Is Nothing Then ' Found? Exit Function End If Next End Function 20 6.1.4. Grafikexport mit Report nach Winword Das folgende Makro kopiert den Inhalt des aktuellen Datenfensters ins Clipboard, startet MS Word 2000, kopiert die Daten hinein, und überlässt Ihnen dann weitere Manipulationen. Datei "CopyToWord.vabs": ' Copy contents of a window to the clipboard. ' and then paste it into a new word document. Sub Main If Not ActiveNode.DataAvailable Then MsgBox "This macro needs an open data window." Exit Sub End If Set hn = ActiveNode hn.Show ' Now it should be the active window. hn.Windows(1).Copy ' Word must be on the machine. ' The following commands are Word commands. Set Word = CreateObject("Word.Application") Word.Visible = True Word.Documents.Add Word.Selection.TypeText hn.HistoryFile.DisplayName & "-" & hn.Name Word.Selection.TypeParagraph Word.Selection.Paste End Sub Der kleinere Teil des Makros beinhaltet Analyzer-Kommandos, der größere WordAutomation-Kommandos, die wir hier nicht weiter erörtern werden. Greifen Sie dafür bitte auf die von Microsoft mitgelieferte Word-Dokumentation zurück. Erwähnenswert ist die Funktion "CreateObject", mit deren Hilfe Sie fremde Applikationen starten können, um sie dann mit OLE-Automation fernzusteuern. Dies gilt für die MicrosoftOffice-Programme (Word, Excel, Powerpoint, Access, Outlook) und viele andere (Visio, SPSS usw.). Wenn Sie einen einfachen Report für einen Datensatz benötigen, können Sie das folgende Makro verwenden. Es funktioniert ebenfalls mit Word 2000. Die Word-Applikation wird wieder aus dem Makro heraus ferngesteuert. Das Makro erzeugt eine neue Datei mit den Namen der aktuellen History-Datei und des aktuellen Datensatzes. Die neue Datei wird im Exportverzeichnis des aktuellen Workspaces abgelegt. Das Exportverzeichnis legen Sie im Analyzer unter File > Edit Workspace fest. Datei "WordReport.vabs": ' Copy contents of the active window to the clipboard. ' Create a word document. ' Write title. ' Paste clipboard contents. Sub Main On Error GoTo CheckError If Not ActiveNode.DataAvailable Then MsgBox "This macro needs an open data window." Exit Sub End If Set hn = ActiveNode hn.Windows(1).Copy ' When the active node has valid data it also has at ' least one attached window. ' Save new document in export folder. Dim sOutput As String ' Build output file name. sOutput = CurrentWorkspace.ExportFileFolder & "\" & hn.HistoryFile.DisplayName & _ "-" & hn.Name & ".doc" Vision Analyzer Makro-Kochbuch 21 ' MS Word 97 must be on the machine. ' The following commands are Word commands. ' Look at the office documentation for the object model of word. Set WordDoc = CreateObject("Word.Document") WordDoc.Select Dim Sel As Object Set Sel = WordDoc.Application.Selection ' Reference word selection object. ' Save current font. With Sel.Font OldBold = .Bold : OldName = .Name .Bold = True : .Name = "Arial" End With ' Write caption. Sel.TypeText hn.HistoryFile.DisplayName & "-" & hn.Name Sel.TypeParagraph ' Restore font With Sel.Font .Bold = OldBold : .Name = OldName End With Sel.Paste Sel.MoveEnd Sel.TypeParagraph WordDoc.SaveAs sOutput ' Save document. WordDoc.Close Exit Sub CheckError: MsgBox Err.Description, vbExclamation, "Error" End Sub 22 6.2. Datenmanipulation 6.2.1. Marker entfernen, setzen, umbenennen Makros können vorhandene Marker entfernen und neue Marker setzen. Die Umbenennung eines Markers erfolgt durch das Entfernen und darauffolgende Setzen eines neuen Markers an derselben Position. Die Hauptanwendung für die Markermanipulation ist die Vorbereitung für eine spezielle Segmentierung. Obwohl das Segmentierungsmodul des Analyzers mit der Advanced Boolean Expression (ABE) eine sehr mächtige Methode der intelligenten Segmentierung enthält, kann es nicht alle denkbaren Segmentierungsalgorithmen berücksichtigten. Sie sollten aber trotzdem vor dem Einsatz eines Makros überprüfen, ob die ABE eventuell Ihr Problem löst. Hier kommt unser erstes Beispiel. Es sollen die ersten 5 Stimuli in einem Datensatz ignoriert werden und dann die nächsten 500 Stimuli in die Mittelung eingehen. Das folgende Makro löscht dafür einfach alle nicht benötigten Stimuli aus dem Datensatz. Befinden sich nicht genügend Stimuli im Datensatz, wird eine Fehlermeldung ausgegeben. Makro "500Stimuli.vabs": ' Remove all stimulus markers from 1 to 5 and > 505. ' -> keep exactly 500 stimuli. Sub Main Dim nhn As New NewHistoryNode nhn.Create "500 Stim", ActiveNode Dim Mks As Markers Dim mk As Marker Dim i As Long Set Mks = ActiveNode.Dataset.Markers For Each mk In Mks If mk.Type = "Stimulus" Then i = i + 1 If i < 6 Or i > 505 Then nhn.RemoveMarker mk.ChannelNumber, mk.Position, mk.Points, mk.Type, _ mk.Description End If End If Next If i < 505 Then ' Not enough markers? MsgBox i & " Markers in Dataset!", "Macro 500Stimuli" Exit Sub End If nhn.Finish End Sub Wenn in die Mittelung nur jeder dritte Stimulus "S 1" eingehen soll, haben Sie zwei Möglichkeiten, dies zu bewerkstelligen. 1. Löschen Sie alle anderen Stimuli "S 1" aus dem Datensatz und segmentieren dann nach den verbliebenen Stimuli. 2. Benennen Sie jeden dritten "S 1"-Stimulus um, und segmentieren Sie dann nach Stimuli mit dem neuen Namen. Das Makro "ThirdS1a.vabs" verwendet die erste Methode: ' Search for "S 1" stimulus markers and erase them if they are not ' divisible by 3, i.e. keep every third stimulus marker "S 1". Sub Main On Error GoTo CheckError Dim sDescription As String Vision Analyzer Makro-Kochbuch 23 sDescription = "S 1" ' Change the string for a different stimulus, be carefully with ' spaces in the name, "S 1" contains two spaces. Dim nhn As New NewHistoryNode nhn.Create "Third S1a", ActiveNode Dim Mks As Markers Set Mks = ActiveNode.Dataset.Markers Dim mk As Marker Dim i As Long For Each mk In Mks If mk.Type = "Stimulus" And mk.Description = sDescription Then i = i + 1 If i Mod 3 Then ' Not divisible by 3? nhn.RemoveMarker 0, mk.Position, mk.Points, mk.Type, mk.Description End If End If Next nhn.Finish ' Finish creation. Exit Sub CheckError: ' Error MsgBox Err.Description End Sub Wenn Sie eine ähnliche Fragestellung haben, so können Sie die sechste Zeile ändern, um einen anderen Stimulus zu verwenden ("sDescription = ..."), sowie die Zeile Nummer 17 ("if i Mod 3 then") um ein anderes Teilungsverhältnis einzustellen. Das Makro "ThirdS1b.vabs" verwendet die zweite Methode, d.h. es benennt jeden dritten "S 1"-Marker um: ' Search for "S 1" stimulus markers and rename them if they are ' divisible by 3, i.e. rename every third stimulus marker "S 1" to "1000Hz". Sub Main On Error GoTo CheckError Dim sDescription As String sDescription = "S 1" ' Change the string for a different stimulus, be carefully with ' spaces in the name, "S 1" contains two spaces. Dim nhn As New NewHistoryNode nhn.Create "Third S1b", ActiveNode Dim Mks As Markers Set Mks = ActiveNode.Dataset.Markers Dim mk As Marker Dim i As Long For Each mk In Mks If mk.Type = "Stimulus" And mk.Description = sDescription Then i = i + 1 If i Mod 3 = 0 Then ' Divisible by 3? ' Rename marker: remove / add nhn.RemoveMarker 0, mk.Position, mk.Points, mk.Type, mk.Description nhn.AddMarker 0, mk.Position, mk.Points, mk.Type, "1000Hz" End If End If Next nhn.Finish ' Finish creation. Exit Sub CheckError: ' Error MsgBox Err.Description End Sub 6.2.2. Erzeugen neuer Daten Im folgenden Makro wird ein neuer Datensatz als Child-Knoten eines vorhandenen erzeugt. Dieser enthält die gleichgerichteten Daten des Ursprungsdatensatzes. Da die Daten nicht vererbt werden, müssen Eigenschaften und Marker explizit gesetzt werden. Hier werden sie durch die Prozeduren "CopyProperties" und "CopyMarkers" vom Ursprungsdatensatz kopiert. Dieses Prozeduren können aufgrund ihrer Kapselung sehr einfach auf andere Makros übertragen werden. 24 Datei "Rectify Data.vabs": ' Rectify data of the active node. Sub Main If Not ActiveNode.DataAvailable Then MsgBox "This macro needs an open data window." Exit Sub End If Dim ds As Dataset Set ds = ActiveNode.Dataset ' Limit operation to small data sets, i.e. Averages etc. If ds.Length > 10000 Then MsgBox "Data set contains " & ds.Length & _ " data points (macro is limited to 10000 data points)." Exit Sub End If Dim nhn As New NewHistoryNode ' Create new data set. nhn.Create "Rectify", ActiveNode, "", False, ds.Type, ds.Channels.Count, ds.Length, _ ds.SamplingInterval ' Description of operation (operation info) nhn.Description = "Rectify all channels" ' Copy properties. CopyProperties ds, nhn ' Copy markers. CopyMarkers ds, nhn ' Read / modify / write data Dim fData() As Single Dim Chs As Channels Set Chs = ds.Channels Dim ch As Channel For i = 1 To Chs.Count Set ch = Chs(i) ' Read ch.GetData 1, ds.Length, fData ' Modify For j = 1 To ds.Length fData(j) = Abs(fData(j)) Next ' Write nhn.WriteData i, 1, ds.Length, fData Next nhn.Finish End Sub ' Copy properties from source node to target node. Sub CopyProperties(dsSrc As Dataset, nhnTarget As NewHistoryNode) Dim i As Long Dim Chs As Channels Set Chs = dsSrc.Channels Dim ch As Channel For i = 1 To Chs.Count Set ch = Chs(i) nhnTarget.SetChannelName i, ch.Name nhnTarget.SetRefChannelName i, ch.ReferenceChannel nhnTarget.SetChannelUnit i, ch.Unit Dim pos As ChannelPosition Set pos = ch.Position nhnTarget.SetChannelPosition i, pos.Radius, pos.Theta, pos.Phi Next nhnTarget.SegmentationType = dsSrc.SegmentationType nhnTarget.Averaged = dsSrc.Averaged End Sub ' Copy markers from source node to target node. Sub CopyMarkers(dsSrc As Dataset, nhnTarget As NewHistoryNode) Dim mk As Marker Dim Mks As Markers Set Mks = dsSrc.Markers For Each mk In Mks nhnTarget.AddMarker mk.ChannelNumber, mk.Position, mk.Points, _ mk.Type, mk.Description, mk.Invisible Next End Sub Vision Analyzer Makro-Kochbuch 25 Das folgende Makro erstellt eine neue sekundäre History-Datei, die alle "FP1"-Kanäle aller "Average"-Knoten der primären History-Dateien im aktuellen Workspace erhält. Datei "Collect FP1.vabs": ' Look in each primary history file for history node "Average" with channel "Fp1". ' If the node and the channel exist, add the channel to a new secondary ' history file called "Collect Fp1", history node "Fp1". Option Explicit Sub Main Dim sFiles() As String ' Container for valid history file names. Dim hf As HistoryFile Dim hn As HistoryNode Dim nCount As Long, nLength As Long, nType As Long Dim fSamplingInterval As Double ' First count number of files that match the criterions. For Each hf In HistoryFiles If hf.LinkedData Then ' Primary history file? hf.Open Set hn = hf.FindNode("Average") If Not hn Is Nothing Then If Not hn.Dataset("Fp1") Is Nothing Then If nCount = 0 Then ' Use first data set length as reference nLength = hn.Dataset.Length nType = hn.Dataset.Type fSamplingInterval = hn.Dataset.SamplingInterval End If ' Only data sets with the same length. If nLength = hn.Dataset.Length Then nCount = nCount + 1 ReDim Preserve sFiles(1 To nCount) ' Resize container of names. sFiles(nCount) = hf.DisplayName End If End If End If hf.Close End If Next ' Now we know the number of channels for the new history node. Dim nhn As New NewHistoryNode HistoryFiles.KillFile "Collect Fp1" ' Kill secondary history file if it exists. ' Create a new history file called "Collect Fp1" with the node "Fp1" nhn.Create "Fp1", Nothing, "Collect Fp1", False, nType, nCount, nLength, fSamplingInterval Dim i As Long Dim fData() As Single For i = 1 To nCount nhn.SetChannelName i, sFiles(i) & "-Fp1" ' Set name of new channel ' Copy data. Set hf = HistoryFiles(sFiles(i)) hf.Open Set hn = hf.FindNode("Average") Dim ch As Channel Set ch = hn.Dataset("Fp1") ch.GetData 1, nLength, fData nhn.WriteData i, 1, nLength, fData hf.Close Next nhn.Finish End Sub 6.2.3. Einlesen von Stimulatordaten aus externen Dateien Das folgende Makro liest Stimulus / Reaktionsdaten aus einer Textdatei. Es wird hierbei davon ausgegangen, dass die zugehörigen Stimulus-Marker auch im EEG vorhanden sind. Die Korrektheit der Reaktion dient als Informationsbasis um die Stimulusmarker entsprechend umzubenennen. Die Markerbeschreibungen werden um "-c" erweitert, wenn die Reaktion korrekt war ("correct") oder um "-i", wenn die Reaktion falsch ("incorrect") war. 26 Die Textdatei enthält fünf durch Leerzeichen getrennte Spalten mit Nummern: Die erste Spalte enthält die Ordinalnummer der Stimuli, die vierte Spalte Informationen über die Richtigkeit der Reaktion, wobei 0 für falsch und 1 für korrekt steht. Die anderen Spalten werden ignoriert. Wenn eine Zeile mit einem nichtnumerischen Zeichen (außer Leerzeichen) beginnt, so wird sie ignoriert. Führende Leerzeichen in den Zeilen werden ebenfalls ignoriert. Beispiel einer Zeile: 12 0 0 1 0 Hier haben wir den zwölften Stimulus (Spalte 1) und die Reaktion ist korrekt (Spalte 4). Stimulusdateien mit diesem Aufbau sind häufig anzutreffen. Sind Ihre Dateien anders aufgebaut, so können Sie das Makro leicht anpassen. Die Stimulus-Info-Datei muss sich im Rohdatenverzeichnis des aktuellen Workspaces befinden. Ihr Basisname entspricht dem Basisnamen der zugehörigen EEG-Datei, die Endung lautet ".stm". Beispiel: Roh-EEG: E0000001.eeg, zugehörige Stimulus-Info-Datei: E0000001.stm. Durch diese Namensregel lässt sich das Einlesen der Reaktionsdaten in Templates automatisieren. Datei "ReadResponses.vabs": ' ' ' ' ' ' ' ' ' ' ' ' This macro reads stimulus / response information from a text file. It is assumed that the stimuli are also recorded in the EEG. The correctness of the response is used to rename the stimulus markers. A marker's description is expanded with "-c" if the response is correct and with "-i" if it is incorrect. The text file contains five columns with numbers: The first column contains the stimulus number, the fourth column the correctness where 1 indicates "correct" and 0 indicates "incorrect". The other columns are ignored. If a line starts with a non digit character, the line is skipped. Leading spaces are ignored. The file must be in the raw data folder and has the extension ".stm". It's base name must be the same as the base name of the raw eeg file. For example when the raw file is called "E0000001.eeg" the name of the corresponding stimulus information file is "E0000001.stm". Const sExtension As String = ".stm" ' Extension of stimulus information file. ' Change if your info files have a different extension. Sub Main If Not ActiveNode.DataAvailable Then MsgBox "This macro needs an open data window." Exit Sub End If Dim sFolder As String ' Folder / directory of stimulus information file. ' Set folder (directory) of the stimulus information file to the raw file folder. ' Change the following line if your info files are located in a different folder. sFolder = CurrentWorkspace.RawFileFolder ' Build the full file name Dim sStimFile As String sStimFile = sFolder & "\" & ActiveNode.HistoryFile.Name & sExtension Dim nFile As Integer nFile = FreeFile ' Get a free file handle number. Open sStimFile For Input As nFile ' Open stimulus info file Dim tblCorrectness() As Long ' Array of correctness flags Dim nStimCount As Long ' Number of stimulus information in the stim. info file ' Read in lines and fill tblCorrectness array. Do Until EOF(nFile) Dim sLine As String Line Input #nFile, sLine sLine = Trim(sLine) ' Remove leading spaces. ' Check wether the first character is a number. If IsNumeric(Left(sLine, 1)) Then ' Retrieve the different numbers. Dim cols(1 To 5) As Single ' Array of numbers for each column. Vision Analyzer Makro-Kochbuch 27 Dim i As Long, nPos As Long, nStim As Long, nCorrectness As Long For i = 1 To 5 nPos = InStr(sLine, " ") If nPos > 0 Then cols(i) = CSng(Left(sLine, nPos - 1)) sLine = LTrim(Mid(sLine, nPos)) else cols(i) = CSng(sLine) End If Next nStim = CLng(cols(1)) ' Stimulus number is in column 1. ' Handle ascending, descending and no order of stimuli. If nStim > nStimCount Then ReDim Preserve tblCorrectness(1 To nStim) nStimCount = nStim End If tblCorrectness(nStim) = CLng(cols(4)) ' Correctness flag is in column 4. End If Loop Close nFile ' Now we have all informations we need to set the stimulus markers. Dim nhn As New NewHistoryNode nhn.Create "Correctness", ActiveNode Dim Mks As Markers Dim mk As Marker Set Mks = ActiveNode.Dataset.Markers Dim nStimuliFound As Long, nCorrectResponses As Long, nIncorrectResponses As Long For Each mk In Mks If mk.Type = "Stimulus" Then nStimuliFound = nStimuliFound + 1 ' Leave loop if no more entries are in tblCorrectness. If nStimuliFound = nStimCount Then Exit For Dim sDescription As String sDescription = mk.Description If tblCorrectness(nStimuliFound) > 0 Then ' Correct response? sDescription = sDescription & "-c" nCorrectResponses = nCorrectResponses + 1 Else sDescription = sDescription & "-i" nIncorrectResponses = nIncorrectResponses + 1 End If ' Call procedure to rename the marker. RenameMarkerDescription mk, nhn, sDescription End If Next ' Write descriptions for operation infos. ' Operation, inherited by templates nhn.Description = "Checked responses for correctness and coded stimulus markers with " & _ "'-c' (correct) or '-i' (incorrect)" & vbCrLf & vbCrLf ' Operation results, not inherited by templates nhn.Description2 = "Correct responses found: " & nCorrectResponses & vbCrLf & _ "Incorrect responses found: " & nIncorrectResponses nhn.Finish End Sub ' It is not possible to rename a marker description directly. This procedure does ' this by removing a marker, and then adding a new on. Sub RenameMarkerDescription(mk As Marker, nhn As NewHistoryNode, sNewDescription As String) With mk nhn.RemoveMarker .ChannelNumber, .Position, .Points, .Type, .Description nhn.AddMarker .ChannelNumber, .Position, .Points, .Type, sNewDescription End With End Sub 6.2.4. Einlesen von Kanalpositionen aus externen Dateien Das folgende Makro liest Elektrodenpositionen aus einer Positionsdatei und setzt sie in einem neuen Datensatz. 28 Die Positionsdatei hat den folgenden Zeilenaufbau: <Elektrodennamen>, <Radius>, <Theta>, <Phi> Beispiel: Fp1,1,-92,-72 Fp2,1,92,72 Zeilen, die mit dem Kommentarzeichen # starten, werden ignoriert. Die genaue Definition der Elektrodenpositionen, wie sie im Analyzer verwendet werden, entnehmen Sie bitte dem Anhand des Benutzerhandbuchs. Die Positionsdatei muss sich im Rohdatenverzeichnis des aktuellen Workspaces befinden. Ihr Basisname entspricht dem Basisnamen der zugehörigen EEG-Datei, die Endung lautet ".pos". Beispiel: Roh-EEG: E0000001.eeg, zugehörige Positionsdatei: E0000001.pos. Datei "ReadPositions.vabs": ' This macro reads electrode positions from a text file. ' The text file has the following line format ' (of course without the leading comment character "'"): ' <Electrode Name>,<Radius>,<Theta>,<Phi> ' Example: ' ' Fp1,1,-92,-72 ' Fp2,1,92,72 ' ' The file must be in the raw data folder and has the extension ".pos". It's base name must ' be the same as the base name of the raw eeg file. For example when the raw file is called ' "E0000001.eeg" the name of the corresponding position file is "E0000001.pos". Option Explicit Const sExtension As String = ".pos" ' Extension of electrode position file. ' Change if your info files have a different extension. Sub Main If Not ActiveNode.DataAvailable Then MsgBox "This macro needs an open data window." Exit Sub End If Dim sFolder As String ' Folder / directory of stimulus information file. ' Set folder (directory) of the position file to the raw file folder. ' Change the following line if your position files are located in a different folder. sFolder = CurrentWorkspace.RawFileFolder ' Build the full file name Dim sPosFile As String sPosFile = sFolder & "\" & ActiveNode.HistoryFile.Name & sExtension Dim nFile As Integer nFile = FreeFile ' Get a free file handle number. Open sPosFile For Input As nFile ' Open position info file ' Create new node. Dim nhn As New NewHistoryNode nhn.Create "Read Pos", ActiveNode ' Read in lines. Do Until EOF(nFile) Dim sLine As String Line Input #nFile, sLine sLine = Trim(sLine) ' Remove leading spaces. ' Check wether the first character is a comment character. If Not Left(sLine, 1) = "#" Then ' No comment? ' Retrieve the different columns. Dim cols(1 To 4) As String ' Array of strings for each column. Dim i As Long, nChannel As Long, nPos As Long For i = 1 To 4 Vision Analyzer Makro-Kochbuch 29 nPos = InStr(sLine, ",") If nPos > 0 Then cols(i) = Left(sLine, nPos - 1) sLine = LTrim(Mid(sLine, nPos + 1)) Else cols(i) = sLine End If Next ' Do we have a corresponding channel in the data set? nChannel = GetChannelIndex(ActiveNode, cols(1)) If nChannel > 0 Then nhn.SetChannelPosition nChannel, CLng(cols(2)), CLng(cols(3)), _ CLng(cols(4)) End If End If Loop Close nFile ' Write descriptions for operation infos. ' Operation, inherited by templates nhn.Description = "Read electrode positions from external file." & vbCrLf & vbCrLf ' Operation results, not inherited by templates nhn.Description2 = "Position info file: " & sPosFile & vbCrLf nhn.Finish End Sub ' Get the index of the channel that matches the given label in the given history node. Function GetChannelIndex(hn As HistoryNode, sLabel As String) As Long Dim Chs As Channels Dim ch As Channel Set Chs = hn.Dataset.Channels Dim i As Long For i = 1 To Chs.Count If StrComp(Chs(i).Name, sLabel, 1) = 0 Then ' Found? GetChannelIndex = i Exit Function End If Next End Function Eine modifizierte Version dieses Makros mit dem Namen "ReadPosXYZ.vabs" liest Koordinaten im XYZ-Format und konvertiert diese in das interne Koordinatensystem um. 6.2.5. Exportieren von Frequenzdaten in eine ASCII-Datei Das folgende Makro exportiert aus dem aktuell dargestellten Frequenzdatensatz das AlphaBand, das hier von 7,5 bis 12,5 Hz definiert ist. Es werden alle Werte, die in diesen Bereich fallen, exportiert. Mit wenig Aufwand ließe sich stattdessen auch nur der mittlere Wert exportieren. Es wird hier automatisch überprüft, ob eventuell komplexe Frequenzdaten vorliegen. In diesem Falle werden die Beträge exportiert. Das Makro erzeugt eine neue Datei basierend auf den Namen der aktuellen History-Datei und des aktuellen Datensatzes. Die neue Datei wird im Exportverzeichnis des aktuellen Workspaces abgelegt. Das Exportverzeichnis legen Sie im Analyzer unter File > Edit Workspace fest. Datei "ExportAlpha.vabs": ' Export a frequency interval into an ASCII file. Option Explicit ' Define band: Const fIntervalStart = 7.5 ' Start in Hertz Const fIntervalLength = 5 ' Length in Hertz Sub Main If Not ActiveNode.DataAvailable Then 30 MsgBox "This macro needs an open data window." Exit Sub End If Dim ds As Dataset Set ds = ActiveNode.Dataset If ds.Type <> viDtFrequencyDomain And ds.Type <> viDtFrequencyDomainComplex Then MsgBox "This macro expects data in the frequency domain." Exit Sub End If ' Build filename based on the export folder, the history file and the history node. Dim sFilename As String sFilename = CurrentWorkspace.ExportFileFolder & "\" & ActiveNode.HistoryFile.DisplayName _ & "_" & ActiveNode.Name & "_Alpha.txt" ' Check for the interval. Dim nFirstPoint As Long, nPoints As Long ' First data point nFirstPoint = fIntervalStart / ds.SamplingInterval + 1 nPoints = fIntervalLength / ds.SamplingInterval If nFirstPoint + nPoints - 1 > ds.Length Then ' Out of range? MsgBox "The requested interval is out of range." Exit Sub End If Dim nFile As Long ' File handle nFile = FreeFile ' Create output file. Open sFilename For Output As nFile Dim ch As Channel For Each ch In ds.Channels ' Write channel names first. Print #nFile, ch.Name; Dim fData() As Single ch.GetData nFirstPoint, nPoints, fData Dim i As Long Dim fValue As Single For i = 1 To nPoints ' Complex data ? -> convert. If ds.Type = viDtFrequencyDomainComplex Then ' fData() contains nPoints * 2 values if complex fValue = Sqr(fData((i - 1 ) * 2 + 1)^2 + fData((i - 1 ) * 2 + 2)^2) Else fValue = fData(i) End If Print #nFile, " " & fValue; Next Print #nFile ' CrLf Next Close nFile End Sub Soll dieses Makro in einer History-Vorlage verwendet werden, um immer wieder automatisch das Alpha-Band zu exportieren, können Sie den folgenden Trick anwenden. Fügen Sie zwischen den letzten beiden Zeilen die Anweisungen zum Erstellen eines neuen HistoryKnotens ein. Dieser Knoten dient nur als Heimat für das Makro, ändert aber nichts an den Daten ("ExportAlpa2.vabs"): Close nFile ' Build a new data set as home of the macro. This allows the usage of the macro ' in a history template. Dim nhn As New NewHistoryNode nhn.Create "Export Alpha", ActiveNode nhn.Description = "Export Alpha Band" & vbCrLf nhn.Description2 = "Exported to '" & sFilename & "'" nhn.Finish End Sub Vision Analyzer Makro-Kochbuch 31 6.3. Dynamische Parametrisierung Einige Transformationen können über das "Transformation"-Objekt aufgerufen werden. Da das Makro die Parameter für die Transformationen, z.B. basierend auf dem Ergebnis einer FFT o.ä., zur Laufzeit berechnen und übergeben kann, sprechen wir von einer dynamischen Parametrisierung. Die Liste der Transformationen, die Sie mit einem Makro aufrufen können, sowie ihre Parameter-Syntax finden Sie im Kapitel "Aufrufbare Transformationen" des Ole-AutomationReferenzhandbuchs. Die Vorteile der dynamischen Parametrisierung mit Hilfe vorhandener Transformationen gegenüber einer vollständigen Implementierung der Algorithmen im Makro sind: Algorithmen müssen nicht neu entwickelt werden. Die Datenberechung ist erheblich schneller, es können auch Rohdaten schnell transformiert werden. Der Platzbedarf in der History-Datei ist normalerweise gering, da die meisten Transformationen ihre Daten erst bei Anforderung berechnen und nicht in der HistoryDatei speichern. Einige sehr interessante Möglichkeiten ergeben sich aus der Kombination von Makros und der Formelauswerter-Transformation. Sie könnten z.B. neue Kanäle aus vorhandenen berechnen und hierbei die tatsächlich gemessenen Kanalpositionen verwenden, statt auf Standardpositionen zurückzugreifen. Das folgende Beispiel verwendet die Filter-Transformation. Hierbei sollen Rohdaten basierend auf einer FFT-Analyse im Alpha-Band mit einem Bandpass gefiltert werden. Zuerst wird dafür, ebenfalls ausgehend von den Rohdaten, die folgende Transformationssequenz durchgeführt: Segmentation, Artifact Rejection, FFT, Average. Es soll nun die Frequenz im Alpha-Band des Kanals "O1", die die stärkste Amplitude aufweist, für eine Bandpass-Filterung (+/-3Hz) aller Kanäle des Rohdatensatzes verwendet werden. Die nachfolgende Abbildung zeigt die Relation zwischen den Knoten. Abbildung 6-1: Relation zwischen den Knoten Das Makro erzeugt den Knoten "Alpha Band Filter" basierend auf einer Analyse des Knotens "Average". Im Makro finden Sie den Aufruf "Transformation.TryLater". Dieser Befehl sollte verwendet werden, wenn Daten aus einem Nebenzweig des History-Baums benötigt werden. Es ist nämlich möglich, dass beim Einsatz dieses Baumes in einer History-Vorlage der Zweig 32 "Alpha Band Filter" zuerst berechnet wird. In diesem Falle fehlen aber noch die FFT-Daten. "TryLater" veranlasst den Vorlagen-Prozessor, mit dem nächsten Zweig fortzufahren und später noch einmal zu versuchen, den Knoten "Alpha Band Filter" zu berechnen. Sollte dies wiederholt nicht möglich sein, obwohl keine neuen Knoten mehr berechnet werden können, gibt der Vorlagen-Prozessor auf. Datei " DynParameterization.vabs": ' Example for Dynamic Parameterization ' This macro looks for a history node 'Average' that contains an averaged FFT. ' If found, it looks in the Alpha band for the maximum amplitude in channel "O1". ' Then it uses the frequency at the maximum amplitude as input parameter for ' a bandpass (+/-3Hz) to filter the active data set with the 'Filters' transformation. Option Explicit ' Define band: Const fIntervalStart = 7.5 ' Start in Hertz Const fIntervalLength = 5 ' Length in Hertz Const fBandwidth = 6 ' Bandwidth (+/-3Hz) Const sTestChannel = "O1" ' Test channel, i.e. channel where the Alpha band is checked. Sub Main If Not ActiveNode.DataAvailable Then MsgBox "This macro needs an open data window." Exit Sub End If On Error Resume Next ' Look for node 'Average' that is in frequency domain Dim FFTAverageNode As HistoryNode Set FFTAverageNode = ActiveNode.HistoryFile.FindNode("Average") Do ' No more history node? -> try later if in template mode. If FFTAverageNode Is Nothing Then Transformation.TryLater ' If the macro has not terminated here, we are not in template mode. MsgBox "Missing history node 'Average' (FFT average)" Exit Sub End If Dim ds As Dataset Set ds = FFTAverageNode.Dataset ' Frequency domain? If ds.Type = viDtFrequencyDomain Or ds.Type = viDtFrequencyDomainComplex Then Exit Do End If ' Not frequency domain? -> Look for the next history node with the same name. Set FFTAverageNode = ActiveNode.HistoryFile.FindNextNode Loop ' Now we have our averaged FFT history node. ' Lets get the data from the test channel. Dim ch As Channel Set ch = ds(sTestChannel) If ch Is Nothing Then Message "Missing test channel '" & sTestChannel & "'" Exit Sub End If ' Check for the interval. Dim nFirstPoint As Long, nPoints As Long nFirstPoint = fIntervalStart / ds.SamplingInterval + 1 nPoints = fIntervalLength / ds.SamplingInterval If nFirstPoint + nPoints - 1 > ds.Length Then ' Out of range? Message "The requested interval is out of range." Exit Sub End If ' Look for maximum value in the defined interval. Dim fData() As Single ch.GetData nFirstPoint, nPoints, fData Dim i As Long Dim fValue As Single, fMax As Single Dim nMaxPosition As Long Vision Analyzer Makro-Kochbuch 33 fMax = -1 nMaxPosition = 0 For i = 1 To nPoints ' Complex data ? -> convert. If ds.Type = viDtFrequencyDomainComplex Then ' fData() contains nPoints * 2 values if complex fValue = Sqr(fData((i - 1 ) * 2 + 1)^2 + fData((i - 1 ) * 2 + 2)^2) Else fValue = fData(i) End If If fValue > fMax Then ' New maximum found? fMax = fValue nMaxPosition = i - 1 End If Next ' Convert position to frequency Dim fFrequency As Single fFrequency = (nFirstPoint + nMaxPosition - 1) * ds.SamplingInterval ' Build parameter string Dim sParameters As String sParameters = "Lowcutoff=" & SingleToString(fFrequency - fBandWidth / 2) _ & ",24;highcutoff=" & SingleToString(fFrequency + fBandWidth / 2) & ",24" ' Perform the transformation Transformation.Do "Filters", sParameters, ActiveNode, "Alpha Band Filter" Exit Sub CheckError: Resume Next End Sub ' The function converts a single value to ' is always a dot ('.'), even in European Function SingleToString(fValue As Single) SingleToString = Replace(Str(fValue), End Function 34 a string. In opposite to 'Str' the decimal delimeter countries. As String ",", ".") 7. Tipps für Fortgeschrittene 7.1. Variablendeklaration Wie bereits im Kapitel "Kurzer Streifzug durch Basic" erwähnt, können Sie optional Variablen explizit deklarieren: Dim fData as Single Sie können aber auch den Zwang zur Deklaration einführen. Dafür verwenden Sie die Anweisung: Option Explicit die Sie als erste Zeile des Makros oberhalb von "Sub Main" einfügen. In diesem Falle erhalten Sie eine Fehlermeldung, wenn Sie das Makro ausführen wollen, und es undeklarierte Variablen verwendet. Der Vorteil liegt darin, dass Tippfehler bei Variablennamen nicht mehr möglich sind, die sonst zur impliziten Erstellung von Variablen geführt hätten. Beispiel: Dim fValue as Single fValue = 1 fValue = fVolue + 1 Hier haben wir in der letzten Zeile statt "fValue + 1" "fVolue + 1" geschrieben. Ohne "Option Explicit" gibt es keine Fehlermeldung, sondern nur ein falsches Resultat. "fVolue" wird implizit erzeugt und auf den Wert 0 gesetzt. Solche Fehler sind bei größeren Makros äußerst schwer zu finden, so dass sich der Einsatz von "Option Explicit" dort meistens bezahlt macht. Vision Analyzer Makro-Kochbuch 35 7.2. Eigene Dialogboxen Neben den Ein- und Ausgabemöglichkeiten "InputBox" und "MsgBox" können Sie auch komplexere Dialogboxen erzeugen. Beginnen Sie dazu ein neues Makro mit Macro > New. Anschließend wählen Sie Edit > UserDialog. Es erscheint ein Dialog, der Ihnen die Erstellung einer Dialogvorlage ermöglicht. Abbildung 7-1: Editor für Dialogvorlage Klicken Sie nun mit der Maus auf das folgende Symbol. Anschließend klicken Sie auf das gepunktete Feld rechts daneben. Es entsteht eine Textbox mit dem Text ".TextBox1". Wiederholen Sie die Aktion mit dem folgenden Symbol. Ihr Dialog sollte nun eine Textbox und einen OK-Button enthalten. Sie können mit der Maus Position und Größe der Elemente, einschließlich der Dialogbox selbst ändern. Betätigen Sie anschließend den folgenden Button. Sie befinden sich wieder im Texteditor. Das Programm hat den Code für den Dialog automatisch eingefügt. Er sieht etwa wie folgt aus: Sub Main Begin Dialog UserDialog 400,203 ' %GRID:10,7,1,1 36 TextBox 70,49,270,21,.TextBox1 OKButton 110,112,90,21 End Dialog Dim dlg As UserDialog Dialog dlg End Sub Führen Sie nun das Makro aus, so erscheint ein Dialog mit der Textbox und dem OK-Button. Sie können Text eingeben und den OK-Button betätigen. Wie kommen Sie aber nun an den eingegebenen Text? Ganz einfach, die automatisch deklarierte Variable "dlg" ist ein Objekt mit der Eigenschaft "TextBox1". "dlg.TextBox1" liefert also den Text, wie das folgende erweiterte Makro zeigt. Sub Main Begin Dialog UserDialog 400,203 ' %GRID:10,7,1,1 TextBox 70,49,270,21,.TextBox1 OKButton 110,112,90,21 End Dialog Dim dlg As UserDialog Dialog dlg ' Start dialog. MsgBox dlg.TextBox1 ' Show user input. End Sub Die Zeile "MsgBox dlg.TextBox1" gibt den eingegebenen Text wieder aus. Sie können auch einen voreingestellten Text bestimmen, der vom Anwender optional überschrieben werden kann. Dafür weisen Sie "dlg.TextBox1" einen Text zu, bevor der Dialog angezeigt wird: Sub Main Begin Dialog UserDialog 400,203 ' TextBox 70,49,270,21,.TextBox1 OKButton 110,112,90,21 End Dialog Dim dlg As UserDialog dlg.TextBox1 = "Hello world" ' Dialog dlg ' MsgBox dlg.TextBox1 ' End Sub %GRID:10,7,1,1 Assign default text. Start dialog. Show user input. Wollen Sie den Dialog erweitern oder verändern, so bewegen Sie den Cursor zwischen "Begin Dialog ..." und "End Dialog" und wählen dann wieder Edit > UserDialog. Sie können nun weitere Elemente in den Dialog einbauen, wie Bilder, Radio-Buttons, Auswahllisten und vieles mehr. Betätigen Sie die Taste "F1", um sich Informationen über die verschiedenen Elemente des Dialog-Editors zu verschaffen. Nach Beendigung des Dialog-Editors können Sie in der Online-Hilfe weitere Informationen über die Möglichkeiten des benutzerdefinierten Dialogs erhalten. Vision Analyzer Makro-Kochbuch 37 7.3. Funktionen / Prozeduren Sie können eigene Funktionen und Prozeduren definieren, um die Übersichtlichkeit eines Makros zu erhöhen oder seine Länge zu reduzieren. Im folgenden Beispiel wird der Prästimulusinterval ausgegeben. Dafür sucht die Funktion "FindTimeZero" einen Marker des Typs "Time 0" und liefert dessen Position zurück. ' Print time 0 of the active node. Sub Main If Not ActiveNode.ContainsData Then MsgBox "No active node found." Exit Sub End If Dim ds As Dataset Set ds = ActiveNode.Dataset ' Sampling interval is in microseconds -> convert to ms. MsgBox "Prestimulus: " & (FindTimeZero(ds) - 1) * ds.SamplingInterval / 1e3 & "ms" End Sub ' Find position of time 0 marker (prestimulus interval length) Function FindTimeZero(ds As Dataset) As Long Dim mk As Marker FindTimeZero = 1 For Each mk In ds.Markers ' Case insensitive comparison. If StrComp(mk.Type, "Time 0", 1) = 0 Then FindTimeZero = mk.Position Exit Function End If Next End Function Das folgende Beispiel definiert die Prozedur "RenameMarkerDescription", die die Beschreibung eines Markers ändert. ' Rename all stimulus markers with the description "S 1" to "Hand". Sub Main Dim nhn As New NewHistoryNode nhn.Create "S1->Hand", ActiveNode Dim Markers As Markers Dim mk As Marker Set Markers = ActiveNode.Dataset.Markers For Each mk In Markers If mk.Type = "Stimulus" And mk.Description = "S 1" Then RenameMarkerDescription mk, nhn, "Hand" End If Next Mk nhn.Finish End Sub ' It is not possible to rename a marker description directly. This procedure does ' this by removing a marker, and then adding a new one. Sub RenameMarkerDescription(mk As Marker, nhn As NewHistoryNode, NewDescription As String) nhn.RemoveMarker mk.ChannelNumber, mk.Position, mk.Points, mk.Type, mk.Description nhn.AddMarker mk.ChannelNumber, mk.Position, mk.Points, mk.Type, NewDescription End Sub 38 7.4. Unterdrückung von Dialogen in History-Vorlagen Wenn Sie, ähnlich wie beim Analyzer in den meisten Transformationen, Eingabeparameter vom Anwender annehmen wollen, wenn er das Makro direkt aufruft, nicht jedoch wenn das Makro in einer History-Vorlage gespeichert ist, können Sie zum Speichern der Parameter die Eigenschaft "NewHistoryNode.Description" verwenden. Wenn ein neues "NewHistoryNode"Objekt beim Aufruf eines Makros erzeugt wird, so ist "Description" leer, im VorlagenKontext jedoch gefüllt mit dem Text, den Sie bei der Ausführung des Makros gesetzt haben. Sie können also die Benutzereingabe in "Description" speichern. Nach dem Erstellen des neuen Knotens mit "NewHistoryNode.Create()" überprüfen Sie, ob "Description" leer ist. Wenn ja, lassen Sie den Anwender eine Eingabe tätigen, sonst befinden Sie sich im VorlagenKontext und werten "Description" aus. Das folgende Beispiel illustriert das Verfahren. Datei "RenameMarkersInteractive.vabs" ' Rename markers ' User input for old/new name, skipped input in template processing Sub Main If Not ActiveNode.DataAvailable Then MsgBox "This macro needs an open data window." Exit Sub End If Dim nhn As New NewHistoryNode Dim sOldName As String, sNewName As String nhn.Create "Renamed Markers", ActiveNode If nhn.Description = "" Then ' Interactive mode sOldName = InputBox("Enter markers name", "Rename Markers") sNewName = InputBox("Enter new name", "Rename Markers") nhn.Description = "Rename Markers" & vbCrLf & "Old name: " & sOldName & _ vbCrLf & "New name: " & sNewName Else ' Template mode ' Retrieve names from description text Dim nPos As Integer, sTemp As String sTemp = nhn.Description nPos = InStr(sTemp, "Old name: ") If nPos > 0 Then sTemp = Mid(sTemp, nPos + Len("Old name: ")) nPos = InStr(sTemp, vbCrLf) sOldName = Left(sTemp, nPos - 1) sNewName = Mid(sTemp, nPos + 2 + Len("New name: ")) End If End If If sOldName = "" Then MsgBox "Missing old name" Exit Sub ElseIf sNewName = "" Then MsgBox "Missing new name" Exit Sub End If Dim Markers As Markers Dim mk As Marker Set Markers = ActiveNode.Dataset.Markers For Each mk In Markers If mk.Description = sOldName Then RenameMarkerDescription mk, nhn, sNewName End If Next Mk nhn.Finish End Sub ... Vision Analyzer Makro-Kochbuch 39 7.5. Debugging ("Entwanzen", Fehlersuche) Unter Debugging verstehen wir hier die Fehlersuche in einem Makro. Neben der einfachen visuellen Inspektion des Makrotextes können Sie das Makro zeilenweise durchlaufen, jederzeit Variablenwerte abfragen und Haltepunkte setzen, an denen das Makro pausiert. Anhand des folgenden Beispiels werden wir diese Schritte demonstrieren. Sub Main i = 1 i = i + 1 MsgBox i End Sub Tippen Sie das Makro ein. Betätigen Sie anschließend die Taste F8. Das Fenster ist gespalten in einen oberen und einen unteren Bereich. Oben befinden sich vier Reiter mit den Titeln "Immediate", "Watch", "Stack" und "Loaded". Uns interessiert in dieser Demonstration nur der "Immediate"-Reiter. Die erste Zeile des Makros ist gelb markiert. Diese Markierung zeigt die aktuelle Makrozeile an. Betätigen Sie nun die F8-Taste noch zweimal. Die Markierung befindet sich nun in der dritten Zeile, "i = i + 1". Klicken Sie nun mit der Maus in das "Immediate"-Fenster. Geben Sie den folgenden Text ein: ? i Betätigen Sie anschließend die Enter-Taste. Es erscheint "1%". "%" steht für Ganzzahl. Wichtig ist aber vor allen Dingen, dass Sie den Inhalt der Variablen auslesen können. Betätigen Sie nun die F8-Taste und fragen Sie noch einmal nach dem Wert von i. Jetzt erscheint der Wert "2%". Wenn Sie sonst nichts weiter wissen wollen, so betätigen Sie die Taste F5 und das Programm läuft durch bis zum Ende. Um einen Haltepunkt zu setzen, bewegen Sie den Cursor z.B. in die vierte Zeile und drücken Sie die Taste F9. Die Zeile wird dunkelrot hinterlegt. Starten Sie nun das Makro mit F5. Es stoppt in der vierten Zeile. Mit einer weiteren Betätigung von F5 setzt das Makro die Ausführung fort. Wir haben eben die Debug-Session mit den Funktionstasten durchgespielt. Sie können alternativ auch die Menüs Macro und Debug, bzw. deren Untermenüs verwenden. Auch die Werkzeugleiste bietet die entsprechenden Befehle an. Allerdings geht es mit den Funktionstasten am schnellsten. Sie können übrigens nicht nur Variablenwerte abfragen, sondern auch den Analyzer erforschen und Kommandos schicken. Betätigen Sie dazu den Menüpunkt View > Always Split. Jetzt ist das Fenster auch gespalten, wenn kein Makro ausgeführt wird. Klicken Sie mit der Maus in das "Immediate"-Fenster und tippen Sie ? HistoryFiles(1).Name und betätigen anschließend die Enter-Taste. Der Name Ihrer ersten History-Datei erscheint. Die Eingabe "HistoryFiles(1).Open" öffnet die Datei und "HistoryFiles(1).Close" schließt sie wieder. Sie können also hier die Wirkung der verschiedenen Automation-Befehle interaktiv testen. Weitere Informationen zum Thema Debugging erhalten Sie in der Online-Hilfe. 40