Commit fb59d2bd2572e55ed5b23fe0e41fb653f1bc5ca7
1 parent
bf95d589
Exists in
master
and in
68 other branches
ADD: Dicom preview class - not working yet
Showing
3 changed files
with
527 additions
and
311 deletions
Show diff stats
.gitattributes
| @@ -114,6 +114,7 @@ invesalius/gui/data_notebook.py -text | @@ -114,6 +114,7 @@ invesalius/gui/data_notebook.py -text | ||
| 114 | invesalius/gui/default_tasks.py -text | 114 | invesalius/gui/default_tasks.py -text |
| 115 | invesalius/gui/default_viewers.py -text | 115 | invesalius/gui/default_viewers.py -text |
| 116 | invesalius/gui/dialogs.py -text | 116 | invesalius/gui/dialogs.py -text |
| 117 | +invesalius/gui/dicom_preview_panel.py -text | ||
| 117 | invesalius/gui/frame.py -text | 118 | invesalius/gui/frame.py -text |
| 118 | invesalius/gui/import_data_wizard.py -text | 119 | invesalius/gui/import_data_wizard.py -text |
| 119 | invesalius/gui/import_panel.py -text | 120 | invesalius/gui/import_panel.py -text |
| @@ -0,0 +1,305 @@ | @@ -0,0 +1,305 @@ | ||
| 1 | +#!/usr/bin/env python | ||
| 2 | +# -*- coding: UTF-8 -*- | ||
| 3 | + | ||
| 4 | +import wx | ||
| 5 | +import vtk | ||
| 6 | +import vtkgdcm | ||
| 7 | + | ||
| 8 | +from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor | ||
| 9 | +#from reader import dicom_reader | ||
| 10 | + | ||
| 11 | +myEVT_SELECT = wx.NewEventType() | ||
| 12 | +# This event occurs when the user select a preview | ||
| 13 | +EVT_SELECT = wx.PyEventBinder(myEVT_SELECT, 1) | ||
| 14 | + | ||
| 15 | +myEVT_SELECT_SERIE = wx.NewEventType() | ||
| 16 | +# This event occurs when the user select a preview | ||
| 17 | +EVT_SELECT_SERIE = wx.PyEventBinder(myEVT_SELECT_SERIE, 1) | ||
| 18 | + | ||
| 19 | +class PreviewEvent(wx.PyCommandEvent): | ||
| 20 | + def __init__(self , evtType, id): | ||
| 21 | + wx.PyCommandEvent.__init__(self, evtType, id) | ||
| 22 | + | ||
| 23 | + def GetSelectID(self): | ||
| 24 | + return self.SelectedID | ||
| 25 | + | ||
| 26 | + def SetSelectedID(self, id): | ||
| 27 | + self.SelectedID = id | ||
| 28 | + | ||
| 29 | + | ||
| 30 | +class SerieEvent(PreviewEvent): | ||
| 31 | + def __init__(self , evtType, id): | ||
| 32 | + super(SerieEvent, self).__init__(evtType, id) | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +class DicomLoader(object): | ||
| 36 | + """ | ||
| 37 | + Responsible for load dicom files. A dictionary like behavior | ||
| 38 | + """ | ||
| 39 | + def __init__(self): | ||
| 40 | + self.loaded_dicoms = {} | ||
| 41 | + | ||
| 42 | + def __getitem__(self, filename): | ||
| 43 | + """ | ||
| 44 | + Especial method to behave like dictionary | ||
| 45 | + """ | ||
| 46 | + try: | ||
| 47 | + return self.loaded_dicoms[filename] | ||
| 48 | + except KeyError: | ||
| 49 | + #print "Except" | ||
| 50 | + self._load_dicom_files(filename) | ||
| 51 | + return self.loaded_dicoms[filename] | ||
| 52 | + | ||
| 53 | + def _load_dicom_files(self, filename, window=150, level=230): | ||
| 54 | + reader = vtkgdcm.vtkGDCMImageReader() | ||
| 55 | + reader.SetFileName(filename) | ||
| 56 | + imagedata = reader.GetOutput() | ||
| 57 | + | ||
| 58 | + scale = imagedata.GetScalarRange() | ||
| 59 | + | ||
| 60 | + cast = vtk.vtkImageMapToWindowLevelColors() | ||
| 61 | + cast.SetInput(imagedata) | ||
| 62 | + cast.SetWindow(float(window)) | ||
| 63 | + cast.SetLevel(float(level)) | ||
| 64 | + | ||
| 65 | + self.loaded_dicoms[filename] = cast.GetOutput() | ||
| 66 | + | ||
| 67 | + | ||
| 68 | +class Preview(wx.Panel): | ||
| 69 | + """ | ||
| 70 | + Where the images will be showed. | ||
| 71 | + """ | ||
| 72 | + dicom_loader = DicomLoader() | ||
| 73 | + def __init__(self, parent): | ||
| 74 | + super(Preview, self).__init__(parent) | ||
| 75 | + # Will it be white? | ||
| 76 | + self.SetBackgroundColour((255, 255, 255)) | ||
| 77 | + self.sizer = wx.BoxSizer(wx.VERTICAL) | ||
| 78 | + self.SetSizer(self.sizer) | ||
| 79 | + self._init_ui() | ||
| 80 | + self._init_vtk() | ||
| 81 | + self._bind_events() | ||
| 82 | + | ||
| 83 | + def _init_ui(self): | ||
| 84 | + self.title = wx.StaticText(self, -1, "Image", | ||
| 85 | + style=wx.ALIGN_CENTER) | ||
| 86 | + | ||
| 87 | + self.subtitle = wx.StaticText(self, -1, "Image", | ||
| 88 | + style=wx.ALIGN_CENTER) | ||
| 89 | + | ||
| 90 | + self.interactor = wxVTKRenderWindowInteractor(self, -1, size=(70, 70)) | ||
| 91 | + | ||
| 92 | + self.sizer.Add(self.title, 0, wx.ALIGN_CENTER_HORIZONTAL) | ||
| 93 | + self.sizer.Add(self.subtitle, 0, wx.ALIGN_CENTER_HORIZONTAL) | ||
| 94 | + self.sizer.Add(self.interactor, 0, wx.ALIGN_CENTER_HORIZONTAL\ | ||
| 95 | + | wx.ALL, 5) | ||
| 96 | + | ||
| 97 | + def _init_vtk(self): | ||
| 98 | + self.actor = vtk.vtkImageActor() | ||
| 99 | + | ||
| 100 | + self.render = vtk.vtkRenderer() | ||
| 101 | + | ||
| 102 | + self.interactor.SetInteractorStyle(None) | ||
| 103 | + self.interactor.GetRenderWindow().AddRenderer(self.render) | ||
| 104 | + | ||
| 105 | + self.render.AddActor(self.actor) | ||
| 106 | + | ||
| 107 | + | ||
| 108 | + def _bind_events(self): | ||
| 109 | + self.Bind( wx.EVT_LEFT_DCLICK, self.OnSelect) | ||
| 110 | + | ||
| 111 | + def OnSelect(self, evt): | ||
| 112 | + evt = PreviewEvent(myEVT_SELECT, self.GetId()) | ||
| 113 | + evt.SetSelectedID(self.ID) | ||
| 114 | + self.GetEventHandler().ProcessEvent(evt) | ||
| 115 | + | ||
| 116 | + def SetTitle(self, title): | ||
| 117 | + self.title.SetLabel(title) | ||
| 118 | + | ||
| 119 | + def SetSubtitle(self, subtitle): | ||
| 120 | + self.subtitle.SetLabel(subtitle) | ||
| 121 | + | ||
| 122 | + def SetImage(self, image_data): | ||
| 123 | + """ | ||
| 124 | + Set a Image to preview. | ||
| 125 | + """ | ||
| 126 | + filename, window, level, title, subtitle = image_data | ||
| 127 | + print image_data | ||
| 128 | + self.SetTitle(title) | ||
| 129 | + self.SetSubtitle(subtitle) | ||
| 130 | + #self.ID = image_file[5] # todo: check if this is necessary | ||
| 131 | + | ||
| 132 | + # TODO: enhace interface | ||
| 133 | + imagedata = Preview.dicom_loader[filename] | ||
| 134 | + self.actor.SetInput(imagedata) | ||
| 135 | + self.render.ResetCamera() | ||
| 136 | + | ||
| 137 | +class DicomPreviewSeries(wx.Panel): | ||
| 138 | + """A dicom series preview panel""" | ||
| 139 | + def __init__(self, parent): | ||
| 140 | + super(DicomPreviewSeries, self).__init__(parent) | ||
| 141 | + # TODO: 3 pixels between the previews is a good idea? | ||
| 142 | + # I have to test. | ||
| 143 | + self.sizer = wx.BoxSizer(wx.HORIZONTAL) | ||
| 144 | + self.SetSizer(self.sizer) | ||
| 145 | + self.displayed_position = 0 | ||
| 146 | + self._init_ui() | ||
| 147 | + | ||
| 148 | + def _init_ui(self): | ||
| 149 | + self.scroll = wx.ScrollBar(self, style=wx.SB_VERTICAL) | ||
| 150 | + self.grid = wx.GridSizer(rows=3, cols=5, vgap=3, hgap=3) | ||
| 151 | + self.sizer.Add(self.grid) | ||
| 152 | + self.sizer.Add(self.scroll, 0, wx.EXPAND) | ||
| 153 | + self._Add_Panels_Preview() | ||
| 154 | + self._bind_events() | ||
| 155 | + | ||
| 156 | + def _Add_Panels_Preview(self): | ||
| 157 | + self.previews = [] | ||
| 158 | + for i in xrange(3): | ||
| 159 | + for j in xrange(5): | ||
| 160 | + p = Preview(self) | ||
| 161 | + p.Hide() | ||
| 162 | + self.previews.append(p) | ||
| 163 | + self.grid.Add(p, i, j) | ||
| 164 | + | ||
| 165 | + def _bind_events(self): | ||
| 166 | + # When the user scrolls the window | ||
| 167 | + self.Bind(wx.EVT_SCROLL, self.OnScroll) | ||
| 168 | + self.Bind(EVT_SELECT, self.OnSelect) | ||
| 169 | + | ||
| 170 | + def OnSelect(self, evt): | ||
| 171 | + my_evt = SerieEvent(myEVT_SELECT_SERIE, self.GetId()) | ||
| 172 | + my_evt.SetSelectedID(evt.GetSelectID()) | ||
| 173 | + self.GetEventHandler().ProcessEvent(my_evt) | ||
| 174 | + | ||
| 175 | + def SetDicomDirectory(self, directory): | ||
| 176 | + print "SetDicomDirectory" | ||
| 177 | + self.directory = directory | ||
| 178 | + self.series = dicom_reader.GetSeries(directory)[0] | ||
| 179 | + print "keys", [key[0] for key in self.series.keys()] | ||
| 180 | + | ||
| 181 | + s = self.series | ||
| 182 | + for k in s.keys(): | ||
| 183 | + print "------ PESSOA ---------" | ||
| 184 | + print "%s (%d series)"%(k[0], len(s[k])-1) | ||
| 185 | + for ns in range(1,len(s[k])): | ||
| 186 | + print "------ SERIE ---------" | ||
| 187 | + print "unnamed" | ||
| 188 | + print "age %s" %(s[k][ns][8]) | ||
| 189 | + print "date acquired %s %s" %(s[k][ns][0], s[k][ns][4]) | ||
| 190 | + print "birthdate %s" %(s[k][ns][23]) | ||
| 191 | + print "institution %s" %(s[k][ns][6]) | ||
| 192 | + | ||
| 193 | + # TODO: I need to improve this | ||
| 194 | + self.files = [(self.series[i][0][0][8], # Filename | ||
| 195 | + self.series[i][0][0][12], # Window Level | ||
| 196 | + self.series[i][0][0][13], # Window Width | ||
| 197 | + "unnamed", #% (n + 1), # Title | ||
| 198 | + "%d Images" % len(self.series[i][0]), # Subtitle | ||
| 199 | + i) for n, i in enumerate(self.series)] | ||
| 200 | + | ||
| 201 | + def SetDicomSeries(self, files): | ||
| 202 | + self.files = files | ||
| 203 | + scroll_range = len(files)/5 | ||
| 204 | + if scroll_range * 5 < len(files): | ||
| 205 | + scroll_range +=1 | ||
| 206 | + self.scroll.SetScrollbar(0, 3, scroll_range, 5) | ||
| 207 | + self._display_previews() | ||
| 208 | + | ||
| 209 | + 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() | ||
| 218 | + | ||
| 219 | + def OnScroll(self, evt): | ||
| 220 | + self.displayed_position = evt.GetPosition() | ||
| 221 | + [i.Hide() for i in self.previews] | ||
| 222 | + self._display_previews() | ||
| 223 | + | ||
| 224 | + | ||
| 225 | +class DicomPreview(wx.Panel): | ||
| 226 | + """A dicom preview panel""" | ||
| 227 | + def __init__(self, parent): | ||
| 228 | + super(DicomPreview, self).__init__(parent) | ||
| 229 | + # TODO: 3 pixels between the previews is a good idea? | ||
| 230 | + # I have to test. | ||
| 231 | + self.sizer = wx.BoxSizer(wx.HORIZONTAL) | ||
| 232 | + self.SetSizer(self.sizer) | ||
| 233 | + self.displayed_position = 0 | ||
| 234 | + self.nhidden_last_display = 0 | ||
| 235 | + self._init_ui() | ||
| 236 | + | ||
| 237 | + def _init_ui(self): | ||
| 238 | + self.scroll = wx.ScrollBar(self, style=wx.SB_VERTICAL) | ||
| 239 | + self.grid = wx.GridSizer(rows=3, cols=5, vgap=3, hgap=3) | ||
| 240 | + self.sizer.Add(self.grid) | ||
| 241 | + self.sizer.Add(self.scroll, 0, wx.EXPAND) | ||
| 242 | + self._Add_Panels_Preview() | ||
| 243 | + self._bind_events() | ||
| 244 | + | ||
| 245 | + def _Add_Panels_Preview(self): | ||
| 246 | + self.previews = [] | ||
| 247 | + for i in xrange(3): | ||
| 248 | + for j in xrange(5): | ||
| 249 | + p = Preview(self) | ||
| 250 | + #p.Hide() | ||
| 251 | + self.previews.append(p) | ||
| 252 | + self.grid.Add(p, i, j) | ||
| 253 | + | ||
| 254 | + def _bind_events(self): | ||
| 255 | + # When the user scrolls the window | ||
| 256 | + self.Bind(wx.EVT_SCROLL, self.OnScroll) | ||
| 257 | + | ||
| 258 | + def SetDicomDirectory(self, directory): | ||
| 259 | + self.directory = directory | ||
| 260 | + self.series = dicom_reader.GetSeries(directory)[0] | ||
| 261 | + | ||
| 262 | + def SetDicomSerie(self, serie): | ||
| 263 | + k = serie | ||
| 264 | + self.files = [(i[8], | ||
| 265 | + i[12], | ||
| 266 | + i[13], | ||
| 267 | + "Image %d" % (n + 1), # Title | ||
| 268 | + "%s"% str(i[3][2]), # Spacing | ||
| 269 | + n)for n, i in enumerate(self.series[k][0])] | ||
| 270 | + scroll_range = len(self.files)/5 | ||
| 271 | + if scroll_range * 5 < len(self.files): | ||
| 272 | + scroll_range +=1 | ||
| 273 | + self.scroll.SetScrollbar(0, 3, scroll_range, 5) | ||
| 274 | + self._display_previews() | ||
| 275 | + | ||
| 276 | + def _display_previews(self): | ||
| 277 | + initial = self.displayed_position * 5 | ||
| 278 | + final = initial + 15 | ||
| 279 | + | ||
| 280 | + if len(self.files) < final: | ||
| 281 | + for i in xrange(final-len(self.files)): | ||
| 282 | + try: | ||
| 283 | + self.previews[-i-1].Hide() | ||
| 284 | + except IndexError: | ||
| 285 | + #print "doesn't exist!" | ||
| 286 | + pass | ||
| 287 | + self.nhidden_last_display = final-len(self.files) | ||
| 288 | + else: | ||
| 289 | + if self.nhidden_last_display: | ||
| 290 | + for i in xrange(self.nhidden_last_display): | ||
| 291 | + try: | ||
| 292 | + self.previews[-i-1].Show() | ||
| 293 | + except IndexError: | ||
| 294 | + #print "doesn't exist!" | ||
| 295 | + pass | ||
| 296 | + self.nhidden_last_display = 0 | ||
| 297 | + | ||
| 298 | + for f, p in zip(self.files[initial:final], self.previews): | ||
| 299 | + p.SetImage(f) | ||
| 300 | + p.interactor.Render() | ||
| 301 | + | ||
| 302 | + def OnScroll(self, evt): | ||
| 303 | + if self.displayed_position != evt.GetPosition(): | ||
| 304 | + self.displayed_position = evt.GetPosition() | ||
| 305 | + self._display_previews() |
invesalius/reader/dicom_grouper.py
| @@ -19,340 +19,250 @@ | @@ -19,340 +19,250 @@ | ||
| 19 | # detalhes. | 19 | # detalhes. |
| 20 | #--------------------------------------------------------------------- | 20 | #--------------------------------------------------------------------- |
| 21 | 21 | ||
| 22 | -import dicom | ||
| 23 | 22 | ||
| 24 | -class DicomGroups: | ||
| 25 | - """ | ||
| 26 | - It is possible to separate sets of a set | ||
| 27 | - of files dicom. | ||
| 28 | - | ||
| 29 | - To use: | ||
| 30 | - list_dicoms = [c:\a.dcm, c:\a1.gdcm] | ||
| 31 | - dicom_splitter = DicomGroups() | ||
| 32 | - dicom_splitter.SetFileList(list_dicoms) | ||
| 33 | - dicom_splitter.Update() | ||
| 34 | - splitted = dicom_splitter.GetOutput() | ||
| 35 | - """ | ||
| 36 | - | ||
| 37 | - def __init__(self): | ||
| 38 | - self.parser = dicom.Parser() | ||
| 39 | - #List of DICOM from Directory | ||
| 40 | - self.filenamelist = [] | ||
| 41 | - # List of DICOM with Informations | ||
| 42 | - self.filelist = [] | ||
| 43 | - #is output | ||
| 44 | - self.groups_dcm = {} | ||
| 45 | - #It is the kind of group that was used. | ||
| 46 | - self.split_type = 0 | ||
| 47 | - | ||
| 48 | - def SetFileList(self, filenamelist): | ||
| 49 | - """ | ||
| 50 | - Input is a list with the complete address | ||
| 51 | - of each DICOM. | ||
| 52 | - """ | ||
| 53 | - self.filenamelist = filenamelist | ||
| 54 | - | ||
| 55 | - def Update(self): | ||
| 56 | - """ | ||
| 57 | - Trigger processing group of series | ||
| 58 | - """ | ||
| 59 | - | ||
| 60 | - self.__ParseFiles() | ||
| 61 | - | ||
| 62 | - self.__Split1() | ||
| 63 | - | ||
| 64 | - if (len(self.GetOutput().keys()) == len(self.filenamelist)): | ||
| 65 | - self.__Split2() | ||
| 66 | - self.split_type = 1 | ||
| 67 | - | ||
| 68 | - self.__Split3() | ||
| 69 | - | ||
| 70 | - self.__UpdateZSpacing() | ||
| 71 | - | ||
| 72 | - | ||
| 73 | - def GetOutput(self): | ||
| 74 | - """ | ||
| 75 | - Returns a dictionary with groups | ||
| 76 | - of DICOM. | ||
| 77 | - """ | ||
| 78 | - return self.groups_dcm | 23 | +# --------------------------------------------------------- |
| 24 | +# PROBLEM 1 | ||
| 25 | +# There are times when there are lots of groups on dict, but | ||
| 26 | +# each group contains only one slice (DICOM file). | ||
| 27 | +# | ||
| 28 | +# Equipments / manufacturer: | ||
| 29 | +# TODO | ||
| 30 | +# | ||
| 31 | +# Cases: | ||
| 32 | +# TODO 0031, 0056, 1093 | ||
| 33 | +# | ||
| 34 | +# What occurs in these cases: | ||
| 35 | +# <dicom.image.number> and <dicom.acquisition.series_number> | ||
| 36 | +# were swapped | ||
| 79 | 37 | ||
| 80 | - def GetSplitterType(self): | ||
| 81 | - """ | ||
| 82 | - Return Integer with the SplitterType | ||
| 83 | - 0 option used the name of patient information, | ||
| 84 | - id of the study, number of series and orientation | ||
| 85 | - of the image (AXIAL, SAGITAL and CORONAL). | ||
| 86 | - 1 option was checked is used to splitted the if distance | ||
| 87 | - of the images (tag image position) is more 2x Thickness. | ||
| 88 | - """ | ||
| 89 | - return self.split_type | ||
| 90 | 38 | ||
| 91 | - def __ParseFiles(self): | ||
| 92 | - """ | ||
| 93 | - It generates a list with information | ||
| 94 | - concerning dicom files within a directory. | ||
| 95 | - The Input is a List containing andress of | ||
| 96 | - the DICOM | ||
| 97 | - """ | ||
| 98 | - filenamelist = self.filenamelist | ||
| 99 | - filelist = self.filelist | ||
| 100 | - parser = self.parser | ||
| 101 | - for x in xrange(len(filenamelist)): | 39 | +# ----------------------------------------------------------- |
| 40 | +# PROBLEM 2 | ||
| 41 | +# Two slices (DICOM file) inside a group have the same | ||
| 42 | +# position. | ||
| 43 | +# | ||
| 44 | +# Equipments / manufacturer: | ||
| 45 | +# TODO | ||
| 46 | +# | ||
| 47 | +# Cases: | ||
| 48 | +# TODO 0031, 0056, 1093 | ||
| 49 | +# | ||
| 50 | +# What occurs in these cases: | ||
| 51 | +# <dicom.image.number> and <dicom.acquisition.series_number> | ||
| 52 | +# were swapped | ||
| 102 | 53 | ||
| 103 | - if not parser.SetFileName(filenamelist[x]): | ||
| 104 | - return None | 54 | +ORIENT_MAP = {"SAGITTAL":0, "AXIAL":1, "CORONAL":2} |
| 105 | 55 | ||
| 106 | - information = dicom.Dicom() | ||
| 107 | - information.SetParser(parser) | ||
| 108 | 56 | ||
| 109 | - self.filelist.append(information) | ||
| 110 | - self.filelist = filelist | ||
| 111 | - | 57 | +class DicomGroup: |
| 58 | + def __init__(self): | ||
| 59 | + # key: | ||
| 60 | + # (dicom.patient.name, dicom.acquisition.id_study, | ||
| 61 | + # dicom.acquisition.series_number, | ||
| 62 | + # dicom.image.orientation_label, index) | ||
| 63 | + self.key = () | ||
| 64 | + self.slices_dict = {} # slice_position: Dicom.dicom | ||
| 65 | + # IDEA (13/10): Represent internally as dictionary, | ||
| 66 | + # externally as list | ||
| 67 | + self.nslices = 0 | ||
| 68 | + | ||
| 69 | + def AddSlice(self, dicom): | ||
| 70 | + pos = dicom.image.position | ||
| 71 | + if pos not in self.slices_dict.keys(): | ||
| 72 | + self.slices_dict[pos] = dicom | ||
| 73 | + self.nslices += 1 | ||
| 74 | + return True | ||
| 75 | + else: | ||
| 76 | + return False | ||
| 77 | + | ||
| 78 | + def GetList(self): | ||
| 79 | + # Should be called when user selects this group | ||
| 80 | + # This list will be used to create the vtkImageData | ||
| 81 | + # (interpolated) | ||
| 82 | + return self.slices_dict.values() | ||
| 83 | + | ||
| 84 | + def GetSortedList(self): | ||
| 85 | + # This will be used to fix problem 1, after merging | ||
| 86 | + # single DicomGroups of same study_id and orientation | ||
| 87 | + list_ = self.slices_dict_values() | ||
| 88 | + axis = ORIENT_MAP[self.key[3]] | ||
| 89 | + list_ = sorted(list_, key = lambda dicom:dicom.image.position[axis]) | ||
| 90 | + return list_ | ||
| 91 | + | ||
| 92 | + | ||
| 93 | +class PatientGroup: | ||
| 94 | + def __init__(self): | ||
| 95 | + # key: | ||
| 96 | + # (dicom.patient.name, dicom.patient.id) | ||
| 97 | + self.key = () | ||
| 98 | + self.groups_dict = {} # group_key: DicomGroup | ||
| 99 | + self.slices = 0 | ||
| 100 | + | ||
| 101 | + def AddFile(self, dicom, index=0): | ||
| 102 | + # Given general DICOM information, we group slices according | ||
| 103 | + # to main series information (group_key) | ||
| 104 | + | ||
| 105 | + # WARN: This was defined after years of experience | ||
| 106 | + # (2003-2009), so THINK TWICE before changing group_key | ||
| 107 | + | ||
| 108 | + # Problem 2 is being fixed by the way this method is | ||
| 109 | + # implemented, dinamically during new dicom's addition | ||
| 110 | + group_key = (dicom.patient.name, | ||
| 111 | + dicom.acquisition.id_study, | ||
| 112 | + dicom.acquisition.series_number, | ||
| 113 | + dicom.image.orientation_label, | ||
| 114 | + index) # This will be used to deal with Problem 2 | ||
| 112 | 115 | ||
| 113 | - def __GetInformations(self, ind): | ||
| 114 | - """ | ||
| 115 | - Return a list referring to a specific DICOM | ||
| 116 | - in dictionary. In some cases it is necessary | ||
| 117 | - to pass only the index | ||
| 118 | - """ | ||
| 119 | - filelist = self.filelist | ||
| 120 | - return filelist[ind] | ||
| 121 | - | 116 | + # Does this group exist? Best case ;) |
| 117 | + if group_key not in self.groups_dict.keys(): | ||
| 118 | + group = DicomGroup() | ||
| 119 | + group.AddSlice(dicom) | ||
| 120 | + self.groups_dict[group_key] = group | ||
| 121 | + # Group exists... Lets try to add slice | ||
| 122 | + else: | ||
| 123 | + group = self.groups_dicom[group_key] | ||
| 124 | + slice_added = group.AddSlice(dicom) | ||
| 125 | + if not slice_added: | ||
| 126 | + # If we're here, then Problem 2 occured | ||
| 127 | + # TODO: Optimize recursion | ||
| 128 | + self.AddFile(file_path, index+1) | ||
| 122 | 129 | ||
| 123 | - def __Split1(self): | ||
| 124 | - """ | ||
| 125 | - Bring together the series under the name of | ||
| 126 | - the patient, id of the study, serial number | ||
| 127 | - and label orientation of the image. | ||
| 128 | - """ | ||
| 129 | - groups_dcm = self.groups_dcm | ||
| 130 | - | ||
| 131 | - for x in xrange(len(self.filelist)): | ||
| 132 | - information = self.__GetInformations(x) | 130 | + def Update(self): |
| 131 | + # Ideally, AddFile would be sufficient for splitting DICOM | ||
| 132 | + # files into groups (series). However, this does not work for | ||
| 133 | + # acquisitions / equipments and manufacturers. | ||
| 133 | 134 | ||
| 134 | - key = (information.patient.name, information.acquisition.id_study,\ | ||
| 135 | - information.acquisition.serie_number, information.image.orientation_label) | 135 | + # Although DICOM is a protocol, each one uses its fields in a |
| 136 | + # different manner | ||
| 136 | 137 | ||
| 137 | - if (key in groups_dcm.keys()): | ||
| 138 | - groups_dcm[key].append(information) | ||
| 139 | - else: | ||
| 140 | - groups_dcm[key] = [information] | 138 | + # Check if Problem 1 occurs (n groups with 1 slice each) |
| 139 | + is_there_problem_1 = False | ||
| 140 | + if (self.ndicom == len(self.groups_dicom)) and\ | ||
| 141 | + (self.ndicom > 1): | ||
| 142 | + is_there_problem_1 = True | ||
| 141 | 143 | ||
| 142 | - self.groups_dcm = groups_dcm | 144 | + # Fix Problem 1 |
| 145 | + if is_there_problem_1: | ||
| 146 | + self.groups_dict = self.FixProblem1(self.groups_dict) | ||
| 143 | 147 | ||
| 148 | + def GetGroups(self): | ||
| 149 | + return self.groups_dict.values() | ||
| 144 | 150 | ||
| 145 | - def __Split2(self): | 151 | + def FixProblem1(self, dict): |
| 146 | """ | 152 | """ |
| 147 | - Separate according to the difference of the current | ||
| 148 | - share with the next. | ||
| 149 | - If the else them is higher than the z axis | ||
| 150 | - multiplied by two. | 153 | + Merge multiple DICOM groups in case Problem 1 (description |
| 154 | + above) occurs. | ||
| 155 | + | ||
| 156 | + WARN: We've implemented an heuristic to try to solve | ||
| 157 | + the problem. There is no scientific background and this aims | ||
| 158 | + to be a workaround to exams which are not in conformance with | ||
| 159 | + the DICOM protocol. | ||
| 151 | """ | 160 | """ |
| 152 | - self.groups_dcm = {} | ||
| 153 | - cont_series = 0 | ||
| 154 | - groups_dcm = self.groups_dcm | ||
| 155 | - | ||
| 156 | - #Through in the series (index of the dictionary) | ||
| 157 | - for x in xrange(len(self.filelist)): | ||
| 158 | - #Slices through in the serie | ||
| 159 | - information = self.__GetInformations(x) | ||
| 160 | - key = (information.patient.name, cont_series) | ||
| 161 | - | ||
| 162 | - #If there exists only one slice. | ||
| 163 | - if (len(self.filelist) > 1): | ||
| 164 | - #If the number of slices in the series is | ||
| 165 | - #less than the all number of slices. | ||
| 166 | - #It is necessary to whether and the | ||
| 167 | - #last slice. | ||
| 168 | - if ((x < len(self.filelist) and (x != len(self.filelist) - 1))): | ||
| 169 | - #position of next slice | ||
| 170 | - information = self.__GetInformations(x + 1) | ||
| 171 | - image_position_prox = information.image.position | ||
| 172 | - print image_position_prox | ||
| 173 | - else: | ||
| 174 | - #slice up the position. | ||
| 175 | - information = self.__GetInformations(x - 1) | ||
| 176 | - image_position_prox = information.image.position | ||
| 177 | - #According to the orientation of the image subtraction | ||
| 178 | - #will be between a specific position in the vector of positions. | ||
| 179 | - image_orientation_label = information.image.orientation_label | ||
| 180 | - image_position = information.image.position | ||
| 181 | - if(image_orientation_label == "SAGITTAL"): | ||
| 182 | - dif_image_position = image_position_prox[0] - image_position[0] | ||
| 183 | - | ||
| 184 | - elif (image_orientation_label == "AXIAL"): | ||
| 185 | - dif_image_position = image_position_prox[1] - image_position[1] | ||
| 186 | - else: | ||
| 187 | - dif_image_position = image_position_prox[2] - image_position[2] | ||
| 188 | - | ||
| 189 | - #If the difference in the positions is less than the | ||
| 190 | - #spacing z-axis (thickness) multiplied by two. | ||
| 191 | - #Otherwise this key create and add value | ||
| 192 | - spacing = information.image.spacing | ||
| 193 | - if ((dif_image_position) <= spacing[2] * 2): | ||
| 194 | - #If there is already such a key in the dictionary, | ||
| 195 | - #only adds value. Otherwise this key create in the | ||
| 196 | - #dictionary and add value | ||
| 197 | - if (key in groups_dcm.keys()): | ||
| 198 | - groups_dcm[key].append(information) | ||
| 199 | - else: | ||
| 200 | - groups_dcm[key] = [information] | 161 | + # Divide existing groups into 2 groups: |
| 162 | + dict_final = {} # 1 | ||
| 163 | + # those containing "3D photos" and undefined | ||
| 164 | + # orientation - these won't be changed (groups_lost). | ||
| 165 | + | ||
| 166 | + dict_to_change = {} # 2 | ||
| 167 | + # which can be re-grouped according to our heuristic | ||
| 168 | + | ||
| 169 | + # split existing groups in these two types of group, based on | ||
| 170 | + # orientation label | ||
| 171 | + | ||
| 172 | + # 1st STEP: RE-GROUP | ||
| 173 | + for group_key in dict: | ||
| 174 | + # values used as key of the new dictionary | ||
| 175 | + orientation = dict[group_key].image.orientation_label | ||
| 176 | + study_id = dict[group_key].acquisition.id_study | ||
| 177 | + # if axial, coronal or sagittal | ||
| 178 | + if orientation in ORIENT_MAP: | ||
| 179 | + group_key_s = (orientation, study_id) | ||
| 180 | + # If this method was called, there is only one slice | ||
| 181 | + # in this group (dicom) | ||
| 182 | + dicom = dict[group_key].GetList()[0] | ||
| 183 | + if group_key_s not in dict_to_change.keys(): | ||
| 184 | + group = DicomGroup() | ||
| 185 | + group.AddSlice(dicom) | ||
| 186 | + dict_to_change[group_key_s] = group | ||
| 201 | else: | 187 | else: |
| 202 | - cont_series = cont_series + 1 | ||
| 203 | - groups_dcm[key] = [information] | ||
| 204 | - | 188 | + group = dict_to_change[group_key_s] |
| 189 | + group.AddSlice(dicom) | ||
| 205 | else: | 190 | else: |
| 206 | - | ||
| 207 | - if (cont_series in groups_dcm.keys()): | ||
| 208 | - groups_dcm[key].append(information) | ||
| 209 | - else: | ||
| 210 | - groups_dcm[key] = [information] | ||
| 211 | - | ||
| 212 | - cont_series = cont_series + 1 | ||
| 213 | - | ||
| 214 | - self.groups_dcm = groups_dcm | ||
| 215 | - | ||
| 216 | - | ||
| 217 | - def __Split3(self): | ||
| 218 | - """ | ||
| 219 | - Separate the slice with the positions | ||
| 220 | - repeated. | ||
| 221 | - """ | ||
| 222 | - groups_dcm = self.groups_dcm | ||
| 223 | - groups_dcm_ = {} | ||
| 224 | - size_groups = len(groups_dcm.keys()) | ||
| 225 | - tmp1 = {} | ||
| 226 | - tmp_list = [] | ||
| 227 | - | ||
| 228 | - #goes according to the serial number | ||
| 229 | - #already separated. | ||
| 230 | - for n in xrange(size_groups): | ||
| 231 | - | ||
| 232 | - #Key of dictionary | ||
| 233 | - key = groups_dcm.keys()[n] | ||
| 234 | - | ||
| 235 | - #Number of slices in the series | ||
| 236 | - size_list = len(groups_dcm[key]) | ||
| 237 | - | ||
| 238 | - for y in xrange(size_list): | ||
| 239 | - | ||
| 240 | - #Slices walks in the series | ||
| 241 | - information = groups_dcm[key][y] | ||
| 242 | - | ||
| 243 | - #Generate new key to dictionary | ||
| 244 | - image_pos = information.image.position | ||
| 245 | - key_ = (image_pos[0], image_pos[1], image_pos[2]) | ||
| 246 | - | ||
| 247 | - #Add informations in the list | ||
| 248 | - list = [information] | ||
| 249 | - | ||
| 250 | - #If list Null, create dictionary | ||
| 251 | - #and add list with information | ||
| 252 | - #after add in a temporary list | ||
| 253 | - if (tmp_list == []): | ||
| 254 | - tmp = {} | ||
| 255 | - tmp[key_] = information | ||
| 256 | - tmp_list.append(tmp) | ||
| 257 | - | 191 | + dict_final[group_key] = dict[group_key] |
| 192 | + | ||
| 193 | + # group_counter will be used as key to DicomGroups created | ||
| 194 | + # while checking differences | ||
| 195 | + group_counter = 0 | ||
| 196 | + for group_key in dict_to_change: | ||
| 197 | + # 2nd STEP: SORT | ||
| 198 | + sorted_list = dict_to_change[group_key].GetSortedList() | ||
| 199 | + | ||
| 200 | + # 3rd STEP: CHECK DIFFERENCES | ||
| 201 | + axis = ORIENT_MAP[group_key[0]] # based on orientation | ||
| 202 | + for index in xrange(len(sorted_list)-1): | ||
| 203 | + current = sorted_list[index] | ||
| 204 | + next = sorted_list[index+1] | ||
| 205 | + | ||
| 206 | + pos_current = current.image.position[axis] | ||
| 207 | + pos_next = current.image.position[axis] | ||
| 208 | + spacing = current.image.spacing | ||
| 209 | + | ||
| 210 | + if (pos_next - pos_current) <= (spacing[2] * 2): | ||
| 211 | + if group_counter in dict_final: | ||
| 212 | + dict_final[group_counter].AddSlice(current) | ||
| 213 | + else: | ||
| 214 | + group = DicomGroup() | ||
| 215 | + group.AddSlice(current) | ||
| 216 | + dict_final[group_counter] = group | ||
| 258 | else: | 217 | else: |
| 259 | - b = len(tmp_list) | ||
| 260 | - a = 0 | ||
| 261 | - #flag is to control when be necessary | ||
| 262 | - #to create another position in the list. | ||
| 263 | - flag = 0 | ||
| 264 | - | ||
| 265 | - while a < b: | ||
| 266 | - #if there is to share the same | ||
| 267 | - #position create new key in the | ||
| 268 | - #dictionary | 218 | + group_counter +=1 |
| 219 | + group = DicomGroup() | ||
| 220 | + group.AddSlice(current) | ||
| 221 | + dict_final[group_counter] = group | ||
| 269 | 222 | ||
| 270 | - if not (key_ in (tmp_list[a]).keys()): | ||
| 271 | - (tmp_list[a])[key_] = information | ||
| 272 | - flag = 1 | ||
| 273 | - a = a + 1 | 223 | + return dict_final |
| 274 | 224 | ||
| 275 | - if (flag == 0): | ||
| 276 | - tmp = {} | ||
| 277 | - tmp[key_] = information | ||
| 278 | 225 | ||
| 279 | - tmp_list.append(tmp) | 226 | +class DicomPatientGrouper: |
| 227 | + # read file, check if it is dicom... | ||
| 228 | + # dicom = dicom.Dicom | ||
| 229 | + # grouper = DicomPatientGrouper() | ||
| 230 | + # grouper.AddFile(dicom) | ||
| 231 | + # ... (repeat to all files on folder) | ||
| 232 | + # grouper.Update() | ||
| 233 | + # groups = GetPatientGroups() | ||
| 280 | 234 | ||
| 281 | - | ||
| 282 | - #for each item on the list is created | ||
| 283 | - #a new position in the dictionary. | ||
| 284 | - size_tmp_list = len(tmp_list) | 235 | + def __init__(self): |
| 236 | + self.patients_dict = {} | ||
| 237 | + | ||
| 238 | + def AddFile(self, dicom): | ||
| 239 | + patient_key = (dicom.patient.name, | ||
| 240 | + dicom.patient.id) | ||
| 241 | + | ||
| 242 | + # Does this patient exist? | ||
| 243 | + if patient_key not in self.patients_dict.keys(): | ||
| 244 | + patient = PatientGroup() | ||
| 245 | + patient.AddSlice(dicom) | ||
| 246 | + self.patients_dict[patient_key] = patient | ||
| 247 | + # Patient exists... Lets add group to it | ||
| 248 | + else: | ||
| 249 | + patient = self.patients_dict[patient_key] | ||
| 250 | + patient.AddFile(dicom) | ||
| 285 | 251 | ||
| 286 | - for x in xrange(size_tmp_list): | ||
| 287 | - | ||
| 288 | - tmp1 = tmp_list[x] | ||
| 289 | - for m in xrange(len(tmp1.keys())): | ||
| 290 | - | ||
| 291 | - key = tmp1.keys()[m] | ||
| 292 | - information = tmp1[key] | ||
| 293 | - | ||
| 294 | - #new_key = (x,information.patient.name, information.image.orientation_label, | ||
| 295 | - # information.acquisition.serie_number) | ||
| 296 | - | ||
| 297 | - new_key = (information.patient.name, None, x, | ||
| 298 | - information.image.orientation_label) | ||
| 299 | - | ||
| 300 | - if (new_key in groups_dcm_.keys()): | ||
| 301 | - groups_dcm_[new_key].append(information) | ||
| 302 | - else: | ||
| 303 | - groups_dcm_[new_key] = [information] | ||
| 304 | - | ||
| 305 | - #the number of previously existing number is | ||
| 306 | - #greater or equal then the group keeps up, | ||
| 307 | - #but maintains the same group of positions. | ||
| 308 | - if len(self.groups_dcm.keys()) < len(groups_dcm_.keys()): | ||
| 309 | - self.groups_dcm = groups_dcm_ | ||
| 310 | - | ||
| 311 | - for j in xrange(len(self.groups_dcm.keys())): | ||
| 312 | - key = self.groups_dcm.keys()[j] | ||
| 313 | - self.groups_dcm[key].sort(key=lambda x: x.image.number) | ||
| 314 | - | ||
| 315 | - | ||
| 316 | - def __UpdateZSpacing(self): | 252 | + def Update(self): |
| 253 | + for patient in self.patients_dict.values(): | ||
| 254 | + patient.Update() | ||
| 255 | + | ||
| 256 | + def GetPatientsGroups(self): | ||
| 317 | """ | 257 | """ |
| 318 | - Calculate Z spacing from slices | 258 | + How to use: |
| 259 | + patient_list = grouper.GetPatientsGroups() | ||
| 260 | + for patient in patient_list: | ||
| 261 | + group_list = patient.GetGroups() | ||
| 262 | + for group in group_list: | ||
| 263 | + group.GetList() | ||
| 264 | + # :) you've got a list of dicom.Dicom | ||
| 265 | + # of the same series | ||
| 319 | """ | 266 | """ |
| 267 | + return self.patients_dict.values() | ||
| 320 | 268 | ||
| 321 | - for x in xrange(len(self.groups_dcm.keys())): | ||
| 322 | - | ||
| 323 | - key = self.groups_dcm.keys()[x] | ||
| 324 | - information = self.groups_dcm[key][0] | ||
| 325 | - if (len(self.groups_dcm[key]) > 1): | ||
| 326 | - #Catch a slice of middle and the next to find the spacing. | ||
| 327 | - center = len(self.groups_dcm[key])/2 | ||
| 328 | - if (center == 1): | ||
| 329 | - center = 0 | ||
| 330 | - | ||
| 331 | - information = self.groups_dcm[key][center] | ||
| 332 | - current_position = information.image.position | ||
| 333 | - | ||
| 334 | - information = self.groups_dcm[key][center + 1] | ||
| 335 | - next_position = information.image.position | ||
| 336 | - | ||
| 337 | - try: | ||
| 338 | - information = self.groups_dcm[self.groups_dcm.keys()[x]][3] | ||
| 339 | - image_orientation_label = information.image.orientation_label | ||
| 340 | - | ||
| 341 | - except(IndexError): | ||
| 342 | - image_orientation_label = None | ||
| 343 | - | ||
| 344 | - if(image_orientation_label == "SAGITTAL"): | ||
| 345 | - spacing = current_position[0] - next_position[0] | ||
| 346 | - elif(image_orientation_label == "CORONAL"): | ||
| 347 | - spacing = current_position[1] - next_position[1] | ||
| 348 | - else: | ||
| 349 | - spacing = current_position[2] - next_position[2] | ||
| 350 | - | ||
| 351 | - spacing = abs(spacing) | ||
| 352 | - | ||
| 353 | - else: | ||
| 354 | - spacing = None | ||
| 355 | - | ||
| 356 | - for information in self.groups_dcm[key]: | ||
| 357 | - if information.image.spacing: | ||
| 358 | - information.image.spacing[2] = spacing |