|
| 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