Commit afdb3ce1763e0e37fb385d31236baefa4de23fcd

Authored by Thiago Franco de Moraes
Committed by GitHub
1 parent 9a7e323e
Exists in master

Open Multiframe dicom (#133)

* Loading multiframe dicom using the import gui

* Showing multiframe thumbnails

* Showing the thumbnail in the slice viewer from preview

* Set slide to the number of slices or numberofframes
invesalius/control.py
... ... @@ -404,8 +404,9 @@ class Controller():
404 404 Publisher.sendMessage('Begin busy cursor')
405 405 else:
406 406 #Is None if user canceled the load
407   - self.progress_dialog.Close()
408   - self.progress_dialog = None
  407 + if self.progress_dialog is not None:
  408 + self.progress_dialog.Close()
  409 + self.progress_dialog = None
409 410  
410 411 def OnLoadImportPanel(self, evt):
411 412 patient_series = evt.data
... ... @@ -795,47 +796,53 @@ class Controller():
795 796 xyspacing = dicom.image.spacing
796 797 orientation = dicom.image.orientation_label
797 798  
  799 + wl = float(dicom.image.level)
  800 + ww = float(dicom.image.window)
  801 +
798 802 if sop_class_uid == '1.2.840.10008.5.1.4.1.1.7': #Secondary Capture Image Storage
799 803 use_dcmspacing = 1
800 804 else:
801 805 use_dcmspacing = 0
802 806  
803 807 imagedata = None
804   -
805   - sx, sy = size
806   - n_slices = len(filelist)
807   - resolution_percentage = utils.calculate_resizing_tofitmemory(int(sx), int(sy), n_slices, bits/8)
808   -
809   - if resolution_percentage < 1.0 and gui:
810   - re_dialog = dialog.ResizeImageDialog()
811   - re_dialog.SetValue(int(resolution_percentage*100))
812   - re_dialog_value = re_dialog.ShowModal()
813   - re_dialog.Close()
814   -
815   - if re_dialog_value == wx.ID_OK:
816   - percentage = re_dialog.GetValue()
817   - resolution_percentage = percentage / 100.0
818   - else:
819   - return
820 808  
821   - xyspacing = xyspacing[0] / resolution_percentage, xyspacing[1] / resolution_percentage
822   -
823   -
824   - wl = float(dicom.image.level)
825   - ww = float(dicom.image.window)
826   - self.matrix, scalar_range, self.filename = image_utils.dcm2memmap(filelist, size,
827   - orientation, resolution_percentage)
  809 + if dicom.image.number_of_frames == 1:
  810 + sx, sy = size
  811 + n_slices = len(filelist)
  812 + resolution_percentage = utils.calculate_resizing_tofitmemory(int(sx), int(sy), n_slices, bits/8)
  813 +
  814 + if resolution_percentage < 1.0 and gui:
  815 + re_dialog = dialog.ResizeImageDialog()
  816 + re_dialog.SetValue(int(resolution_percentage*100))
  817 + re_dialog_value = re_dialog.ShowModal()
  818 + re_dialog.Close()
  819 +
  820 + if re_dialog_value == wx.ID_OK:
  821 + percentage = re_dialog.GetValue()
  822 + resolution_percentage = percentage / 100.0
  823 + else:
  824 + return
  825 +
  826 + xyspacing = xyspacing[0] / resolution_percentage, xyspacing[1] / resolution_percentage
  827 +
  828 + self.matrix, scalar_range, self.filename = image_utils.dcm2memmap(filelist, size,
  829 + orientation, resolution_percentage)
  830 +
  831 + print xyspacing, zspacing
  832 + if orientation == 'AXIAL':
  833 + spacing = xyspacing[0], xyspacing[1], zspacing
  834 + elif orientation == 'CORONAL':
  835 + spacing = xyspacing[0], zspacing, xyspacing[1]
  836 + elif orientation == 'SAGITTAL':
  837 + spacing = zspacing, xyspacing[1], xyspacing[0]
  838 + else:
  839 + self.matrix, spacing, scalar_range, self.filename = image_utils.dcmmf2memmap(filelist[0], orientation)
828 840  
829 841 self.Slice = sl.Slice()
830 842 self.Slice.matrix = self.matrix
831 843 self.Slice.matrix_filename = self.filename
832 844  
833   - if orientation == 'AXIAL':
834   - self.Slice.spacing = xyspacing[0], xyspacing[1], zspacing
835   - elif orientation == 'CORONAL':
836   - self.Slice.spacing = xyspacing[0], zspacing, xyspacing[1]
837   - elif orientation == 'SAGITTAL':
838   - self.Slice.spacing = zspacing, xyspacing[1], xyspacing[0]
  845 + self.Slice.spacing = spacing
839 846  
840 847 # 1(a): Fix gantry tilt, if any
841 848 tilt_value = dicom.acquisition.tilt
... ...
invesalius/data/imagedata_utils.py
... ... @@ -19,6 +19,7 @@
19 19  
20 20 import math
21 21 import os
  22 +import sys
22 23 import tempfile
23 24  
24 25 import gdcm
... ... @@ -35,6 +36,16 @@ from invesalius.data import vtk_utils as vtk_utils
35 36 import invesalius.reader.bitmap_reader as bitmap_reader
36 37 import invesalius.utils as utils
37 38 import invesalius.data.converters as converters
  39 +
  40 +if sys.platform == 'win32':
  41 + try:
  42 + import win32api
  43 + _has_win32api = True
  44 + except ImportError:
  45 + _has_win32api = False
  46 +else:
  47 + _has_win32api = False
  48 +
38 49 # TODO: Test cases which are originally in sagittal/coronal orientation
39 50 # and have gantry
40 51  
... ... @@ -246,11 +257,79 @@ def ExtractVOI(imagedata,xi,xf,yi,yf,zi,zf):
246 257 """
247 258 voi = vtk.vtkExtractVOI()
248 259 voi.SetVOI(xi,xf,yi,yf,zi,zf)
249   - voi.SetInput(imagedata)
  260 + voi.SetInputData(imagedata)
250 261 voi.SetSampleRate(1, 1, 1)
251 262 voi.Update()
252 263 return voi.GetOutput()
253 264  
  265 +
  266 +def create_dicom_thumbnails(filename, window=None, level=None):
  267 + rvtk = vtkgdcm.vtkGDCMImageReader()
  268 + rvtk.SetFileName(filename)
  269 + rvtk.Update()
  270 +
  271 + img = rvtk.GetOutput()
  272 + if window is None or level is None:
  273 + _min, _max = img.GetScalarRange()
  274 + window = _max - _min
  275 + level = _min + window / 2
  276 +
  277 + dx, dy, dz = img.GetDimensions()
  278 +
  279 + if dz > 1:
  280 + thumbnail_paths = []
  281 + for i in xrange(dz):
  282 + img_slice = ExtractVOI(img, 0, dx-1, 0, dy-1, i, i+1)
  283 +
  284 + colorer = vtk.vtkImageMapToWindowLevelColors()
  285 + colorer.SetInputData(img_slice)
  286 + colorer.SetWindow(window)
  287 + colorer.SetLevel(level)
  288 + colorer.SetOutputFormatToRGB()
  289 + colorer.Update()
  290 +
  291 + resample = vtk.vtkImageResample()
  292 + resample.SetInputData(colorer.GetOutput())
  293 + resample.SetAxisMagnificationFactor ( 0, 0.25 )
  294 + resample.SetAxisMagnificationFactor ( 1, 0.25 )
  295 + resample.SetAxisMagnificationFactor ( 2, 1 )
  296 + resample.Update()
  297 +
  298 + thumbnail_path = tempfile.mktemp()
  299 +
  300 + write_png = vtk.vtkPNGWriter()
  301 + write_png.SetInputData(resample.GetOutput())
  302 + write_png.SetFileName(thumbnail_path)
  303 + write_png.Write()
  304 +
  305 + thumbnail_paths.append(thumbnail_path)
  306 +
  307 + return thumbnail_paths
  308 + else:
  309 + colorer = vtk.vtkImageMapToWindowLevelColors()
  310 + colorer.SetInputData(img)
  311 + colorer.SetWindow(window)
  312 + colorer.SetLevel(level)
  313 + colorer.SetOutputFormatToRGB()
  314 + colorer.Update()
  315 +
  316 + resample = vtk.vtkImageResample()
  317 + resample.SetInputData(colorer.GetOutput())
  318 + resample.SetAxisMagnificationFactor ( 0, 0.25 )
  319 + resample.SetAxisMagnificationFactor ( 1, 0.25 )
  320 + resample.SetAxisMagnificationFactor ( 2, 1 )
  321 + resample.Update()
  322 +
  323 + thumbnail_path = tempfile.mktemp()
  324 +
  325 + write_png = vtk.vtkPNGWriter()
  326 + write_png.SetInputData(resample.GetOutput())
  327 + write_png.SetFileName(thumbnail_path)
  328 + write_png.Write()
  329 +
  330 + return thumbnail_path
  331 +
  332 +
254 333 def CreateImageData(filelist, zspacing, xyspacing,size,
255 334 bits, use_dcmspacing):
256 335 message = _("Generating multiplanar visualization...")
... ... @@ -587,6 +666,29 @@ def dcm2memmap(files, slice_size, orientation, resolution_percentage):
587 666 return matrix, scalar_range, temp_file
588 667  
589 668  
  669 +def dcmmf2memmap(dcm_file, orientation):
  670 + r = vtkgdcm.vtkGDCMImageReader()
  671 + r.SetFileName(dcm_file)
  672 + r.Update()
  673 +
  674 + temp_file = tempfile.mktemp()
  675 +
  676 + o = r.GetOutput()
  677 + x, y, z = o.GetDimensions()
  678 + spacing = o.GetSpacing()
  679 +
  680 + matrix = numpy.memmap(temp_file, mode='w+', dtype='int16', shape=(z, y, x))
  681 +
  682 + d = numpy_support.vtk_to_numpy(o.GetPointData().GetScalars())
  683 + d.shape = z, y, x
  684 + matrix[:] = d
  685 + matrix.flush()
  686 +
  687 + scalar_range = matrix.min(), matrix.max()
  688 +
  689 + return matrix, spacing, scalar_range, temp_file
  690 +
  691 +
590 692 def img2memmap(group):
591 693 """
592 694 From a nibabel image data creates a memmap file in the temp folder and
... ...
invesalius/data/vtk_utils.py
... ... @@ -52,6 +52,8 @@ def ShowProgress(number_of_filters = 1,
52 52  
53 53 # when the pipeline is larger than 1, we have to consider this object
54 54 # percentage
  55 + if number_of_filters < 1:
  56 + number_of_filters = 1
55 57 ratio = (100.0 / number_of_filters)
56 58  
57 59 def UpdateProgress(obj, label=""):
... ...
invesalius/gui/dicom_preview_panel.py
... ... @@ -115,7 +115,7 @@ class DicomInfo(object):
115 115 """
116 116 Keep the informations and the image used by preview.
117 117 """
118   - def __init__(self, id, dicom, title, subtitle):
  118 + def __init__(self, id, dicom, title, subtitle, n=0):
119 119 self.id = id
120 120 self.dicom = dicom
121 121 self.title = title
... ... @@ -123,12 +123,15 @@ class DicomInfo(object):
123 123 self._preview = None
124 124 self.selected = False
125 125 self.filename = ""
  126 + self._slice = n
126 127  
127 128 @property
128 129 def preview(self):
129   -
130 130 if not self._preview:
131   - bmp = wx.Bitmap(self.dicom.image.thumbnail_path, wx.BITMAP_TYPE_PNG)
  131 + if isinstance(self.dicom.image.thumbnail_path, list):
  132 + bmp = wx.Bitmap(self.dicom.image.thumbnail_path[self._slice], wx.BITMAP_TYPE_PNG)
  133 + else:
  134 + bmp = wx.Bitmap(self.dicom.image.thumbnail_path, wx.BITMAP_TYPE_PNG)
132 135 self._preview = bmp.ConvertToImage()
133 136 return self._preview
134 137  
... ... @@ -539,11 +542,22 @@ class DicomPreviewSlice(wx.Panel):
539 542 dicom_files = group.GetHandSortedList()
540 543 n = 0
541 544 for dicom in dicom_files:
542   - info = DicomInfo(n, dicom,
543   - _("Image %d") % (dicom.image.number),
544   - "%.2f" % (dicom.image.position[2]))
545   - self.files.append(info)
546   - n+=1
  545 + if isinstance(dicom.image.thumbnail_path, list):
  546 + _slice = 0
  547 + for thumbnail in dicom.image.thumbnail_path:
  548 + print thumbnail
  549 + info = DicomInfo(n, dicom,
  550 + _("Image %d") % (n),
  551 + "%.2f" % (dicom.image.position[2]), _slice)
  552 + self.files.append(info)
  553 + n+=1
  554 + _slice += 1
  555 + else:
  556 + info = DicomInfo(n, dicom,
  557 + _("Image %d") % (dicom.image.number),
  558 + "%.2f" % (dicom.image.position[2]))
  559 + self.files.append(info)
  560 + n+=1
547 561  
548 562 scroll_range = len(self.files)/NCOLS
549 563 if scroll_range * NCOLS < len(self.files):
... ... @@ -560,12 +574,23 @@ class DicomPreviewSlice(wx.Panel):
560 574 dicom_files = group.GetHandSortedList()
561 575 n = 0
562 576 for dicom in dicom_files:
563   - info = DicomInfo(n, dicom,
564   - _("Image %d") % (dicom.image.number),
565   - "%.2f" % (dicom.image.position[2]),
566   - )
567   - self.files.append(info)
568   - n+=1
  577 + if isinstance(dicom.image.thumbnail_path, list):
  578 + _slice = 0
  579 + for thumbnail in dicom.image.thumbnail_path:
  580 + print thumbnail
  581 + info = DicomInfo(n, dicom,
  582 + _("Image %d") % int(n),
  583 + "%.2f" % (dicom.image.position[2]), _slice)
  584 + self.files.append(info)
  585 + n+=1
  586 + _slice += 1
  587 + else:
  588 + info = DicomInfo(n, dicom,
  589 + _("Image %d") % int(dicom.image.number),
  590 + "%.2f" % (dicom.image.position[2]),
  591 + )
  592 + self.files.append(info)
  593 + n+=1
569 594  
570 595 scroll_range = len(self.files)/NCOLS
571 596 if scroll_range * NCOLS < len(self.files):
... ... @@ -803,14 +828,20 @@ class SingleImagePreview(wx.Panel):
803 828 def SetDicomGroup(self, group):
804 829 self.dicom_list = group.GetHandSortedList()
805 830 self.current_index = 0
806   - self.nimages = len(self.dicom_list)
  831 + if len(self.dicom_list) > 1:
  832 + self.nimages = len(self.dicom_list)
  833 + else:
  834 + self.nimages = self.dicom_list[0].image.number_of_frames
807 835 # GUI
808 836 self.slider.SetMax(self.nimages-1)
809 837 self.slider.SetValue(0)
810 838 self.ShowSlice()
811 839  
812 840 def ShowSlice(self, index = 0):
813   - dicom = self.dicom_list[index]
  841 + try:
  842 + dicom = self.dicom_list[index]
  843 + except IndexError:
  844 + dicom = self.dicom_list[0]
814 845  
815 846 # UPDATE GUI
816 847 ## Text related to size
... ... @@ -845,28 +876,41 @@ class SingleImagePreview(wx.Panel):
845 876 dicom.acquisition.time)
846 877 self.text_acquisition.SetValue(value)
847 878  
848   - rdicom = vtkgdcm.vtkGDCMImageReader()
849   - if _has_win32api:
850   - rdicom.SetFileName(win32api.GetShortPathName(dicom.image.file).encode(const.FS_ENCODE))
  879 + if isinstance(dicom.image.thumbnail_path, list):
  880 + reader = vtk.vtkPNGReader()
  881 + if _has_win32api:
  882 + reader.SetFileName(win32api.GetShortPathName(dicom.image.thumbnail_path[index]).encode(const.FS_ENCODE))
  883 + else:
  884 + reader.SetFileName(dicom.image.thumbnail_path[index])
  885 + reader.Update()
  886 +
  887 + image = reader.GetOutput()
  888 +
851 889 else:
852   - rdicom.SetFileName(dicom.image.file)
853   - rdicom.Update()
854   -
855   - # ADJUST CONTRAST
856   - window_level = dicom.image.level
857   - window_width = dicom.image.window
858   - colorer = vtk.vtkImageMapToWindowLevelColors()
859   - colorer.SetInputConnection(rdicom.GetOutputPort())
860   - colorer.SetWindow(float(window_width))
861   - colorer.SetLevel(float(window_level))
862   - colorer.Update()
  890 + rdicom = vtkgdcm.vtkGDCMImageReader()
  891 + if _has_win32api:
  892 + rdicom.SetFileName(win32api.GetShortPathName(dicom.image.file).encode(const.FS_ENCODE))
  893 + else:
  894 + rdicom.SetFileName(dicom.image.file)
  895 + rdicom.Update()
  896 +
  897 + # ADJUST CONTRAST
  898 + window_level = dicom.image.level
  899 + window_width = dicom.image.window
  900 + colorer = vtk.vtkImageMapToWindowLevelColors()
  901 + colorer.SetInputConnection(rdicom.GetOutputPort())
  902 + colorer.SetWindow(float(window_width))
  903 + colorer.SetLevel(float(window_level))
  904 + colorer.Update()
  905 +
  906 + image = colorer.GetOutput()
863 907  
864 908 if self.actor is None:
865 909 self.actor = vtk.vtkImageActor()
866 910 self.renderer.AddActor(self.actor)
867 911  
868 912 # PLOT IMAGE INTO VIEWER
869   - self.actor.SetInputData(colorer.GetOutput())
  913 + self.actor.SetInputData(image)
870 914 self.renderer.ResetCamera()
871 915 self.interactor.Render()
872 916  
... ...
invesalius/reader/dicom.py
... ... @@ -1154,8 +1154,20 @@ class Parser():
1154 1154 if (data):
1155 1155 return int(data)
1156 1156 return ""
1157   -
1158   -
  1157 +
  1158 + def GetNumberOfFrames(self):
  1159 + """
  1160 + Number of frames in a multi-frame image.
  1161 +
  1162 + DICOM standard tag (0x0028, 0x0008) was used.
  1163 + """
  1164 + try:
  1165 + data = self.data_image[str(0x028)][str(0x0008)]
  1166 + except KeyError:
  1167 + return 1
  1168 + return int(data)
  1169 +
  1170 +
1159 1171 def GetPatientBirthDate(self):
1160 1172 """
1161 1173 Return string containing the patient's birth date using the
... ... @@ -1498,11 +1510,11 @@ class Parser():
1498 1510 try:
1499 1511 data = self.data_image[str(0x0020)][str(0x0013)]
1500 1512 except(KeyError):
1501   - return ""
  1513 + return 0
1502 1514  
1503 1515 if (data):
1504 1516 return int(data)
1505   - return ""
  1517 + return 0
1506 1518  
1507 1519 def GetStudyDescription(self):
1508 1520 """
... ... @@ -1954,6 +1966,8 @@ class Image(object):
1954 1966 self.bits_allocad = parser._GetBitsAllocated()
1955 1967 self.thumbnail_path = parser.thumbnail_path
1956 1968  
  1969 + self.number_of_frames = parser.GetNumberOfFrames()
  1970 +
1957 1971 if (parser.GetImageThickness()):
1958 1972 try:
1959 1973 spacing.append(parser.GetImageThickness())
... ...
invesalius/reader/dicom_grouper.py
... ... @@ -94,22 +94,22 @@ class DicomGroup:
94 94 if not self.dicom:
95 95 self.dicom = dicom
96 96  
97   - pos = tuple(dicom.image.position)
98   -
  97 + pos = tuple(dicom.image.position)
  98 +
99 99 #Case to test: \other\higroma
100   - #condition created, if any dicom with the same
  100 + #condition created, if any dicom with the same
101 101 #position, but 3D, leaving the same series.
102 102 if not "DERIVED" in dicom.image.type:
103 103 #if any dicom with the same position
104 104 if pos not in self.slices_dict.keys():
105 105 self.slices_dict[pos] = dicom
106   - self.nslices += 1
  106 + self.nslices += dicom.image.number_of_frames
107 107 return True
108 108 else:
109 109 return False
110 110 else:
111 111 self.slices_dict[dicom.image.number] = dicom
112   - self.nslices += 1
  112 + self.nslices += dicom.image.number_of_frames
113 113 return True
114 114  
115 115 def GetList(self):
... ...
invesalius/reader/dicom_reader.py
... ... @@ -36,6 +36,8 @@ import invesalius.session as session
36 36 import glob
37 37 import invesalius.utils as utils
38 38  
  39 +from invesalius.data import imagedata_utils
  40 +
39 41 import plistlib
40 42  
41 43 if sys.platform == 'win32':
... ... @@ -187,45 +189,22 @@ class LoadDicom:
187 189  
188 190  
189 191 # -------------- To Create DICOM Thumbnail -----------
190   - rvtk = vtkgdcm.vtkGDCMImageReader()
191 192  
192   - if _has_win32api:
193   - print 'dicom', win32api.GetShortPathName(self.filepath)
194   - rvtk.SetFileName(win32api.GetShortPathName(self.filepath).encode(const.FS_ENCODE))
195   - else:
196   - rvtk.SetFileName(self.filepath)
197   - rvtk.Update()
198   -
  193 +
199 194 try:
200 195 data = data_dict[str(0x028)][str(0x1050)]
201 196 level = [float(value) for value in data.split('\\')][0]
202 197 data = data_dict[str(0x028)][str(0x1051)]
203 198 window = [float(value) for value in data.split('\\')][0]
204 199 except(KeyError, ValueError):
205   - level = 300.0
206   - window = 2000.0
207   -
208   - colorer = vtk.vtkImageMapToWindowLevelColors()
209   - colorer.SetInputConnection(rvtk.GetOutputPort())
210   - colorer.SetWindow(float(window))
211   - colorer.SetLevel(float(level))
212   - colorer.SetOutputFormatToRGB()
213   - colorer.Update()
214   -
215   - resample = vtk.vtkImageResample()
216   - resample.SetInputConnection(colorer.GetOutputPort())
217   - resample.SetAxisMagnificationFactor ( 0, 0.25 )
218   - resample.SetAxisMagnificationFactor ( 1, 0.25 )
219   - resample.SetAxisMagnificationFactor ( 2, 1 )
220   - resample.Update()
221   -
222   - thumbnail_path = tempfile.mktemp()
223   -
224   - write_png = vtk.vtkPNGWriter()
225   - write_png.SetInputConnection(resample.GetOutputPort())
226   - write_png.SetFileName(thumbnail_path)
227   - write_png.Write()
228   -
  200 + level = None
  201 + window = None
  202 +
  203 + if _has_win32api:
  204 + thumbnail_path = imagedata_utils.create_dicom_thumbnails(win32api.GetShortPathName(self.filepath).encode(const.FS_ENCODE), window, level)
  205 + else:
  206 + thumbnail_path = imagedata_utils.create_dicom_thumbnails(self.filepath, window, level)
  207 +
229 208 #------ Verify the orientation --------------------------------
230 209  
231 210 img = reader.GetImage()
... ... @@ -362,12 +341,14 @@ class ProgressDicomReader:
362 341  
363 342 y = yGetDicomGroups(path, recursive)
364 343 for value_progress in y:
  344 + print ">>>>", value_progress
365 345 if not self.running:
366 346 break
367 347 if isinstance(value_progress, tuple):
368 348 self.UpdateLoadFileProgress(value_progress)
369 349 else:
370 350 self.EndLoadFile(value_progress)
  351 + self.UpdateLoadFileProgress(None)
371 352  
372 353 #Is necessary in the case user cancel
373 354 #the load, ensure that dicomdialog is closed
... ...