Commit 4cca86b9e7131daad20cc6d893f1f27dc324fcfd
1 parent
5f72b336
Exists in
master
and in
67 other branches
ENH: Added interface to image reduction
Showing
4 changed files
with
129 additions
and
39 deletions
Show diff stats
invesalius/control.py
@@ -19,12 +19,12 @@ | @@ -19,12 +19,12 @@ | ||
19 | import math | 19 | import math |
20 | import os | 20 | import os |
21 | import plistlib | 21 | import plistlib |
22 | - | 22 | +import wx |
23 | import numpy | 23 | import numpy |
24 | from wx.lib.pubsub import pub as Publisher | 24 | from wx.lib.pubsub import pub as Publisher |
25 | 25 | ||
26 | import constants as const | 26 | import constants as const |
27 | -import data.imagedata_utils as utils | 27 | +import data.imagedata_utils as image_utils |
28 | import data.mask as msk | 28 | import data.mask as msk |
29 | import data.measures | 29 | import data.measures |
30 | import data.slice_ as sl | 30 | import data.slice_ as sl |
@@ -37,7 +37,7 @@ import reader.dicom_grouper as dg | @@ -37,7 +37,7 @@ import reader.dicom_grouper as dg | ||
37 | import reader.dicom_reader as dcm | 37 | import reader.dicom_reader as dcm |
38 | import session as ses | 38 | import session as ses |
39 | 39 | ||
40 | -from utils import debug | 40 | +import utils |
41 | 41 | ||
42 | DEFAULT_THRESH_MODE = 0 | 42 | DEFAULT_THRESH_MODE = 0 |
43 | 43 | ||
@@ -171,14 +171,14 @@ class Controller(): | @@ -171,14 +171,14 @@ class Controller(): | ||
171 | try: | 171 | try: |
172 | filename = session.project_path[1] | 172 | filename = session.project_path[1] |
173 | except(AttributeError): | 173 | except(AttributeError): |
174 | - debug("Project doesn't exist") | 174 | + utils.debug("Project doesn't exist") |
175 | filename = None | 175 | filename = None |
176 | 176 | ||
177 | if (filename): | 177 | if (filename): |
178 | if (st == const.PROJ_NEW) or (st == const.PROJ_CHANGE): | 178 | if (st == const.PROJ_NEW) or (st == const.PROJ_CHANGE): |
179 | answer = dialog.SaveChangesDialog(filename, self.frame) | 179 | answer = dialog.SaveChangesDialog(filename, self.frame) |
180 | if not answer: | 180 | if not answer: |
181 | - debug("Close without changes") | 181 | + utils.debug("Close without changes") |
182 | self.CloseProject() | 182 | self.CloseProject() |
183 | Publisher.sendMessage("Enable state project", False) | 183 | Publisher.sendMessage("Enable state project", False) |
184 | Publisher.sendMessage('Set project name') | 184 | Publisher.sendMessage('Set project name') |
@@ -186,14 +186,14 @@ class Controller(): | @@ -186,14 +186,14 @@ class Controller(): | ||
186 | Publisher.sendMessage("Exit") | 186 | Publisher.sendMessage("Exit") |
187 | elif answer == 1: | 187 | elif answer == 1: |
188 | self.ShowDialogSaveProject() | 188 | self.ShowDialogSaveProject() |
189 | - debug("Save changes and close") | 189 | + utils.debug("Save changes and close") |
190 | self.CloseProject() | 190 | self.CloseProject() |
191 | Publisher.sendMessage("Enable state project", False) | 191 | Publisher.sendMessage("Enable state project", False) |
192 | Publisher.sendMessage('Set project name') | 192 | Publisher.sendMessage('Set project name') |
193 | Publisher.sendMessage("Stop Config Recording") | 193 | Publisher.sendMessage("Stop Config Recording") |
194 | Publisher.sendMessage("Exit") | 194 | Publisher.sendMessage("Exit") |
195 | elif answer == -1: | 195 | elif answer == -1: |
196 | - debug("Cancel") | 196 | + utils.debug("Cancel") |
197 | else: | 197 | else: |
198 | self.CloseProject() | 198 | self.CloseProject() |
199 | Publisher.sendMessage("Enable state project", False) | 199 | Publisher.sendMessage("Enable state project", False) |
@@ -347,7 +347,7 @@ class Controller(): | @@ -347,7 +347,7 @@ class Controller(): | ||
347 | self.CreateAnalyzeProject(imagedata) | 347 | self.CreateAnalyzeProject(imagedata) |
348 | # OPTION 3: Nothing... | 348 | # OPTION 3: Nothing... |
349 | else: | 349 | else: |
350 | - debug("No medical images found on given directory") | 350 | + utils.debug("No medical images found on given directory") |
351 | return | 351 | return |
352 | self.LoadProject() | 352 | self.LoadProject() |
353 | Publisher.sendMessage("Enable state project", True) | 353 | Publisher.sendMessage("Enable state project", True) |
@@ -420,7 +420,7 @@ class Controller(): | @@ -420,7 +420,7 @@ class Controller(): | ||
420 | #TODO: Verify if all Analyse are in AXIAL orientation | 420 | #TODO: Verify if all Analyse are in AXIAL orientation |
421 | 421 | ||
422 | # To get Z, X, Y (used by InVesaliu), not X, Y, Z | 422 | # To get Z, X, Y (used by InVesaliu), not X, Y, Z |
423 | - matrix, matrix_filename = utils.analyze2mmap(imagedata) | 423 | + matrix, matrix_filename = image_utils.analyze2mmap(imagedata) |
424 | if header['orient'] == 0: | 424 | if header['orient'] == 0: |
425 | proj.original_orientation = const.AXIAL | 425 | proj.original_orientation = const.AXIAL |
426 | elif header['orient'] == 1: | 426 | elif header['orient'] == 1: |
@@ -492,7 +492,7 @@ class Controller(): | @@ -492,7 +492,7 @@ class Controller(): | ||
492 | interval += 1 | 492 | interval += 1 |
493 | filelist = dicom_group.GetFilenameList()[::interval] | 493 | filelist = dicom_group.GetFilenameList()[::interval] |
494 | if not filelist: | 494 | if not filelist: |
495 | - debug("Not used the IPPSorter") | 495 | + utils.debug("Not used the IPPSorter") |
496 | filelist = [i.image.file for i in dicom_group.GetHandSortedList()[::interval]] | 496 | filelist = [i.image.file for i in dicom_group.GetHandSortedList()[::interval]] |
497 | 497 | ||
498 | if file_range != None and file_range[1] > file_range[0]: | 498 | if file_range != None and file_range[1] > file_range[0]: |
@@ -500,9 +500,6 @@ class Controller(): | @@ -500,9 +500,6 @@ class Controller(): | ||
500 | 500 | ||
501 | zspacing = dicom_group.zspacing * interval | 501 | zspacing = dicom_group.zspacing * interval |
502 | 502 | ||
503 | - print "\n=======================================" | ||
504 | - print ">>>>>>>>>>>>>>>>>> zspacing", zspacing, interval | ||
505 | - print "\n=======================================" | ||
506 | size = dicom.image.size | 503 | size = dicom.image.size |
507 | bits = dicom.image.bits_allocad | 504 | bits = dicom.image.bits_allocad |
508 | sop_class_uid = dicom.acquisition.sop_class_uid | 505 | sop_class_uid = dicom.acquisition.sop_class_uid |
@@ -514,16 +511,30 @@ class Controller(): | @@ -514,16 +511,30 @@ class Controller(): | ||
514 | else: | 511 | else: |
515 | use_dcmspacing = 0 | 512 | use_dcmspacing = 0 |
516 | 513 | ||
517 | - #imagedata = utils.CreateImageData(filelist, zspacing, xyspacing,size, | ||
518 | - #bits, use_dcmspacing) | ||
519 | - | ||
520 | imagedata = None | 514 | imagedata = None |
515 | + | ||
516 | + sx, sy = size | ||
517 | + n_slices = len(filelist) | ||
518 | + resolution_percentage = utils.calculate_resizing_tofitmemory(int(sx), int(sy), n_slices, bits/8) | ||
519 | + | ||
520 | + if resolution_percentage < 1.0: | ||
521 | + re_dialog = dialog.ResizeImageDialog() | ||
522 | + re_dialog.SetValue(int(resolution_percentage*100)) | ||
523 | + re_dialog_value = re_dialog.ShowModal() | ||
524 | + | ||
525 | + if re_dialog_value == wx.ID_OK: | ||
526 | + percentage = re_dialog.GetValue() | ||
527 | + resolution_percentage = percentage / 100.0 | ||
528 | + else: | ||
529 | + return | ||
521 | 530 | ||
522 | - | 531 | + xyspacing = xyspacing[0] / resolution_percentage, xyspacing[1] / resolution_percentage |
532 | + | ||
533 | + | ||
523 | wl = float(dicom.image.level) | 534 | wl = float(dicom.image.level) |
524 | ww = float(dicom.image.window) | 535 | ww = float(dicom.image.window) |
525 | - self.matrix, scalar_range, self.filename = utils.dcm2memmap(filelist, size, | ||
526 | - orientation) | 536 | + self.matrix, scalar_range, self.filename = image_utils.dcm2memmap(filelist, size, |
537 | + orientation, resolution_percentage) | ||
527 | 538 | ||
528 | self.Slice = sl.Slice() | 539 | self.Slice = sl.Slice() |
529 | self.Slice.matrix = self.matrix | 540 | self.Slice.matrix = self.matrix |
@@ -543,10 +554,10 @@ class Controller(): | @@ -543,10 +554,10 @@ class Controller(): | ||
543 | message = _("Fix gantry tilt applying the degrees below") | 554 | message = _("Fix gantry tilt applying the degrees below") |
544 | value = -1*tilt_value | 555 | value = -1*tilt_value |
545 | tilt_value = dialog.ShowNumberDialog(message, value) | 556 | tilt_value = dialog.ShowNumberDialog(message, value) |
546 | - utils.FixGantryTilt(self.matrix, self.Slice.spacing, tilt_value) | 557 | + image_utils.FixGantryTilt(self.matrix, self.Slice.spacing, tilt_value) |
547 | elif (tilt_value) and not (gui): | 558 | elif (tilt_value) and not (gui): |
548 | tilt_value = -1*tilt_value | 559 | tilt_value = -1*tilt_value |
549 | - utils.FixGantryTilt(self.matrix, self.Slice.spacing, tilt_value) | 560 | + image_utils.FixGantryTilt(self.matrix, self.Slice.spacing, tilt_value) |
550 | 561 | ||
551 | self.Slice.window_level = wl | 562 | self.Slice.window_level = wl |
552 | self.Slice.window_width = ww | 563 | self.Slice.window_width = ww |
invesalius/data/imagedata_utils.py
@@ -57,21 +57,19 @@ def ResampleImage3D(imagedata, value): | @@ -57,21 +57,19 @@ def ResampleImage3D(imagedata, value): | ||
57 | 57 | ||
58 | return resample.GetOutput() | 58 | return resample.GetOutput() |
59 | 59 | ||
60 | -def ResampleImage2D(imagedata, px, py, | 60 | +def ResampleImage2D(imagedata, px=None, py=None, resolution_percentage = None, |
61 | update_progress = None): | 61 | update_progress = None): |
62 | """ | 62 | """ |
63 | Resample vtkImageData matrix. | 63 | Resample vtkImageData matrix. |
64 | """ | 64 | """ |
65 | + | ||
65 | extent = imagedata.GetExtent() | 66 | extent = imagedata.GetExtent() |
66 | spacing = imagedata.GetSpacing() | 67 | spacing = imagedata.GetSpacing() |
68 | + dimensions = imagedata.GetDimensions() | ||
67 | 69 | ||
68 | - | ||
69 | - #if extent[1]==extent[3]: | ||
70 | - # f = extent[1] | ||
71 | - #elif extent[1]==extent[5]: | ||
72 | - # f = extent[1] | ||
73 | - #elif extent[3]==extent[5]: | ||
74 | - # f = extent[3] | 70 | + if resolution_percentage: |
71 | + px = math.ceil(dimensions[0] * resolution_percentage) | ||
72 | + py = math.ceil(dimensions[1] * resolution_percentage) | ||
75 | 73 | ||
76 | if abs(extent[1]-extent[3]) < abs(extent[3]-extent[5]): | 74 | if abs(extent[1]-extent[3]) < abs(extent[3]-extent[5]): |
77 | f = extent[1] | 75 | f = extent[1] |
@@ -418,7 +416,7 @@ class ImageCreator: | @@ -418,7 +416,7 @@ class ImageCreator: | ||
418 | 416 | ||
419 | return imagedata | 417 | return imagedata |
420 | 418 | ||
421 | -def dcm2memmap(files, slice_size, orientation): | 419 | +def dcm2memmap(files, slice_size, orientation, resolution_percentage): |
422 | """ | 420 | """ |
423 | From a list of dicom files it creates memmap file in the temp folder and | 421 | From a list of dicom files it creates memmap file in the temp folder and |
424 | returns it and its related filename. | 422 | returns it and its related filename. |
@@ -429,21 +427,43 @@ def dcm2memmap(files, slice_size, orientation): | @@ -429,21 +427,43 @@ def dcm2memmap(files, slice_size, orientation): | ||
429 | temp_file = tempfile.mktemp() | 427 | temp_file = tempfile.mktemp() |
430 | 428 | ||
431 | if orientation == 'SAGITTAL': | 429 | if orientation == 'SAGITTAL': |
432 | - shape = slice_size[0], slice_size[1], len(files) | 430 | + if resolution_percentage == 1.0: |
431 | + shape = slice_size[0], slice_size[1], len(files) | ||
432 | + else: | ||
433 | + shape = math.ceil(slice_size[0]*resolution_percentage),\ | ||
434 | + math.ceil(slice_size[1]*resolution_percentage), len(files) | ||
435 | + | ||
433 | elif orientation == 'CORONAL': | 436 | elif orientation == 'CORONAL': |
434 | - shape = slice_size[1], len(files), slice_size[0] | 437 | + if resolution_percentage == 1.0: |
438 | + shape = slice_size[1], len(files), slice_size[0] | ||
439 | + else: | ||
440 | + shape = math.ceil(slice_size[1]*resolution_percentage), len(files),\ | ||
441 | + math.ceil(slice_size[0]*resolution_percentage) | ||
435 | else: | 442 | else: |
436 | - shape = len(files), slice_size[1], slice_size[0] | 443 | + if resolution_percentage == 1.0: |
444 | + shape = len(files), slice_size[1], slice_size[0] | ||
445 | + else: | ||
446 | + shape = len(files), math.ceil(slice_size[1]*resolution_percentage),\ | ||
447 | + math.ceil(slice_size[0]*resolution_percentage) | ||
448 | + | ||
437 | matrix = numpy.memmap(temp_file, mode='w+', dtype='int16', shape=shape) | 449 | matrix = numpy.memmap(temp_file, mode='w+', dtype='int16', shape=shape) |
438 | dcm_reader = vtkgdcm.vtkGDCMImageReader() | 450 | dcm_reader = vtkgdcm.vtkGDCMImageReader() |
439 | cont = 0 | 451 | cont = 0 |
440 | max_scalar = None | 452 | max_scalar = None |
441 | min_scalar = None | 453 | min_scalar = None |
454 | + | ||
442 | for n, f in enumerate(files): | 455 | for n, f in enumerate(files): |
443 | dcm_reader.SetFileName(f) | 456 | dcm_reader.SetFileName(f) |
444 | dcm_reader.Update() | 457 | dcm_reader.Update() |
445 | image = dcm_reader.GetOutput() | 458 | image = dcm_reader.GetOutput() |
446 | 459 | ||
460 | + if resolution_percentage != 1.0: | ||
461 | + image_resized = ResampleImage2D(image, px=None, py=None,\ | ||
462 | + resolution_percentage = resolution_percentage, update_progress = None) | ||
463 | + | ||
464 | + image = image_resized | ||
465 | + print ">>>>>>>>>", image.GetDimensions() | ||
466 | + | ||
447 | min_aux, max_aux = image.GetScalarRange() | 467 | min_aux, max_aux = image.GetScalarRange() |
448 | if min_scalar is None or min_aux < min_scalar: | 468 | if min_scalar is None or min_aux < min_scalar: |
449 | min_scalar = min_aux | 469 | min_scalar = min_aux |
invesalius/gui/dialogs.py
@@ -82,6 +82,65 @@ class NumberDialog(wx.Dialog): | @@ -82,6 +82,65 @@ class NumberDialog(wx.Dialog): | ||
82 | def GetValue(self): | 82 | def GetValue(self): |
83 | return self.num_ctrl.GetValue() | 83 | return self.num_ctrl.GetValue() |
84 | 84 | ||
85 | + | ||
86 | +class ResizeImageDialog(wx.Dialog): | ||
87 | + | ||
88 | + def __init__(self):#, message, value=0): | ||
89 | + pre = wx.PreDialog() | ||
90 | + pre.Create(None, -1, "InVesalius 3", size=wx.DefaultSize, | ||
91 | + pos=wx.DefaultPosition, | ||
92 | + style=wx.DEFAULT_DIALOG_STYLE) | ||
93 | + self.PostCreate(pre) | ||
94 | + | ||
95 | + lbl_message = wx.StaticText(self, -1, _("Your operational system is 32bits or have \nlow memory. It's recommended to reduce the image resolution.")) | ||
96 | + | ||
97 | + icon = wx.ArtProvider.GetBitmap(wx.ART_WARNING, wx.ART_MESSAGE_BOX, (32,32)) | ||
98 | + bmp = wx.StaticBitmap(self, -1, icon) | ||
99 | + | ||
100 | + btn_ok = wx.Button(self, wx.ID_OK) | ||
101 | + btn_ok.SetHelpText("Value will be applied.") | ||
102 | + btn_ok.SetDefault() | ||
103 | + | ||
104 | + btn_cancel = wx.Button(self, wx.ID_CANCEL) | ||
105 | + btn_cancel.SetHelpText("Value will not be applied.") | ||
106 | + | ||
107 | + btn_sizer = wx.StdDialogButtonSizer() | ||
108 | + btn_sizer.AddButton(btn_ok) | ||
109 | + btn_sizer.AddButton(btn_cancel) | ||
110 | + btn_sizer.Realize() | ||
111 | + | ||
112 | + lbl_message_percent = wx.StaticText(self, -1, _("Resolution percentage")) | ||
113 | + | ||
114 | + num_ctrl_percent = wx.SpinCtrl(self, -1, "", (30, 50)) | ||
115 | + num_ctrl_percent.SetRange(20,100) | ||
116 | + self.num_ctrl_porcent = num_ctrl_percent | ||
117 | + | ||
118 | + sizer_percent = wx.BoxSizer(wx.HORIZONTAL) | ||
119 | + sizer_percent.Add(lbl_message_percent, 0, wx.ALIGN_CENTRE|wx.ALL, 5) | ||
120 | + sizer_percent.Add(num_ctrl_percent, 0, wx.ALIGN_CENTRE|wx.ALL, 5) | ||
121 | + | ||
122 | + sizer_itens = wx.BoxSizer(wx.VERTICAL) | ||
123 | + sizer_itens.Add(lbl_message, 0, wx.ALIGN_CENTRE_VERTICAL|wx.ALL|wx.EXPAND, 5) | ||
124 | + sizer_itens.AddSizer(sizer_percent, 0, wx.ALIGN_CENTRE|wx.ALL, 5) | ||
125 | + sizer_itens.Add(btn_sizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 25) | ||
126 | + | ||
127 | + sizer_general = wx.BoxSizer(wx.HORIZONTAL) | ||
128 | + sizer_general.Add(bmp, 0, wx.ALIGN_CENTRE|wx.ALL, 15) | ||
129 | + sizer_general.AddSizer(sizer_itens, 90, wx.ALIGN_CENTRE|wx.ALL, 10) | ||
130 | + | ||
131 | + self.SetSizer(sizer_general) | ||
132 | + sizer_itens.Fit(self) | ||
133 | + self.Layout() | ||
134 | + self.SetAutoLayout(True) | ||
135 | + self.Centre() | ||
136 | + | ||
137 | + def SetValue(self, value): | ||
138 | + self.num_ctrl_porcent.SetValue(value) | ||
139 | + | ||
140 | + def GetValue(self): | ||
141 | + return self.num_ctrl_porcent.GetValue() | ||
142 | + | ||
143 | + | ||
85 | def ShowNumberDialog(message, value=0): | 144 | def ShowNumberDialog(message, value=0): |
86 | dlg = NumberDialog(message, value) | 145 | dlg = NumberDialog(message, value) |
87 | dlg.SetValue(value) | 146 | dlg.SetValue(value) |
invesalius/utils.py
@@ -213,7 +213,7 @@ def calculate_resizing_tofitmemory(x_size,y_size,n_slices,byte): | @@ -213,7 +213,7 @@ def calculate_resizing_tofitmemory(x_size,y_size,n_slices,byte): | ||
213 | n_slices: number of slices | 213 | n_slices: number of slices |
214 | byte: bytes allocated for each pixel sample | 214 | byte: bytes allocated for each pixel sample |
215 | """ | 215 | """ |
216 | - imagesize = x_size * y_size * n_slices * byte | 216 | + imagesize = x_size * y_size * n_slices * byte * 17 |
217 | 217 | ||
218 | sg = sigar.open() | 218 | sg = sigar.open() |
219 | ram_free = sg.mem().actual_free() | 219 | ram_free = sg.mem().actual_free() |
@@ -221,6 +221,9 @@ def calculate_resizing_tofitmemory(x_size,y_size,n_slices,byte): | @@ -221,6 +221,9 @@ def calculate_resizing_tofitmemory(x_size,y_size,n_slices,byte): | ||
221 | swap_free = sg.swap().free() | 221 | swap_free = sg.swap().free() |
222 | sg.close() | 222 | sg.close() |
223 | 223 | ||
224 | + print "RAM FREE", ram_free | ||
225 | + print "RAM_TOTAL", ram_total | ||
226 | + | ||
224 | if (sys.platform == 'win32'): | 227 | if (sys.platform == 'win32'): |
225 | if (platform.architecture()[0] == '32bit'): | 228 | if (platform.architecture()[0] == '32bit'): |
226 | if ram_free>1400000000: | 229 | if ram_free>1400000000: |
@@ -237,14 +240,11 @@ def calculate_resizing_tofitmemory(x_size,y_size,n_slices,byte): | @@ -237,14 +240,11 @@ def calculate_resizing_tofitmemory(x_size,y_size,n_slices,byte): | ||
237 | 240 | ||
238 | if (swap_free>ram_total): | 241 | if (swap_free>ram_total): |
239 | swap_free=ram_total | 242 | swap_free=ram_total |
240 | - resize = (float((ram_free+0.5*swap_free)/imagesize)) | 243 | + resize = (float((ram_free+0.5*swap_free)/imagesize)) |
241 | resize=math.sqrt(resize) # this gives the "resize" for each axis x and y | 244 | resize=math.sqrt(resize) # this gives the "resize" for each axis x and y |
242 | if (resize>1): | 245 | if (resize>1): |
243 | resize=1 | 246 | resize=1 |
244 | - return (100*resize) | ||
245 | - | ||
246 | - | ||
247 | - | 247 | + return round(resize,2) |
248 | 248 | ||
249 | 249 | ||
250 | def predict_memory(nfiles, x, y, p): | 250 | def predict_memory(nfiles, x, y, p): |