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): |