Derek Merck derek_merck@brown.edu
Brown University and Rhode Island Hospital
https://github.com/derekmerck/DIANA
Hospital picture archive and communications systems (PACS) are not well suited for "big data" analysis. It is difficult to identify and extract datasets in bulk, and moreover, high resolution data is often not even stored in the clinical systems.
DIANA is a DICOM imaging informatics platform that can be attached to the clinical systems with a very small footprint, and then tuned to support a range of tasks from high-resolution image archival to cohort discovery to radiation dose monitoring.
It is similar to XNAT in providing DICOM services, image data indexing, REST endpoints for scripting, and user access control. It is dissimilar in that it is not a monolithic codebase, but an amalgamation of free and free and open source (FOSS) systems. Indeed, XNAT can be setup as a component of DIANA.
- Docker for service virtualization
- Ansible, pyyaml, jinja2 for service orchestration
- Orthanc for DICOM storage and bridging DICOM to REST
- Postgresql 9.5+ for scalable Orthanc backend
- Splunk 6.6+ for data indexing
- Python 2.7.11+ for scripting
Setup an image archive and a Splunk index.
$ ...
Ansible scripts for reproducible configurations
- DICOM image archives and routing (Orthanc)
- data index and forwarding (Splunk)
Splunk apps and dashboards for informatics and data review
status
(services introspection)rad_rx
(DICOM SRDR)workload
(hl7)
Python gateway API's for scripting indexing or data transfer jobs.
update_index
pacs_crawler
montage_crawler
find_studies
(identify image populations)get_studies
(build secondary image registries)
Extensions supporting high-throughput microscopy data and image analytics and archive
- Monitoring for Phenix Opera logs
- Read spreadsheets of data properties
- Post-processing including ROI cropping and 3D CLAHE
- Use disk compressed storage b/c of all the zeros
- get_samples Find sample cores in each well, extract ROI on pull
role: dicom_archive
-
Create an index role
-
Create a DB role
-
Create n+m DICOM repo roles
- n archive workers
- m queues, muxes
-
Create an http reverse proxy role for archive workers
-
Create a bridge role repo/series -> index
-
Install the dicom-series dashboards
Creating the index first allows the system to ship logs directly to the index
role: pacs_crawler
-
Create an index role
-
Create a DICOM repo role
-
Create a bridge role repo/remote -> index
-
Register the Orthanc repo with the PACS as a valid query source and move target
-
Install the remote-studies dashboards
role: vpn_forwarder
- Create a VPN client role
- Create a DICOM repo role
- configure as a queue that receives locally and forwards to the VPN
Great for small, low powered devices at remote facilities that need to send images or structured dose reports to a primary archive. Building on ARM (Raspberry Pi 3) is problematic.
role: dose_monitor
-
Create an index role
-
Create a DICOM repo role
-
Create a bridge role repo/dose -> index
-
Point modality dose reports to the dose repo
-
Install the dose-monitoring dashboards
Occassionally the EHR will be unavailable to automatically assign accession numbers or protocols as the studies are performed. For example, we recently lost LifeChart for 4 hours, and ended up with about 40 studies that had absent or hand-input improper accession numbers assigned. Because the index assumes the accession numbers are correct, this can lead to missing studies in the summary information.
Reconciling this is somewhat complicated, but can be scripted with CopyDICOM.
index = Splunk gateway dicom_node = Orthanc with structured dose reports remote_bridge = Orthanc with access to the PACS where the updated accession numbers live
- Extract the Study Instance UID of the bad reports from Splunk
study_ids = index.DoQuery( "index=dose_reports | ..."" )
- Ask the PACS for the updated accession numbers for those StudyInstanceUIDs.
for study_id in study_ids:
accessions.append( remote_bridge.DoRemoteQuery( {"StudyInstanceUID": study_id} ) )
- Modify each report in the DICOM node
For study_id, accession_num in zip( study_ids, accessions ):
dicom_node.modify( {"StudyInstanceUID": study_id}, {"AccessionNumber": accession_num} )
- Drop the bad reports from the indices
index.DoQuery( "index=dicom_series | ... | del" )
index.DoQuery( "index=dose_reports | ... | del" )
- Re-index the modified files and update the dose registry
index.UpdateSeries( dicom_node )
index.UpdateDoseRegistry( dicom_node )
role: hl7_consumer
-
Create an index role
-
Create an FTP forwarder role
-
Install dashboards
MIT