Option for converting 'SR" instances to an encapsulated PDF

Please reference a previous post:

https://groups.google.com/g/orthanc-users/c/VjQGgdSS_Ec/m/Ok3IClJJAQAJ

I started to look into this a bit further, implementing some of the suggestions mentioned here:
https://book.orthanc-server.com/plugins/stone-webviewer.html#faq

Still a work in progress, but I basically use:

orthanc.RegisterReceivedInstanceCallback(ReceivedInstanceCallback)

to check if an incoming instance is an “SR” modality type. If so, it converts it:

  1. To HTML using DCMTK dsr2html.
  2. Converts that to a new instance of modality type “OT” as an encapsulated PDF from the generated HTML

I tested briefly with a sample SR instance from the repo: sr_document.dcm

and that seems to have some promise as a way to keep the original SR instance, but then also create a PDF version that the Stone Viewer and other Viewers can display. Probably not that hard to also add a custom stylesheet as well.

I have attached the test .dcm document as well as a screen shot of what shows up in the Stone Viewer after uploading the SR through the Explorer2 interface. The original SR has the eye with a slash through it. The converted document is a viewable PDF.

I’ll update one of my GitHub repos with the code after a little more testing and development.

One thing I’m not quite sure about is how to keep the generated instance as part of the original study, since the orthanc ID for the study, although I could probably move the code to the orthanc.ChangeType.NEW_INSTANCE, although this seems to also keep it as part of the parent study:

dicomdata = dict()
dicomdata[‘Force’] = True
dicomdata[‘Tags’] = {
“PatientID”:jsonTags[‘PatientID’],
“PatientName”:jsonTags[‘PatientName’],
“PatientBirthDate”:jsonTags[‘PatientBirthDate’],
“PatientSex”:jsonTags[‘PatientSex’],
“Modality”:“OT”,
“SeriesDescription”: jsonTags[‘SeriesDescription’]+“, SR Converted to PDF”,
“SequenceName” : “NA”,
“ImageComments”:“SR Converted using dsr2html & wkhtmltopdf”,
“SOPClassUID”:“1.2.840.10008.5.1.4.1.1.7.4”,
“StudyInstanceUID”:jsonTags[‘StudyInstanceUID’]
}
dicomdata[‘Content’] = “data:application/pdf;base64,”+encoded;
convertedSR = json.loads(orthanc.RestApiPost(‘/tools/create-dicom’, json.dumps(dicomdata)))

Stephen D. Scotti

sr_document.dcm (4.89 KB)

this would come in handy if/when implemented. Good luck.

If you want to try that you could add this as a method in a Python Plug-in Script, but you need to:

  1. Install wkhtmltopdf in your Orthanc container, using .deb packages via wget that have the ‘QT’ support also.

  2. Install dcmtk in your Orthanc container, or figure out how to call DcmtkBridge from the Plug-in, to make the dsr2html executable available.

  3. Tweak the script to fit your configuration.

  4. Tweak where it saves /development/temp.dcm /development/temp.html" in the container in your docker-compose, or mkdir

I extracted it from my master so there might be some stuff missing.

DOCKERFILE

FROM osimis/orthanc:22.9.2
ENV DEBIAN_FRONTEND=noninteractive
ENV HTTP_BUNDLE_DEFAULTS=false
ENV AC_BUNDLE_DEFAULTS=false

RUN mkdir /python
# /python is bound to the host folder ./orthanc_python, but Orthanc needs to be restarted to see changes.
# /lua-scripts already exists in the container, and bound to lua in this folder. No need to restart to see changes, at least seems that way.

RUN apt-get update && apt-get --assume-yes install -y
wget
xz-utils
fontconfig
libfreetype6
libjpeg62-turbo
libpng16-16
libx11-6
libxcb1
libxext6
libxrender1
xfonts-75dpi
xfonts-100dpi
xfonts-scalable
xfonts-base
*dcmtk *
libpq-dev

added libpq-dev for import psycopg2

# Below is for the ARM M1 Architecture, library to support the PDF functions

RUN wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.buster_arm64.deb

RUN dpkg -i wkhtmltox_0.12.6-1.buster_arm64.deb

# Below is for the AMD Architecture, library to support the PDF functions
RUN wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.buster_amd64.deb
RUN dpkg -i wkhtmltox_0.12.6-1.buster_amd64.deb

RUN pip3 install pydicom pynetdicom pdfkit imgkit hl7 wkhtmltopdf mysql-connector-python requests
RUN pip3 install psycopg2

COPY docker-entrypoint.sh /

python.py (Plug-in Script)

import shutil
import pdfkit # https://pypi.org/project/pdfkit/, sudo python3 -m pip install pdfkit
from pdfkit import configuration
from pdfkit import from_string
import pydicom # https://github.com/pydicom/pydicom, sudo python3 -m pip install pydicom
from pydicom.datadict import dictionary_keyword
from pydicom import dcmread, dcmwrite
from pydicom.filebase import DicomFileLike
from pydicom.dataset import Dataset, FileDataset, FileMetaDataset
from pydicom.uid import ExplicitVRLittleEndian, generate_uid

def ReceivedInstanceCallback(receivedDicom, origin):

Only do the modifications if via DICOM and ideally filter by AET.

orthanc.LogWarning('DICOM instance received in ReceivedInstanceCallback from ’ + str(origin))
dataset = dcmread(BytesIO(receivedDicom))
jsonTags = json.loads(orthanc.DicomBufferToJson(receivedDicom, orthanc.DicomToJsonFormat.HUMAN, orthanc.DicomToJsonFlags.NONE, 0))

orthanc.LogWarning(json.dumps(jsonTags, indent = 2, sort_keys = True))

if origin == orthanc.InstanceOrigin.DICOM_PROTOCOL:

Do Nothing for now

return orthanc.ReceivedInstanceAction.KEEP_AS_IS, None

elif origin == orthanc.InstanceOrigin.REST_API:

if “Modality” in jsonTags and jsonTags[‘Modality’] == “SR”:
orthanc.LogWarning("NEW SR INSTANCE VIA RESTAPI: "+json.dumps(jsonTags, indent = 2, sort_keys = True))

logging.info(“NEW SR INSTANCE VIA RESTAPI”+json.dumps(jsonTags, indent = 2, sort_keys = True))

If it an SR Modality type, use dcmtk dsr2html to convert to HTML, then

use wkhtmltopdf to convert to an encapsulated PDF for easier display.

see https://support.dcmtk.org/docs/dsr2html.html

pathtobinary = shutil.which(“dsr2html”)
dataset.save_as(“/development/temp.dcm” ,write_like_original=True)
cmd = pathtobinary + " -Ei /development/temp.dcm /development/temp.html"
os.system(cmd) # returns the exit status
pathtobinary = shutil.which(“wkhtmltopdf”)
config = pdfkit.configuration(wkhtmltopdf=pathtobinary)
options = {
‘page-size’: ‘Letter’,
‘margin-top’: ‘0.75in’,
‘margin-right’: ‘0.75in’,
‘margin-bottom’: ‘0.75in’,
‘margin-left’: ‘0.75in’,
‘footer-line’:‘’,
‘footer-font-size’:‘12’,
‘footer-center’: ‘Page [page] of [toPage], [date]’,
‘encoding’: ‘utf-8’
}
pdf = pdfkit.from_file**(“/development/temp.html”**, False,options=options)
encoded = base64.b64encode(pdf).decode()
dicomdata = dict()
dicomdata[‘Force’] = True
dicomdata[‘Tags’] = {
“PatientID”:jsonTags[‘PatientID’],
“PatientName”:jsonTags[‘PatientName’],
“PatientBirthDate”:jsonTags[‘PatientBirthDate’],
“PatientSex”:jsonTags[‘PatientSex’],
“Modality”:“OT”,
“SeriesDescription”: jsonTags[‘SeriesDescription’]+“, SR Converted to PDF”,
“SequenceName” : “NA”,
“ImageComments”:“SR Converted using dsr2html & wkhtmltopdf”,
“SOPClassUID”:“1.2.840.10008.5.1.4.1.1.7.4”,
“StudyInstanceUID”:jsonTags[‘StudyInstanceUID’]
}
dicomdata[‘Content’] = “data:application/pdf;base64,”+encoded;
convertedSR = json.loads(orthanc.RestApiPost(‘/tools/create-dicom’, json.dumps(dicomdata)))
return orthanc.ReceivedInstanceAction.MODIFY, dataset_to_bytes(dataset)

Load up a sample SR instance through the RESTAPI / Explorer
It should create a PDF version of the SR.

/sds

Hi Stephen!

I am glad you found a solution for this.

Unfortunately, as an Orthanc-Beginner (and generally not a very experienced coder) I am having troubles even implementing the first step and the whole process looks fairly complicated. Could you (or anyone else who has used this) explain to me where/how to run “orthanc.RegisterReceivedInstanceCallback(ReceivedInstanceCallback)”?

Or have you shared your code somewhere?

I would be very happy if I could view SRs within Stone Viewer.

I think OHIF supports that out-of-the-box, and MedDream does also, although I could revisit that code if I still have it somewhere.

Wow, thanks for the quick reply!

My issue is that I am not currently not getting anywhere with the OHIF plugin either, so I thought I would try my luck with Stone Viewer. But if it is apparently supported “out of the box” with OHIF, I guess I will continue trying to figure that out.

Edit: In other words, I get the impression that the OHIF community is less active than the Orthanc one, as some issues I have encountered were posted there by others months ago and never got a reply.
I was hoping it might be different if I try Stone Viewer due to the active Orthanc community.

Hello,

The mainline of the Stone Web viewer includes preliminary support for TID 1500 and TID 1410. What we are missing now are sample DICOM-SR files, together with their referenced DICOM instances. Feel free to share such sample files if you want the Stone Web viewer to officially start supporting DICOM-SR in the next few months.

Kind Regards,
Sébastien-

For anyone who is searching for simple dicom SR viewer on OHIF here is python endpoint:

import requests
import re
import subprocess
import os

def GetSrHTML(output, uri, **request):
    try:
        instanceUid = re.search(r"/get-sr-html/([^/]+)", uri).group(1)

        raw = requests.get(
            f'http://localhost:8042/instances/{instanceUid}/file',
            auth=(os.getenv('ORTHANC_USERNAME'), os.getenv('ORTHANC_PASSWORD'))
        )

        if raw.status_code != 200:
            output.AnswerBuffer(f"Error fetching instance file: {raw.status_code}", 'text/plain')
            return

        process = subprocess.Popen(
            ['dsr2html', '-'],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        html, error = process.communicate(input=raw.content)
        output.AnswerBuffer(html or '', 'text/html')
    except Exception as e:
        print('get-sr-html error ', e)
        output.SendHttpStatus(500, 'get-sr-html')

    
orthanc.RegisterRestCallback('/get-sr-html/(.*)', GetSrHTML)

.
.
.
Call this endpoint in OHIF frontend and just render it in the modal window or iframe or whatever when user clicks to any SR series thumbnail:

  <div
      dangerouslySetInnerHTML={{ __html: res.data }}
      className="sr-viewer"
  ></div>

Dockerfile:

FROM orthancteam/orthanc:24.8.3

RUN apt-get update && apt install -y python3-venv dcmtk
RUN python3 -m venv /.venv

RUN /.venv/bin/pip install pydicom requests
ENV PYTHONPATH=/.venv/lib64/python3.11/site-packages/

COPY orthanc.json /etc/orthanc/
RUN mkdir /python
COPY ./study.py /python/
2 Likes