Skip to content

Commit

Permalink
PACS integration is workign well
Browse files Browse the repository at this point in the history
  • Loading branch information
lext committed Dec 26, 2019
1 parent 588b6ee commit 54d8615
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 30 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
snapshots_knee_grading/*
*.egg-info
snapshots_release_kneel/*
snapshots_release/*
deepknee-frontend/*
deepknee-backend-broker/*
pacs-integration/*
pics/*
logs/*
.git/
2 changes: 2 additions & 0 deletions docker/Dockerfile.cpu
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ FROM miptmloulu/kneel:cpu

MAINTAINER Aleksei Tiulpin, University of Oulu, Version 1.0

RUN pip install pynetdicom

RUN mkdir -p /opt/pkg-deepknee/
COPY . /opt/pkg-deepknee/
RUN pip install /opt/pkg-deepknee/
2 changes: 2 additions & 0 deletions docker/Dockerfile.gpu
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ FROM miptmloulu/kneel:gpu

MAINTAINER Aleksei Tiulpin, University of Oulu, Version 1.0

RUN pip install pynetdicom

RUN mkdir -p /opt/pkg-deepknee/
COPY . /opt/pkg-deepknee/
RUN pip install /opt/pkg-deepknee/
10 changes: 7 additions & 3 deletions docker/docker-compose-cpu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ services:
container_name: orthanc-pacs
ports:
- "6000:4242"
- "8042:8042"
- "6001:8042"
volumes:
- type: bind
source: ../pacs-integration/orthanc.json
Expand All @@ -60,16 +60,20 @@ services:
condition: service_started
orthanc-pacs:
condition: service_started
image: "miptmloulu/kneel:cpu"
image: "miptmloulu/deepknee:cpu"
container_name: dicom-router
volumes:
- type: bind
source: ../pacs-integration/change_polling.py
target: /opt/change_polling.py
entrypoint: ["python", "-u", "/opt/change_polling.py",
"--deepknee_addr", "http://deepknee",
"--deepknee_port", "5001",
"--orthanc_addr", "http://orthanc-pacs",
"--orthanc_port", "8042"]
"--orthanc_http_port", "8042",
"--orthanc_dicom_port", "4242",
'--remote_pacs_addr', 'orthanc-pacs',
'--remote_pacs_port', '4242']
backend-broker:
depends_on:
- kneel
Expand Down
10 changes: 7 additions & 3 deletions docker/docker-compose-gpu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ services:
container_name: orthanc-pacs
ports:
- "6000:4242"
- "8042:8042"
- "6001:8042"
volumes:
- type: bind
source: ../pacs-integration/orthanc.json
Expand All @@ -64,16 +64,20 @@ services:
condition: service_started
orthanc-pacs:
condition: service_started
image: "miptmloulu/kneel:cpu"
image: "miptmloulu/deepknee:cpu"
container_name: dicom-router
volumes:
- type: bind
source: ../pacs-integration/change_polling.py
target: /opt/change_polling.py
entrypoint: ["python", "-u", "/opt/change_polling.py",
"--deepknee_addr", "http://deepknee",
"--deepknee_port", "5001",
"--orthanc_addr", "http://orthanc-pacs",
"--orthanc_port", "8042"]
"--orthanc_http_port", "8042",
"--orthanc_dicom_port", "4242",
'--remote_pacs_addr', 'orthanc-pacs',
'--remote_pacs_port', '4242']
backend-broker:
depends_on:
- kneel
Expand Down
130 changes: 106 additions & 24 deletions pacs-integration/change_polling.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,120 @@
# -*- coding: utf-8 -*-
import queue
import argparse
import requests
import base64
import logging
import queue
import time

import requests
from pydicom import dcmread
from pydicom.filebase import DicomBytesIO
from pydicom.uid import ImplicitVRLittleEndian
from pydicom.uid import generate_uid
from pynetdicom import AE, VerificationPresentationContexts

queue = queue.Queue()


def ingestion_loop():
logger.log(logging.INFO, 'Creating application entity...')
ae = AE(ae_title=b'DEEPKNEE')
ae.requested_contexts = VerificationPresentationContexts
ae.add_requested_context('1.2.840.10008.5.1.4.1.1.1.1', transfer_syntax=ImplicitVRLittleEndian)

current = 0
response = requests.get(f'{args.orthanc_addr}:{args.orthanc_port}/changes?since={current}&limit=10',
auth=('deepknee', 'deepknee'))
base_url = f'{args.orthanc_addr}:{args.orthanc_http_port}'
response = requests.get(f'{base_url}/changes?since={current}&limit=10', auth=('deepknee', 'deepknee'))
if response.status_code == 200:
print('Connection to orthanc is healthy.')
logger.log(logging.INFO, 'Connection to Orthanc via REST is healthy')

# Orthanc addr must have http, but DICOM communicates via sockets
assoc = ae.associate(args.orthanc_addr.split('http://')[1], args.orthanc_dicom_port)
if assoc.is_established:
logger.log(logging.INFO, 'Connection to Orthanc via DICOM is healthy')
assoc.release()

assoc = ae.associate(args.remote_pacs_addr, args.remote_pacs_port)
if assoc.is_established:
logger.log(logging.INFO, 'Connection to Remote PACS via DICOM is healthy')
assoc.release()

while True:
response = requests.get(f'{args.orthanc_addr}:{args.orthanc_port}/changes?since={current}&limit=10',
auth=('deepknee', 'deepknee'))
response = requests.get(f'{base_url}/changes?since={current}&limit=10', auth=('deepknee', 'deepknee'))
response = response.json()
for change in response['Changes']:
# We must also filter by the imaged body part in the future
if change['ChangeType'] == 'NewInstance':
response = requests.get(f'{args.orthanc_addr}:{args.orthanc_port}/instances/{change["ID"]}/file',
auth=('deepknee', 'deepknee'))
dicom_base64 = base64.b64encode(response.content).decode('ascii')

response = requests.post(f'{args.deepknee_addr}:{args.deepknee_port}/deepknee/predict/bilateral',
json={'dicom': dicom_base64})
if response.status_code != 200:
print('Request has failed!')
logger.log(logging.INFO, 'Identified new received instance in Orthanc. '
'Checking if it has been created by DeepKnee...')
# We should not analyze the instances if they are produced by DeepKnee
# Checking if it was verified by DeepKnee
resp_verifier = requests.get(f'{base_url}/instances/{change["ID"]}/content/0040-a027',
auth=('deepknee', 'deepknee'))
resp_verifier.encoding = 'utf-8'
resp_content = requests.get(f'{base_url}/instances/{change["ID"]}/content/0070-0080',
auth=('deepknee', 'deepknee'))

resp_content.encoding = 'utf-8'

if resp_verifier.text.strip("\x00 ") == 'UniOulu-DeepKnee' and \
resp_content.text.strip("\x00 ") == 'DEEPKNEE-XRAY':
continue

# Once we are sure that the instance is new, we need to go ahead with teh analysis
response = requests.get(f'{base_url}/instances/{change["ID"]}/file', auth=('deepknee', 'deepknee'))

logger.log(logging.INFO, 'Instance has been retrieved from Orthanc')
dicom_raw_bytes = response.content
dcm = dcmread(DicomBytesIO(dicom_raw_bytes))

dicom_base64 = base64.b64encode(dicom_raw_bytes).decode('ascii')
logger.log(logging.INFO, 'Sending API request to DeepKnee core')
url = f'{args.deepknee_addr}:{args.deepknee_port}/deepknee/predict/bilateral'
response_deepknee = requests.post(url, json={'dicom': dicom_base64})

if response_deepknee.status_code != 200:
logger.log(logging.INFO, 'DeepKnee analysis has failed')
else:
response_res = response.json()
print(response_res['R']['kl'], response_res['L']['kl'])
response = requests.delete(f'{args.orthanc_addr}:{args.orthanc_port}/instances/{change["ID"]}',
auth=('deepknee', 'deepknee'))
if response.status_code == 200:
print('Instance has been removed from the orthanc router')
logger.log(logging.INFO, 'Getting rid of the instance in Orthanc')
if args.orthanc_addr.split('http://')[1] != args.remote_pacs_addr and \
args.orthanc_dicom_port != args.remote_pacs_port:
response = requests.delete(f'{base_url}/instances/{change["ID"]}',
auth=('deepknee', 'deepknee'))
if response.status_code == 200:
logger.log(logging.INFO, 'Instance has been removed from the Orthanc')
else:
logger.log(logging.INFO, 'Remote PACS is DeepKnee. The instance will not be removed.')

logger.log(logging.INFO, 'DeepKnee has successfully analyzed the image. Routing...')

# Report
deepknee_json = response_deepknee.json()
dcm.add_new([0x40, 0xa160], 'LO', 'KL_right: {}, KL_left: {}'.format(deepknee_json['R']['kl'],
deepknee_json['L']['kl']))
# Verifier
dcm.add_new([0x40, 0xa027], 'LO', 'UniOulu-DeepKnee')
# Content label
dcm.add_new([0x70, 0x80], 'CS', 'DEEPKNEE-XRAY')

dcm[0x08, 0x8].value = 'DERIVED'
# Instance_UUID
current_uuid = dcm[0x08, 0x18].value
dcm[0x08, 0x18].value = generate_uid(prefix='.'.join(current_uuid.split('.')[:-1])+'.')
# Series UUID
current_uuid = dcm[0x20, 0x0e].value
dcm[0x20, 0x0e].value = generate_uid(prefix='.'.join(current_uuid.split('.')[:-1])+'.')
logger.log(logging.INFO, 'Connecting to Orthanc over DICOM')
assoc = ae.associate(args.remote_pacs_addr, args.remote_pacs_port)
if assoc.is_established:
logger.log(logging.INFO, 'Association with Orthanc has been established. Routing..')
routing_status = assoc.send_c_store(dcm)
logger.log(logging.INFO, f'Routing finished. Status: {routing_status}')
assoc.release()

else:
# Here there should be a code to remove the change from the pacs
# Now nothing is done here
pass

current += 1
time.sleep(1)

Expand All @@ -48,10 +123,17 @@ def ingestion_loop():
parser = argparse.ArgumentParser()
parser.add_argument('--deepknee_addr', default='http://127.0.0.1', help='DeepKnee address')
parser.add_argument('--deepknee_port', default=5001, help='DeepKnee backend port')

parser.add_argument('--orthanc_addr', default='http://127.0.0.1', help='The host address that runs Orthanc')
parser.add_argument('--orthanc_port', type=int, default=6001, help='Orthanc REST API port')
parser.add_argument('--orthanc_http_port', type=int, default=6001, help='Orthanc REST API port')
parser.add_argument('--orthanc_dicom_port', type=int, default=6000, help='Orthanc DICOM port')

parser.add_argument('--remote_pacs_addr', default='http://127.0.0.1', help='Remote PACS IP addr')
parser.add_argument('--remote_pacs_port', type=int, default=8042, help='Remote PACS IP addr')
parser.add_argument('--remote_pacs_port', type=int, default=6000, help='Remote PACS port')
args = parser.parse_args()

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
logger = logging.getLogger(f'dicom-router')

ingestion_loop()

0 comments on commit 54d8615

Please sign in to comment.