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 | 114 | invesalius/gui/default_tasks.py -text |
115 | 115 | invesalius/gui/default_viewers.py -text |
116 | 116 | invesalius/gui/dialogs.py -text |
117 | +invesalius/gui/dicom_preview_panel.py -text | |
117 | 118 | invesalius/gui/frame.py -text |
118 | 119 | invesalius/gui/import_data_wizard.py -text |
119 | 120 | invesalius/gui/import_panel.py -text | ... | ... |
... | ... | @@ -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 | 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 | 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 | 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 | 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 | ... | ... |