Skip to content

Commit 28e16b5

Browse files
author
Daniel Borup
committed
Introduce DICOM nodes from readdicom project
1 parent b9619d3 commit 28e16b5

File tree

3 files changed

+763
-0
lines changed

3 files changed

+763
-0
lines changed

fileIO/GPI/DICOMheader_GPI.py

Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
# Author: Ryan Robison
2+
# Date: 2018 Sept 14
3+
4+
import os
5+
import time
6+
import gpi
7+
from gpi import QtWidgets, QtGui, QtCore
8+
import numpy as np
9+
10+
# WIDGETS
11+
class TextBoxes(gpi.GenericWidgetGroup):
12+
"""A set of non-editable text boxes"""
13+
valueChanged = gpi.Signal()
14+
15+
def __init__(self, title, parent=None):
16+
super(TextBoxes, self).__init__(title, parent)
17+
18+
# at least one text box
19+
self.boxes = []
20+
self.boxes.append(QtWidgets.QLabel())
21+
self.vbox = QtWidgets.QVBoxLayout()
22+
self.vbox.setSpacing(0)
23+
for box in self.boxes:
24+
self.vbox.addWidget(box)
25+
self.setLayout(self.vbox)
26+
27+
# setters
28+
def set_strings(self, strlist):
29+
"""str | Set the string (str)."""
30+
ind = 0
31+
if len(strlist) < len(self.boxes):
32+
strlendiff = len(self.boxes) - len(strlist)
33+
strlist.extend(strlendiff*[''])
34+
self.log.warn("TextBoxes: list of strings shorter than number of "\
35+
"boxes. Filling rest with empty strings.")
36+
37+
if len(strlist) > len(self.boxes):
38+
self.log.warn("TextBoxes: list of strings longer than number of "\
39+
"boxes. Only using first portion of strings.")
40+
41+
for box in self.boxes:
42+
string = strlist[ind]
43+
box.setText(string)
44+
ind = ind+1
45+
46+
def set_length(self, length):
47+
# add specified number of text boxes
48+
while length > len(self.boxes):
49+
newbox = QtWidgets.QLabel()
50+
self.boxes.append(newbox)
51+
self.vbox.addWidget(newbox)
52+
while length < len(self.boxes):
53+
oldbox = self.boxes.pop()
54+
oldbox.setParent(None)
55+
56+
if length != len(self.boxes):
57+
log.critical("StringBoxes: length not properly set!")
58+
59+
60+
class StringBoxes(gpi.GenericWidgetGroup):
61+
"""A set of editable string boxes"""
62+
valueChanged = gpi.Signal()
63+
64+
def __init__(self, title, parent=None):
65+
super(StringBoxes, self).__init__(title, parent)
66+
67+
# at least one string box
68+
self.boxes = []
69+
self.boxes.append(QtWidgets.QLineEdit())
70+
self.vbox = QtWidgets.QVBoxLayout()
71+
self.vbox.setSpacing(0)
72+
for box in self.boxes:
73+
self.vbox.addWidget(box)
74+
box.returnPressed.connect(self.valueChanged)
75+
self.setLayout(self.vbox)
76+
77+
# setters
78+
def set_strings(self, strlist):
79+
"""str | Set the string (str)."""
80+
ind = 0
81+
if len(strlist) < len(self.boxes):
82+
strlendiff = len(self.boxes) - len(strlist)
83+
strlist.extend(strlendiff*[''])
84+
log.warn("StringBoxes: list of strings shorter than number of \
85+
boxes. Filling rest with empty strings.")
86+
87+
if len(strlist) > len(self.boxes):
88+
log.warn("StringBoxes: list of strings longer than number of \
89+
boxes. Only using first portion of strings.")
90+
91+
for box in self.boxes:
92+
string = strlist[ind]
93+
box.setText(string)
94+
ind = ind+1
95+
96+
def set_length(self, length):
97+
# add specified number of string boxes
98+
while length > len(self.boxes):
99+
newbox = QtWidgets.QLineEdit()
100+
self.boxes.append(newbox)
101+
newbox.returnPressed.connect(self.valueChanged)
102+
self.vbox.addWidget(newbox)
103+
while length < len(self.boxes):
104+
oldbox = self.boxes.pop()
105+
oldbox.setParent(None)
106+
107+
if length != len(self.boxes):
108+
log.critical("StringBoxes: length not properly set!")
109+
110+
def get_strings(self):
111+
strings = []
112+
for box in self.boxes:
113+
strings.append(box.text())
114+
return strings
115+
116+
117+
class DICOM_HDR_GROUP(gpi.GenericWidgetGroup):
118+
"""A combination of Textboxes and StringBoxes to allow for easy display of
119+
DICOM header info and editing of the values"""
120+
valueChanged = gpi.Signal()
121+
122+
def __init__(self, title, parent=None):
123+
super(DICOM_HDR_GROUP, self).__init__(title, parent)
124+
self._val = {}
125+
self._val['tags'] = ''
126+
self._val['desc'] = ''
127+
self._val['VRs'] = ''
128+
self._val['values'] = ''
129+
130+
self.tb1 = TextBoxes('Tags:')
131+
self.tb1.set_length(1)
132+
self.tb2 = TextBoxes('Descriptions:')
133+
self.tb2.set_length(1)
134+
self.tb3 = TextBoxes('VRs:')
135+
self.tb3.set_length(1)
136+
137+
self.sb = StringBoxes('Values:')
138+
self.sb.set_length(1)
139+
self.sb.valueChanged.connect(self.valueChanged)
140+
141+
vbox = QtWidgets.QHBoxLayout()
142+
vbox.addWidget(self.tb1)
143+
vbox.addWidget(self.tb2)
144+
vbox.addWidget(self.tb3)
145+
vbox.addWidget(self.sb)
146+
vbox.setStretch(0, 0)
147+
vbox.setStretch(1, 0)
148+
vbox.setStretch(2, 0)
149+
vbox.setStretch(3, 0)
150+
vbox.setContentsMargins(0, 0, 0, 0) # we don't need margins here
151+
vbox.setSpacing(0)
152+
self.setLayout(vbox)
153+
154+
# setters
155+
def set_info(self, val):
156+
"""A python-dict containing: tags, desc, and VRs parms. """
157+
if 'tags' in val:
158+
self._val['tags'] = val['tags']
159+
self.setTagsQuietly(val['tags'])
160+
if 'desc' in val:
161+
self._val['desc'] = val['desc']
162+
self.setDescriptionsQuietly(val['desc'])
163+
if 'VRs' in val:
164+
self._val['VRs'] = val['VRs']
165+
self.setVRsQuietly(val['VRs'])
166+
if 'values' in val:
167+
self._val['values'] = val['values']
168+
self.setValuesQuietly(val['values'])
169+
170+
def set_length(self, length):
171+
self.tb1.set_length(length)
172+
self.tb2.set_length(length)
173+
self.tb3.set_length(length)
174+
self.sb.set_length(length)
175+
176+
# getters
177+
def get_val(self):
178+
return self.sb.get_strings()
179+
180+
# support
181+
def setTagsQuietly(self, val):
182+
self.tb1.blockSignals(True)
183+
self.tb1.set_strings(val)
184+
self.tb1.blockSignals(False)
185+
186+
def setDescriptionsQuietly(self, val):
187+
self.tb2.blockSignals(True)
188+
self.tb2.set_strings(val)
189+
self.tb2.blockSignals(False)
190+
191+
def setVRsQuietly(self, val):
192+
self.tb3.blockSignals(True)
193+
self.tb3.set_strings(val)
194+
self.tb3.blockSignals(False)
195+
196+
def setValuesQuietly(self, val):
197+
self.sb.blockSignals(True)
198+
self.sb.set_strings(val)
199+
self.sb.blockSignals(False)
200+
201+
202+
class ExternalNode(gpi.NodeAPI):
203+
"""This node takes in a dictionary of DICOM hdr info from ReadDICOM,
204+
displays the values for the selected image, and optionally allows for
205+
editing the header values. An edited DICOM header can be passed as an
206+
input to WriteDICOM.
207+
208+
INPUT: GPI DICOM dictionary
209+
OUTPUT: Modified DICOM dictionary
210+
211+
WIDGETS:
212+
Display By - choose whether to display all hdr information for one image or
213+
a single tag across all images
214+
Select Image - select image for which the header will be displayed
215+
Select Tag - select which Tag to display for all images
216+
Propogate Change to All Images - choose whether to propogate changes to
217+
DICOM header values to all image headers in the GPI DICOM dictionary.
218+
Dicom Header - header information for one image (or all images, one tag).
219+
The header contains the following information for each entry:
220+
1) Tag - the unique tag for each header entry, hexidecimal in format,
221+
comprised of a DICOM group number and DICOM element number.
222+
2) Description - A short description of each entry
223+
3) VR - The DICOM value representation describing the format and type
224+
of each entry.
225+
4) Value - the current value of the entry. This is the only field that
226+
is editable using DICOMheader.
227+
"""
228+
229+
def execType(self):
230+
# default executable type
231+
# return gpi.GPI_THREAD
232+
return gpi.GPI_PROCESS # this is the safest
233+
# return gpi.GPI_APPLOOP
234+
235+
def initUI(self):
236+
237+
# Widgets
238+
self.addWidget('ExclusivePushButtons', 'Display By', buttons=['Image','Tag'],val=0)
239+
self.addWidget('ComboBox', 'Select Image', items=[])
240+
self.addWidget('ComboBox', 'Select Tag', items=[])
241+
self.addWidget('PushButton', 'Reset', toggle=False)
242+
self.addWidget('PushButton', 'Propogate Change to All Images', toggle=False)
243+
self.addWidget('DICOM_HDR_GROUP', 'Dicom Header')
244+
245+
# IO Ports
246+
self.addInPort(title='Dicom Dict In', type='DICT')
247+
self.addOutPort(title='Dicom Dict Out', type='DICT')
248+
249+
def validate(self):
250+
import gpi_core.fileIO.dicomlib as dcm
251+
import imp
252+
imp.reload(dcm)
253+
254+
if (('Display By' in self.widgetEvents()) or
255+
('Dicom Dict In' in self.portEvents())):
256+
hdr = self.getData('Dicom Dict In')
257+
displayBy = self.getVal('Display By')
258+
if displayBy == 0:
259+
keys = list(hdr.keys())
260+
numvals = len(list(hdr[keys[0]].keys()))
261+
self.setAttr('Select Image', items=keys, visible=True)
262+
self.setAttr('Select Tag', visible=False)
263+
# self.setAttr('Images:', visible=False)
264+
# self.setAttr('Tags:', visible=True)
265+
else:
266+
keys = list(hdr[list(hdr.keys())[0]].keys())
267+
numvals = len(hdr.keys())
268+
self.setAttr('Select Image', visible=False)
269+
self.setAttr('Select Tag', items=keys, visible=True)
270+
# self.setAttr('Images:', visible=True)
271+
# self.setAttr('Tags:', visible=False)
272+
self.setAttr('Dicom Header', length = numvals)
273+
274+
275+
def compute(self):
276+
277+
import numpy as np
278+
import re
279+
import gpi_core.fileIO.dicomlib as dcm
280+
281+
hdr = self.getData('Dicom Dict In')
282+
displayBy = self.getVal('Display By')
283+
reset = self.getVal('Reset')
284+
propogate = self.getVal('Propogate Change to All Images')
285+
286+
if ((reset) or ('Dicom Dict In' in self.portEvents())):
287+
dicomDict = hdr
288+
else:
289+
dicomDict = self.getData('Dicom Dict Out')
290+
if (dicomDict == None):
291+
dicomDict = hdr
292+
293+
if ((reset) or ('Dicom Dict In' in self.portEvents()) or
294+
('Display By' in self.widgetEvents()) or
295+
('Select Image' in self.widgetEvents()) or
296+
('Select Tag' in self.widgetEvents())):
297+
if displayBy == 0:
298+
tags = []
299+
descs = []
300+
VRs = []
301+
values = []
302+
img = self.getVal('Select Image')
303+
info = dicomDict[img]
304+
for key in info.keys():
305+
desc = info[key][0]
306+
VR = info[key][1]
307+
val = info[key][2]
308+
tags.append(str(key))
309+
descs.append(desc)
310+
VRs.append(VR)
311+
if VR != 'SQ':
312+
values.append(val)
313+
else:
314+
values.append(str(val))
315+
# if VR == 'SQ':
316+
# for item in val:
317+
# for key1 in item.keys():
318+
# desc1 = item[key1][0]
319+
# VR1 = item[key1][1]
320+
# val1 = item[key1][2]
321+
# tags.append(' '+str(key1))
322+
# descs.append(desc1)
323+
# VRs.append(VR1)
324+
# values.append(val1)
325+
val = {'tags': tags, 'desc': descs, 'VRs': VRs, 'values': values}
326+
self.setAttr('Dicom Header', info=val)
327+
328+
else:
329+
imgs = []
330+
descs = []
331+
VRs = []
332+
values = []
333+
tag = self.getVal('Select Tag')
334+
for key in dicomDict.keys():
335+
desc = dicomDict[key][tag][0]
336+
VR = dicomDict[key][tag][1]
337+
val = dicomDict[key][tag][2]
338+
imgs.append(str(key))
339+
descs.append(desc)
340+
VRs.append(VR)
341+
values.append(val)
342+
val = {'tags': imgs, 'desc': descs, 'VRs': VRs, 'values': values}
343+
self.setAttr('Dicom Header', info=val)
344+
345+
if ('Dicom Header' in self.widgetEvents()):
346+
newInfo = self.getVal('Dicom Header')
347+
index = 0
348+
if displayBy == 0:
349+
img = self.getVal('Select Image')
350+
info = hdr[img]
351+
for key in info.keys():
352+
val = info[key][2]
353+
newval = newInfo[index]
354+
info[key][2] = newval
355+
index = index + 1
356+
dicomDict[img] = info
357+
else:
358+
tag = self.getVal('Select Tag')
359+
for key in hdr.keys():
360+
val = hdr[key][tag][2]
361+
newval = newInfo[index]
362+
dicomDict[key][tag][2] = newval
363+
index = index + 1
364+
365+
if ('Propogate Change to All Images' in self.widgetEvents()):
366+
if displayBy == 0:
367+
curimg = self.getVal('Select Image')
368+
for tag in hdr[curimg].keys():
369+
oldval = hdr[curimg][tag][2]
370+
newval = dicomDict[curimg][tag][2]
371+
if (oldval != newval):
372+
for img in hdr.keys():
373+
dicomDict[img][tag][2] = newval
374+
else:
375+
tag = self.getVal('Select Tag')
376+
for img in hdr.keys(): # find the first changed value
377+
oldval = hdr[img][tag][2]
378+
curval = dicomDict[img][tag][2]
379+
if (oldval != curval):
380+
# update all entries in the header table
381+
values = len(hdr.keys())*[curval]
382+
val = {'values':values}
383+
self.setAttr('Dicom Header', info=val)
384+
break
385+
# now update the dicom header dictionary
386+
newval = self.getVal('Dicom Header')[0]
387+
for img in hdr.keys():
388+
dicomDict[img][tag][2] = newval
389+
390+
self.setData('Dicom Dict Out', dicomDict)
391+
392+
return(0)

0 commit comments

Comments
 (0)