Anwendungsentwicklung mit DICOM-Anbindung meistern
Inhaltsverzeichnis zum Aufklappen
Moderne medizinische Diagnostik basiert häufig auf bildgebenden Verfahren wie Computertomografie (CT) und Magnetresonanztomografie (MRT). Die dabei erzeugten Bilder müssen gespeichert, ausgetauscht und dem medizinischen Personal in unterschiedlichen Anwendungen zur Verfügung gestellt werden.
Genau hier kommt Digital Imaging and Communications in Medicine (DICOM) ins Spiel. Der Standard regelt die Speicherung und den Austausch von Informationen im medizinischen Bilddatenmanagement. Für die Anwendungsentwicklung bringt DICOM jedoch einige Besonderheiten und eine beachtliche Komplexität mit sich.
In diesem Blogbeitrag möchte ich zeigen, woher diese Komplexität kommt und wie sich in der Entwicklung damit umgehen lässt. Ein wichtiger Baustein sind vollautomatisierte Integrationstests. An einem Beispiel zeige ich, wie sich eine entsprechende Testumgebung aufsetzen lässt.
Eine detaillierte Einführung in DICOM soll es an dieser Stelle nicht geben. Dafür ist der Standard schlicht zu umfangreich. Auch die gezeigten Beispiele dienen vor allem als Erklärungshilfe und erheben keinen Anspruch auf Produktionsreife.
Komplexität von DICOM
Was macht die Anwendungsentwicklung mit DICOM-Anbindungen so komplex?
Seit 1993 wird der DICOM-Standard kontinuierlich und abwärtskompatibel weiterentwickelt. Im selben Zeitraum hat sich die Webentwicklung von vergleichsweise statischen Seiten mit HTML und Perl hin zu Microservice-Anwendungen mit TypeScript entwickelt.
Die Rückwärtskompatibilität ermöglicht es, auch medizinische Geräte zuverlässig weiter zu nutzen, die seit mehr als 30 Jahren im Einsatz sind. Gleichzeitig bleiben dadurch ältere Konzepte im Standard erhalten. Verbesserte Features entstehen häufig parallel, anstatt bestehende Ansätze vollständig zu ersetzen.
Hinzu kommt, dass DICOM sowohl ein eigenes Netzwerkprotokoll als auch ein eigenes Datenformat definiert. Das entsprechende Know-how ist unter Entwicklern weniger verbreitet und auch die verfügbaren Software-Tools sind spezifischer.
In der heutigen Anwendungsentwicklung werden beispielsweise Representational State Transfer (REST)-APIs und JSON-Datenformate bevorzugt. Dieses Bedürfnis greift der DICOMweb-Standard auf. Er bietet moderne API-Konzepte und versucht, den unveränderlichen DICOM-Kern zu abstrahieren.
Eine weitere Besonderheit ist die DICOM-Kompatibilität. Sie bedeutet nicht zwangsläufig, dass ein System den vollständigen Standard unterstützt. Stattdessen kann eine festgelegte Teilmenge genügen. Das ist sinnvoll, denn eine CT-Maschine muss beispielsweise nur die Teile des Standards erfüllen, die notwendig sind, um CT-Bilder zur Verfügung zu stellen.
Eine DICOM-Dateiverwaltungssoftware kann dagegen CT- und MRI-Bilder in unterschiedlicher Kombination unterstützen, also keines von beiden, nur eines oder beide, und dennoch DICOM-fähig sein.
Beim Verbindungsaufbau handeln die Kommunikationsteilnehmer ihre Fähigkeiten und die erlaubten Services untereinander aus. Genau hier kann es bei einer Systemintegration zu Überraschungen kommen: Zwei DICOM-fähige Systeme sind nicht automatisch in allen benötigten Funktionen miteinander kompatibel.
DICOM-Komplexität überwinden
Die größten Herausforderungen bei der DICOM-Entwicklung liegen also in der über Jahrzehnte gewachsenen Komplexität durch Abwärtskompatibilität, der Systemintegration mit zahlreichen Konfigurationsmöglichkeiten und der vergleichsweise geringen Verfügbarkeit von Expertise.
Ein wirksamer Weg, diesen Herausforderungen zu begegnen, sind vollständig automatisierte Tests. Sie können Komplexität und Randbedingungen zwar nicht beseitigen, aber frühzeitig sichtbar machen, wenn daraus Fehler entstehen.
Auch ungewöhnliche Kundensysteme und spezielle Konfigurationen mit entsprechenden Feldfehlern lassen sich gezielt nachstellen. Gleichzeitig helfen automatisierte Tests dabei, Wissen im Team aufzubauen. Entwickler mit wenig DICOM-Erfahrung können sich schrittweise in die Besonderheiten des Standards einarbeiten und erhalten durch einen verlässlichen Testharnisch schnelles Feedback.
Besonders wichtig sind dabei Integrationstests. Sie lassen sich vollständig automatisieren und bilden gleichzeitig die Komplexität der Systemintegration ab.
Unittests sind für viele DICOM-Szenarien dagegen nur eingeschränkt geeignet. Ein großer Teil der relevanten Tests betrifft Netzwerkkommunikation und Bilddateien. Beides würde in klassischen Unittests üblicherweise gemockt und damit genau die Aspekte ausblenden, die bei DICOM besonders fehleranfällig sind.
Unittests, die mit echten Dateien arbeiten, sind zwar möglich. Ob sie dann noch der klassischen Definition eines Unittests entsprechen, soll an dieser Stelle nicht diskutiert werden. Durch die notwendigen I/O-Operationen verlieren sie zudem einen Teil ihres Geschwindigkeitsvorteils.
Ergänzend können solche Tests dennoch sinnvoll sein. Im folgenden Beispiel konzentrieren wir uns jedoch auf DICOM-Integrationstests, da sie für die beschriebenen Herausforderungen die größere Bedeutung haben.
DICOM-Integrationstests
Ein einfacher Integrationstest besteht aus einem Setup, der eigentlichen Testausführung und einem Verifizierungsschritt. Das Beispiel nutzt .NET. Für die Kommunikation verwenden wir ausschließlich DICOM und die Bibliothek FellowOakDicom.
Setup des Orthanc Servers
Als DICOM-Server nutzen wir Orthanc, einen leichtgewichtigen Open-Source-DICOM-Server. Er bringt zudem einen Web-Viewer mit, der während der Entwicklung hilfreich ist. Über Docker lässt sich Orthanc einfach in eine Pipeline integrieren. Das docker-compose.yml:
services:
orthanc:
image: orthancteam/orthanc
ports:
- "4242:4242"
volumes:
- orthanc-db:/var/lib/orthanc/db
- ./orthanc.json:/etc/orthanc/orthanc.json
environment:
ORTHANC__NAME: "TestOrthanc"
ORTHANC__DICOM_AET: "ORTHANC"
volumes:
orthanc-db:
Der Server lädt beim Start eine Konfigurationsdatei. Damit lassen sich beliebige Konfigurationen echter Kundensysteme oder, wie in unserem Fall, eine einfache Testkonfiguration nachstellen.
Zum Aufsetzen der Testumgebung spielen wir eine einfache Testdatei von https://github.com/robyoung/dicom-test-files ein. Dafür nutzen wir den C-Store-Service des DICOM-Standards. Mit FellowOakDicom müssen wir lediglich einen Client erstellen, die Datei laden und sie als CStoreRequest an unseren Orthanc Server senden.
var file = DicomFile.Open("CT_small.dcm");
var request = new DicomCStoreRequest(file);
var client = DicomClientFactory.Create("localhost", 4242, false, "SCU", "SCP");
await client.AddRequestAsync(request);
await client.SendAsync();
Über die Web-UI des Orthanc Servers können wir sofort prüfen, ob unser Setup funktioniert.
{
"Name": "Orthanc",
"DicomAet": "ORTHANC",
"DicomPort": 4242,
"DicomCheckCalledAet": false,
"DicomModalities": {
"SCU": [ "SCU", "host.docker.internal", 11112 ]
},
"RemoteAccessAllowed": true
}
Verifikation
Die Testausführung beinhaltet den Aufruf unseres Produktivcodes. In diesem Fall nehmen wir an, dass unsere Anwendung eine Datei über DICOM geladen, mit zusätzlichen Informationen angereichert und anschließend wieder auf dem DICOM-Server gespeichert hat. Denkbar sind beispielsweise farbliche Markierungen innerhalb des Bildes.
Im Verifizierungsschritt laden wir dieses bestimmte CT-Bild erneut und können einen binären Vergleich mit einem Vergleichsbild durchführen.
Am einfachsten wäre es, das Bild über die REST-API des Orthanc Servers von seinem festen Speicherort herunterzuladen und für den Vergleich zu verwenden.
Doch was passiert, wenn wir einen anderen Testserver einsetzen, der diese Möglichkeit nicht bietet? Oder wenn genau die Serverfunktionalität Gegenstand unserer Entwicklung ist?
Dann müssen wir die Datei über DICOM laden. Wie das funktioniert, zeige ich abschließend am Beispiel eines CT-Bildes.
Laden eines CT-Bildes über DICOM
Das Datenmodell von DICOM funktioniert nicht dateibasiert. Stattdessen organisiert DICOM die Daten logisch in einer Hierarchie. Eine Instanz ist einer Serie untergeordnet. Die Serie gehört zu einer Studie und die Studie wiederum zu einem Patienten.
In unserem Beispiel handelt es sich bei der Instanz um ein CT-Bild. Eine Instanz könnte jedoch auch ein Structured Report sein, also ein maschinenlesbarer Befund, der selbst kein Bild ist.
Der Speicherort wird anhand der Metadaten und mithilfe des C-Find-Service ermittelt. Anschließend können wir das CT-Bild über seine Instanz-ID laden.
var studyReadRequest = new DicomCFindRequest(DicomQueryRetrieveLevel.Study)
{
Dataset = new DicomDataset
{{ DicomTag.QueryRetrieveLevel, "STUDY" },{ DicomTag.PatientName, "Jan Kasper" },{ DicomTag.StudyInstanceUID, "" },},
};
var studyUid = string.Empty;
studyReadRequest.OnResponseReceived += async (req, resp) =>
{
var requestSeriesRead = CreateSeriesReadRequest();
requestSeriesRead.OnResponseReceived += async (req, resp) =>
{
studyUid = resp.Dataset.GetSingleValue<string>(DicomTag.StudyInstanceUID);
};
await client.AddRequestAsync(requestSeriesRead);
await client.SendAsync();
};
await client.AddRequestAsync(request);
await client.SendAsync();
Für eine C-Find-Anfrage benötigen wir ein DICOM-Dataset. Dieses beschreibt, welche Daten wir suchen und welche Informationen wir dazu zurückerhalten möchten.
In unserem Beispiel suchen wir nach den vorhandenen Studien des Patienten Jan Kasper. Nachdem wir die Studien empfangen haben, wählen wir die gesuchte Studie aus. Der Einfachheit halber verwenden wir im Beispiel die erste Studie.
Mit derselben Logik fragen wir anschließend die Serien ab. Diesmal grenzen wir die Suche jedoch über die Studien-ID statt über den Patientennamen ein. Diesen Vorgang setzen wir fort, bis das gesuchte Bild über Studien-ID, Serien-ID und Instanz-ID eindeutig identifiziert ist.
Das Herunterladen eines CT-Bildes funktioniert über den C-GET-Service. C-GET wurde später ergänzt und wird daher nicht von allen Systemen unterstützt. Genau hier zeigt sich erneut die Komplexität von DICOM. Unter Umständen fällt erst während der Systemintegration auf, dass dieses Feature auf dem Zielserver nicht verfügbar ist. Zudem müssen wir unseren DICOM-Client so konfigurieren, dass er gegenüber dem Server das Speichern von CT-Dateien anbietet.
var cget = new DicomCGetRequest(studyUid, seriesUid, sopUid);
client.OnCStoreRequest += OnCStoreRequestAsync;
var pcs = DicomPresentationContext.GetScpRolePresentationContextsFromStorageUids(
DicomStorageCategory.Image,
DicomTransferSyntax.ExplicitVRLittleEndian,
DicomTransferSyntax.ImplicitVRLittleEndian,
DicomTransferSyntax.ImplicitVRBigEndian
);
client.AdditionalPresentationContexts.AddRange(pcs);
await client.AddRequestAsync(cget);
await client.SendAsync();
DICOM-Komplexität beherrschbar machen
DICOM ist komplex. Jahrzehntelange Abwärtskompatibilität, unterschiedliche Funktionsumfänge und zahlreiche Konfigurationsmöglichkeiten machen die Integration anspruchsvoll. Gerade deshalb reicht es nicht, sich darauf zu verlassen, dass zwei DICOM-fähige Systeme problemlos miteinander kommunizieren.
Vollautomatisierte Integrationstests schaffen hier Sicherheit. Mit Docker, Orthanc und FellowOakDicom lässt sich in .NET eine Testumgebung aufbauen, in der sich reale Konfigurationen nachstellen, Beispieldaten einspielen und zentrale DICOM-Abläufe automatisiert prüfen lassen.
Die Komplexität von DICOM verschwindet dadurch nicht. Sie wird aber früher sichtbar, reproduzierbar und damit beherrschbarer. Genau das ist eine wichtige Voraussetzung für die Entwicklung qualitativ hochwertiger DICOM-Anwendungen.
Hinweise:
Hier finden Sie weitere Informationen zu DICOM.
Suchen Sie nach einem Team für Ihre Softwareentwicklung oder -modernisierung? Dann laden Sie sich den t2informatik Steckbrief herunter oder sprechen Sie mit uns über Ihr Projekt.
Wollen Sie als Meinungsmacherin oder Kommunikator über DICOM diskutieren? Dann teilen Sie den Beitrag gerne in Ihrem Netzwerk.
Jan Kasper hat zwei weitere Beiträge im t2informatik Blog veröffentlicht:

Jan Kasper
Jan Phillip Kasper ist Softwareentwickler. Er startete im Embedded Bereich mit C++ und wechselte später wegen des starken Toolings zu C#. Seine erste technische Liebe gilt den Unit Tests, im Systemtest spürt er mit leiser Schadenfreude skurrile Fehler auf. Und privat? Da baut er mit seinen Söhnen Technikspielzeug und lässt sie gelegentlich sogar selbst damit spielen.
Im t2informatik Blog veröffentlichen wir Beträge für Menschen in Organisationen. Für diese Menschen entwickeln und modernisieren wir Software. Pragmatisch. ✔️ Persönlich. ✔️ Professionell. ✔️ Ein Klick hier und Sie erfahren mehr.

