11.05.2013 Aufrufe

Java Framework Development zur ... - RheinAhrCampus

Java Framework Development zur ... - RheinAhrCampus

Java Framework Development zur ... - RheinAhrCampus

MEHR ANZEIGEN
WENIGER ANZEIGEN

Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.

YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.

<strong>Java</strong> <strong>Framework</strong> <strong>Development</strong> <strong>zur</strong><br />

biomedizinischen Bildverarbeitung und<br />

Vorhersage<br />

Predict 4H<br />

Bachelorarbeit<br />

im Studiengang Biomathematik<br />

Fachhochschule Koblenz, Rheinahrcampus Remagen<br />

Vorgelegt von:<br />

Martin Haunhorst<br />

Geb. am 17.12.1985 in Bonn<br />

Interner Betreuer: Prof. Dr. Heiko Neeb<br />

Zweitbetreuer: Marcus Fassbender<br />

Remagen, 14.08.2009


Danksagung<br />

D a n k s a g u n g | I<br />

Ich möchte an dieser Stelle einigen Personen danken, ohne deren Hilfe diese Arbeit sicherlich<br />

nicht erschienen wäre, schon gar nicht in dieser Form.<br />

Zunächst danke ich meinem Betreuer Prof. Dr. Heiko Neeb, der zu jederzeit ein offenes Ohr<br />

für mich hatte und mir bei vielen Problemen <strong>zur</strong> Seite stand, was oftmals viel Zeit und mit<br />

Sicherheit auch viele Nerven in Anspruch genommen hat.<br />

Auch Jürgen Redwanz, Dieter Gruschinski sowie meinem Zweitbetreuer Marcus Fassbender<br />

möchte ich meinen Dank aussprechen, da sie zusammen diverse Probleme, die beim Arbeiten<br />

aufgetreten sind, behoben haben, was nicht selten etwas länger gedauert hat.<br />

Nicht vergessen darf ich an dieser Stelle meine Kommilitonen Lisa, Vyara und Roman Benke,<br />

die mir bei Problemen jederzeit hilfsbereit <strong>zur</strong> Seite standen. Speziell Roman, der mit mir an<br />

diesem Projekt gearbeitet hat, musste oft für meine Fragen herhalten.<br />

Zu guter Letzt möchte ich meiner Familie und insbesondere meiner Freundin Melanie Reuß<br />

danken, die viele Stunden auf mich verzichten musste, damit ich diese Arbeit anfertigen<br />

konnte. Ihr möchte ich diese Arbeit widmen.


Zusammenfassung<br />

Z u s a m m e n f a s s u n g | II<br />

Die Aufgabe dieser Bachelorarbeit ist die Implementierung eines Konzeptes in <strong>Java</strong>. Konkret<br />

geht es um die Entwicklung eines <strong>Java</strong>-basierten Bildverarbeitungstools – predict 4H<br />

(Haunhorst, 2009 S. 3). Hintergrund hierbei ist, dass es nach wie vor keine Software gibt, mit<br />

deren Hilfe sich Studienergebnisse schnell und unkompliziert in praxisrelevante Ergebnisse<br />

oder Vorhersagemodelle umwandeln lassen (Neeb-Burlacu, 2008). Dies liegt zu einem großen<br />

Teil daran, dass die Studienergebnisse zwar oftmals in praxisrelevante Programme ‚übersetzt‘<br />

werden, diese aber in der Regel nur Prototypen sind. Zudem beruhen diese Prototypen meist<br />

auf kommerzieller Software, sodass keine Weiterentwicklung stattfindet. Die Programme<br />

werden also letztlich nicht im klinischen Alltag eingesetzt. Hier setzt predict 4H an, indem es<br />

eine Open-Source Software anbietet, mit der dieser Schritt getätigt werden kann. Die zu<br />

erzeugende Plattform wird deshalb alle allgemein benötigten Module enthalten, sodass das<br />

dabei entstehende Programm später leicht an verschiedene Studien angepasst werden kann,<br />

z.B. für Multiple Sklerose. Hierzu müssen lediglich die spezifischen Funktionen als weitere<br />

Module ‚angemeldet‘ werden und können so genutzt werden.<br />

Das Programm soll also so modular aufgebaut sein, dass es als Plattform für spezialisierte<br />

Anwendungen dienen kann. Die Idee ist, dass sich Funktionen hinzufügen, ändern oder<br />

löschen lassen, ohne viele Änderungen an dem Programm-<strong>Framework</strong>, also dem Programm-<br />

Grundgerüst, vornehmen zu müssen.<br />

Das Konzept dazu wurde im Rahmen eines Praxisprojektes (Haunhorst, 2009) ausgearbeitet.


Inhaltsverzeichnis<br />

I n h a l t s v e r z e i c h n i s | III<br />

DANKSAGUNG ............................................................................................................... I<br />

ZUSAMMENFASSUNG ................................................................................................. II<br />

INHALTSVERZEICHNIS ............................................................................................... III<br />

VERZEICHNIS DER ABKÜRZUNGEN UND AKRONYME ......................................... V<br />

1. MOTIVATION .......................................................................................................... 1<br />

1.1. Programmaufbau .....................................................................................................................................2<br />

1.2. Einführung in die verwendeten Programme .........................................................................................3<br />

1.2.1. Eclipse RCP ..........................................................................................................................................3<br />

1.2.2. DICOM .................................................................................................................................................4<br />

1.2.3. ImageJ ...................................................................................................................................................4<br />

1.2.4. MySQL .................................................................................................................................................4<br />

2. IMPLEMENTIERUNG DES KONZEPTES ............................................................. 5<br />

2.1. Grundlagen...............................................................................................................................................5<br />

2.1.1. DataBrowser .........................................................................................................................................5<br />

2.1.2. Question ................................................................................................................................................5<br />

2.1.3. Atom_OID ............................................................................................................................................6<br />

2.1.4. PDAtom ................................................................................................................................................6<br />

2.2. Modularität ..............................................................................................................................................7<br />

2.2.1. Perspektiven ........................................................................................................................................10<br />

2.2.2. Maus-Klick-Aktionen .........................................................................................................................11<br />

2.2.3. Benutzereingabe ..................................................................................................................................15<br />

2.2.4. Modularität – Fazit ..............................................................................................................................18<br />

2.3. Trennung der Benutzeroberfläche .......................................................................................................19<br />

2.4. Persistente Speicherung ........................................................................................................................21<br />

2.4.1. Datenbankanbindung ..........................................................................................................................21<br />

2.4.2. Chain of Responsibility .......................................................................................................................22


I n h a l t s v e r z e i c h n i s | IV<br />

2.5. Item Tree ................................................................................................................................................23<br />

2.5.1. Observer Pattern ..................................................................................................................................25<br />

3. VALIDIERUNG DER PROGRAMMFUNKTIONALITÄT ..................................... 29<br />

4. AUSBLICK ............................................................................................................ 32<br />

ABBILDUNGSVERZEICHNIS ...................................................................................... VI<br />

LITERATURVERZEICHNIS ......................................................................................... VII<br />

SELBSTSTÄNDIGKEITSERKLÄRUNG .................................................................... VIII


V e r z e i c h n i s d e r A b k ü r z u n g e n u n d A k r o n y m e | V<br />

Verzeichnis der Abkürzungen und Akronyme<br />

GUI Graphical User Interface<br />

ID Identifier<br />

OID Object Identifier<br />

SQL Structured Query Language<br />

IDE Integrated <strong>Development</strong> Environment<br />

API Application Programming Interface<br />

CT Computertomographie<br />

MRT<br />

RCP<br />

Magnetresonanztomographie<br />

Rich Client Platform


1. Motivation<br />

M o t i v a t i o n | 1<br />

Diese Bachelorarbeit ist ein Teilprojekt von predict 4H – ‚Professional Remagen Diagnostic<br />

Imaging and Classification Toll - For Health‘ – und wurde unter der Leitung von Prof. Dr.<br />

Heiko Neeb absolviert. predict 4H soll „eine Open-Source Software-Plattform hervorbringen,<br />

mit deren Hilfe es ermöglicht werden soll, in kurzer Zeit individualisierte Anwendungen zu<br />

erzeugen, die bei der Durchführung klinischer Studien sowie radiologischer Diagnostik helfen<br />

und schließlich einen schnellen Übertrag der Forschungsergebnisse in klinische<br />

Routineanwendungen gewährleisten. Mit anderen Worten sollen die später entstehenden<br />

Programme helfen, Krankheiten, Krankheitsverläufe etc. vorherzusagen“ (Haunhorst, 2009 S.<br />

3). Damit predict 4H für verschiedene Studien einsetzbar ist, ohne dass für jede Studie große<br />

Änderungen an dem Programm vorgenommen werden müssen, muss das Programm-<br />

Grundgerüst – auch <strong>Framework</strong> genannt – modular aufgebaut sein. Ein Verändern der<br />

Funktionalität darf also nur in wenigen Bereichen Änderungen hervorrufen.<br />

Neben der Modularität sind die persistente Speicherung 1 der Daten und die einfache<br />

Handhabung der Benutzeroberfläche hier die wichtigsten Ziele. Persistent bedeutet in diesem<br />

Zusammenhang sowohl, dass die Daten beim Beenden des Programms nicht verloren gehen,<br />

als auch, dass sämtliche Daten eindeutig abgespeichert werden. Eine einfache Handhabung<br />

meint hier vor allem, dass die Benutzeroberfläche selbsterklärend sein muss, alle Funktionen<br />

also direkt zu finden sein müssen. Auch soll die Benutzeroberfläche unverändert bleiben, egal<br />

für welche Problemstellung das Programm spezialisiert wurde. Selbsterklärend muss die<br />

Oberfläche sein, damit nicht nur ausgebildete Analysten, sondern auch Ärzte und die<br />

Patienten selbst mit dem Programm arbeiten können. Die Unveränderlichkeit der Oberfläche<br />

ist wichtig, um den Arbeitsaufwand im Praxiseinsatz zu minimieren. Ist ein Arzt oder Analyst<br />

zum Beispiel für mehrere Studien zuständig, muss er sich auf diese Weise nicht jedes Mal neu<br />

in das Programm einarbeiten.<br />

1 Persistente Speicherung meint die Speicherung von Daten (oder Objekten) in nichtflüchtigen Speichermedien<br />

wie Dateisystemen oder Datenbanken. Dabei erhalten die Daten/Objekte eindeutige und dauerhafte<br />

Identifikatoren.


1.1. Programmaufbau<br />

Abb. 1.1 Programmaufbau<br />

M o t i v a t i o n | 2<br />

Wie in Abb. 1.1 zu sehen, ist die Programmoberfläche grundsätzlich in zwei Teile gegliedert.<br />

Auf der linken Seite befindet sich der so genannte Item Tree, also ein Objekt-Baum. In diesem<br />

sollen zunächst die im Programm näher definierten ‚Hauptlisten’ dargestellt werden, hier<br />

Patient und Study (siehe Kapitel 2.5). Besitzt ein Objekt ihm zugehörige ‚Unterobjekte’, soll<br />

ein +-Zeichen neben diesem Objekt angezeigt werden. Klickt man auf ein solches +-Zeichen<br />

neben diesen Namen, sollen alle zu dieser Liste gehörenden Objekte erscheinen. Bei Patient<br />

sollen also alle Patienten erscheinen, bei Study alle Studien.<br />

Klickt man auf das +-Zeichen neben einem solchen Objekt, soll angezeigt werden, was es für<br />

Unterkategorien bei diesem Objekt gibt. Wie in der Abbildung zu sehen, wird für den<br />

Patienten ‚Müller‘ aufgeführt, dass es die Unterkategorie Measurement gibt. Es gibt zu<br />

diesem Patienten also Messungen, welche man mit einem Klick auf das entsprechende +-<br />

Zeichen angezeigt bekommt. Analog ist in dieser Abbildung zu erkennen, dass die Studie<br />

Schlafzeiten die Unterkategorie Patient besitzt, die wiederum den Patienten ‚Müller‘ enthält.<br />

Das Patientenobjekt Müller muss hier über dieselben Unterkategorien und Unterobjekte<br />

verfügen wie in der Patientenhauptliste, da es sich auf denselben Patienten bezieht. Prinzipiell


M o t i v a t i o n | 3<br />

sind für jedes Objekt beliebig viele Unterkategorien denkbar. Die Realisierung des Item Tree<br />

mit den aufgeführten Eigenschaften ist die Hauptaufgabe dieser Bachelorarbeit gewesen.<br />

Die rechte Seite enthält das von Roman Benke programmierte Bildvisualisierungstool (Benke,<br />

2009) auf das in dieser Arbeit nicht näher eingegangen wird.<br />

Die beiden Teile der Oberfläche und ihre zugehörigen Klassen gehören beide <strong>zur</strong> GUI und<br />

müssen auch miteinander kommunizieren können. Dennoch sollen sie unabhängig<br />

voneinander gehalten werden, sodass jeder Teil unabhängig von dem anderen verändert oder<br />

gar ausgetauscht werden kann (siehe Kapitel 2.3).<br />

1.2. Einführung in die verwendeten Programme<br />

Grundsätzlich wurde mit Hilfe der kostenfreien Programmieroberfläche Eclipse in der<br />

Programmiersprache <strong>Java</strong> gearbeitet. Die genauere Auflistung der verwendeten Programme<br />

und Eclipse-Plugins findet sich in der Diplomarbeit von Roman Benke (Benke, 2009).<br />

Diese Arbeit beschränkt sich auf die hierfür wichtigsten Programme und Eclipse-Plugins,<br />

welche im Folgenden kurz vorgestellt werden.<br />

1.2.1. Eclipse RCP<br />

Hier ist vor allem die so genannte Rich Client Platform, kurz RCP, wichtig. Dieses Eclipse-<br />

Plug-In ermöglicht es, Anwendungen zu erzeugen, die zwar auf dem Eclipse <strong>Framework</strong><br />

basieren, von der Eclipse-IDE, also der Eclipse-Entwicklungsumgebung, jedoch unabhängig<br />

sind (Wik09). Mit anderen Worten erhält man auf diese Weise eine grundlegende grafische<br />

Oberfläche, auch Graphical User Interface – kurz GUI – genannt, ohne viel Zeit für deren<br />

Erstellung zu benötigen. Dies ist für dieses Projekt besonders wichtig, da die zu erstellende<br />

Plattform viele Funktionalitäten bieten und, wie bereits erwähnt, modular aufgebaut sein soll,<br />

was beides mit viel Zeitaufwand verbunden ist. Dank des RCP-Plugins konnte das<br />

Augenmerk auf diese Ziele gelegt werden, da nicht viel Zeit für die Erstellung der Oberfläche<br />

und des Programmgrundgerüstes erübrigt werden musste.<br />

Bei der Erstellung eines RCP-Projektes wird auch eine plugins genannte Datei erstellt, in der<br />

unter anderem definiert wird, welche Klasse die Startklasse des Programms sein soll, welche<br />

Klassen Perspektiven und welche Views definieren. Diese Einstellungen lassen sich mit Hilfe<br />

dieser Datei jederzeit leicht verändern und bedingen somit keinen Programmieraufwand.


1.2.2. DICOM<br />

M o t i v a t i o n | 4<br />

Digital Imaging and Communications in Medicine, kurz DICOM, ist ein<br />

Datenspeicherstandard, der im klinischen Umfeld von den meisten Anwendern genutzt wird,<br />

meist <strong>zur</strong> digitalen Bildspeicherung. Über die reine Bildinformationsspeicherung hinaus<br />

ermöglicht dieses Format das Speichern von Zusatzinformationen, etwa Patientendaten oder<br />

Kommunikationsprotokollen.<br />

Die predict 4H -Programmplattform nutzt dieses Format <strong>zur</strong> Speicherung und Verwaltung der<br />

Mess-und Analysebilder.<br />

1.2.3. ImageJ<br />

ImageJ ist ein auf <strong>Java</strong> basierendes, frei zugängliches Bildverarbeitungstool, dessen<br />

application programming interface, kurz API, also die Schnittstelle <strong>zur</strong><br />

Anwendungsprogrammierung auf Quelltextebene, so gestaltet ist, dass es als Bibliothek in ein<br />

<strong>Java</strong>-Programm aufgenommen werden kann. Dadurch kann dieses Programm auf sämtliche<br />

Funktionen von ImageJ zugreifen.<br />

Für dieses Projekt ist dies insbesondere deswegen wichtig, da hier viel mit Bildern, vor allem<br />

im DICOM-Format, umgegangen werden muss. Diese Arbeit nutzt die Funktionen von<br />

ImageJ vor allem, um Bilder bearbeiten und anschließend ohne Informationsverlust im<br />

DICOM-Format speichern zu können. Dies ist relevant, da jeder Informationsverlust eine<br />

spätere klinische Auswertung der Daten erheblich verschlechtern würde.<br />

1.2.4. MySQL<br />

MySQL ist ein Datenbankverwaltungssystem, das auf der Datenbanksprache SQL, Structured<br />

Query Language, aufbaut. SQL gehört zu den so genannten relationalen Datenbanksprachen,<br />

also denen, die Tabellen mathematisch beschreiben. Das Besondere an den relationalen<br />

Datenbanken ist, dass sie trotz dieser abstrakten mathematischen Grundlage im Vergleich zu<br />

anderen Datenbanken leicht zu handhaben sind.<br />

Das fertige Programm soll später zwar viele verschiedene Datenbanksysteme verwenden<br />

können, bislang wurde jedoch nur die Datenbankanbindung für MySQL und PostgreSQL<br />

implementiert. Gearbeitet wurde bislang zudem nur mit MySQL. So konnten die<br />

grundlegenden Funktionen der Datenbankanbindung an dieser Datenbank getestet werden,<br />

was einen späteren Übertrag auf andere Systeme erleichtert.


2. Implementierung des Konzeptes<br />

I m p l e m e n t i e r u n g d e s K o n z e p t e s | 5<br />

Das zuvor ausgearbeitete Konzept sieht, wie bereits erwähnt, einen modularen Aufbau vor, da<br />

predict 4H eine Plattform für individuell einsetzbare Bildverarbeitungstools sein soll. Dazu<br />

wurden verschiedene Design Pattern (Haunhorst, 2009) verwendet, die einen groben Aufbau<br />

des Programms vorschlagen. Im Folgenden wird immer wieder darauf eingegangen, wie diese<br />

Design Pattern implementiert wurden.<br />

Des Weiteren muss die Programm-Plattform eine persistente Speicherung der Daten<br />

garantieren und sämtliche Daten in Form eines Objekt-Baumes anzeigen (siehe Kapitel 1.1).<br />

Auch die Lösung dieser Problemstellung wird in den folgenden Kapiteln vorgestellt.<br />

2.1. Grundlagen<br />

Zunächst einmal werden einige grundlegende Klassen des Programms vorgestellt, die im<br />

weiteren Verlauf immer wieder benutzt werden.<br />

2.1.1. DataBrowser<br />

Der DataBrowser dient als Vermittler zwischen den Programmelementen (siehe Kapitel 2.2).<br />

Er ist statisch 1 aufgebaut, so dass jede Klasse Zugriff auf seine Funktionen hat, ohne ihn<br />

erzeugen zu müssen. Somit werden auch sämtliche Attribute nur einmal erzeugt, weshalb die<br />

im DataBrowser gespeicherten Werte eindeutig bleiben. Auf diese Weise wird eine<br />

konsistente Speicherung dieser Werte sichergestellt, solange das Programm läuft.<br />

2.1.2. Question<br />

Diese Klasse ist neben dem DataBrowser die einzige, die allen Programmelementen bekannt<br />

ist. Sie dient dazu, Benutzereingaben abzufragen, indem in dieser Klasse die Frage bzw.<br />

Aufforderung <strong>zur</strong> Eingabe, die Art der Eingabe und ein paar zusätzliche Informationen<br />

gespeichert werden können. Die Art der Eingabe wird mittels Integer-Klassenkonstanten<br />

festgelegt. So kann zum Beispiel die Frage ‚Name eingeben‘ lauten und die Art der Eingabe<br />

die Klassenkonstante für eine ‚normale Benutzereingabe’ sein. Als weitere Eingabearten sind<br />

1 Eine statisch aufgebaute Klasse besitzt nur statische Methoden und Variablen.<br />

Auf eine statische Methode/Variable kann von anderen Klassen direkt zugegriffen werden, sofern auf die<br />

Methode/Variable überhaupt von außen zugegriffen werden kann, diese also öffentlich ist. Es muss dazu keine<br />

Instanz der statischen Klasse erzeugt werden.


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 6<br />

Eingaben mit Browse-Button und Combo-Inputs implementiert. Des Weiteren gibt es die<br />

Möglichkeit über weitere Integer-Klassenkonstanten festzulegen, ob als Antwort nur ein<br />

Datum, ein Alter, eine ganze Zahl oder ähnliches akzeptiert werden soll und ob diese Antwort<br />

auch ausgelassen werden darf (siehe Kapitel 2.2.3).<br />

2.1.3. Atom_OID<br />

Das Programm muss mit vielen Daten umgehen können, sei es von Patienten oder Studien<br />

oder mit diversen Bildern. Das bedeutet auch, dass die Datenmenge mitunter sehr groß wird.<br />

Um dabei alle Daten eindeutig zuordnen zu können, wurde ein Object Identifier, also ein<br />

eindeutiger ‚Objekt-Identifizierer‘, namens Atom_OID erstellt. In diesem Object Identifier,<br />

kurz OID, werden unter anderem der Typ des Objektes und eine eindeutige ganze Zahl<br />

gespeichert. Der Typ könnte also zum Beispiel Patient, Studie oder Messung sein. Für jeden<br />

dieser Typen gibt es dann eine eindeutig zugeordnete ganze Zahl. Da viel mit<br />

Datenbankobjekten gearbeitet wird, gibt es zudem eine DB_OID, welche eine Unterklasse<br />

von Atom_OID ist und speziell für Datenbankobjekte gedacht ist.<br />

Aus diesen OIDs kann das Programm jederzeit erfragen, um welchen Datentyp es sich<br />

handelt. Auf diese Weise können die verschiedenen Anwendungen ihre Zuständigkeit für<br />

dieses Objekt überprüfen.<br />

2.1.4. PDAtom<br />

Um die vorhandenen Daten, sei es aus einer Datenbank oder einem Filesystem, in dem<br />

Programm nutzen zu können, werden Sie als PDAtom-Objekte gespeichert. Diese Objekte<br />

enthalten alle wichtigen Informationen der Daten, vor allem ist hier auch die Atom_OID<br />

gespeichert. Für Datenbankobjekte gibt es die spezielle Unterklasse DBEntry. Sowohl<br />

PDAtom als auch DBEntry sind abstrakte Klassen und können nicht direkt als Objekte erzeugt<br />

werden. Aus diesem Grunde gibt es für sämtliche Datenbankobjekt-Typen jeweils eine eigene<br />

Unterklasse von DBEntry, also beispielsweise Patient, Measurement und Study. In diesen<br />

speziellen Unterklassen sind dann typspezifische Informationen gespeichert. Gleiches gilt<br />

auch für PDAtom, für das es eine Unterklasse <strong>zur</strong> Speicherung von Bildern gibt, nämlich<br />

P4Image (Benke, 2009).<br />

Für jeden dieser Objekt-Typen gibt es eine PDAtomList bzw. eine DBEntryList, in denen<br />

jeweils alle Objekte dieses Typs gespeichert werden. Jede dieser Listen besitzt zudem eine


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 7<br />

PDAtomFactory bzw. eine DBEntryFactory, die zum Erzeugen der Objekte dient (siehe<br />

Kapitel 2.4.1).<br />

2.2. Modularität<br />

Wie bereits erwähnt muss zu entwickelnde Programm-Plattform leicht an konkrete<br />

Bedürfnisse einer Studie angepasst werden können. Es ist also wichtig, dass ein zusätzlicher<br />

Filter oder sogar eine Sammlung von Filtern in das Programm eingebaut werden kann, ohne<br />

dass an dem Programm selber große Änderungen nötig sind.<br />

Dazu ist es zum einen wichtig, dass jede Anwendung dieselbe Schnittstelle implementiert.<br />

Diese Schnittstelle heißt hier Application. Zum Anderen soll die Benutzeroberfläche nicht<br />

direkt mit den Anwendungen kommunizieren, sondern über einen sogenannten Adapter,<br />

gemäß dem Adapter Pattern (Haunhorst, 2009 S. 12 f).<br />

Ersteres birgt den Vorteil, dass mit jeder Anwendung gleich kommuniziert werden kann, die<br />

Programm-Plattform, auch <strong>Framework</strong> genannt, also die Anwendungen nicht genauer kennen<br />

muss. Die Kommunikation über Schnittstellen ist Teil des MVC Pattern (Haunhorst, 2009 S.<br />

5 f), welches die Trennung der Hauptelemente – Anwendung, GUI, Datenmodell – eines<br />

Programms beschreibt. Hier ist vor allem die Trennung der Anwendungen von der GUI<br />

wichtig. Eine exakte Trennung zwischen Datenmodell und Anwendung ist weder nötig noch<br />

möglich, da jedes Modell im Datenmodell zu einer Anwendung gehört.<br />

Die Verwendung eines Adapters ermöglicht es, Änderungen an einem zentralen Ort, nämlich<br />

der Adapter-Klasse auszuführen. Soll also zum Beispiel eine Anwendung hinzugefügt<br />

werden, die völlig neue Methoden beinhaltet, können unter Umständen Änderungen an der<br />

Anwendungs-Schnittstelle notwendig werden. Dies führt jedoch dazu, dass all die Stellen im<br />

<strong>Framework</strong>, die sich auf die Anwendungs-Schnittstelle beziehen, geändert werden müssen.<br />

Mitunter können diese Stellen aber im gesamten <strong>Framework</strong> verteilt sein, sodass die<br />

Änderungen sehr aufwendig werden können. Nutzt man hier einen Adapter, ist die Änderung<br />

nur noch in dieser Klasse zu tätigen, da alle anderen Programmteile weiterhin dieselben<br />

Methoden im Adapter aufrufen können.


In unserem Programm kommen<br />

diese Prinzipien ebenfalls <strong>zur</strong><br />

Anwendung, wie in Abb. 2.1 zu<br />

erkennen. Dabei wird der Adapter<br />

als DataBrowser bezeichnet. Dieser<br />

kennt zum Einen sämtliche<br />

Schnittstellen, also die Schnittstellen<br />

der Anwendungen und der GUI, und<br />

zum Anderen weiß der<br />

I m p l e m e n t i e r u n g d e s K o n z e p t e s | 8<br />

DataBrowser, welche Anwendungen es gibt. Als Beispiel sind derzeit die Anwendungen<br />

Admin, Reconconstruction – Recon – und Analysis vorhanden. Soll nun eine weitere<br />

Anwendung verwendet werden, etwa Predict, so muss diese zunächst die Anwendungs-<br />

Schnittstelle implementieren 1 – sowie die bereits vorhandenen Anwendungen auch – und<br />

zudem beim Start des Programms beim DataBrowser angemeldet werden. Da der<br />

DataBrowser als statische Klasse nicht erzeugt werden muss und in diesem Programm auch<br />

nicht erzeugt wird, geschieht die Anmeldung der Anwendungen im statischen Block der<br />

Klasse. Dabei besitzt der DataBrowser eine applications genannte Liste, zu der die einzelnen<br />

Anwendungen hinzugefügt werden (vgl. Abb. 2.1), was mit dem zuvor erwähnten<br />

‚Anmelden‘ gemeint war.<br />

Der DataBrowser erfragt dann alle wichtigen Daten bei den angemeldeten Anwendungen.<br />

Dass diese die Fragen ‚beantworten’ können, ist über die Implementierung der gemeinsamen<br />

Anwendungs-Schnittstelle sichergestellt worden. Konkret erfragt der DataBrowser<br />

beispielsweise die ID der Perspektive, die zu der Anwendung gehört. Hintergrund hierbei ist,<br />

dass jede Anwendung eine eigene Perspektive erhält, da der Nutzer auf diese Weise über das<br />

Öffnen bzw. Wechseln der Perspektive bestimmen kann, welche Anwendung gestartet werden<br />

soll. Im weiteren Verlauf der Arbeit wird der Grund für dieses Vorgehen noch erläutert<br />

werden (siehe Kapitel 2.2.1).<br />

1 Die Anwendungs-Schnittstelle meint hier kein Interface, sondern eine abstrakte Klasse, von der alle<br />

Anwendungs-Klassen erben. Der Vorteil einer abstrakten Klasse gegenüber einem Interface ist, dass hier neben<br />

den abstrakten auch fertig implementierte Methoden angeboten werden können. Diese Schnittstelle wird also<br />

nicht im klassischen Sinne implementiert, sondern die Unterklasse muss von dieser Klasse erben und die nötigen<br />

Methoden bereitstellen.<br />

Abb. 2.1 Anmeldung von Anwendungen


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 9<br />

Die erste angemeldete Anwendung wird automatisch <strong>zur</strong> ‚Hauptanwendung’ deklariert, das<br />

heißt, dass die zu dieser Anwendung gehörige Perspektive beim Start des Programms geöffnet<br />

wird. Auch bestimmt diese Anwendung, welche Listen in den Item Tree eingebaut werden<br />

(siehe Kapitel 2.5).<br />

Sobald eine Perspektive geöffnet wird – also schon beim Start des Programms das erste Mal –<br />

erfragt diese beim DataBrowser, für welche Perspektiven ShortCuts, also Zugriff-Buttons,<br />

erstellt werden sollen, wobei der DataBrowser die Perspektiven-IDs übergibt, die er von den<br />

angemeldeten Anwendungen erhalten hat.<br />

Abb. 2.2 verdeutlicht dieses Schema noch einmal. Im DataBrowser werden alle<br />

Anwendungen in der Liste applications und alle PerspektivenIDs in der Liste perspectives<br />

gespeichert. Nachdem, wie bereits am Anfang des Kapitels beschrieben, die Anwendungen<br />

angemeldet wurden, wird im statischen Block des DataBrowsers die setPerspectives-Methode<br />

aufgerufen. Diese durchläuft eine Schleife über alle Anwendungen aus der Liste applications,<br />

wobei von der jeweiligen Anwendung die ID der zugehörigen Perspektive über die<br />

getPerspectiveID-Methode erfragt und in der Perspektiven-Liste gespeichert wird. Daraufhin<br />

erfragt der DataBrowser bei der Hauptanwendung, also der Anwendung, die in der<br />

Anwendungs-Liste an erster Stelle steht, über die getMainLists-Methode die Hauptlisten, die<br />

dann im Item Tree dargestellt werden sollen (siehe Kapitel 2.5).<br />

Abb. 2.2 Sequenzdiagramm Programmstart<br />

Hier ist keine konkrete Anwendung, sondern die Anwendungsschnittstelle dargestellt, um zu<br />

verdeutlichen, dass diese für die Sicherstellung der benötigten Methoden zuständig ist.


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 10<br />

Aus diesem Grunde wurden die beiden genannten Methoden – getPerspectiveID und<br />

getMainLists – nochmals separat aufgeführt.<br />

Wird nun eine Perspektive erstellt, ruft diese im DataBrowser die Methode setShortCuts auf,<br />

wobei sie das <strong>zur</strong> Perspektive gehörende Layout 1 übergibt. In dieser Methode läuft eine<br />

Schleife über alle PerspektivenIDs aus der Perspektiven-Liste, wobei für jede Perspektive die<br />

Methode addPerspectiveShortCut des übergebenen Layouts aufgerufen wird. Dabei wird<br />

jeweils die PerspektivenID als Argument übergeben (vgl. Abb. 2.2). Hier zeigt sich, wie<br />

vorteilhaft die Verwendung von Eclipse RCP ist. Statt jeden ShortCut aufwendig kreieren zu<br />

müssen, reicht es, die addPerspectiveShortCut-Methode des Layouts mit der jeweiligen<br />

PerspektivenID auf<strong>zur</strong>ufen, wodurch der entsprechende ShortCut automatisch erstellt wird.<br />

2.2.1. Perspektiven<br />

Wie bereits erwähnt, gehört zu jeder Anwendung eine eigene Perspektive, die die<br />

Perspektiven-Schnittstelle implementiert. So könnte die Admin-Anwendung die<br />

Hauptanwendung des Programms sein und ihre Perspektive somit beim Start des Programms<br />

geöffnet werden (vgl. Abb. 2.3 A). Möchte der Nutzer nun beispielsweise Funktionen der<br />

Reconstruction-Anwendung nutzen, muss er dazu lediglich über den entsprechenden ShortCut<br />

die Reconstruction-Perspektive öffnen (vgl. Abb. 2.3 B und C). Die Admin-Anwendung/-<br />

Perspektive bleibt zwar geöffnet, ihre Funktionen können jedoch nur genutzt werden, wenn<br />

der Nutzer über den entsprechenden ShortCut diese Perspektive wieder als aktiv deklariert,<br />

was die Reconstruction-Anwendung/-Perspektive automatisch inaktiv – aber weiterhin<br />

geöffnet – werden lässt (vgl. Abb. 2.3 D).<br />

Wie in Abb. 2.3 zu sehen, ist die derzeit aktive Anwendung/Perspektive jeweils weiß<br />

unterlegt, die inaktiven grau.<br />

Diese Perspektiven verwenden derzeit alle dieselben Views, scheinen also zunächst nicht<br />

wichtig zu sein. Der DataBrowser muss allerdings zu jeder Zeit wissen, welche Anwendung<br />

der Nutzer gerade verwendet bzw. verwenden möchte (siehe Kapitel 2.2.2). Dazu kann der<br />

DataBrowser den Namen der gerade geöffneten Perspektive abrufen und mit den Namen der<br />

Anwendungen vergleichen. Auf diese Weise ist jederzeit bekannt, welche Anwendung<br />

geöffnet ist. Die Alternative dazu wäre ein zusätzlicher Button gewesen, mit dessen Hilfe der<br />

Nutzer bestimmen kann, welche Anwendung geöffnet sein soll. Dies über die Perspektiven zu<br />

1 Mit Hilfe der Layout-Klasse kann auf das durch Eclipse-RCP erstellte Design und Menü der<br />

Benutzeroberfläche zugegriffen und dieses dementsprechend verändert werden


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 11<br />

lösen birgt den Vorteil, dass auf diese Weise prinzipiell jede Anwendung eigene Views<br />

erhalten kann, falls dies nötig sein sollte (siehe auch Kapitel 2.3).<br />

Abb. 2.3 Perspektivenwechsel<br />

Bisher ist eine View für den Item Tree und eine für den bildvisualisierenden Teil der<br />

Oberfläche definiert (siehe Kapitel 1.1). Denkbar ist zum Beispiel eine weitere View, die statt<br />

der bildvisualisierenden View für spezielle Anwendungen eingesetzt wird. So könnte für eine<br />

Krankheitsvorhersage-Anwendung eine spezielle View zum Visualisieren und Bearbeiten von<br />

Diagrammen benötigt werden.<br />

Auch hier ist dem MVC Pattern genüge getan. Zwar sind die Perspektiven, die Teil der GUI<br />

sind, direkt mit ihren zugehörigen Anwendungen assoziiert und für einige Anwendungen<br />

können spezielle Views nötig sein, doch davon abgesehen kennt die GUI keine Anwendung,<br />

sondern bezieht sämtliche Informationen von dem DataBrowser. Da die meisten<br />

Anwendungen keine spezielle View benötigen, wird meist als einzige Veränderung in der GUI<br />

die Erstellung einer Perspektive nötig sein. Auch diese Veränderung entfällt, falls nur<br />

Änderungen innerhalb einer Anwendung vorgenommen werden.<br />

2.2.2. Maus-Klick-Aktionen<br />

Innerhalb des Item Trees soll der Nutzer Maus-Klick-Aktionen ausführen können. Sowohl<br />

Doppel- wie auch Rechts-Klick-Aktionen. Natürlich hängt die Auswahl der Aktionen von der<br />

geöffneten Anwendung und dem Objekt ab, das angeklickt wurde.


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 12<br />

In Abb. 2.4 ist zu erkennen, dass der Nutzer sich in der Analysis-Anwendung (weiß unterlegt)<br />

befindet. Beim Rechts-Klick auf das Messungs-Objekt ‚MRT’ erscheint ein Auswahldialog<br />

von verschiedenen Aktionen. Ebenso in Abb. 2.5, allerdings ist hier die Admin-Anwendung<br />

geöffnet, weshalb der angezeigte Auswahldialog andere Möglichkeiten bietet. Hier ist schön<br />

zu sehen, wie flexibel das Programm mit Rechts-Klicken umgehen können muss.<br />

Abb. 2.4 Rechts-Klick in der Analysis-Anwendung<br />

Normalerweise müsste nun für jede Aktion eine eigene Klasse geschrieben werden. Dies<br />

würde aber jedwede Modularität zunichte machen, da die Aktionen von den Anwendungen<br />

abhängig sind, die Anwendungen aber der GUI nicht bekannt sein sollen.<br />

Die Lösung liegt in der flexiblen Definition der Klick-Aktions-Klassen. Hiervon wurden nur<br />

zwei erstellt, eine für Doppel- und eine für Rechts-Klick-Aktionen. Die ItemTreeView, also<br />

die Ansicht des Item Trees, erstellt beim Start des Programms nur ein Doppel-Klick-Objekt<br />

und eine Liste mit Rechts-Klick-Objekten. Die Größe dieser Liste erfragt die ItemTreeView<br />

beim DataBrowser, welcher wiederum alle angemeldeten Anwendungen durchgeht und die<br />

jeweils maximale Anzahl an Rechts-Klick-Aktionen erfragt. Das Maximum davon wird<br />

wiederum an die ItemTreeView übergeben.<br />

Abb. 2.5 Rechts-Klick in der Admin-Anwendung<br />

In Abb. 2.6 wird dieses Vorgehen veranschaulicht, wobei noch einmal gut zu erkennen ist,<br />

wie der DataBrowser als ‚Vermittler‘ zwischen der GUI und den Anwendungen fungiert.<br />

Auch hier wurde die Anwendungs-Schnittstelle mit der Methode getMaxRightsClicks<br />

aufgeführt, um zu verdeutlichen, dass diese Methode durch die Schnittstelle garantiert wird.<br />

Nachdem die ItemTreeView die maximale Anzahl an Rechts-Klick-Aktionen erhalten hat,<br />

erstellt sie zunächst nur eine Liste mit eben so vielen Rechts-Klick-Aktionen wie die<br />

maximale Anzahl angibt. In der Abbildung wurde vereinfacht new List(maxRightClicks)


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 13<br />

geschrieben, was jedoch bedeuten soll, dass eine Liste des Typs<br />

LinkedList, also eine Liste, die Rechts-Klick-Aktionen speichern kann,<br />

erstellt und mit der entsprechenden Anzahl an Rechts-Klick-Aktionen – Objekten des Typs<br />

RightClickAction – gefüllt wird.<br />

Abb. 2.6 Erstellen der Rechts-Klick-Aktionen<br />

Doppel-Klick-Aktionen sind hierbei sehr einfach zu handhaben, da bei einem Doppel-Klick<br />

lediglich das angeklickte Objekt, bzw. dessen OID 1 , an den DataBrowser übergeben werden<br />

muss, welcher diese OID dann an die geöffnete Anwendung weitergibt. Die Anwendung kann<br />

von der OID dann den Typ des Objektes erfragen und eine dazu passende Aktion starten.<br />

Rechts-Klick-Aktionen sind insofern schwieriger zu handhaben, als bei einem Rechts-Klick<br />

auf ein Objekt verschiedene Aktionen aufgelistet werden müssen (siehe Abb. 2.4 und Abb.<br />

2.5) aus denen der Nutzer dann eine auswählen kann. Hier muss also schon bekannt sein,<br />

welche Aktionen <strong>zur</strong> Auswahl stehen.<br />

Die Lösung liegt in der variablen Gestaltung der Rechts-Klick-Objekte. Diese müssen<br />

lediglich beim Start des Programms erzeugt werden, weshalb hier wie zuvor beschrieben die<br />

maximale Anzahl an Rechts-Klick-Aktionen aller Anwendungen bekannt sein muss. Ein<br />

späteres Erzeugen von Klick-Aktionen ist nicht mehr möglich bzw. sinnlos, da eine View nur<br />

die Klick-Aktionen akzeptiert, die bei Erzeugung der View mit erzeugt wurden.<br />

Nun kann bei diesen Klick-Aktionen nachträglich der anzuzeigende Text verändert werden.<br />

Ebenso kann in einer View jederzeit bestimmt werden, welche der zu Beginn erzeugten Klick-<br />

Aktionen aktiviert sein sollen. Genau dieses wird nun ausgenutzt, indem die ItemTreeView bei<br />

jedem Rechts-Klick des Nutzers das angeklickte Objekt, bzw. dessen OID, über den<br />

1 Allgemein eine Atom_OID, bei Objekten aus einer Datenbank eine DB_OID, siehe Kapitel 2.1.3.


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 14<br />

DataBrowser an die geöffnete Anwendung übergibt. In dieser ist definiert, welche Aktionen<br />

für diesen Objekt-Typ angeboten werden. Diese Aktionen, bzw. die dazugehörigen<br />

Aktionstexte, werden von der Anwendung als Liste an den DataBrowser <strong>zur</strong>ückgegeben, der<br />

diese an die ItemTreeView weitergibt.<br />

Abb. 2.7 Bezeichnung der Rechts-Klick-Aktionen<br />

In Abb. 2.7 ist dieses Vorgehen dargestellt. Hierbei wurde symbolisch das Setzen der<br />

Aktionstexte aufgeführt als rightClicks.get(i).setText(action). Dabei ist rightClicks die Liste<br />

der Rechts-Klick-Aktionen, die nun in einer Schleife durchlaufen werden muss, wobei ‚i‘ der<br />

Laufindex ist. Nun werden so viele Rechts-Klick-Aktionen aktiviert, wie Aktionen übergeben<br />

wurden. Für jeden Text in der Aktionsliste aktiviert die ItemTreeView also eine Rechts-Klick-<br />

Aktion und ändert den Text dieser Aktion in den entsprechenden Text aus der Liste, indem es<br />

für das entsprechende Objekt die setText-Methode aufruft und den Text aus der Liste<br />

übergibt. Auf diese Weise kann der Nutzer nun zwischen all den von der geöffneten<br />

Anwendung für dieses Objekt angebotenen Aktionen auswählen, ohne dass diese in der<br />

ItemTreeView bekannt sein müssen.<br />

Wählt der Nutzer nun eine Aktion aus, verläuft der Vorgang analog zu dem bei Doppel-Klick-<br />

Aktionen:<br />

Das ausgewählte Objekt wird zusammen mit der ausgewählten Aktion, bzw. dem zugehörigen<br />

Text, über den DataBrowser an die geöffnete Anwendung übergeben, welche ihre für dieses<br />

Objekt und diesen Aktionstext definierte Aktion ausführt.<br />

Dank dieser Vorgehensweise bleibt die Unabhängigkeit zwischen GUI und den<br />

Anwendungen und damit die Modularität vollständig bewahrt.


2.2.3. Benutzereingabe<br />

I m p l e m e n t i e r u n g d e s K o n z e p t e s | 15<br />

Nachdem der Nutzer eine Maus-Klick-Aktion getätigt hat, wie in Kapitel 2.2.2 beschrieben,<br />

führt die Anwendung eine entsprechende objektspezifische Aktion durch. Einige der Rechts-<br />

Klick-Aktionen bedingen jedoch eine Benutzereingabe, zum Beispiel um ein neues<br />

Patientenobjekt anzulegen. Die Anwendung selber darf allerdings keinerlei Anfragen an den<br />

Nutzer stellen, da dies Aufgabe der GUI ist und somit die Modularität aufgegeben werden<br />

müsste. Aus diesem Grund stellt die Anwendung nun eine Anfrage an den DataBrowser,<br />

wobei eine Liste der Fragen – als Question 1 -Objekte – sowie ein Titel für das zu öffnende<br />

Eingabefenster und die übergreifende Fragestellung übergeben werden. Für einen neuen<br />

Patienten könnte die Frageliste also Fragen nach Name, Vorname, Geschlecht und Alter<br />

enthalten, der Fenstertitel könnte ‚Patienten hinzufügen‘ lauten und die übergreifende<br />

Fragestellung ‚Bitte geben Sie die Patientendaten ein. ‘ (vergleiche<br />

Abb. 2.8). In den einzelnen Question-Objekten ist dabei jeweils gespeichert, welchen<br />

Fragetyp diese Frage hat und welche Antwortmöglichkeiten der Nutzer dabei hat. Der<br />

DataBrowser leitet die Anfrage an die ItemTreeView weiter, welche als Teil der GUI einen<br />

Dialog gemäß den Angaben der Question-Objekte erstellt und die entsprechenden<br />

Benutzereingaben als Liste – analog <strong>zur</strong> Frageliste – über den DataBrowser an die<br />

Anwendung <strong>zur</strong>ückgibt. Genauer erstellt nicht die ItemTreeView selber den Dialog, sondern<br />

überlässt dies der Klasse InputDialog. Diese Klasse wurde erstellt, um die komplexe<br />

Funktionalität <strong>zur</strong> Erstellung dieser Dialoge aus der ItemTreeView-Klasse auszulagern und<br />

diese somit übersichtlicher zu halten. Die InputDialog-Klasse erstellt dazu ein neues Fenster<br />

mit dem von der Anwendung festgelegten Fenstertitel. Die erste Zeile dieses Fensters wird<br />

dann mit der von der Anwendung gelieferten allgemeinen Fragestellung gefüllt (vgl.<br />

Abb. 2.8). Dann wird in einer Schleife die ebenfalls übergebene Liste der Question-Objekte<br />

durchlaufen. Dabei wird für jedes Objekt eine neue Zeile erstellt. Jede dieser Frage-Zeilen ist<br />

in mehrere Spalten unterteilt. In der ersten Spalte wird die in dem zugehörigen Question-<br />

Objekt gespeicherte Frage aufgeführt. Daneben wird ein Text 2 -Feld erzeugt, in das der<br />

Benutzer seine Eingabe tätigen kann. Es wird ebenfalls eine dritte Spalte erstellt, worauf noch<br />

eingegangen wird.<br />

1 Die Question-Klasse dient als Übermittler von Benutzereingabe-Einstellungen. Siehe dazu Kapitel 2.1.2.<br />

2 Die Klasse Text dient <strong>zur</strong> Abfrage von Benutzereingaben. Sie erzeugt ein Feld, in dem der Nutzer seine<br />

Eingabe tätigen kann.


In Abb. 2.8 ist gut zu<br />

erkennen, dass die Fragen<br />

nach Name, Vorname und<br />

Alter beantwortet werden<br />

müssen (rote Felder). In<br />

jedem Question-Objekt wird<br />

also gespeichert, ob eine<br />

Frage beantwortet werden<br />

muss oder nicht. Muss sie<br />

beantwortet werden, wird das<br />

I m p l e m e n t i e r u n g d e s K o n z e p t e s | 16<br />

entsprechende Text-Feld rot markiert, bis die Frage beantwortet ist. Die Antwort auf die Frage<br />

nach dem Geschlecht des Patienten kann in diesem Beispiel nur aus einer Liste<br />

(männlich/weiblich) ausgewählt werden. Dies wird erreicht, indem als Eingabeart der<br />

sogenannte Combo-Input ausgewählt wird. Dabei kann von der Anwendung frei definiert<br />

werden, welche Auswahlmöglichkeiten angeboten werden sollen. Dazu werden diese<br />

Möglichkeiten in dem zugehörigen Question-Objekt als Liste gespeichert.<br />

Werden nun andere Parameter gewählt, um beispielsweise eine Messung hinzuzufügen, wie in<br />

Abb. 2.9 zu sehen, wird ein völlig anderer Dialog erstellt und angezeigt. In diesem Fall ist<br />

sowohl der Fenstertitel ‚Messung hinzufügen‘ als auch die übergreifende Fragestellung ‚Bitte<br />

geben Sie die Messdaten ein‘ eine andere als in Abb. 2.8. Auch die Anzahl der Fragen beträgt<br />

nun nur noch drei. Hier ist also gut zu erkennen, dass ein solcher Dialog flexibel gestaltet<br />

werden kann.<br />

Bei der ersten Frage – nach dem Pfad der Messungs-Datei – hat man nun die Möglichkeit<br />

über einen Browse-Button eine Datei auszuwählen und so den Pfad der Datei zu bestimmen<br />

(Abb. 2.9, im Hintergrund). Die zweite Frage – nach dem Aufnahmedatum der Messung –<br />

wurde mit ‚heute' beantwortet, was aber nicht als Eingabe akzeptiert wird. In diesem Fall<br />

wurde von der Anwendung der Eingabetyp als Datum gesetzt, weshalb nur Eingaben dieser<br />

Form angenommen werden.<br />

Abb. 2.8 Benutzereingabe eines neuen Patienten<br />

Sowohl solche zusätzliche Buttons sowie derartige Fehler werden auf der rechten Seite des<br />

Dialogs angezeigt, also in der zuvor erwähnten dritten Spalte der entsprechenden Frage-Zeile.


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 17<br />

Abb. 2.9 Benutzereingabe einer neuen Messung<br />

Um die Benutzereingaben auf solche Bedingungen zu prüfen, wurde die Klasse InputListener<br />

erstellt. Sie implementiert die Schnittstelle Listener 1 , wodurch sie einem Text-Feld als<br />

Listener zugewiesen werden und somit auf Eingaben in dieses Text-Feld reagieren kann. Jedes<br />

Text-Feld – und somit jede Frage – erhält ein eigenes InputListener-Objekt, welches gemäß<br />

den im zugehörigen Question-Objekt gespeicherten Parametern die Eingabe überprüft. Hier<br />

wird also unter anderem geprüft, ob eine Eingabe zwingend erforderlich ist, ob nur Zahlen<br />

oder nur ein Datum eingegeben werden dürfen und ob die Eingabe eine bestimmte Anzahl an<br />

Zeichen enthalten soll. Dazu sind in der InputListener-Klasse verschiedene Methoden<br />

bereitgestellt. Hier ist es wieder von Vorteil, die Klassen flexibel zu gestalten, da so nur eine<br />

einzige Listener-Klasse geschrieben werden muss. Als Ergänzung dazu wurde die Klasse<br />

InputException erstellt. Diese erbt von der Klasse Exception und kennzeichnet somit einen<br />

speziellen Fehlertyp. Der Vorteil, diesen speziellen Fehlertyp zu erstellen, liegt darin, dass in<br />

dieser Klasse weitere Parameter definiert werden können. So enthält sie unter anderem den<br />

boolean-Parameter toDelete, welcher standardmäßig auf false, also falsch, eingestellt ist, bei<br />

Bedarf aber geändert werden kann. Die InputListener-Klasse verwendet diesen Parameter, um<br />

1 Die Aufgabe eines Listeners ist es, beim Auftretens eines bestimmten Ereignisses eine zuvor definierte Aktion<br />

auszuführen (Sehring, 2002). Zu diesem Zweck kann ein Listener beispielsweise einem Text-Objekt/Text-Feld<br />

zugeweisen werden, um auf eine Eingabe in dieses Feld reagieren zu können.


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 18<br />

zu bestimmen, ob eine falsche Benutzereingabe gleich bei der Eingabe gelöscht wird –<br />

toDelete hat den Wert true, also wahr – oder ob die Eingabe stehen gelassen wird – toDelete<br />

hat den Wert false, also falsch. Die Eingabe zunächst stehen zu lassen ist immer dann<br />

sinnvoll, wenn sie durch weitere Zeichen korrigiert werden kann. Ist also zum Beispiel eine<br />

ganze Zahl gefordert, die nicht kleiner als 100 sein darf, ist die Eingabe 5 falsch. Gibt der<br />

Nutzer nun aber noch die Ziffern 2 und 3 an, lautet die gesamte Eingabe 523, womit sie die<br />

Bedingung erfüllt. Würde die falsche Benutzereingabe hier direkt gelöscht werden, könnte der<br />

Nutzer die Bedingung niemals erfüllen. Hingegen ist es für die gleiche Bedingung durchaus<br />

sinnvoll, dass Eingaben von Buchstaben oder anderen Zeichen, die keine Zahlen sind, sofort<br />

gelöscht werden.<br />

Neben diesen gibt es noch unzählige weitere Einstellungsmöglichkeiten. So kann man etwa<br />

ein Mindest- oder Höchstalter, ein frühestes oder spätestes Aufnahmedatum und eine<br />

minimale oder maximale Länge der Eingabe setzen.<br />

Wichtig ist hierbei, dass all diese Einstellungen von der gerade aktiven Anwendung gesetzt<br />

werden, die Anwendung also eine Benutzereingabe flexibel gestalten und entgegennehmen<br />

kann, ohne, dass sich GUI und Anwendung hierbei näher kennen müssen. Auch muss hier<br />

nicht für jede Benutzereingabe eine eigene Klasse geschrieben werden, sondern es genügt,<br />

dank der flexiblen Gestaltung der Klassen, eine einzige Klasse, die InputDialog-Klasse, der je<br />

nach Anforderung verschiedene Parameter übergeben werden können. Die einzige Bedingung<br />

dafür ist, dass sowohl GUI als auch Anwendung die Question-Klasse kennen. Diese wird in<br />

diesem Programm aber ohnehin als Teil des Adapters angesehen, da die in der Question-<br />

Klasse definierte Funktionalität auch im DataBrowser hätte gespeichert werden können, was<br />

aber zu einer sehr unübersichtlichen Klasse geführt hätte.<br />

2.2.4. Modularität – Fazit<br />

Dank dieser Vorgehensweise ist es gelungen, die Programm-Plattform stark modular zu<br />

gestalten:<br />

Soll nur eine Funktion innerhalb einer bereits bestehenden Anwendung hinzugefügt werden,<br />

reicht es, diese in der Anwendung selbst an der entsprechenden Stelle einzubauen. Soll also<br />

beispielsweise in der Reconstruction-Anwendung ein Mittelwertfilter als Rechts-Klick-Aktion<br />

neu angeboten werden, so muss zunächst ein entsprechender Text erstellt werden, etwa<br />

‚Wende Mittelwertfilter an‘. Dieser Text soll dem Nutzer sinnvollerweise beim Rechts-Klick<br />

auf Bild-Objekte im Item Tree angezeigt werden, entsprechend muss dies in der


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 19<br />

Reconstruction-Anwendung abgefragt werden. Damit der Mittelwertfilter beim Tätigen dieser<br />

Rechts-Klick-Aktion angewendet wird, muss er in der Reconstruction-Anwendung an<br />

entsprechender Stelle eingebaut werden. Sämtliche andere Klassen bleiben hierbei<br />

unverändert.<br />

Soll eine komplett neue Anwendung in das Programm integriert werden, etwa ein<br />

Vorhersage-Anwendung predict, so muss diese zunächst die Anwendungs-Schnittstelle<br />

korrekt implementieren. Tut sie das, muss sie lediglich in dem static-Block des DataBrowsers<br />

angemeldet werden. Zusätzlich muss eine entsprechende Perspektive erstellt werden, deren ID<br />

in der Anwendung gespeichert ist.<br />

Unter Umständen kann es notwendig sein, verschiedene Methoden in den DataBrowser zu<br />

implementieren, falls die neue Funktion oder eine Funktion der neuen Anwendung Zugriff auf<br />

Werte benötigt, die der DataBrowser bis dahin nicht angeboten hat. Im Regelfall sollte aber<br />

eine solche Änderung entfallen, da der DataBrowser Zugriff auf alle wesentlichen Elemente<br />

bietet und sämtliche speziellere Daten in der Regel nur in der Anwendung selbst bzw. dem<br />

zugehörigen Datenmodell definiert sind, die Anwendung diese also nicht beim DataBrowser<br />

erfragen muss.<br />

Auf diese Weise ist es möglich komplexe Funktionalitäten in das Programm einzubauen, ohne<br />

große Änderungen im <strong>Framework</strong> vornehmen zu müssen. Die Änderungen beschränken sich<br />

im Wesentlichen auf die zu ändernde oder hinzuzufügende Anwendung selbst, unter<br />

Umständen eine neue Perspektive und den DataBrowser.<br />

2.3. Trennung der Benutzeroberfläche<br />

In Kapitel 1.1 wurde die grundsätzliche Trennung der eingesetzten Views beschrieben. Diese<br />

ist wichtig, da die Views unabhängig voneinander verändert und ausgetauscht werden sollen.<br />

Letztlich trägt auch diese Forderung zum modularen Aufbau bei. Dennoch wird sie hier<br />

separat betrachtet, da sie nicht unmittelbar mit dem Verändern der Programm-Funktionalität<br />

verbunden ist. Vielmehr kann es sogar sinnvoll sein, bei der gleichen Studie – also auch bei<br />

gleicher Funktionalität – für verschiedene Nutzer unterschiedliche Views einzusetzen. Zum<br />

Beispiel ist für den Arzt hauptsächlich die administrative Funktionalität des Programms<br />

wichtig, so dass eine entsprechend ausgerichtete View unter Umständen genügt und weitere<br />

Views den Arzt nur stören würden. Der Analyst hingegen möchte vor allem Bilder<br />

visualisieren und bearbeiten können, so dass eine darauf ausgelegte View eingebaut sein<br />

muss. Wichtig ist jedoch, dass solche Veränderungen der Views nach Möglichkeit zu


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 20<br />

vermeiden sind, da jede Änderung an einem Programm ein gewisses Fehlerrisiko mit sich<br />

bringt und zusätzliche Arbeit bedeutet. Vielmehr sollte auf eine allgemein nutzbare und<br />

intuitiv aufgebaute Oberfläche hingearbeitet werden. In Einzelfällen kann eine solche<br />

Veränderung aber durchaus sinnvoll sein.<br />

Da die Views Teile der GUI sind, scheint eine Trennung zunächst nicht machbar und<br />

tatsächlich sind hier der Modularität Grenzen gesetzt. Allerdings ist es doch gelungen, die<br />

Verknüpfung zwischen diesen Views auf ein Minimum zu reduzieren.<br />

Dazu müssen die Views zunächst als Eclipse RCP Views gekennzeichnet werden, um von dem<br />

Eclipse RCP Projekt akzeptiert zu werden. Wie in Abb. 2.10 dargestellt, müssen die Views<br />

von der abstrakten Klasse ViewPart erben, welche<br />

sowohl von der abstrakten Klasse WorkbenchPart<br />

erbt als auch die Schnittstelle IViewPart<br />

implementiert. Dies ist nötig, um von Eclipse RCP als<br />

View akzeptiert zu werden. Nun wird in jeder View<br />

eine ID als String-Parameter gespeichert. Die View<br />

kann nun in der plugins-Datei 1 des Projektes als<br />

Projekt-View definiert werden, wobei die in der View<br />

gespeicherte ID angegeben werden muss. Jede so<br />

definierte View kann dann in jedwede Perspektive mit Hilfe der jeweiligen ID eingebaut<br />

werden. Dabei kann angegeben werden, welche Größe die View – relativ <strong>zur</strong> gesamten<br />

Perspektive – beim Öffnen der Perspektive haben soll. Diese Größen können später manuell<br />

vom Nutzer angepasst werden. Somit ist es also möglich, in zwei Perspektiven dieselben<br />

Views zu verwenden, jedoch mit unterschiedlichen Startgrößen. Dies kann nützlich sein, falls<br />

sich der Verwendungszweck der Perspektiven unterscheidet und unterschiedliche Views als<br />

‚wichtig‘ angesehen werden. Dies ist auch eine Lösung, um die Benutzeroberfläche mit<br />

einfachen Änderungen den Anforderungen des Nutzers anzupassen. So könnten die Ärzte und<br />

Analysten mit den selben Views arbeiten, wobei die jeweils wichtigen Views entsprechend<br />

größer erstellt werden, anstatt dass hier, wie am Anfang des Kapitels beschrieben,<br />

unterschiedliche Views verwendet werden. Prinzipiell ist die Anzahl der eingebauten Views<br />

nicht beschränkt, wobei viele verschiedene Views schnell zu Unübersichtlichkeit führen und<br />

somit vermieden werden sollten.<br />

1 In der plugins-Datei eines RCP-Projektes werden verschiedene Einstellungen gespeichert, siehe dazu Kapitel<br />

1.2.1<br />

Abb. 2.10 Erstellung einer View


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 21<br />

Die einzelnen Views sind auf diese Weise völlig unabhängig voneinander gehalten und<br />

müssen lediglich in den Perspektiven bekannt sein.<br />

Zusätzlich muss der DataBrowser die jeweiligen View-Klassen und deren angebotenen<br />

Methoden kennen, da er auf diese zugreifen können muss. Auch hier wird der Vorteil des<br />

Einsatzes eines Adapters wieder deutlich:<br />

Sämtliche Anwendungen rufen auch nach Veränderungen an den Views dieselben Methoden<br />

im DataBrowser auf. Lediglich dieser muss angepasst werden, womit die Änderungen wieder<br />

zentral in dieser Klasse vorgenommen werden können.<br />

2.4. Persistente Speicherung<br />

Ebenso wichtig wie die Anpassungsfähigkeit des Programms an neue Funktionen ist die<br />

persistente Speicherung der (erzeugten) Daten. Hierzu ist eine komplexe<br />

Datenbankanbindung nötig, die nach Möglichkeit jedoch komplett im Datenmodell, also<br />

völlig unabhängig von GUI und den Anwendungen, sein soll. Eine Einschränkung gibt es<br />

hierbei allerdings, da eine Anwendung je nach Typ eines Objektes die auszuführende Aktion<br />

wählt. Jede Anwendung muss also zumindest die Datentypen kennen, mit denen sie umgehen<br />

kann. Die Datenspeicherung und das Einlesen der Daten jedoch bleibt Aufgabe des<br />

Datenmodells.<br />

2.4.1. Datenbankanbindung<br />

Die Datenbankanbindung ist hauptsächlich in der Klasse DB_Connect definiert. Dies ist die<br />

einzige Klasse, die die gesamte Datenbank kennen muss. Hier werden sämtliche Tabellen als<br />

DBEntryLists gespeichert. Diese DBEntryLists erben von PDAtomList, welche im<br />

Wesentlichen eine Liste von PDAtom-Objekten bzw. DBEntry-Objekten ist. In diesen Listen<br />

werden allerdings noch zusätzliche Informationen wie der Typ der Liste, die zugewiesene<br />

PDAtomFactory und eine Liste mit bekannten PDAtomLists gespeichert.<br />

Das Einlesen der Daten aus der Datenbank übernehmen spezifische DBEntryFactory-<br />

Unterklassen, es gibt also für jede Tabelle in der Datenbank eine eigene Unterklasse. Eine<br />

Änderung der Datenbank bedingt somit eine große Änderung im Datenmodell, da für jede<br />

neue Tabelle eine neue Klasse definiert werden muss. Dies ist aber nicht weiter schlimm, da<br />

jede Datenbankanbindung exakt an die zugehörige Datenbank angepasst werden muss, also<br />

schon das Umbenennen einer einzigen Spalte zu notwendigen Änderungen in der<br />

Datenbankanbindung führt. Gerade aus diesem Grund ist es wichtig, die Datenbank zentral


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 22<br />

auf einem Server zu verwalten, um versehentliche Falschbenennungen von Tabellen oder<br />

Spalten seitens der einzelnen Anwender zu vermeiden (siehe Kapitel 4).<br />

In den DBEntryFactory-Unterklassen und der allgemeinen DBEntryFactory-Klasse sind die<br />

genauen SQL-Abfragen definiert. An dieser Stelle und in der Klasse DB_Connect ist noch<br />

Handlungsbedarf, da bislang nur SQL-basierte Datenbanken genutzt werden können.<br />

Der Vorteil des Factory Pattern (Haunhorst, 2009 S. 6 ff) wird dennoch jetzt bereits deutlich:<br />

Der größte Teil der Datenbankanbindung – der Teil, der bei einer Veränderung der Datenbank<br />

ebenfalls verändert werden muss – ist in den Fabriken definiert, sodass die einzelnen<br />

PDAtom-Unterklassen unverändert bleiben können. Da ein ganzer Teil der<br />

Datenbankanbindung für alle Fabriken gleich ist, kann dieser Teil sogar in der Fabrik-<br />

Oberklasse definiert werden. Somit machen Veränderungen in diesem Bereich nur in dieser<br />

einen Klasse Veränderungen notwendig. In diesen Fabriken können zudem Verknüpfungen<br />

zwischen mehreren Tabellen der Datenbank erzeugt werden, falls dies notwendig ist. Die<br />

zugehörigen PDAtom-Unterklassen bleiben somit unverändert und voneinander unabhängig.<br />

2.4.2. Chain of Responsibility<br />

Interessant ist in diesem Zusammenhang auch die Verwendung des Chain of Responsibility<br />

Pattern (Haunhorst, 2009 S. 10 ff). Dieses kommt ins Spiel, sobald ein Objekt in einer<br />

Tabelle bzw. der zugehörigen PDAtomList gesucht wird, sich das Objekt jedoch nicht in<br />

dieser Tabelle, sondern in einer anderen, befindet. In der Praxis bedeutet das, dass<br />

beispielsweise ein bestimmter Patient gesucht wird, dem Anfragesteller jedoch lediglich die<br />

Studienliste bekannt ist. Als Anfragesteller wird hier der Programmteil bezeichnet, der die<br />

Suchanfrage nach dem Patienten stellt. Aufgrund der Modularität kann es dabei schnell<br />

vorkommen, dass der Anfragesteller nicht alle Listen kennt. Dann stellt er die Suchanfrage an<br />

alle Listen, die er kennt.<br />

Wie in Kapitel 2.4.1 beschrieben ist jede Tabelle der Datenbank in der DB_Connect- Klasse<br />

als DBEntryList gespeichert. Beispielsweise könnte es die Tabellen ‚Studie’, ‚Patient’ und<br />

‚Messung’ geben, wobei dem Anfragesteller nur die Tabelle ‚Studie‘ bekannt ist. Wird nun<br />

nach einem Patienten gesucht, muss dies in der Tabelle ‚Studie’ geschehen, da die Tabelle<br />

‚Patient’ dem Anfragesteller hier nicht bekannt ist. Die Suchanfrage muss also an die<br />

zugehörige DBEntryList gestellt werden, welche ihre gespeicherten Einträge – die Studien –<br />

nach einem passenden Eintrag durchsucht. Dazu überprüft die Liste zunächst, ob das gesuchte<br />

Objekt überhaupt vom Typ der Listeneinträge ist, ob der Patient also eine Studie ist. In der


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 23<br />

DBEntryList ist zudem gespeichert, welche Listen bekannt sind. Der Studienliste ist zum<br />

Beispiel die Patientenliste bekannt, der Patientenliste wiederum ist die Messungsliste bekannt.<br />

Da der gesuchte Objekttyp ‚Patient‘ nicht zum eigenen Typ ‚Studie‘ passt, leitet die<br />

Studienliste die Suchanfrage an alle ihr bekannten Listen weiter, welche wiederum prüfen, ob<br />

der gesuchte Datentyp zu ihren jeweiligen Daten passt. Passt er nicht, wird die Suchanfrage<br />

wiederum an alle bekannten Listen weitergeleitet. Falls der Datentyp jedoch passt, werden<br />

alle Listeneinträge nach dem gesuchten Objekt durchsucht. Hier würde also die Studienliste<br />

die Anfrage unter anderem an die Patientenliste übergeben, bei welcher der Datentyp ‚Patient’<br />

passt. Die Patientenliste wird also durchsucht und das gesuchte Objekt, falls bereits<br />

vorhanden, <strong>zur</strong>ückgegeben.<br />

Ist das gesuchte Objekt nicht vorhanden, kann mittels eines weiteren Parameters bestimmt<br />

werden, ob es erzeugt werden soll oder nicht.<br />

2.5. Item Tree<br />

Wie in Kapitel 1.1 beschrieben soll dem Nutzer ein Objekt-Baum, ein so genannter Item Tree,<br />

angezeigt werden. Dieser soll zunächst die im Programm definierten Hauptlisten anzeigen.<br />

Diese Listen werden von der Hauptanwendung, also der zuerst beim DataBrowser<br />

angemeldeten Anwendung, bzw. von deren Hauptmodel – ebenfalls das zuerst angemeldete<br />

Model – definiert. In diesem Modell sind die Hauptlisten als PDAtomLists gespeichert.<br />

Wird nun das Programm gestartet, also auch die ItemTreeView erstellt, wird durch die<br />

ItemTreeView die Methode initialize der MakeTree-Klasse aufgerufen. Diese erfragt zunächst<br />

die Hauptlisten vom DataBrowser,<br />

der die Anfrage an seine<br />

Hauptapplikation weiterleitet,<br />

welche die Hauptlisten schließlich<br />

von ihrem Hauptmodel erfragt und<br />

über den DataBrowser an die<br />

MakeTree-Klasse <strong>zur</strong>ückgibt (vgl.<br />

Abb. 2.11). Die Hauptanwendung<br />

ist hierbei die erste Anwendung in<br />

der Anwendungsliste des<br />

DataBrowsers und deren<br />

Hauptmodel ist das erste Model in<br />

Abb. 2.11 Initialisierung des Item Trees


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 24<br />

deren Modelliste. Daraufhin erstellt die initialize-Methode für jede Hauptliste ein Objekt in<br />

dem Item Tree, wobei dieses Objekt nach dem Namen der jeweiligen Hauptliste benannt wird.<br />

Zunächst war es das Ziel, es dabei zu belassen und alle weiteren Objekte erst bei Bedarf zu<br />

erstellen, um möglichst wenige Systemressourcen für den Item Tree zu benötigen. Die Inhalte<br />

einer Hauptliste sollten also erst angezeigt werden, wenn auf das +-Zeichen neben dem<br />

entsprechenden Objekt im Item Tree geklickt wird. Das Problem dabei ist jedoch, dass die<br />

+-Zeichen nur dann zu sehen sind, wenn bereits Untereinträge für dieses Objekt vorliegen.<br />

Aus diesem Grunde wird nun immer eine Ebene mehr geladen als benötigt.<br />

Abb. 2.12 veranschaulicht dieses Prinzip:<br />

Zu Beginn ist im Item Tree jede Hauptliste als Objekt dargestellt und deren Einträge bereits<br />

im Programm geladen. In der Abbildung gibt es also die drei Hauptlisten ‚Element1‘,<br />

‚Element2‘ und ‚Element3‘, welche angezeigt werden. Zusätzlich sind deren jeweiligen<br />

Unterelemente bereits geladen. Klickt man nun auf das +-Zeichen neben einer der<br />

Hauptlisten, werden deren Einträge – Unterelemente – angezeigt und die nächste Ebene wird<br />

geladen. Hier ist zu erkennen, dass die Hauptliste ‚Element1‘ expandiert wurde und somit nur<br />

deren Einträge angezeigt und die entsprechenden nächsten Unterelemente geladen werden.<br />

Die anderen Hauptlisten bleiben davon unberührt. Analog lässt sich das Schema weiterführen,<br />

falls nun ‚Element1a‘ expandiert würde, was ‚Element1b‘ unverändert lassen würde.<br />

Abb. 2.12 Laden des Item Trees<br />

Konkret heißt das, dass zunächst zum Beispiel die Hauptlisten Patient und Study geladen<br />

werden und die gleichnamigen Objekte angezeigt werden. Expandiert man nun die Patient-<br />

Liste werden alle Patienten angezeigt und zu jedem Patienten die nächste Ebene geladen.<br />

Diese Ebene beinhaltet nun Objekte, die die verschiedenen Datentypen repräsentieren, die zu<br />

diesem Patienten gehören. Also könnte dort zum Beispiel Measurement stehen, da es zu<br />

diesem Patienten Messungen gibt. Diese Objekte werden nun alle erstellt, aber nicht<br />

angezeigt. Da diese Objekte jedoch nur einen Namen enthalten und pro Patient und Datentyp


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 25<br />

nur ein Objekt erstellt wird, benötigt dieser Vorgang nur wenige Systemressourcen.<br />

Expandiert man nun ein Patientenobjekt, werden die eben genannten Unterkategorien dieses<br />

Patienten angezeigt und zu jeder Unterkategorie die nächste Ebene geladen. Wird also das<br />

Patientenobjekt ‚Müller‘ expandiert, wird angezeigt, welche Datentypen für Müller <strong>zur</strong><br />

Verfügung stehen, also zum Beispiel Messungen, und gleichzeitig lädt das Programm die<br />

nächste Ebene dieses Patienten, in diesem Fall also alle Messungen. Sämtliche andere<br />

Einträge des Item Tree bleiben dabei unverändert. Das heißt, dass trotz dieses Umstands,<br />

immer eine Ebene mehr laden zu müssen als angezeigt wird, kaum zusätzliche<br />

Systemressourcen benötigt werden. Hierbei können auch mehrere Datentypen, also<br />

Unterkategorien, vorhanden sein, wenn zum Beispiel zu einem Patienten nicht nur<br />

Messungen, sondern auch rekonstruierte Bilder, Analyseergebnisse oder ähnliches vorhanden<br />

sind.<br />

2.5.1. Observer Pattern<br />

Für den Item Tree sind die Vorteile des Observer Pattern (Haunhorst, 2009 S. 9 f) von<br />

entscheidender Bedeutung. Das Observer Pattern beschäftigt sich mit der Frage, wie Objekte,<br />

die einander nicht näher kennen, Informationen über ihren jeweiligen Zustand austauschen<br />

können. Die wichtigste Aufgabe, die mit Hilfe dieses Pattern bewerkstelligt werden kann, ist<br />

die Verknüpfung der im Item Tree angezeigten Elemente mit den dazugehörigen PDAtom-<br />

Objekten. Dies ist vor allem dann von Bedeutung, wenn ein PDAtom-Objekt mit mehreren<br />

Elementen im Item Tree verknüpft ist. So ist jeder Patient in der Patientenliste und in jeder<br />

Studie, an der er teilnimmt, aufgeführt. Es gibt also entsprechend viele Einträge für diesen<br />

Patienten im Item Tree. Soll nun der Name des Patienten geändert werden, muss dies sowohl<br />

bei dem zugehörigen PDAtom-Objekt wie bei all seinen Einträgen im Item Tree geschehen.<br />

Bisher ist diese Observer-Funktionalität nicht implementiert. Geplant ist das Vorgehen<br />

folgendermaßen:<br />

Die im Item Tree angezeigten Elemente sind vom Typ AdminTreeObject und bieten zunächst<br />

keinerlei diesbezügliche Funktionen. Die AdminTreeObject-Klasse wurde speziell für den<br />

Item Tree entwickelt und dient in erster Linie dazu, den Namen und die Atom_OID des<br />

Elementes zu speichern. Der Name wird benötigt, um ihn im Item Tree anzeigen zu können.<br />

Mit Hilfe der Atom_OID ist jederzeit überprüfbar, mit welchem Daten-Objekt dieses Element<br />

assoziiert ist. Wenn nun die AdminTreeObject-Klasse zusätzlich die Observer-Schnittstelle<br />

implementiert, stehen ihr viele Funktionen des Observer Pattern <strong>zur</strong> Verfügung. Jede


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 26<br />

AdminTreeObject-Instanz ist damit ein potentieller Beobachter anderer Objekte. Mit jeder<br />

dieser Instanzen ist zudem ein PDAtom-Objekt assoziiert, welches über die in beiden<br />

Objekten gespeicherte Atom_OID leicht zu identifizieren ist. In diesem PDAtom-Objekt sind<br />

alle wichtigen Parameter des Daten-Objektes gespeichert. Auf diese Weise muss für das<br />

Erstellen des Item Trees nur eine Instanz der AdminTreeObject-Klasse mit wenigen<br />

Parametern erzeugt werden. Erst wenn auf das Datenobjekt konkret zugegriffen wird, muss<br />

auch das PDAtom-Objekt erzeugt werden, welches aufgrund der höheren Anzahl an<br />

Parametern mehr Systemressourcen benötigt. Nun muss die PDAtom-Klasse noch die<br />

Observable-Schnittstelle implementieren, wodurch ihr ebenfalls viele Funktionen des<br />

Observer Pattern zugänglich sind, diesmal jedoch Funktionen des beobachteten Objektes.<br />

Jedes PDAtom-Objekt ist somit ein potentielles Subjekt und kann von Beobachtern – etwa des<br />

Typs AdminTreeObject – beobachtet werden. Auf diese Weise werden alle wichtigen Daten,<br />

wie der anzuzeigende Name des Objektes, in den AdminTreeParent-Instanzen, die im Item<br />

Tree angezeigt werden, aktuell gehalten.<br />

Konkret werden hier nur ein paar Funktionen des Observer Pattern benötigt:<br />

Zum Einen braucht die PDAtom-Klasse eine Liste, in der die Beobachter gespeichert werden<br />

und eine Methode, mit Hilfe derer sich ein Beobachter anmelden – also in die Liste eintragen<br />

– kann. Desweiteren muss eine update-Methode in dieser Klasse vorhanden sein, die bei einer<br />

Veränderung des Namens oder der Atom_OID die Beobachter benachrichtigt. Um die<br />

Beobachter benachrichtigen zu können, müssen diese entsprechende Methoden anbieten, etwa<br />

‚benachrichtige‘ oder ‚aktualisiere‘. Dabei sollte der veränderte Wert mit übergeben werden.<br />

Abb. 2.13 zeigt, wie dieses Schema konkret zu implementieren ist. Dabei sind die hierfür<br />

wichtigen Parameter ‚name‘, ‚oid‘ und ‚observer‘ und die wichtigen Methoden ‚aktualisiere‘,<br />

‚registriere‘ und ‚update‘ dargestellt.<br />

Bei den Methoden ist in Klammern der jeweils zu übergebende und hinter der Methode der<br />

<strong>zur</strong>ückgegebene Wert dargestellt. Die Liste mit den angemeldeten Beobachtern ist hier<br />

‚observer‘ genannt und die dazugehörige Methode zum An- und Abmelden der Beobachter<br />

heißt ‚registriere‘. Diese Methode kann also von einem Beobachter aufgerufen werden, wobei<br />

er sich selbst als Argument übergibt. Dadurch wird er in die Beobachterliste eingetragen.<br />

Analog sollte eine Methode angeboten werden, mit Hilfe derer sich ein Beobachter wieder aus<br />

dieser Liste austragen kann. Da dieser Vorgang jedoch analog zum Anmelden abläuft, wird<br />

dieser Punkt hier nicht weiter aufgegriffen. In der Abbildung ist ebenfalls zu erkennen, dass in<br />

der AdminTreeObject-Klasse die aktualisiere-Methode implementiert sein muss, wobei diese<br />

für die Werte ‚name‘, also einen String-Wert, und OID, also eine Atom_OID, gelten muss.


I m p l e m e n t i e r u n g d e s K o n z e p t e s | 27<br />

Abb. 2.13 Realisierung des Observer Pattern<br />

Diese Methode wird wiederum von der update-Methode derjenigen PDAtom-Objekte<br />

aufgerufen, bei denen diese AdminTreeObject-Instanz als Beobachter registriert ist. Da in der<br />

OID auch der Name des Objektes gespeichert ist (siehe<br />

Abb. 2.13), reicht es, die aktualisiere-Methode für die OID, also den Objekttyp Atom_OID,<br />

anzubieten. Dass der Name überhaupt in einem separaten Parameter gespeichert wird, liegt an<br />

dem modularen Aufbau des Programmes, da es Programmteile in der GUI gibt, die zwar den<br />

Namen eines Objektes benötigen – etwa zum Anzeigen desselben – jedoch die Klasse<br />

Atom_OID nicht kennen. In der Methode <strong>zur</strong> Aktualisierung der Atom_OID muss folgerichtig<br />

der separat gespeicherte Name ebenfalls auf den neuen Wert aus der OID geändert werden.<br />

Ebenso muss sichergestellt werden, dass bei einer Veränderung des separat oder in der<br />

Atom_OID gespeicherten Namens in einem PDAtom-Objekt der jeweils anders gespeicherte<br />

Name mit aktualisiert wird. Dies lässt sich gut in der update-Methode dieser Klasse<br />

realisieren, die, wie zuvor gefordert, bei jeder Veränderung dieser Parameter aufgerufen wird.<br />

Abb. 2.14 zeigt einen möglichen Ablauf der update-Methode der PDAtom-Klasse:<br />

Die update-Methode wird immer dann aufgerufen, wenn eine Änderung an dem PDAtom-<br />

Objekt vorgenommen wurde. In diesem Fall führen jedoch nur Änderungen an dem Namen<br />

oder der OID zu weiteren Aktionen. Wird einer dieser Werte geändert, muss, wie zuvor<br />

gefordert, der separat und der in der OID gespeicherte Name abgeglichen werden. Ist also der<br />

separat gespeicherte Name verändert worden (erster Fall in der Abbildung), wird der in der<br />

OID gespeicherte Name auf diesen Wert gesetzt. Wird umgekehrt der in der OID gespeicherte<br />

Name geändert (zweiter Fall in der Abbildung), wird analog dazu der separat gespeicherte<br />

Name aktualisiert. Wird die OID geändert, der in der OID und der separat gespeicherte Name<br />

jedoch nicht, ist kein Abgleich nötig. Es könnte also zum Beispiel die in der OID gespeicherte


Zahl verändert werden. Dadurch wird die<br />

update-Methode zwar aktiv, ein<br />

Abgleich der Namen ist aber nicht nötig.<br />

In jedem Fall durchläuft die update-<br />

Methode danach in einer Schleife alle<br />

AdminTreeObject-Instanzen, die bei<br />

diesem PDAtom-Objekt als Beobachter<br />

registriert sind, also alle Beobachter in<br />

der Liste observer. Bei jedem dieser<br />

Beobachter wird die aktualisiere-<br />

Methode aufgerufen, wobei die<br />

aktualisierte OID übergeben wird. In der<br />

aktualisiere-Methode wird nun zunächst<br />

I m p l e m e n t i e r u n g d e s K o n z e p t e s | 28<br />

die in der AdminTreeObject-Instanz gespeicherte OID der vom PDAtom-Objekt übergebenen<br />

OID gleichgesetzt. Daraufhin wird der separat gespeicherte Name auf den in der – jetzt bereits<br />

aktualisierten – OID gespeicherten geändert.<br />

Abb. 2.14 Sequenzdiagramm der update-Methode


V a l i d i e r u n g d e r P r o g r a m m f u n k t i o n a l i t ä t | 29<br />

3. Validierung der Programmfunktionalität<br />

Zwar sind derzeit bereits exemplarisch Anwendungen <strong>zur</strong> Administration, Rekonstruktion<br />

und Analyse der Daten definiert, explizite Funktionen bieten diese jedoch bislang nicht. Zur<br />

Demonstration der Funktionsweise des Programmes wurde aus diesem Grunde eine<br />

Differenzbilderzeugungsfunktion in der Analysis-Anwendung implementiert. Diese zunächst<br />

sehr einfache Operation dient im Wesentlichen <strong>zur</strong> Veranschaulichung des prinzipiellen<br />

Ablaufs. So müssen hierfür, ebenso wie für jeden anderen Filter, Bilder – in dem Fall zwei –<br />

einer Anwendung zugänglich gemacht werden. Ebenso muss die Anwendung die von ihr<br />

produzierten Bilder – oder allgemein Daten – dem Programm zugänglich machen können.<br />

Was genau die Anwendung mit den Bilder macht, ist dabei also für die Anbindung an das<br />

Programm unwichtig und nur in der Anwendung selbst definiert.<br />

Diese Differenzbild-Funktion soll als Rechts-Klick-Aktion angeboten werden, wenn der<br />

Nutzer die Analysis-Perspektive geöffnet hat – sich also in der Analysis-Anwendung befindet<br />

– und auf ein Bild-Objekt klickt. Dazu ist in der Analysis-Anwendung ein Rechts-Klick-Text<br />

definiert, nämlich ‚Von diesem Bild ein Differenzbild erzeugen‘, welcher bei einem<br />

entsprechenden Rechts-Klick an die ItemTreeView <strong>zur</strong>ückgegeben wird (siehe Kapitel 2.2.2).<br />

Wird diese Aktion nun ausgewählt, speichert die Analysis-Anwendung die übergebene OID<br />

des Bildes in dem Parameter firstOID. Zusätzlich wird der boolean-Parameter namens<br />

secondElement, der standardmäßig auf false gesetzt ist, auf true geändert. Somit weiß die<br />

Anwendung, dass das erste Element bereits gewählt ist. Um das zweite Element zu wählen, ist<br />

ein neuer Rechts-Klick-Text nötig. Hierbei ist die variable Gestaltung der Rechts-Klick-<br />

Aktionen nützlich, da nun bei einem Rechts-Klick auf ein weiteres Bild ein anderer Text<br />

übergeben werden kann, nämlich ‚Dieses Bild von dem ersten subtrahieren‘.<br />

In Abb. 3.1 ist zu erkennen, dass dazu lediglich eine simple if-Abfrage in der Anwendung<br />

nötig ist, wobei der Parameter secondElement als Argument genutzt wird. Ist der Wert von<br />

secondElement false, wird der erste Text <strong>zur</strong>ückgegeben, bei true der zweite. Eine solche if-<br />

Abfrage wird auch in der Doppel-Klick-Methode genutzt, da bei einem Doppel-Klick auf ein<br />

zweites Bild – ein erstes Bild wurde also schon ausgewählt – ebenfalls die<br />

Differenzbildfunktion aufgerufen werden soll. Ist hingegen noch kein erstes Bild ausgewählt,<br />

soll bei einem Doppel-Klick das Bild lediglich angezeigt werden. Wird nun eine solche<br />

Doppel-Klick-Aktion oder die entsprechende Rechts-Klick-Aktion bei einem zweiten Bild<br />

ausgeführt, wird die eigentliche Differenzbild-Methode in der Analysis-Anwendung<br />

aufgerufen, wobei die OID des zweiten Bildes übergeben wird.


V a l i d i e r u n g d e r P r o g r a m m f u n k t i o n a l i t ä t | 30<br />

Abb. 3.1 Erzeugung eines Differenzbildes<br />

An dieser Stelle könnte also jedwede andere Funktionalität eingebaut werden, ohne dass an<br />

dem prinzipiellen Vorgang etwas geändert werden müsste.<br />

In diesem Fall werden in der Differenzbild-Methode zunächst die Parameter firstOID und<br />

secondElement auf ihre ursprünglichen Werte gesetzt, damit nach Ausführung dieser Funktion<br />

wieder ein neues erstes Bild gewählt werden könnte – für ein neues Differenzbild. Der Wert<br />

von firstOID wird also gelöscht und secondElement wird wieder auf false gesetzt. Dabei wird<br />

die in firstOID gespeicherte OID jedoch temporär innerhalb der Methode gespeichert, um<br />

noch genutzt werden zu können. Um nun an das tatsächliche Bild zu gelangen gibt es<br />

verschiedene Möglichkeiten. Hier wurde der in der OID gespeicherte Pfad des Bildes genutzt,<br />

um es zu laden. Prinzipiell könnte auch das dazugehörige PDAtom-Objekt – hier also ein<br />

P4Image 1 -Objekt – genutzt werden. Da die ursprünglichen Bilder – und somit die P4Image-<br />

Objekte – jedoch nicht verändert werden, ist dies hier nicht nötig.<br />

Nachdem das Differenzbild nun durch simple Subtraktion der jeweiligen Pixelwerte erzeugt<br />

wurde, muss es dem restlichen Programm wieder <strong>zur</strong> Verfügung gestellt werden. Dazu wird<br />

ein P4Image-Objekt mit den Pixelwerten des erzeugten Bildes erstellt. Der darin gespeicherte<br />

Name wird aus den Namen der verwendeten Bilder zusammengestellt. Zudem werden die<br />

OIDs der verwendeten Bilder in der Liste partnerOIDs 2 dieses Objektes gespeichert, sodass<br />

jederzeit erkenntlich ist, welche Bilder genutzt wurden. Dieses Objektes wird nun an die<br />

addChildToTree-Methode des DataBrowsers übergeben, die dieses Objekt in den Item Tree<br />

einbaut. Auf diese Weise ist das erzeugte Bild also dem restlichen Programm zugänglich<br />

gemacht worden. Hierbei muss noch an der Speicherung der erzeugten Bilder gearbeitet<br />

werden, da diese derzeit nicht im DICOM-Format gespeichert werden können. Dabei ist<br />

denkbar, das Bild direkt bei seiner Erzeugung in der Datenbank und in einem Filesystem zu<br />

1 P4Image ist eine Unterklasse von PDAtom und dient der Bildspeicherung. Siehe dazu Kapitel 2.1.4.<br />

2 In der Liste partnerOIDs speichert ein PDAtom-Objekt, mit welchen anderen Objekten es verknüpft ist.


V a l i d i e r u n g d e r P r o g r a m m f u n k t i o n a l i t ä t | 31<br />

speichern, oder aber erst nach entsprechender Aufforderung des Nutzers. Sobald das Bild<br />

gespeichert wird, muss der entsprechende Pfad in der OID des zugehörigen P4Image-<br />

Objektes gespeichert werden.<br />

Bislang geht bei einem Programmneustart die Information, welche Bilder verwendet wurden,<br />

verloren. Eine mögliche Lösung dieses Problems ist, die erzeugten – und in einem Filesystem<br />

gespeicherten – Bildern nach einem bestimmten Muster zu benennen, aus dem das Programm<br />

bei einem Neustart diese Information erhalten kann. Ein sinnvolles Muster ist beispielsweise,<br />

in dem Namen des erzeugten Bildes die OIDs der verwendeten Bilder mit einzubauen. Eine<br />

weitere Variante ist, in einer zusätzlichen Datenbank-Tabelle solche Verlinkungen zu<br />

speichern. Dabei dürfen jedoch keineswegs die Bilder selbst in der Datenbank gespeichert<br />

werden, sondern die zugehörigen Pfade.


4. Ausblick<br />

A u s b l i c k | 32<br />

So gut ein Konzept auch ausgearbeitet ist, bei der Realisierung/Implementierung desselben<br />

tauchen immer wieder Fehler auf bzw. werden immer wieder Teile des Konzeptes verändert.<br />

Sei es, weil Funktionen leichter als zuvor geplant realisiert werden können oder aber weil<br />

gewisse Teile der Planung sich so nicht implementieren lassen. Dies hat sich vor allem durch<br />

den Einsatz von Eclipse RCP bemerkbar gemacht. Zwar wurde durch dieses Plug-In das<br />

Erstellen eines lauffähigen Programms erleichtert, doch kam es immer wieder zu<br />

Schwierigkeiten, da es mit vielen Funktionen anderer Klassen nicht <strong>zur</strong>echt kommt.<br />

Von diesen Änderungen abgesehen, ist es gelungen, die im Konzept festgelegten Ziele zu<br />

erreichen. Jetzt bereits ist ein großer Teil der grundlegenden Funktionalität realisiert worden.<br />

Dabei ist es bisher gelungen das Programm als Plattform zu gestalten, also so modular<br />

aufzubauen, dass Änderungen in einem Programmteil keine – oder möglichst wenige –<br />

Änderungen in anderen Programmteilen bedingen. Gleichzeitig wurde eine<br />

Benutzeroberfläche geschaffen, die unabhängig von den eingebauten Anwendungen ist. Auf<br />

diese Weise kann das Programm für unterschiedliche Zwecke genutzt werden, ohne dass der<br />

Nutzer sich jedes Mal in die Benutzeroberfläche einarbeiten muss.<br />

Noch ausgearbeitet werden muss die Datenbankanbindung, speziell die Speicherung von<br />

neuen Daten und das Anbinden von weiteren Datenbanktypen. Ziel ist es, mit zentral auf<br />

Servern gespeicherten Datenbanken zu arbeiten, auf die der Nutzer dann zugreifen kann.<br />

Bisher muss die Datenbank lokal auf dem verwendeten Rechner gespeichert werden. In<br />

Kapitel 2.4.1 wurde beschrieben, wie eng eine Datenbankanbindung an die jeweilige<br />

Datenbank gebunden ist. Muss die Datenbank nun bei jedem Nutzer lokal erstellt werden, ist<br />

dies eine große Fehlerquelle, da schon das versehentlich falsche Benennen einer einzigen<br />

Tabelle oder Spalte zu Problemen führt. Wird die Datenbank hingegen zentral auf einem<br />

Server verwaltet, wird nicht nur dieses Problem umgangen, sondern zugleich auch<br />

sichergestellt, dass jeder Nutzer auf dieselben Daten zugreift, die Daten also eindeutig<br />

abgespeichert sind. Auch die in Kapitel 3 angesprochene Speicherung erstellter Daten in<br />

einem Filesystem muss noch ausgearbeitet werden, sodass diese Daten nach einem<br />

Programmneustart wieder eindeutig zuzuordnen sind.<br />

Zudem existiert noch keine Rechteverwaltung. So soll ein Arzt auf die Patientendaten<br />

derjenigen Patienten Zugriff haben, die er behandelt. Ein Patient selbst soll jedoch nur auf<br />

seine eigenen Daten Zugriff haben und auch diese nur bedingt verändern können.<br />

Auch muss das Observer Pattern für die Kommunikation von AdminTreeObject-Objekten<br />

und PDAtom-Objekten nach dem in Kapitel 2.5.1 erwähnten Schema umgesetzt werden.


Abbildungsverzeichnis<br />

A b b i l d u n g s v e r z e i c h n i s | VI<br />

Abb. 1.1 Programmaufbau ......................................................................................................................................2<br />

Abb. 2.1 Anmeldung von Anwendungen ..................................................................................................................8<br />

Abb. 2.2 Sequenzdiagramm Programmstart ...........................................................................................................9<br />

Abb. 2.3 Perspektivenwechsel ................................................................................................................................11<br />

Abb. 2.4 Rechts-Klick in der Analysis-Anwendung..................................................................................................12<br />

Abb. 2.5 Rechts-Klick in der Admin-Anwendung ....................................................................................................12<br />

Abb. 2.6 Erstellen der Rechts-Klick-Aktionen..........................................................................................................13<br />

Abb. 2.7 Bezeichnung der Rechts-Klick-Aktionen ...................................................................................................14<br />

Abb. 2.8 Benutzereingabe eines neuen Patienten ..................................................................................................16<br />

Abb. 2.9 Benutzereingabe einer neuen Messung ...................................................................................................17<br />

Abb. 2.10 Erstellung einer View .............................................................................................................................20<br />

Abb. 2.11 Initialisierung des Item Trees .................................................................................................................23<br />

Abb. 2.12 Laden des Item Trees .............................................................................................................................24<br />

Abb. 2.13 Realisierung des Observer Pattern .........................................................................................................27<br />

Abb. 2.14 Sequenzdiagramm der update-Methode ...............................................................................................28<br />

Abb. 3.1 Erzeugung eines Differenzbildes ..............................................................................................................30


Literaturverzeichnis<br />

L i t e r a t u r v e r z e i c h n i s | VII<br />

Diplomarbeit / Verf. Benke Roman // Objektorientiertes Design und Entwicklung einer<br />

Applikation <strong>zur</strong> biomedizinischen Bildverarbeitung - predict4H. - Remagen : [s.n.], 2009.<br />

Interdisziplinäres Hauptpraktikum für Studierende der Elektrotechnik [Online] / Verf.<br />

Sehring Hans-Werner. - April 2002. - 9. August 2009. - http://www.sts.tu-<br />

harburg.de/~r.f.moeller/lectures/ihp-sose-04/Listener.html.<br />

Open Source: <strong>Framework</strong> predict4H überbrückt die Kluft zwischen<br />

Grundlagenforschung und Diagnostik [Artikel] / Verf. Neeb-Burlacu Paula // RESOOM. -<br />

2008. - November/Dezember.<br />

Praxisprojektbericht [Buch] / Verf. Haunhorst Martin. - Remagen : [s.n.], 2009.<br />

Wikipedia [Online]. - 14. Juli 2009. - http://de.wikipedia.org/wiki/Eclipse_(IDE).


Selbstständigkeitserklärung<br />

S e l b s t s t ä n d i g k e i t s e r k l ä r u n g | VIII<br />

Hiermit versichere ich, Martin Haunhorst, dass ich den vorliegenden Bericht selbstständig und<br />

nur unter Verwendung der angegebenen Quellen und Hilfsmittel verfasst habe.<br />

Remagen, 14.08.2009 Martin Haunhorst

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!