Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
NicolasRannou committed Oct 4, 2016
0 parents commit 531d249
Show file tree
Hide file tree
Showing 16 changed files with 672 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Compiled python modules.
*.pyc

# Setuptools distribution folder.
/dist/

# Python egg metadata, regenerated from source files by setuptools.
/*.egg-info
/*.egg
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include README.rst
Empty file added README.rst
Empty file.
29 changes: 29 additions & 0 deletions bin/ptk-echo
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env python
# _
# Pacs ToolKit Find wrapper
#
# (c) 2016 Fetal-Neonatal Neuroimaging & Developmental Science Center
# Boston Children's Hospital
#
# http://childrenshospital.org/FNNDSC/
# [email protected]
#

# FOR DEV PURPOSE
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

import ptk
import argparse

parser = argparse.ArgumentParser(description='Echo PACS')

# PACS settings
parser.add_argument('--aet', action='store', dest='aet', type=str, default='CHRIS-ULTRON-AET', help='aet')
parser.add_argument('--aec', action='store', dest='aec', type=str, default='CHRIS-ULTRON-AEC', help='aec')
parser.add_argument('--serverIP', action='store', dest='server_ip', type=str, default='192.168.1.110', help='PACS server IP')
parser.add_argument('--serverPort', action='store', dest='server_port', type=str, default='4242', help='PACS server port')

opts = parser.parse_args()
output = ptk.echo(vars(opts))
print(output)
39 changes: 39 additions & 0 deletions bin/ptk-find
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env python
# _
# Pacs ToolKit Find wrapper
#
# (c) 2016 Fetal-Neonatal Neuroimaging & Developmental Science Center
# Boston Children's Hospital
#
# http://childrenshospital.org/FNNDSC/
# [email protected]
#

# FOR DEV PURPOSE
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

import ptk
import argparse

parser = argparse.ArgumentParser(description='Find PACS')

# PACS settings
parser.add_argument('--aet', action='store', dest='aet', type=str, default='CHRIS-ULTRON-AET', help='aet')
parser.add_argument('--aec', action='store', dest='aec', type=str, default='CHRIS-ULTRON-AEC', help='aec')
parser.add_argument('--serverIP', action='store', dest='server_ip', type=str, default='192.168.1.110', help='PACS server IP')
parser.add_argument('--serverPort', action='store', dest='server_port', type=str, default='4242', help='PACS server port')

# Query settings
parser.add_argument('--patientID', action='store', dest='PatientID', type=str, default='2175', help='Patient ID')
parser.add_argument('--patientName', action='store', dest='PatientName', type=str, default='', help='Patient name')
parser.add_argument('--patientSex', action='store', dest='PatientSex', type=str, default='', help='Patient sex')
parser.add_argument('--studyDate', action='store', dest='StudyDate', type=str, default='', help='Study date (YYYY/MM/DD)')
parser.add_argument('--modalitiesInStudy', action='store', dest='ModalitiesInStudy', type=str, default='', help='Modalities in study')
parser.add_argument('--performedStationAETitle', action='store', dest='PerformedStationAETitle', type=str, default='', help='Performed station aet')
parser.add_argument('--studyDescription', action='store', dest='StudyDescription', type=str, default='', help='Study description')
parser.add_argument('--seriesDescription', action='store', dest='SeriesDescription', type=str, default='', help='Series Description')

opts = parser.parse_args()
output = ptk.find(vars(opts))
print(output)
25 changes: 25 additions & 0 deletions bin/ptk-listen
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env python
# _
# Pacs ToolKit Listener
#
# (c) 2016 Fetal-Neonatal Neuroimaging & Developmental Science Center
# Boston Children's Hospital
#
# http://childrenshospital.org/FNNDSC/
# [email protected]
#

# FOR DEV PURPOSE
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

import argparse
import ptk

parser = argparse.ArgumentParser(description='DICOM Listener script (based on DCMTK and PyDicom)')
parser.add_argument('-t', '--tmpdir', action='store', dest='tmp_directory', required=True, type=str, help='Directory to store temporary files.')
parser.add_argument('-l', '--logdir', action='store', dest='log_directory', required=True, type=str, help='Directory to store log files.')
parser.add_argument('-d', '--datadir', action='store', dest='data_directory', required=True, type=str, help='Directory to store DICOM files.')

opts = parser.parse_args()
ptk.listen(vars(opts))
29 changes: 29 additions & 0 deletions doc/listener/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## pre-requisites
```
dcmtk
pydicom
```

## configuration
In `xinetd` or in `launchd`, the dicom listener scripts requires the following arguments to be provided:
```
-t: temporary directory
-l: log directory
-d: data directory

```
## service
In `/etc/services` add:
```
chris-ultron 10401/tcp # chris ultron dicom listener
chris-ultron 10401/udp # chris ultron dicom listener
```

## launchd
Add `org.babymri.chris-ultron.plist` in `/Library/LaunchDaemons`.

Load: `sudo launchctl load -w org.babymri.chris-ultron.plist`

Unload: `sudo launchctl unload org.babymri.chris-ultron.plist`

Test: `nc localhost 10401` (must hang)
35 changes: 35 additions & 0 deletions doc/listener/listener.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UserName</key>
<string>nico</string>
<key>Label</key>
<string>org.babymri.chris-ultron</string>
<key>ProgramArguments</key>
<array>
<string>/Users/nico/work/gitroot/chris-ultron-backend/chris_backend/plugins/services/pacslistener/pacslistener.py</string>
<string>-t</string>
<string>/tmp</string>
<string>-l</string>
<string>/tmp/log</string>
<string>-d</string>
<string>/tmp/data</string>
</array>
<key>inetdCompatibility</key>
<dict>
<key>Wait</key>
<false/>
</dict>
<key>Sockets</key>
<dict>
<key>Listeners</key>
<dict>
<key>SockServiceName</key>
<string>chris-ultron</string>
<key>SockType</key>
<string>stream</string>
</dict>
</dict>
</dict>
</plist>
8 changes: 8 additions & 0 deletions ptk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Compiled python modules.
*.pyc

# Setuptools distribution folder.
/dist/

# Python egg metadata, regenerated from source files by setuptools.
/*.egg-info
16 changes: 16 additions & 0 deletions ptk/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from .echo import Echo
from .find import Find
from .listen import Listen
from .move import Move

def echo(opt={}):
return Echo(opt).run()

def find(opt={}):
return Find(opt).run(opt)

def listen(opt={}):
return Listen(opt).run()

def move(opt={}):
return Move(opt).run(opt)
31 changes: 31 additions & 0 deletions ptk/echo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import subprocess
import ptk.utils

class Echo():
"""docstring for Echo."""
def __init__(self, arg):
ptk.utils.init(self, arg)
self.executable = 'echoscu'

def command(self):
command = ' --timeout 5' #5s timeout

return self.executable + ' ' + command + ' ' + self.command_suffix

def run(self):
response = subprocess.run(self.command(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
result = self.handle(response)
return result

def handle(self, echo_response):
std = echo_response.stdout.decode('ascii')
response = {
'status': 'success',
'data': '',
'command': echo_response.args
}
if std != '':
response['status'] = 'error'
response['data'] = std

return response
145 changes: 145 additions & 0 deletions ptk/find.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import subprocess, re
import ptk.utils

class Find():
"""docstring for Find."""
def __init__(self, arg):

ptk.utils.init(self, arg)
self.executable = 'findscu'
# to be moved out
self.postfilter_parameters = {
'PatientSex': '',
'PerformedStationAETitle': '',
'StudyDescription': '',
'SeriesDescription': ''
}

def buildCommand(self, opt={}):
parameters = {
'PatientID': '', # PATIENT INFORMATION
'PatientName': '',
'PatientBirthDate': '',
'PatientSex': '',
'StudyDate': '', # STUDY INFORMATION
'StudyDescription': '',
'StudyInstanceUID': '',
'ModalitiesInStudy': '',
'PerformedStationAETitle': '',
'NumberOfSeriesRelatedInstances': '', # SERIES INFORMATION
'InstanceNumber': '',
'SeriesDate': '',
'SeriesDescription': '',
'SeriesInstanceUID': '',
'QueryRetrieveLevel': 'SERIES'
}

# build query
command = ' -xi'
command += ' -S'

return self.commandWrap(command, parameters, opt)

def commandWrap(self, command, parameters, opt={}):
for key, value in parameters.items():
# update value if provided
if key in opt:
value = opt[key]
# update command
if value != '':
command += ' -k "' + key + '=' + value + '"'
else:
command += ' -k ' + key

print(command)

return self.executable + ' ' + command + ' ' + self.command_suffix

def preparePostFilter(self):
print('prepare post filter')
# $post_filter['PatientSex'] = $patientsex;
# $post_filter['PerformedStationAETitle'] = $station;
# $post_filter['StudyDescription'] = $studydescription;
# $post_filter['SeriesDescription'] = $seriesdescription;

def run(self, opt={}):
print('run Find')
print(opt)
#
#
# find data
response = subprocess.run(self.buildCommand(opt), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
# format response
return self.formatResponse(response)

def checkResponse(self, response):
stdSplit = response.split('\n')
infoCount = 0
errorCount = 0
for line in stdSplit:
if line.startswith('I: '):
infoCount += 1
elif line.startswith('E: '):
errorCount += 1

status = 'error'
if errorCount == 0:
status = 'success'

return status

def parseResponse(self, response):
data = []

uid = 0
stdSplit = response.split('\n')

for line in stdSplit:
if line.startswith('I: ---------------------------'):
data.append({})
data[-1]['uid'] = {}
data[-1]['uid']['tag'] = 0
data[-1]['uid']['value'] = uid
data[-1]['uid']['label'] = 'uid'
uid +=1

elif line.startswith('I: '):
lineSplit = line.split()
if len(lineSplit) >= 8 and re.search('\((.*?)\)', lineSplit[1]) != None:
# extract DICOM tag
tag = re.search('\((.*?)\)', lineSplit[1]).group(0)[1:-1].strip().replace('\x00', '')

# extract value
value = re.search('\[(.*?)\]', line)
if value != None:
value = value.group(0)[1:-1].strip().replace('\x00', '')
else:
value = 'no value provided'

# extract label
label = lineSplit[-1].strip()

data[-1][label] = {}
data[-1][label]['tag'] = tag
data[-1][label]['value'] = value
data[-1][label]['label'] = label

return data

def formatResponse(self, response):
std = response.stdout.decode('ascii')
response = {
'status': 'success',
'data': '',
'command': response.args
}

status = self.checkResponse(std)
if status == 'error':
response['status'] = 'error'
response['data'] = std
else:
response['status'] = 'success'
response['data'] = self.parseResponse(std)

return response
Loading

0 comments on commit 531d249

Please sign in to comment.