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
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-FHIR integration enables seamless access to imaging data within modern healthcare applications.
Related Articles
Explore DICOM-FHIR integration and related topics:
FHIR Programming:
- Basics of FHIR - Understanding FHIR fundamentals
- FHIR Programming using Java HAPI - Creating FHIR Resources - Building FHIR resources
- FHIR Programming using .NET - Creating FHIR Resources - .NET FHIR development
DICOM-FHIR Integration:
- DICOM and FHIR - Integration patterns and strategies
- DICOM, HL7, and IHE Integration - Multi-standard integration
DICOMweb:
- DICOM Basics using Java - DICOMweb - Java DICOMweb implementation
- DICOM Basics using .NET - DICOMweb - .NET DICOMweb implementation
Related Tutorials
- FHIR Tutorial - Complete FHIR development guide