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 | 19 | import math |
| 20 | 20 | import os |
| 21 | 21 | import plistlib |
| 22 | - | |
| 22 | +import wx | |
| 23 | 23 | import numpy |
| 24 | 24 | from wx.lib.pubsub import pub as Publisher |
| 25 | 25 | |
| 26 | 26 | import constants as const |
| 27 | -import data.imagedata_utils as utils | |
| 27 | +import data.imagedata_utils as image_utils | |
| 28 | 28 | import data.mask as msk |
| 29 | 29 | import data.measures |
| 30 | 30 | import data.slice_ as sl |
| ... | ... | @@ -37,7 +37,7 @@ import reader.dicom_grouper as dg |
| 37 | 37 | import reader.dicom_reader as dcm |
| 38 | 38 | import session as ses |
| 39 | 39 | |
| 40 | -from utils import debug | |
| 40 | +import utils | |
| 41 | 41 | |
| 42 | 42 | DEFAULT_THRESH_MODE = 0 |
| 43 | 43 | |
| ... | ... | @@ -171,14 +171,14 @@ class Controller(): |
| 171 | 171 | try: |
| 172 | 172 | filename = session.project_path[1] |
| 173 | 173 | except(AttributeError): |
| 174 | - debug("Project doesn't exist") | |
| 174 | + utils.debug("Project doesn't exist") | |
| 175 | 175 | filename = None |
| 176 | 176 | |
| 177 | 177 | if (filename): |
| 178 | 178 | if (st == const.PROJ_NEW) or (st == const.PROJ_CHANGE): |
| 179 | 179 | answer = dialog.SaveChangesDialog(filename, self.frame) |
| 180 | 180 | if not answer: |
| 181 | - debug("Close without changes") | |
| 181 | + utils.debug("Close without changes") | |
| 182 | 182 | self.CloseProject() |
| 183 | 183 | Publisher.sendMessage("Enable state project", False) |
| 184 | 184 | Publisher.sendMessage('Set project name') |
| ... | ... | @@ -186,14 +186,14 @@ class Controller(): |
| 186 | 186 | Publisher.sendMessage("Exit") |
| 187 | 187 | elif answer == 1: |
| 188 | 188 | self.ShowDialogSaveProject() |
| 189 | - debug("Save changes and close") | |
| 189 | + utils.debug("Save changes and close") | |
| 190 | 190 | self.CloseProject() |
| 191 | 191 | Publisher.sendMessage("Enable state project", False) |
| 192 | 192 | Publisher.sendMessage('Set project name') |
| 193 | 193 | Publisher.sendMessage("Stop Config Recording") |
| 194 | 194 | Publisher.sendMessage("Exit") |
| 195 | 195 | elif answer == -1: |
| 196 | - debug("Cancel") | |
| 196 | + utils.debug("Cancel") | |
| 197 | 197 | else: |
| 198 | 198 | self.CloseProject() |
| 199 | 199 | Publisher.sendMessage("Enable state project", False) |
| ... | ... | @@ -347,7 +347,7 @@ class Controller(): |
| 347 | 347 | self.CreateAnalyzeProject(imagedata) |
| 348 | 348 | # OPTION 3: Nothing... |
| 349 | 349 | else: |
| 350 | - debug("No medical images found on given directory") | |
| 350 | + utils.debug("No medical images found on given directory") | |
| 351 | 351 | return |
| 352 | 352 | self.LoadProject() |
| 353 | 353 | Publisher.sendMessage("Enable state project", True) |
| ... | ... | @@ -420,7 +420,7 @@ class Controller(): |
| 420 | 420 | #TODO: Verify if all Analyse are in AXIAL orientation |
| 421 | 421 | |
| 422 | 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 | 424 | if header['orient'] == 0: |
| 425 | 425 | proj.original_orientation = const.AXIAL |
| 426 | 426 | elif header['orient'] == 1: |
| ... | ... | @@ -492,7 +492,7 @@ class Controller(): |
| 492 | 492 | interval += 1 |
| 493 | 493 | filelist = dicom_group.GetFilenameList()[::interval] |
| 494 | 494 | if not filelist: |
| 495 | - debug("Not used the IPPSorter") | |
| 495 | + utils.debug("Not used the IPPSorter") | |
| 496 | 496 | filelist = [i.image.file for i in dicom_group.GetHandSortedList()[::interval]] |
| 497 | 497 | |
| 498 | 498 | if file_range != None and file_range[1] > file_range[0]: |
| ... | ... | @@ -500,9 +500,6 @@ class Controller(): |
| 500 | 500 | |
| 501 | 501 | zspacing = dicom_group.zspacing * interval |
| 502 | 502 | |
| 503 | - print "\n=======================================" | |
| 504 | - print ">>>>>>>>>>>>>>>>>> zspacing", zspacing, interval | |
| 505 | - print "\n=======================================" | |
| 506 | 503 | size = dicom.image.size |
| 507 | 504 | bits = dicom.image.bits_allocad |
| 508 | 505 | sop_class_uid = dicom.acquisition.sop_class_uid |
| ... | ... | @@ -514,16 +511,30 @@ class Controller(): |
| 514 | 511 | else: |
| 515 | 512 | use_dcmspacing = 0 |
| 516 | 513 | |
| 517 | - #imagedata = utils.CreateImageData(filelist, zspacing, xyspacing,size, | |
| 518 | - #bits, use_dcmspacing) | |
| 519 | - | |
| 520 | 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 | 534 | wl = float(dicom.image.level) |
| 524 | 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 | 539 | self.Slice = sl.Slice() |
| 529 | 540 | self.Slice.matrix = self.matrix |
| ... | ... | @@ -543,10 +554,10 @@ class Controller(): |
| 543 | 554 | message = _("Fix gantry tilt applying the degrees below") |
| 544 | 555 | value = -1*tilt_value |
| 545 | 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 | 558 | elif (tilt_value) and not (gui): |
| 548 | 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 | 562 | self.Slice.window_level = wl |
| 552 | 563 | self.Slice.window_width = ww | ... | ... |
invesalius/data/imagedata_utils.py
| ... | ... | @@ -57,21 +57,19 @@ def ResampleImage3D(imagedata, value): |
| 57 | 57 | |
| 58 | 58 | return resample.GetOutput() |
| 59 | 59 | |
| 60 | -def ResampleImage2D(imagedata, px, py, | |
| 60 | +def ResampleImage2D(imagedata, px=None, py=None, resolution_percentage = None, | |
| 61 | 61 | update_progress = None): |
| 62 | 62 | """ |
| 63 | 63 | Resample vtkImageData matrix. |
| 64 | 64 | """ |
| 65 | + | |
| 65 | 66 | extent = imagedata.GetExtent() |
| 66 | 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 | 74 | if abs(extent[1]-extent[3]) < abs(extent[3]-extent[5]): |
| 77 | 75 | f = extent[1] |
| ... | ... | @@ -418,7 +416,7 @@ class ImageCreator: |
| 418 | 416 | |
| 419 | 417 | return imagedata |
| 420 | 418 | |
| 421 | -def dcm2memmap(files, slice_size, orientation): | |
| 419 | +def dcm2memmap(files, slice_size, orientation, resolution_percentage): | |
| 422 | 420 | """ |
| 423 | 421 | From a list of dicom files it creates memmap file in the temp folder and |
| 424 | 422 | returns it and its related filename. |
| ... | ... | @@ -429,21 +427,43 @@ def dcm2memmap(files, slice_size, orientation): |
| 429 | 427 | temp_file = tempfile.mktemp() |
| 430 | 428 | |
| 431 | 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 | 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 | 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 | 449 | matrix = numpy.memmap(temp_file, mode='w+', dtype='int16', shape=shape) |
| 438 | 450 | dcm_reader = vtkgdcm.vtkGDCMImageReader() |
| 439 | 451 | cont = 0 |
| 440 | 452 | max_scalar = None |
| 441 | 453 | min_scalar = None |
| 454 | + | |
| 442 | 455 | for n, f in enumerate(files): |
| 443 | 456 | dcm_reader.SetFileName(f) |
| 444 | 457 | dcm_reader.Update() |
| 445 | 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 | 467 | min_aux, max_aux = image.GetScalarRange() |
| 448 | 468 | if min_scalar is None or min_aux < min_scalar: |
| 449 | 469 | min_scalar = min_aux | ... | ... |
invesalius/gui/dialogs.py
| ... | ... | @@ -82,6 +82,65 @@ class NumberDialog(wx.Dialog): |
| 82 | 82 | def GetValue(self): |
| 83 | 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 | 144 | def ShowNumberDialog(message, value=0): |
| 86 | 145 | dlg = NumberDialog(message, value) |
| 87 | 146 | dlg.SetValue(value) | ... | ... |
invesalius/utils.py
| ... | ... | @@ -213,7 +213,7 @@ def calculate_resizing_tofitmemory(x_size,y_size,n_slices,byte): |
| 213 | 213 | n_slices: number of slices |
| 214 | 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 | 218 | sg = sigar.open() |
| 219 | 219 | ram_free = sg.mem().actual_free() |
| ... | ... | @@ -221,6 +221,9 @@ def calculate_resizing_tofitmemory(x_size,y_size,n_slices,byte): |
| 221 | 221 | swap_free = sg.swap().free() |
| 222 | 222 | sg.close() |
| 223 | 223 | |
| 224 | + print "RAM FREE", ram_free | |
| 225 | + print "RAM_TOTAL", ram_total | |
| 226 | + | |
| 224 | 227 | if (sys.platform == 'win32'): |
| 225 | 228 | if (platform.architecture()[0] == '32bit'): |
| 226 | 229 | if ram_free>1400000000: |
| ... | ... | @@ -237,14 +240,11 @@ def calculate_resizing_tofitmemory(x_size,y_size,n_slices,byte): |
| 237 | 240 | |
| 238 | 241 | if (swap_free>ram_total): |
| 239 | 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 | 244 | resize=math.sqrt(resize) # this gives the "resize" for each axis x and y |
| 242 | 245 | if (resize>1): |
| 243 | 246 | resize=1 |
| 244 | - return (100*resize) | |
| 245 | - | |
| 246 | - | |
| 247 | - | |
| 247 | + return round(resize,2) | |
| 248 | 248 | |
| 249 | 249 | |
| 250 | 250 | def predict_memory(nfiles, x, y, p): | ... | ... |