Commit 4cca86b9e7131daad20cc6d893f1f27dc324fcfd

Authored by Paulo Henrique Junqueira Amorim
1 parent 5f72b336

ENH: Added interface to image reduction

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