Java Framework Development zur ... - RheinAhrCampus
Java Framework Development zur ... - RheinAhrCampus
Java Framework Development zur ... - RheinAhrCampus
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