Commit c020e9044bc39e052bad3910a09fd2ad783ec787

Authored by tatiana
1 parent f3bf4279

ENH: Import modularization and implementation, working both command line and int…

…erface (still working on slices preview)
invesalius/control.py
... ... @@ -9,6 +9,7 @@ import project as prj
9 9 import data.imagedata_utils as utils
10 10 import data.surface as surface
11 11 import data.volume as volume
  12 +import reader.dicom_grouper
12 13 import gui.dialogs as dialog
13 14 import reader.dicom_reader as dcm
14 15 import reader.analyze_reader as analyze
... ... @@ -23,23 +24,125 @@ class Controller():
23 24 self.__bind_events()
24 25  
25 26 def __bind_events(self):
26   - ps.Publisher().subscribe(self.ImportDirectory, 'Import directory')
  27 + ps.Publisher().subscribe(self.OnImportMedicalImages, 'Import directory')
27 28 ps.Publisher().subscribe(self.StartImportPanel, "Load data to import panel")
28 29 ps.Publisher().subscribe(self.LoadRaycastingPreset,
29 30 'Load raycasting preset')
30 31 ps.Publisher().subscribe(self.SaveRaycastingPreset,
31 32 'Save raycasting preset')
  33 + ps.Publisher().subscribe(self.OnOpenDicomGroup,
  34 + 'Open DICOM group')
32 35  
33 36 def StartImportPanel(self, pubsub_evt):
34 37 # path to directory
35 38 path = pubsub_evt.data
36 39  
37 40 # retrieve DICOM files splited into groups
38   - dicom_series = dcm.GetDicomGroups(path)
39   - ps.Publisher().sendMessage("Load import panel", dicom_series)
  41 + patient_series = dcm.GetDicomGroups(path)
  42 + ps.Publisher().sendMessage("Load import panel", patient_series)
  43 + first_patient = patient_series[0]
  44 + #ps.Publisher().sendMessage("Load dicom preview", first_patient)
  45 +
  46 + def OnImportMedicalImages(self, pubsub_evt):
  47 + directory = pubsub_evt.data
  48 + self.ImportMedicalImages(directory)
  49 +
  50 + def ImportMedicalImages(self, directory):
  51 + # OPTION 1: DICOM?
  52 + patients_groups = dcm.GetDicomGroups(directory)
  53 + if len(patients_groups):
  54 + group = dcm.SelectLargerDicomGroup(patients_groups)
  55 + imagedata, dicom = self.OpenDicomGroup(group, gui=False)
  56 + self.CreateDicomProject(imagedata, dicom)
  57 + # OPTION 2: ANALYZE?
  58 + else:
  59 + imagedata = analyze.ReadDirectory(directory)
  60 + if imagedata:
  61 + self.CreateAnalyzeProject(imagedata)
  62 + # OPTION 3: Nothing...
  63 + else:
  64 + print "No medical images found on given directory"
  65 + return
  66 + self.LoadProject()
  67 +
  68 + def LoadProject(self):
  69 + proj = prj.Project()
  70 + ps.Publisher().sendMessage('Set project name', proj.name)
  71 + ps.Publisher().sendMessage('Load slice to viewer', (proj.imagedata))
  72 + self.LoadImagedataInfo() # TODO: where do we insert this <<<?
  73 + ps.Publisher().sendMessage('Bright and contrast adjustment image',\
  74 + (proj.window, proj.level))
  75 + ps.Publisher().sendMessage('Update window level value',\
  76 + (proj.window, proj.level))
  77 + ps.Publisher().sendMessage('Show content panel')
  78 + ps.Publisher().sendMessage('Update AUI')
  79 +
  80 + def CreateAnalyzeProject(self, imagedata):
  81 + proj = prj.Project()
  82 + proj.name = "Untitled"
  83 + proj.SetAcquisitionModality("MRI")
  84 + proj.imagedata = imagedata
  85 + #TODO: Verify if all Analyse are in AXIAL orientation
  86 + proj.original_orientation = const.AXIAL
  87 + proj.threshold_range = imagedata.GetScalarRange()
  88 + proj.window = proj.threshold_range[1] - proj.threshold_range[0]
  89 + proj.level (0.5 * (proj.threshold_range[1] + proj.threshold_range[0]))
  90 +
  91 + const.THRESHOLD_OUTVALUE = proj.threshold_range[0]
  92 + const.THRESHOLD_INVALUE = proj.threshold_range[1]
  93 + const.WINDOW_LEVEL['Default'] = (proj.window, proj.level)
  94 + const.WINDOW_LEVEL['Manual'] = (proj.window, proj.level)
40 95  
41   - #ps.Publisher().sendMessage("Load dicom preview", series_preview)
42 96  
  97 + def CreateDicomProject(self, imagedata, dicom):
  98 + name_to_const = {"AXIAL":const.AXIAL,
  99 + "CORONAL":const.CORONAL,
  100 + "SAGITTAL":const.SAGITAL}
  101 +
  102 + proj = prj.Project()
  103 + proj.name = dicom.patient.name
  104 + proj.SetAcquisitionModality(dicom.acquisition.modality)
  105 + proj.imagedata = imagedata
  106 + proj.original_orientation =\
  107 + name_to_const[dicom.image.orientation_label]
  108 + proj.window = float(dicom.image.window)
  109 + proj.level = float(dicom.image.level)
  110 + proj.threshold_range = imagedata.GetScalarRange()
  111 +
  112 + const.THRESHOLD_OUTVALUE = proj.threshold_range[0]
  113 + const.THRESHOLD_INVALUE = proj.threshold_range[1]
  114 + const.WINDOW_LEVEL['Default'] = (proj.window, proj.level)
  115 + const.WINDOW_LEVEL['Manual'] = (proj.window, proj.level)
  116 +
  117 + def OnOpenDicomGroup(self, pubsub_evt):
  118 + group = pubsub_evt.data
  119 + imagedata, dicom = self.OpenDicomGroup(group, gui=False)
  120 + self.CreateDicomProject(imagedata, dicom)
  121 + self.LoadProject()
  122 +
  123 + def OpenDicomGroup(self, dicom_group, gui=True):
  124 +
  125 + # Retrieve general DICOM headers
  126 + dicom = dicom_group.GetDicomSample()
  127 +
  128 + # Create imagedata
  129 + filelist = dicom_group.GetFilenameList()
  130 + zspacing = dicom_group.zspacing
  131 + imagedata = utils.CreateImageData(filelist, zspacing)
  132 +
  133 + # 1(a): Fix gantry tilt, if any
  134 + tilt_value = dicom.acquisition.tilt
  135 + if (tilt_value) and (gui):
  136 + # Tell user gantry tilt and fix, according to answer
  137 + message = "Fix gantry tilt applying the degrees bellow"
  138 + value = -1*tilt_value
  139 + tilt_value = dialog.ShowNumberDialog(message, value)
  140 + imagedata = utils.FixGantryTilt(imagedata, tilt_value)
  141 + elif (tilt_value) and not (gui):
  142 + tilt_value = -1*tilt_value
  143 + imagedata = utils.FixGantryTilt(imagedata, tilt_value)
  144 +
  145 + return imagedata, dicom
43 146  
44 147 def ImportDirectory(self, pubsub_evt=None, dir_=None):
45 148 """
... ... @@ -84,19 +187,20 @@ class Controller():
84 187 else:
85 188 "No DICOM files were found. Trying to read with ITK..."
86 189 imagedata = analyze.ReadDirectory(dir_)
87   - acquisition_modality = "MRI"
  190 + if imagedata:
  191 + acquisition_modality = "MRI"
88 192  
89   - #TODO: Verify if all Analyse is AXIAL orientation
90   - orientation = const.AXIAL
  193 + #TODO: Verify if all Analyse is AXIAL orientation
  194 + orientation = const.AXIAL
91 195  
92   - proj.SetAcquisitionModality(acquisition_modality)
93   - proj.imagedata = imagedata
94   - proj.original_orientation = orientation
95   - threshold_range = proj.imagedata.GetScalarRange()
96   - proj.window = window = threshold_range[1] - threshold_range[0]
97   - proj.level = level = (0.5 * (threshold_range[1] + threshold_range[0]))
  196 + proj.SetAcquisitionModality(acquisition_modality)
  197 + proj.imagedata = imagedata
  198 + proj.original_orientation = orientation
  199 + threshold_range = proj.imagedata.GetScalarRange()
  200 + proj.window = window = threshold_range[1] - threshold_range[0]
  201 + proj.level = level = (0.5 * (threshold_range[1] + threshold_range[0]))
98 202  
99   - ps.Publisher().sendMessage('Update window level value',\
  203 + ps.Publisher().sendMessage('Update window level value',\
100 204 (proj.window, proj.level))
101 205  
102 206 if not imagedata:
... ...
invesalius/data/imagedata_utils.py
1 1 import math
2 2 import vtk
  3 +import vtkgdcm
  4 +
  5 +import constants as const
3 6  
4 7 # TODO: Test cases which are originally in sagittal/coronal orientation
5 8 # and have gantry
... ... @@ -162,3 +165,52 @@ def ExtractVOI(imagedata,xi,xf,yi,yf,zi,zf):
162 165 voi.SetSampleRate(1, 1, 1)
163 166 voi.Update()
164 167 return voi.GetOutput()
  168 +
  169 +def CreateImageData(filelist, zspacing):
  170 +
  171 + if not(const.REDUCE_IMAGEDATA_QUALITY):
  172 + array = vtk.vtkStringArray()
  173 + for x in xrange(len(filelist)):
  174 + array.InsertValue(x,filelist[x])
  175 +
  176 + reader = vtkgdcm.vtkGDCMImageReader()
  177 + reader.SetFileNames(array)
  178 + reader.Update()
  179 +
  180 + # The zpacing is a DicomGroup property, so we need to set it
  181 + imagedata = vtk.vtkImageData()
  182 + imagedata.DeepCopy(reader.GetOutput())
  183 + spacing = imagedata.GetSpacing()
  184 + imagedata.SetSpacing(spacing[0], spacing[1], zspacing)
  185 + else:
  186 + # Reformat each slice and future append them
  187 + appender = vtk.vtkImageAppend()
  188 + appender.SetAppendAxis(2) #Define Stack in Z
  189 +
  190 + # Reformat each slice
  191 + for x in xrange(len(filelist)):
  192 + # TODO: We need to check this automatically according
  193 + # to each computer's architecture
  194 + # If the resolution of the matrix is too large
  195 + reader = vtkgdcm.vtkGDCMImageReader()
  196 + reader.SetFileName(filelist[x])
  197 + reader.Update()
  198 +
  199 + #Resample image in x,y dimension
  200 + slice_imagedata = ResampleImage2D(reader.GetOutput(), 256)
  201 +
  202 + #Stack images in Z axes
  203 + appender.AddInput(slice_imagedata)
  204 + appender.Update()
  205 +
  206 + # The zpacing is a DicomGroup property, so we need to set it
  207 + imagedata = vtk.vtkImageData()
  208 + imagedata.DeepCopy(appender.GetOutput())
  209 + spacing = imagedata.GetSpacing()
  210 +
  211 + imagedata.SetSpacing(spacing[0], spacing[1], zspacing)
  212 +
  213 + imagedata.Update()
  214 + return imagedata
  215 +
  216 +
... ...
invesalius/gui/dicom_preview_panel.py
... ... @@ -31,6 +31,27 @@ class SerieEvent(PreviewEvent):
31 31 def __init__(self , evtType, id):
32 32 super(SerieEvent, self).__init__(evtType, id)
33 33  
  34 +class DicomImageData(object):
  35 + def __init__(self):
  36 + pass
  37 +
  38 + def SetInput(self, dicom):
  39 + reader = vtkgdcm.vtkGDCMImageReader()
  40 + reader.SetFileName(dicom.image.file)
  41 + imagedata = reader.GetOutput()
  42 +
  43 + scale = imagedata.GetScalarRange()
  44 +
  45 + cast = vtk.vtkImageMapToWindowLevelColors()
  46 + cast.SetInput(imagedata)
  47 + cast.SetWindow(float(dicom.image.window))
  48 + cast.SetLevel(float(dicom.image.level))
  49 +
  50 + self.imagedata = cast.GetOutput()
  51 +
  52 + def GetOutput(self):
  53 + return self.imagedata
  54 +
34 55  
35 56 class DicomLoader(object):
36 57 """
... ... @@ -119,6 +140,15 @@ class Preview(wx.Panel):
119 140 def SetSubtitle(self, subtitle):
120 141 self.subtitle.SetLabel(subtitle)
121 142  
  143 + def SetGroup(self, group):
  144 + self.SetTitle(group.title)
  145 + self.SetSubtitle("%d images"%(group.nslices))
  146 + d = DicomImageData()
  147 + d.SetInput(group.dicom)
  148 + imagedata = d.GetOutput()
  149 + self.actor.SetInput(imagedata)
  150 + self.render.ResetCamera()
  151 +
122 152 def SetImage(self, image_data):
123 153 """
124 154 Set a Image to preview.
... ... @@ -198,23 +228,23 @@ class DicomPreviewSeries(wx.Panel):
198 228 "%d Images" % len(self.series[i][0]), # Subtitle
199 229 i) for n, i in enumerate(self.series)]
200 230  
201   - def SetDicomSeries(self, files):
202   - self.files = files
203   - scroll_range = len(files)/5
204   - if scroll_range * 5 < len(files):
  231 + def SetDicomSeries(self, patient):
  232 + #self.files = files
  233 + ngroups = patient.ngroups
  234 + self.groups = patient.GetGroups()
  235 +
  236 + scroll_range = ngroups/5
  237 + if scroll_range * 5 < ngroups:
205 238 scroll_range +=1
206 239 self.scroll.SetScrollbar(0, 3, scroll_range, 5)
207 240 self._display_previews()
208 241  
209 242 def _display_previews(self):
210   - initial = self.displayed_position * 5
211   - final = initial + 15
212   - for f, p in zip(self.files[initial:final], self.previews):
213   - print "--------"
214   - print "f:", f
215   - print "p: ", p
216   - p.SetImage(f)
217   - p.Show()
  243 + begin = self.displayed_position * 5
  244 + end = begin + 15
  245 + for group, preview in zip(self.groups[begin:end], self.previews):
  246 + preview.SetGroup(group)
  247 + preview.Show()
218 248  
219 249 def OnScroll(self, evt):
220 250 self.displayed_position = evt.GetPosition()
... ...
invesalius/gui/frame.py
... ... @@ -80,6 +80,11 @@ class Frame(wx.Frame):
80 80 ps.Publisher().subscribe(self.UpdateAui, "Update AUI")
81 81 ps.Publisher().subscribe(self.ShowTask, 'Show task panel')
82 82 ps.Publisher().subscribe(self.HideTask, 'Hide task panel')
  83 + ps.Publisher().subscribe(self.SetProjectName, 'Set project name')
  84 +
  85 + def SetProjectName(self, pubsub_evt):
  86 + proj_name = pubsub_evt.data
  87 + self.SetTitle("InVesalius 3 - %s"%(proj_name))
83 88  
84 89 def UpdateAui(self, pubsub_evt):
85 90 self.aui_manager.Update()
... ... @@ -169,7 +174,9 @@ class Frame(wx.Frame):
169 174  
170 175 def ShowContentPanel(self, evt_pubsub):
171 176 aui_manager = self.aui_manager
  177 + aui_manager.GetPane("Import").Show(0)
172 178 aui_manager.GetPane("Data").Show(1)
  179 + aui_manager.GetPane("Tasks").Show(1)
173 180 aui_manager.Update()
174 181  
175 182 def OnSize(self, evt):
... ...
invesalius/gui/import_panel.py
... ... @@ -130,21 +130,22 @@ class TextPanel(wx.Panel):
130 130  
131 131 tree.Expand(self.root)
132 132  
133   - tree.GetMainWindow().Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
134 133 tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivate)
135 134  
136 135 def OnActivate(self, evt):
137 136 print "OnActivate"
138 137 item = evt.GetItem()
139   - print self.tree.GetItemPyData(item)
140   -
141   -
142   - def OnRightUp(self, evt):
143   - pos = evt.GetPosition()
144   - item, flags, col = self.tree.HitTest(pos)
145   - if item:
146   - print 'Flags: %s, Col:%s, Text: %s' %\
147   - (flags, col, self.tree.GetItemText(item, col))
  138 + group = self.tree.GetItemPyData(item)
  139 + if group:
  140 + print "send"
  141 + ps.Publisher().sendMessage('Open DICOM group',
  142 + group)
  143 +
  144 + else:
  145 + if self.tree.IsExpanded(item):
  146 + self.tree.Collapse(item)
  147 + else:
  148 + self.tree.Expand(item)
148 149  
149 150 def OnSize(self, evt):
150 151 self.tree.SetSize(self.GetSize())
... ...
invesalius/reader/dicom_grouper.py
... ... @@ -51,6 +51,8 @@
51 51 # <dicom.image.number> and <dicom.acquisition.series_number>
52 52 # were swapped
53 53  
  54 +import gdcm
  55 +
54 56 ORIENT_MAP = {"SAGITTAL":0, "CORONAL":1, "AXIAL":2, "OBLIQUE":2}
55 57  
56 58  
... ... @@ -96,7 +98,28 @@ class DicomGroup:
96 98 # (interpolated)
97 99 return self.slices_dict.values()
98 100  
99   - def GetSortedList(self):
  101 + def GetFilenameList(self):
  102 + # Should be called when user selects this group
  103 + # This list will be used to create the vtkImageData
  104 + # (interpolated)
  105 +
  106 + filelist = [dicom.image.file for dicom in
  107 + self.slices_dict.values()]
  108 +
  109 + # Sort slices using GDCM
  110 + if (self.dicom.image.orientation_label <> "CORONAL"):
  111 + #Organize reversed image
  112 + sorter = gdcm.IPPSorter()
  113 + sorter.SetComputeZSpacing(True)
  114 + sorter.SetZSpacingTolerance(1e-10)
  115 + sorter.Sort(filelist)
  116 + filelist = sorter.GetFilenames()
  117 +
  118 + #Getting organized image
  119 + return filelist
  120 +
  121 +
  122 + def GetHandSortedList(self):
100 123 # This will be used to fix problem 1, after merging
101 124 # single DicomGroups of same study_id and orientation
102 125 list_ = self.slices_dict.values()
... ... @@ -106,7 +129,7 @@ class DicomGroup:
106 129 return list_
107 130  
108 131 def UpdateZSpacing(self):
109   - list_ = self.GetSortedList()
  132 + list_ = self.GetHandSortedList()
110 133  
111 134 if (len(list_) > 1):
112 135 dicom = list_[0]
... ... @@ -246,7 +269,7 @@ class PatientGroup:
246 269 group_counter = 0
247 270 for group_key in dict_to_change:
248 271 # 2nd STEP: SORT
249   - sorted_list = dict_to_change[group_key].GetSortedList()
  272 + sorted_list = dict_to_change[group_key].GetHandSortedList()
250 273  
251 274 # 3rd STEP: CHECK DIFFERENCES
252 275 axis = ORIENT_MAP[group_key[0]] # based on orientation
... ...
invesalius/reader/dicom_reader.py
... ... @@ -28,32 +28,26 @@ import dicom
28 28 import dicom_grouper
29 29 import data.imagedata_utils as iu
30 30  
31   -def LoadImages(dir_):
  31 +def ReadDicomGroup(dir_):
32 32  
33 33 patient_group = GetDicomGroups(dir_)
34   - filelist, dicom, zspacing = SelectLargerDicomGroup(patient_group)
35   - filelist = SortFiles(filelist, dicom)
36   - imagedata = CreateImageData(filelist, zspacing)
37   -
38   - return imagedata, dicom
  34 + if len(patient_group) > 0:
  35 + filelist, dicom, zspacing = SelectLargerDicomGroup(patient_group)
  36 + filelist = SortFiles(filelist, dicom)
  37 + imagedata = CreateImageData(filelist, zspacing)
  38 + return imagedata, dicom
  39 + else:
  40 + return False
39 41  
40 42  
41 43 def SelectLargerDicomGroup(patient_group):
42   - nslices_old = 0
  44 + maxslices = 0
43 45 for patient in patient_group:
44 46 group_list = patient.GetGroups()
45 47 for group in group_list:
46   - nslices = group.nslices
47   - print "nslices:", nslices
48   - if (nslices >= nslices_old):
49   - dicoms = group.GetList()
50   - zspacing = group.zspacing
51   - nslices_old = nslices
52   -
53   - filelist = []
54   - for dicom in dicoms:
55   - filelist.append(dicom.image.file)
56   - return filelist, dicom, zspacing
  48 + if group.nslices > maxslices:
  49 + larger_group = group
  50 + return larger_group
57 51  
58 52 def SortFiles(filelist, dicom):
59 53 # Sort slices
... ... @@ -71,53 +65,6 @@ def SortFiles(filelist, dicom):
71 65 return filelist
72 66  
73 67  
74   -def CreateImageData(filelist, zspacing):
75   -
76   - if not(const.REDUCE_IMAGEDATA_QUALITY):
77   - array = vtk.vtkStringArray()
78   - for x in xrange(len(filelist)):
79   - array.InsertValue(x,filelist[x])
80   -
81   - reader = vtkgdcm.vtkGDCMImageReader()
82   - reader.SetFileNames(array)
83   - reader.Update()
84   -
85   - # The zpacing is a DicomGroup property, so we need to set it
86   - imagedata = vtk.vtkImageData()
87   - imagedata.DeepCopy(reader.GetOutput())
88   - spacing = imagedata.GetSpacing()
89   - imagedata.SetSpacing(spacing[0], spacing[1], zspacing)
90   - else:
91   - # Reformat each slice and future append them
92   - appender = vtk.vtkImageAppend()
93   - appender.SetAppendAxis(2) #Define Stack in Z
94   -
95   - # Reformat each slice
96   - for x in xrange(len(filelist)):
97   - # TODO: We need to check this automatically according
98   - # to each computer's architecture
99   - # If the resolution of the matrix is too large
100   - reader = vtkgdcm.vtkGDCMImageReader()
101   - reader.SetFileName(filelist[x])
102   - reader.Update()
103   -
104   - #Resample image in x,y dimension
105   - slice_imagedata = iu.ResampleImage2D(reader.GetOutput(), 256)
106   -
107   - #Stack images in Z axes
108   - appender.AddInput(slice_imagedata)
109   - appender.Update()
110   -
111   - # The zpacing is a DicomGroup property, so we need to set it
112   - imagedata = vtk.vtkImageData()
113   - imagedata.DeepCopy(appender.GetOutput())
114   - spacing = imagedata.GetSpacing()
115   -
116   - imagedata.SetSpacing(spacing[0], spacing[1], zspacing)
117   -
118   - imagedata.Update()
119   - return imagedata
120   -
121 68  
122 69 def GetDicomGroups(directory, recursive=True):
123 70 """
... ...