DICOM and FHIR Integration

Section 18 of 27
67% complete

Integrating DICOM with FHIR enables imaging data to participate in modern healthcare interoperability.

FHIR ImagingStudy Resource

The ImagingStudy resource represents a DICOM study in FHIR.

ImagingStudy Structure

FHIR ImagingStudy Resourcesaravanansubramanian.comstudy wraps one or more series, each containing one or more instancesIMAGINGSTUDY RESOURCEImagingStudytop-level resource · one per DICOM studyidentifier (Study Instance UID)status (available, cancelled, …)subject (Patient reference)encounter (Visit reference)started (Study Date / Time)basedOn (Order reference)endpoint (DICOMweb endpoint)numberOfSeriesnumberOfInstancesprocedureReference · procedureCodereasonCodedescriptionseries[]one entry per DICOM series inside the studyuid (Series Instance UID)numbermodality (CT, MR, US, …)descriptionnumberOfInstancesendpointbodySitelateralityspecimenstartedperformer[]instance[]one entry per SOP instance inside the seriesuid (SOP Instance UID) · sopClassnumber · title

Creating ImagingStudy from DICOM

public class DicomToFhirConverter
{
    public ImagingStudy ConvertToImagingStudy(
        DicomDataSet studyData,
        List<DicomDataSet> seriesData,
        string dicomwebEndpoint)
    {
        var imagingStudy = new ImagingStudy
        {
            Status = ImagingStudyStatus.Available,
            Started = ParseDicomDateTime(
                studyData[DicomTag.StudyDate]?.Value?.ToString(),
                studyData[DicomTag.StudyTime]?.Value?.ToString()
            )
        };

        // Study Instance UID as identifier
        imagingStudy.Identifier.Add(new Identifier
        {
            System = "urn:dicom:uid",
            Value = $"urn:oid:{studyData[DicomTag.StudyInstanceUID]?.Value}"
        });

        // Accession Number
        string accessionNumber = studyData[DicomTag.AccessionNumber]?.Value?.ToString();
        if (!string.IsNullOrEmpty(accessionNumber))
        {
            imagingStudy.Identifier.Add(new Identifier
            {
                Type = new CodeableConcept("http://terminology.hl7.org/CodeSystem/v2-0203", "ACSN"),
                Value = accessionNumber
            });
        }

        // Patient reference
        string patientId = studyData[DicomTag.PatientID]?.Value?.ToString();
        imagingStudy.Subject = new ResourceReference($"Patient/{patientId}");

        // Study description
        imagingStudy.Description = studyData[DicomTag.StudyDescription]?.Value?.ToString();

        // DICOMweb endpoint
        imagingStudy.Endpoint.Add(new ResourceReference
        {
            Reference = dicomwebEndpoint
        });

        // Process series
        int totalInstances = 0;
        foreach (var series in seriesData)
        {
            var fhirSeries = ConvertSeries(series);
            imagingStudy.Series.Add(fhirSeries);
            totalInstances += fhirSeries.NumberOfInstances ?? 0;
        }

        imagingStudy.NumberOfSeries = imagingStudy.Series.Count;
        imagingStudy.NumberOfInstances = totalInstances;

        return imagingStudy;
    }

    private ImagingStudy.SeriesComponent ConvertSeries(DicomDataSet seriesData)
    {
        var series = new ImagingStudy.SeriesComponent
        {
            Uid = seriesData[DicomTag.SeriesInstanceUID]?.Value?.ToString(),
            Number = ParseInt(seriesData[DicomTag.SeriesNumber]?.Value?.ToString()),
            Modality = new Coding
            {
                System = "http://dicom.nema.org/resources/ontology/DCM",
                Code = seriesData[DicomTag.Modality]?.Value?.ToString()
            },
            Description = seriesData[DicomTag.SeriesDescription]?.Value?.ToString()
        };

        // Body site (if available)
        string bodyPart = seriesData[DicomTag.BodyPartExamined]?.Value?.ToString();
        if (!string.IsNullOrEmpty(bodyPart))
        {
            series.BodySite = new Coding
            {
                System = "http://snomed.info/sct",
                Display = bodyPart
            };
        }

        return series;
    }

    private DateTime? ParseDicomDateTime(string date, string time)
    {
        if (string.IsNullOrEmpty(date)) return null;

        string dateTime = date;
        if (!string.IsNullOrEmpty(time))
        {
            dateTime += time.Substring(0, Math.Min(6, time.Length));
        }

        if (DateTime.TryParseExact(dateTime, new[] { "yyyyMMdd", "yyyyMMddHHmmss" },
            null, System.Globalization.DateTimeStyles.None, out DateTime result))
        {
            return result;
        }

        return null;
    }

    private int? ParseInt(string value)
    {
        if (int.TryParse(value, out int result))
            return result;
        return null;
    }
}

FHIR Endpoint for DICOMweb

public class DicomEndpointCreator
{
    public Endpoint CreateDicomWebEndpoint(
        string name,
        string wadoRsUrl,
        string qidoRsUrl,
        string stowRsUrl)
    {
        var endpoint = new Endpoint
        {
            Status = EndpointStatus.Active,
            Name = name,
            ConnectionType = new Coding
            {
                System = "http://terminology.hl7.org/CodeSystem/endpoint-connection-type",
                Code = "dicom-wado-rs"
            }
        };

        // WADO-RS URL
        endpoint.Address = wadoRsUrl;

        // Additional endpoint URLs as extensions
        endpoint.Extension.Add(new Extension
        {
            Url = "http://hl7.org/fhir/uv/ihe-sdc/StructureDefinition/endpoint-qido-rs",
            Value = new FhirUrl(qidoRsUrl)
        });

        endpoint.Extension.Add(new Extension
        {
            Url = "http://hl7.org/fhir/uv/ihe-sdc/StructureDefinition/endpoint-stow-rs",
            Value = new FhirUrl(stowRsUrl)
        });

        // Payload type
        endpoint.PayloadType.Add(new CodeableConcept
        {
            Coding = new List<Coding>
            {
                new Coding
                {
                    System = "http://hl7.org/fhir/resource-types",
                    Code = "ImagingStudy"
                }
            }
        });

        return endpoint;
    }
}

Querying Imaging Studies via FHIR

public class FhirImagingClient
{
    private readonly FhirClient fhirClient;

    public FhirImagingClient(string fhirServerUrl)
    {
        fhirClient = new FhirClient(fhirServerUrl);
    }

    // Find imaging studies for a patient
    public async Task<List<ImagingStudy>> GetPatientImagingStudies(string patientId)
    {
        var searchParams = new SearchParams()
            .Where($"subject=Patient/{patientId}")
            .OrderBy("-started")
            .LimitTo(50);

        Bundle results = await fhirClient.SearchAsync<ImagingStudy>(searchParams);

        return results.Entry
            .Select(e => e.Resource as ImagingStudy)
            .Where(s => s != null)
            .ToList();
    }

    // Find study by DICOM Study Instance UID
    public async Task<ImagingStudy> GetStudyByDicomUid(string studyInstanceUid)
    {
        var searchParams = new SearchParams()
            .Where($"identifier=urn:oid:{studyInstanceUid}");

        Bundle results = await fhirClient.SearchAsync<ImagingStudy>(searchParams);

        return results.Entry.FirstOrDefault()?.Resource as ImagingStudy;
    }

    // Find studies by modality and date range
    public async Task<List<ImagingStudy>> SearchStudies(
        string modality = null,
        DateTime? startDate = null,
        DateTime? endDate = null)
    {
        var searchParams = new SearchParams();

        if (!string.IsNullOrEmpty(modality))
            searchParams.Add("modality", $"http://dicom.nema.org/resources/ontology/DCM|{modality}");

        if (startDate.HasValue)
            searchParams.Add("started", $"ge{startDate.Value:yyyy-MM-dd}");

        if (endDate.HasValue)
            searchParams.Add("started", $"le{endDate.Value:yyyy-MM-dd}");

        Bundle results = await fhirClient.SearchAsync<ImagingStudy>(searchParams);

        return results.Entry
            .Select(e => e.Resource as ImagingStudy)
            .Where(s => s != null)
            .ToList();
    }

    // Get DICOMweb endpoint from ImagingStudy
    public async Task<string> GetDicomWebEndpoint(ImagingStudy study)
    {
        if (study.Endpoint == null || !study.Endpoint.Any())
            return null;

        var endpointRef = study.Endpoint.First();
        var endpoint = await fhirClient.ReadAsync<Endpoint>(endpointRef.Reference);

        return endpoint?.Address;
    }
}

Complete Integration Example

public class DicomFhirIntegrationService
{
    private readonly FhirImagingClient fhirClient;
    private readonly WadoClient wadoClient;
    private readonly DicomToFhirConverter converter;

    public DicomFhirIntegrationService(
        string fhirServerUrl,
        string dicomWebUrl)
    {
        fhirClient = new FhirImagingClient(fhirServerUrl);
        wadoClient = new WadoClient(dicomWebUrl);
        converter = new DicomToFhirConverter();
    }

    // Get patient imaging history with thumbnails
    public async Task<List<ImagingStudySummary>> GetPatientImagingHistory(string patientId)
    {
        var summaries = new List<ImagingStudySummary>();

        // Get studies from FHIR
        var studies = await fhirClient.GetPatientImagingStudies(patientId);

        foreach (var study in studies)
        {
            var summary = new ImagingStudySummary
            {
                StudyInstanceUID = study.Identifier
                    .FirstOrDefault(i => i.System == "urn:dicom:uid")?.Value
                    .Replace("urn:oid:", ""),
                StudyDate = study.Started,
                Description = study.Description,
                Modality = study.Series.FirstOrDefault()?.Modality?.Code,
                NumberOfSeries = study.NumberOfSeries ?? 0,
                NumberOfInstances = study.NumberOfInstances ?? 0
            };

            // Get thumbnail from first instance
            if (study.Series.Any() && study.Series.First().Instance.Any())
            {
                var firstSeries = study.Series.First();
                var firstInstance = firstSeries.Instance.First();

                try
                {
                    summary.Thumbnail = await wadoClient.RetrieveThumbnail(
                        summary.StudyInstanceUID,
                        firstSeries.Uid,
                        firstInstance.Uid
                    );
                }
                catch { /* Thumbnail not available */ }
            }

            summaries.Add(summary);
        }

        return summaries;
    }

    // Retrieve full DICOM study starting from FHIR
    public async Task<List<byte[]>> RetrieveStudyFromFhir(string studyFhirId)
    {
        // Get ImagingStudy from FHIR
        var study = await fhirClient.GetStudyByDicomUid(studyFhirId);
        if (study == null)
            throw new Exception("Study not found in FHIR");

        // Get Study Instance UID
        string studyUid = study.Identifier
            .FirstOrDefault(i => i.System == "urn:dicom:uid")?.Value
            .Replace("urn:oid:", "");

        // Retrieve via DICOMweb
        return await wadoClient.RetrieveStudy(studyUid);
    }
}

public class ImagingStudySummary
{
    public string StudyInstanceUID { get; set; }
    public DateTime? StudyDate { get; set; }
    public string Description { get; set; }
    public string Modality { get; set; }
    public int NumberOfSeries { get; set; }
    public int NumberOfInstances { get; set; }
    public byte[] Thumbnail { get; set; }
}

Key Mappings

DICOM to FHIR Mappingssaravanansubramanian.comhow each DICOM header field lands in an ImagingStudy resourceDICOM TAGFHIR ELEMENTStudy Instance UIDImagingStudy.identifierPatient IDImagingStudy.subjectStudy Date/TimeImagingStudy.startedAccession NumberImagingStudy.identifier (ACSN)Study DescriptionImagingStudy.descriptionSeries Instance UIDseries.uidModalityseries.modalitySeries Numberseries.numberBody Part Examinedseries.bodySiteSOP Instance UIDinstance.uidSOP Class UIDinstance.sopClassInstance Numberinstance.number

DICOM-FHIR integration enables seamless access to imaging data within modern healthcare applications.

Explore DICOM-FHIR integration and related topics:

FHIR Programming:

DICOM-FHIR Integration:

DICOMweb:

Quiz: DICOM and FHIR Integration

Question 1 of 4

What FHIR resource type is used to represent DICOM studies?