Commit 00952d8a152e7758dccbbc8d1cc2d5cde54fefeb

Authored by tfmoraes
1 parent 390a84c8

Generating surface again, from binary (if the mask was edited) or the original.

When the user tries to generates a surface from a edited mask, the user
is aske about the method to generate the surface: Binary, Context aware
smoothing or invesalius3.b2

Squashed commit of the following:

commit 480c88b680a9d79c977d3a8b81b9b25114dad43b
Author: Thiago Franco de Moraes <totonixsame@gmail.com>
Date:   Tue Mar 20 10:56:41 2012 -0300

    Not saving the project when a new case is opened

commit 1233e37982783412fd1a092820ddb55f60c7df79
Author: Thiago Franco de Moraes <totonixsame@gmail.com>
Date:   Tue Mar 20 10:51:04 2012 -0300

    Generating surface from edited masks, the user can select to use the context aware smoothing or be a binary

commit e722ea910db2931b866dfa647fcb5cc3a80d5675
Author: Thiago Franco de Moraes <totonixsame@gmail.com>
Date:   Tue Mar 20 10:47:10 2012 -0300

    Added a new dialog related to the generation surface whose mask was edited
invesalius/control.py
@@ -464,7 +464,7 @@ class Controller(): @@ -464,7 +464,7 @@ class Controller():
464 filename = filename.replace("/", "") #Fix problem case other/Skull_DICOM 464 filename = filename.replace("/", "") #Fix problem case other/Skull_DICOM
465 465
466 dirpath = session.CreateProject(filename) 466 dirpath = session.CreateProject(filename)
467 - proj.SavePlistProject(dirpath, filename) 467 + #proj.SavePlistProject(dirpath, filename)
468 468
469 def OnOpenDicomGroup(self, pubsub_evt): 469 def OnOpenDicomGroup(self, pubsub_evt):
470 group, interval, file_range = pubsub_evt.data 470 group, interval, file_range = pubsub_evt.data
invesalius/data/mask.py
@@ -42,6 +42,7 @@ class Mask(): @@ -42,6 +42,7 @@ class Mask():
42 self.edition_threshold_range = [const.THRESHOLD_OUTVALUE, const.THRESHOLD_INVALUE] 42 self.edition_threshold_range = [const.THRESHOLD_OUTVALUE, const.THRESHOLD_INVALUE]
43 self.is_shown = 1 43 self.is_shown = 1
44 self.edited_points = {} 44 self.edited_points = {}
  45 + self.was_edited = False
45 46
46 def SavePlist(self, filename): 47 def SavePlist(self, filename):
47 mask = {} 48 mask = {}
invesalius/data/slice_.py
@@ -285,6 +285,7 @@ class Slice(object): @@ -285,6 +285,7 @@ class Slice(object):
285 mask = self.buffer_slices[orientation].mask 285 mask = self.buffer_slices[orientation].mask
286 image = self.buffer_slices[orientation].image 286 image = self.buffer_slices[orientation].image
287 thresh_min, thresh_max = self.current_mask.edition_threshold_range 287 thresh_min, thresh_max = self.current_mask.edition_threshold_range
  288 + self.current_mask.was_edited = True
288 289
289 if hasattr(position, '__iter__'): 290 if hasattr(position, '__iter__'):
290 py, px = position 291 py, px = position
@@ -515,7 +516,9 @@ class Slice(object): @@ -515,7 +516,9 @@ class Slice(object):
515 If slice_number is None then all the threshold is calculated for all 516 If slice_number is None then all the threshold is calculated for all
516 slices, otherwise only to indicated slice. 517 slices, otherwise only to indicated slice.
517 """ 518 """
  519 + self.current_mask.was_edited = False
518 thresh_min, thresh_max = threshold_range 520 thresh_min, thresh_max = threshold_range
  521 + print "Threshold"
519 522
520 if self.current_mask.index == index: 523 if self.current_mask.index == index:
521 # TODO: find out a better way to do threshold 524 # TODO: find out a better way to do threshold
@@ -588,23 +591,26 @@ class Slice(object): @@ -588,23 +591,26 @@ class Slice(object):
588 #--------------------------------------------------------------------------- 591 #---------------------------------------------------------------------------
589 592
590 def CreateSurfaceFromIndex(self, pubsub_evt): 593 def CreateSurfaceFromIndex(self, pubsub_evt):
591 - mask_index, overwrite_surface = pubsub_evt.data  
592 - 594 + mask_index, overwrite_surface, algorithm, options = pubsub_evt.data
593 595
594 proj = Project() 596 proj = Project()
595 mask = proj.mask_dict[mask_index] 597 mask = proj.mask_dict[mask_index]
596 598
597 # This is very important. Do not use masks' imagedata. It would mess up 599 # This is very important. Do not use masks' imagedata. It would mess up
598 # surface quality event when using contour 600 # surface quality event when using contour
599 - colour = mask.colour  
600 - threshold = mask.threshold_range  
601 - edited_points = mask.edited_points  
602 -  
603 - self.SetMaskThreshold(mask.index, threshold) 601 + #self.SetMaskThreshold(mask.index, threshold)
  602 + for n in xrange(1, mask.matrix.shape[0]):
  603 + if mask.matrix[n, 0, 0] == 0:
  604 + m = mask.matrix[n, 1:, 1:]
  605 + mask.matrix[n, 1:, 1:] = self.do_threshold_to_a_slice(self.matrix[n-1], m)
604 606
605 mask.matrix.flush() 607 mask.matrix.flush()
606 608
607 - ps.Publisher().sendMessage('Create surface', (self.matrix, self.matrix_filename, mask, self.spacing)) 609 + ps.Publisher().sendMessage('Create surface', (algorithm, options,
  610 + self.matrix,
  611 + self.matrix_filename,
  612 + mask, self.spacing,
  613 + overwrite_surface))
608 614
609 def GetOutput(self): 615 def GetOutput(self):
610 return self.blend_filter.GetOutput() 616 return self.blend_filter.GetOutput()
invesalius/data/surface.py
@@ -21,8 +21,6 @@ import multiprocessing @@ -21,8 +21,6 @@ import multiprocessing
21 import os 21 import os
22 import plistlib 22 import plistlib
23 import random 23 import random
24 -import sys  
25 -import tempfile  
26 24
27 import vtk 25 import vtk
28 import wx.lib.pubsub as ps 26 import wx.lib.pubsub as ps
@@ -35,6 +33,7 @@ import session as ses @@ -35,6 +33,7 @@ import session as ses
35 import surface_process 33 import surface_process
36 import utils as utl 34 import utils as utl
37 import vtk_utils as vu 35 import vtk_utils as vu
  36 +import data.ca_smoothing as ca_smoothing
38 37
39 class Surface(): 38 class Surface():
40 """ 39 """
@@ -363,9 +362,11 @@ class SurfaceManager(): @@ -363,9 +362,11 @@ class SurfaceManager():
363 """ 362 """
364 Create surface actor, save into project and send it to viewer. 363 Create surface actor, save into project and send it to viewer.
365 """ 364 """
366 - matrix, filename_img, mask, spacing = pubsub_evt.data 365 + algorithm, options, matrix, filename_img, mask, spacing, overwrite = pubsub_evt.data
367 min_value, max_value = mask.threshold_range 366 min_value, max_value = mask.threshold_range
368 - fill_holes = True 367 + fill_holes = False
  368 +
  369 + mask.matrix.flush()
369 370
370 #if len(surface_data) == 5: 371 #if len(surface_data) == 5:
371 #imagedata, colour, [min_value, max_value], \ 372 #imagedata, colour, [min_value, max_value], \
@@ -382,7 +383,7 @@ class SurfaceManager(): @@ -382,7 +383,7 @@ class SurfaceManager():
382 383
383 mode = 'CONTOUR' # 'GRAYSCALE' 384 mode = 'CONTOUR' # 'GRAYSCALE'
384 quality=_('Optimal *') 385 quality=_('Optimal *')
385 - keep_largest = True 386 + keep_largest = False
386 surface_name = "" 387 surface_name = ""
387 colour = mask.colour 388 colour = mask.colour
388 389
@@ -411,8 +412,6 @@ class SurfaceManager(): @@ -411,8 +412,6 @@ class SurfaceManager():
411 412
412 language = ses.Session().language 413 language = ses.Session().language
413 414
414 - overwrite = 0  
415 -  
416 if (prj.Project().original_orientation == const.CORONAL): 415 if (prj.Project().original_orientation == const.CORONAL):
417 flip_image = False 416 flip_image = False
418 else: 417 else:
@@ -422,7 +421,7 @@ class SurfaceManager(): @@ -422,7 +421,7 @@ class SurfaceManager():
422 421
423 pipe_in, pipe_out = multiprocessing.Pipe() 422 pipe_in, pipe_out = multiprocessing.Pipe()
424 o_piece = 1 423 o_piece = 1
425 - piece_size = 20 424 + piece_size = 2000
426 425
427 n_pieces = int(round(matrix.shape[0] / piece_size + 0.5, 0)) 426 n_pieces = int(round(matrix.shape[0] / piece_size + 0.5, 0))
428 427
@@ -432,10 +431,18 @@ class SurfaceManager(): @@ -432,10 +431,18 @@ class SurfaceManager():
432 p = [] 431 p = []
433 for i in xrange(n_processors): 432 for i in xrange(n_processors):
434 sp = surface_process.SurfaceProcess(pipe_in, filename_img, 433 sp = surface_process.SurfaceProcess(pipe_in, filename_img,
435 - matrix.shape, matrix.dtype, spacing,  
436 - mode, min_value, max_value,  
437 - decimate_reduction, smooth_relaxation_factor,  
438 - smooth_iterations, language, flip_image, q_in, q_out) 434 + matrix.shape, matrix.dtype,
  435 + mask.temp_file,
  436 + mask.matrix.shape,
  437 + mask.matrix.dtype,
  438 + spacing,
  439 + mode, min_value, max_value,
  440 + decimate_reduction,
  441 + smooth_relaxation_factor,
  442 + smooth_iterations, language,
  443 + flip_image, q_in, q_out,
  444 + mask.was_edited and algorithm != u'InVesalius 3.b2',
  445 + algorithm)
439 p.append(sp) 446 p.append(sp)
440 sp.start() 447 sp.start()
441 448
@@ -471,28 +478,65 @@ class SurfaceManager(): @@ -471,28 +478,65 @@ class SurfaceManager():
471 polydata_append.AddInput(reader.GetOutput()) 478 polydata_append.AddInput(reader.GetOutput())
472 t -= 1 479 t -= 1
473 480
  481 + polydata_append.Update()
474 polydata = polydata_append.GetOutput() 482 polydata = polydata_append.GetOutput()
475 - clean = vtk.vtkCleanPolyData()  
476 - clean.AddObserver("ProgressEvent", lambda obj,evt:  
477 - UpdateProgress(obj, _("Generating 3D surface...")))  
478 - clean.SetInput(polydata)  
479 - clean.PointMergingOn()  
480 - clean.Update()  
481 - polydata = clean.GetOutput()  
482 483
483 - smoother = vtk.vtkWindowedSincPolyDataFilter()  
484 - smoother.AddObserver("ProgressEvent", lambda obj,evt:  
485 - UpdateProgress(obj, _("Generating 3D surface...")))  
486 - smoother.SetInput(polydata)  
487 - smoother.SetNumberOfIterations(smooth_iterations)  
488 - smoother.SetFeatureAngle(120)  
489 - smoother.BoundarySmoothingOn()  
490 - smoother.SetPassBand(0.1)  
491 - #smoother.FeatureEdgeSmoothingOn()  
492 - #smoother.NonManifoldSmoothingOn()  
493 - #smoother.NormalizeCoordinatesOn()  
494 - smoother.Update()  
495 - polydata = smoother.GetOutput() 484 + if algorithm == u'Context aware smoothing':
  485 + normals = vtk.vtkPolyDataNormals()
  486 + normals.AddObserver("ProgressEvent", lambda obj,evt:
  487 + UpdateProgress(obj, _("Generating 3D surface...")))
  488 + normals.SetInput(polydata)
  489 + #normals.SetFeatureAngle(80)
  490 + #normals.AutoOrientNormalsOn()
  491 + normals.ComputeCellNormalsOn()
  492 + normals.Update()
  493 + polydata = normals.GetOutput()
  494 +
  495 + clean = vtk.vtkCleanPolyData()
  496 + clean.AddObserver("ProgressEvent", lambda obj,evt:
  497 + UpdateProgress(obj, _("Generating 3D surface...")))
  498 + clean.SetInput(polydata)
  499 + clean.PointMergingOn()
  500 + clean.Update()
  501 + polydata = clean.GetOutput()
  502 +
  503 + polydata.BuildLinks()
  504 + polydata = ca_smoothing.ca_smoothing(polydata, options['angle'],
  505 + options['max distance'],
  506 + options['min weight'],
  507 + options['steps'])
  508 +
  509 + else:
  510 + smoother = vtk.vtkWindowedSincPolyDataFilter()
  511 + smoother.AddObserver("ProgressEvent", lambda obj,evt:
  512 + UpdateProgress(obj, _("Generating 3D surface...")))
  513 + smoother.SetInput(polydata)
  514 + smoother.SetNumberOfIterations(smooth_iterations)
  515 + smoother.SetFeatureAngle(120)
  516 + smoother.SetEdgeAngle(90.0)
  517 + smoother.BoundarySmoothingOn()
  518 + smoother.SetPassBand(0.1)
  519 + #smoother.FeatureEdgeSmoothingOn()
  520 + #smoother.NonManifoldSmoothingOn()
  521 + #smoother.NormalizeCoordinatesOn()
  522 + smoother.Update()
  523 + polydata = smoother.GetOutput()
  524 +
  525 +
  526 + if decimate_reduction:
  527 + print "Decimating", decimate_reduction
  528 + decimation = vtk.vtkQuadricDecimation()
  529 + decimation.SetInput(polydata)
  530 + decimation.SetTargetReduction(decimate_reduction)
  531 + decimation.AddObserver("ProgressEvent", lambda obj,evt:
  532 + UpdateProgress(obj, _("Generating 3D surface...")))
  533 + #decimation.PreserveTopologyOn()
  534 + #decimation.SplittingOff()
  535 + #decimation.BoundaryVertexDeletionOff()
  536 + decimation.Update()
  537 + polydata = decimation.GetOutput()
  538 +
  539 + to_measure = polydata
496 540
497 if keep_largest: 541 if keep_largest:
498 conn = vtk.vtkPolyDataConnectivityFilter() 542 conn = vtk.vtkPolyDataConnectivityFilter()
@@ -500,17 +544,19 @@ class SurfaceManager(): @@ -500,17 +544,19 @@ class SurfaceManager():
500 conn.SetExtractionModeToLargestRegion() 544 conn.SetExtractionModeToLargestRegion()
501 conn.AddObserver("ProgressEvent", lambda obj,evt: 545 conn.AddObserver("ProgressEvent", lambda obj,evt:
502 UpdateProgress(obj, _("Generating 3D surface..."))) 546 UpdateProgress(obj, _("Generating 3D surface...")))
  547 + conn.Update()
503 polydata = conn.GetOutput() 548 polydata = conn.GetOutput()
504 549
505 - # Filter used to detect and fill holes. Only fill boundary edges holes. 550 + #Filter used to detect and fill holes. Only fill boundary edges holes.
506 #TODO: Hey! This piece of code is the same from 551 #TODO: Hey! This piece of code is the same from
507 - # polydata_utils.FillSurfaceHole, we need to review this. 552 + #polydata_utils.FillSurfaceHole, we need to review this.
508 if fill_holes: 553 if fill_holes:
509 filled_polydata = vtk.vtkFillHolesFilter() 554 filled_polydata = vtk.vtkFillHolesFilter()
510 filled_polydata.SetInput(polydata) 555 filled_polydata.SetInput(polydata)
511 filled_polydata.SetHoleSize(300) 556 filled_polydata.SetHoleSize(300)
512 filled_polydata.AddObserver("ProgressEvent", lambda obj,evt: 557 filled_polydata.AddObserver("ProgressEvent", lambda obj,evt:
513 UpdateProgress(obj, _("Generating 3D surface..."))) 558 UpdateProgress(obj, _("Generating 3D surface...")))
  559 + filled_polydata.Update()
514 polydata = filled_polydata.GetOutput() 560 polydata = filled_polydata.GetOutput()
515 561
516 normals = vtk.vtkPolyDataNormals() 562 normals = vtk.vtkPolyDataNormals()
@@ -572,7 +618,7 @@ class SurfaceManager(): @@ -572,7 +618,7 @@ class SurfaceManager():
572 618
573 # The following lines have to be here, otherwise all volumes disappear 619 # The following lines have to be here, otherwise all volumes disappear
574 measured_polydata = vtk.vtkMassProperties() 620 measured_polydata = vtk.vtkMassProperties()
575 - measured_polydata.SetInput(smoother.GetOutput()) 621 + measured_polydata.SetInput(to_measure)
576 volume = measured_polydata.GetVolume() 622 volume = measured_polydata.GetVolume()
577 surface.volume = volume 623 surface.volume = volume
578 self.last_surface_index = surface.index 624 self.last_surface_index = surface.index
invesalius/data/surface_process.py
@@ -11,9 +11,11 @@ from scipy import ndimage @@ -11,9 +11,11 @@ from scipy import ndimage
11 11
12 class SurfaceProcess(multiprocessing.Process): 12 class SurfaceProcess(multiprocessing.Process):
13 13
14 - def __init__(self, pipe, filename, shape, dtype, spacing, mode, min_value, max_value, 14 + def __init__(self, pipe, filename, shape, dtype, mask_filename,
  15 + mask_shape, mask_dtype, spacing, mode, min_value, max_value,
15 decimate_reduction, smooth_relaxation_factor, 16 decimate_reduction, smooth_relaxation_factor,
16 - smooth_iterations, language, flip_image, q_in, q_out): 17 + smooth_iterations, language, flip_image, q_in, q_out,
  18 + from_binary, algorithm):
17 19
18 multiprocessing.Process.__init__(self) 20 multiprocessing.Process.__init__(self)
19 self.pipe = pipe 21 self.pipe = pipe
@@ -31,10 +33,26 @@ class SurfaceProcess(multiprocessing.Process): @@ -31,10 +33,26 @@ class SurfaceProcess(multiprocessing.Process):
31 self.q_out = q_out 33 self.q_out = q_out
32 self.dtype = dtype 34 self.dtype = dtype
33 self.shape = shape 35 self.shape = shape
  36 + self.from_binary = from_binary
  37 + self.algorithm = algorithm
  38 +
  39 + self.mask_filename = mask_filename
  40 + self.mask_shape = mask_shape
  41 + self.mask_dtype = mask_dtype
34 42
35 def run(self): 43 def run(self):
36 - self.mask = numpy.memmap(self.filename, mode='r', dtype=self.dtype,  
37 - shape=self.shape) 44 + if self.from_binary:
  45 + self.mask = numpy.memmap(self.mask_filename, mode='r',
  46 + dtype=self.mask_dtype,
  47 + shape=self.mask_shape)
  48 + else:
  49 + self.image = numpy.memmap(self.filename, mode='r', dtype=self.dtype,
  50 + shape=self.shape)
  51 + self.mask = numpy.memmap(self.mask_filename, mode='r',
  52 + dtype=self.mask_dtype,
  53 + shape=self.mask_shape)
  54 +
  55 +
38 while 1: 56 while 1:
39 roi = self.q_in.get() 57 roi = self.q_in.get()
40 if roi is None: 58 if roi is None:
@@ -46,59 +64,95 @@ class SurfaceProcess(multiprocessing.Process): @@ -46,59 +64,95 @@ class SurfaceProcess(multiprocessing.Process):
46 self.pipe.send([prog, msg]) 64 self.pipe.send([prog, msg])
47 65
48 def CreateSurface(self, roi): 66 def CreateSurface(self, roi):
49 - smoothed = numpy.array(self.mask[roi])  
50 - image = converters.to_vtk(smoothed, self.spacing, roi.start, 67 + if self.from_binary:
  68 + a_mask = numpy.array(self.mask[roi.start + 1: roi.stop + 1,
  69 + 1:, 1:])
  70 + image = converters.to_vtk(a_mask, self.spacing, roi.start,
51 "AXIAL") 71 "AXIAL")
  72 + else:
  73 + a_image = numpy.array(self.image[roi])
  74 +
  75 + if self.algorithm == u'InVesalius 3.b2':
  76 + a_mask = numpy.array(self.mask[roi.start + 1: roi.stop + 1,
  77 + 1:, 1:])
  78 + a_image[a_mask == 1] = a_image.min() - 1
  79 + a_image[a_mask == 254] = (self.min_value + self.max_value) / 2.0
  80 +
  81 + image = converters.to_vtk(a_image, self.spacing, roi.start,
  82 + "AXIAL")
  83 +
  84 + gauss = vtk.vtkImageGaussianSmooth()
  85 + gauss.SetInput(image)
  86 + gauss.SetRadiusFactor(0.3)
  87 + gauss.Update()
  88 +
  89 + image = gauss.GetOutput()
  90 + else:
  91 + image = converters.to_vtk(a_image, self.spacing, roi.start,
  92 + "AXIAL")
  93 +
52 flip = vtk.vtkImageFlip() 94 flip = vtk.vtkImageFlip()
53 flip.SetInput(image) 95 flip.SetInput(image)
54 flip.SetFilteredAxis(1) 96 flip.SetFilteredAxis(1)
55 flip.FlipAboutOriginOn() 97 flip.FlipAboutOriginOn()
56 flip.Update() 98 flip.Update()
  99 +
  100 + #filename = tempfile.mktemp(suffix='_%s.vti' % (self.pid))
  101 + #writer = vtk.vtkXMLImageDataWriter()
  102 + #writer.SetInput(mask_vtk)
  103 + #writer.SetFileName(filename)
  104 + #writer.Write()
  105 +
  106 + #print "Writing piece", roi, "to", filename
  107 +
57 # Create vtkPolyData from vtkImageData 108 # Create vtkPolyData from vtkImageData
58 #print "Generating Polydata" 109 #print "Generating Polydata"
59 - if self.mode == "CONTOUR": 110 + #if self.mode == "CONTOUR":
60 #print "Contour" 111 #print "Contour"
61 - contour = vtk.vtkContourFilter()  
62 - contour.SetInput(flip.GetOutput()) 112 + contour = vtk.vtkContourFilter()
  113 + contour.SetInput(flip.GetOutput())
  114 + if self.from_binary:
  115 + contour.SetValue(0, 127) # initial threshold
  116 + else:
63 contour.SetValue(0, self.min_value) # initial threshold 117 contour.SetValue(0, self.min_value) # initial threshold
64 contour.SetValue(1, self.max_value) # final threshold 118 contour.SetValue(1, self.max_value) # final threshold
65 - contour.ComputeScalarsOn()  
66 - contour.ComputeGradientsOn()  
67 - contour.ComputeNormalsOn()  
68 - contour.AddObserver("ProgressEvent", lambda obj,evt:  
69 - self.SendProgress(obj, _("Generating 3D surface...")))  
70 - polydata = contour.GetOutput()  
71 - else: #mode == "GRAYSCALE":  
72 - mcubes = vtk.vtkMarchingCubes()  
73 - mcubes.SetInput(flip.GetOutput())  
74 - mcubes.SetValue(0, self.min_value)  
75 - mcubes.SetValue(1, self.max_value)  
76 - mcubes.ComputeScalarsOff()  
77 - mcubes.ComputeGradientsOff()  
78 - mcubes.ComputeNormalsOff()  
79 - mcubes.AddObserver("ProgressEvent", lambda obj,evt:  
80 - self.SendProgress(obj, _("Generating 3D surface...")))  
81 - polydata = mcubes.GetOutput()  
82 -  
83 - triangle = vtk.vtkTriangleFilter()  
84 - triangle.SetInput(polydata)  
85 - triangle.AddObserver("ProgressEvent", lambda obj,evt:  
86 - self.SendProgress(obj, _("Generating 3D surface...")))  
87 - triangle.Update()  
88 - polydata = triangle.GetOutput()  
89 -  
90 - if self.decimate_reduction:  
91 -  
92 - #print "Decimating"  
93 - decimation = vtk.vtkDecimatePro()  
94 - decimation.SetInput(polydata)  
95 - decimation.SetTargetReduction(0.3)  
96 - decimation.AddObserver("ProgressEvent", lambda obj,evt: 119 + contour.ComputeScalarsOn()
  120 + contour.ComputeGradientsOn()
  121 + contour.ComputeNormalsOn()
  122 + contour.AddObserver("ProgressEvent", lambda obj,evt:
97 self.SendProgress(obj, _("Generating 3D surface..."))) 123 self.SendProgress(obj, _("Generating 3D surface...")))
98 - #decimation.PreserveTopologyOn()  
99 - decimation.SplittingOff()  
100 - decimation.BoundaryVertexDeletionOff()  
101 - polydata = decimation.GetOutput() 124 + polydata = contour.GetOutput()
  125 + #else: #mode == "GRAYSCALE":
  126 + #mcubes = vtk.vtkMarchingCubes()
  127 + #mcubes.SetInput(flip.GetOutput())
  128 + #mcubes.SetValue(0, self.min_value)
  129 + #mcubes.SetValue(1, self.max_value)
  130 + #mcubes.ComputeScalarsOff()
  131 + #mcubes.ComputeGradientsOff()
  132 + #mcubes.ComputeNormalsOff()
  133 + #mcubes.AddObserver("ProgressEvent", lambda obj,evt:
  134 + #self.SendProgress(obj, _("Generating 3D surface...")))
  135 + #polydata = mcubes.GetOutput()
  136 +
  137 + #triangle = vtk.vtkTriangleFilter()
  138 + #triangle.SetInput(polydata)
  139 + #triangle.AddObserver("ProgressEvent", lambda obj,evt:
  140 + #self.SendProgress(obj, _("Generating 3D surface...")))
  141 + #triangle.Update()
  142 + #polydata = triangle.GetOutput()
  143 +
  144 + #if self.decimate_reduction:
  145 +
  146 + ##print "Decimating"
  147 + #decimation = vtk.vtkDecimatePro()
  148 + #decimation.SetInput(polydata)
  149 + #decimation.SetTargetReduction(0.3)
  150 + #decimation.AddObserver("ProgressEvent", lambda obj,evt:
  151 + #self.SendProgress(obj, _("Generating 3D surface...")))
  152 + ##decimation.PreserveTopologyOn()
  153 + #decimation.SplittingOff()
  154 + #decimation.BoundaryVertexDeletionOff()
  155 + #polydata = decimation.GetOutput()
102 156
103 self.pipe.send(None) 157 self.pipe.send(None)
104 158
@@ -108,4 +162,6 @@ class SurfaceProcess(multiprocessing.Process): @@ -108,4 +162,6 @@ class SurfaceProcess(multiprocessing.Process):
108 writer.SetFileName(filename) 162 writer.SetFileName(filename)
109 writer.Write() 163 writer.Write()
110 164
  165 + print "Writing piece", roi, "to", filename
  166 +
111 self.q_out.put(filename) 167 self.q_out.put(filename)
invesalius/gui/dialogs.py
@@ -24,6 +24,7 @@ import sys @@ -24,6 +24,7 @@ import sys
24 24
25 import wx 25 import wx
26 from wx.lib import masked 26 from wx.lib import masked
  27 +from wx.lib.agw import floatspin
27 from wx.lib.wordwrap import wordwrap 28 from wx.lib.wordwrap import wordwrap
28 import wx.lib.pubsub as ps 29 import wx.lib.pubsub as ps
29 30
@@ -852,3 +853,114 @@ def ExportPicture(type_=&quot;&quot;): @@ -852,3 +853,114 @@ def ExportPicture(type_=&quot;&quot;):
852 return () 853 return ()
853 854
854 855
  856 +class CAOptions(wx.Panel):
  857 + '''
  858 + Options related to Context aware algorithm:
  859 + Angle: The min angle to a vertex to be considered a staircase vertex;
  860 + Max distance: The max distance a normal vertex must be to calculate its
  861 + weighting;
  862 + Min Weighting: The min weight a vertex must have;
  863 + Steps: The number of iterations the smoothing algorithm have to do.
  864 + '''
  865 + def __init__(self, parent):
  866 + wx.Panel.__init__(self, parent, -1)
  867 + self._build_widgets()
  868 +
  869 + def _build_widgets(self):
  870 + self.angle = floatspin.FloatSpin(self, -1, value=0.7, min_val=0.0,
  871 + max_val=1.0, increment=0.1,
  872 + digits=1)
  873 +
  874 + self.max_distance = floatspin.FloatSpin(self, -1, value=3.0, min_val=0.0,
  875 + max_val=100.0, increment=0.1,
  876 + digits=2)
  877 +
  878 + self.min_weight = floatspin.FloatSpin(self, -1, value=0.2, min_val=0.0,
  879 + max_val=1.0, increment=0.1,
  880 + digits=1)
  881 +
  882 + self.steps = wx.SpinCtrl(self, -1, value='10', min=1, max=100)
  883 +
  884 + layout_sizer = wx.FlexGridSizer(rows=4, cols=2, hgap=5, vgap=5)
  885 + layout_sizer.Add(wx.StaticText(self, -1, u'Angle:'), 0, wx.EXPAND)
  886 + layout_sizer.Add(self.angle, 0, wx.EXPAND)
  887 + layout_sizer.Add(wx.StaticText(self, -1, u'Max. distance:'), 0, wx.EXPAND)
  888 + layout_sizer.Add(self.max_distance, 0, wx.EXPAND)
  889 + layout_sizer.Add(wx.StaticText(self, -1, u'Min. weight:'), 0, wx.EXPAND)
  890 + layout_sizer.Add(self.min_weight, 0, wx.EXPAND)
  891 + layout_sizer.Add(wx.StaticText(self, -1, u'N. steps:'), 0, wx.EXPAND)
  892 + layout_sizer.Add(self.steps, 0, wx.EXPAND)
  893 +
  894 + self.main_sizer = wx.StaticBoxSizer(wx.StaticBox(self, -1, 'Context aware options'), wx.VERTICAL)
  895 + self.main_sizer.Add(layout_sizer, 0, wx.EXPAND | wx.ALL, 5)
  896 + self.SetSizer(self.main_sizer)
  897 +
  898 +class SurfaceDialog(wx.Dialog):
  899 + '''
  900 + This dialog is only shown when the mask whose surface will be generate was
  901 + edited. So far, the only options available are the choice of method to
  902 + generate the surface, Binary or `Context aware smoothing', and options from
  903 + `Context aware smoothing'
  904 + '''
  905 + def __init__(self):
  906 + wx.Dialog.__init__(self, None, -1, u'Surface generation options')
  907 + self._build_widgets()
  908 + self._bind_wx()
  909 +
  910 + def _build_widgets(self):
  911 + btn_ok = wx.Button(self, wx.ID_OK)
  912 + btn_cancel = wx.Button(self, wx.ID_CANCEL)
  913 + btn_sizer = wx.StdDialogButtonSizer()
  914 + btn_sizer.AddButton(btn_ok)
  915 + btn_sizer.AddButton(btn_cancel)
  916 + btn_sizer.Realize()
  917 +
  918 + self.alg_types = {0: u'Context aware smoothing', 1: u'Binary',
  919 + 2: u'InVesalius 3.b2'}
  920 + self.cb_types = wx.ComboBox(self, -1, self.alg_types[0],
  921 + choices=[self.alg_types[i] for i in sorted(self.alg_types)],
  922 + style=wx.CB_READONLY)
  923 +
  924 + self.ca_options = CAOptions(self)
  925 +
  926 + method_sizer = wx.BoxSizer(wx.HORIZONTAL)
  927 + method_sizer.Add(wx.StaticText(self, -1, u'Method:'), 0,
  928 + wx.EXPAND | wx.ALL, 5)
  929 + method_sizer.Add(self.cb_types, 0, wx.EXPAND)
  930 +
  931 + self.main_sizer = wx.BoxSizer(wx.VERTICAL)
  932 + self.main_sizer.Add(method_sizer, 0, wx.EXPAND | wx.ALL, 5)
  933 + self.main_sizer.Add(self.ca_options, 0, wx.EXPAND | wx.ALL, 5)
  934 + self.main_sizer.Add(btn_sizer, 0, wx.EXPAND | wx.ALL, 5)
  935 +
  936 + self.SetSizer(self.main_sizer)
  937 + self.Fit()
  938 +
  939 + def _bind_wx(self):
  940 + self.cb_types.Bind(wx.EVT_COMBOBOX, self._set_cb_types)
  941 +
  942 + def _set_cb_types(self, evt):
  943 + print evt.GetString()
  944 + if self.alg_types[evt.GetSelection()] == self.alg_types[0]:
  945 + self.ca_options.Enable()
  946 + else:
  947 + self.ca_options.Disable()
  948 + evt.Skip()
  949 +
  950 + def GetAlgorithmSelected(self):
  951 + try:
  952 + return self.alg_types[self.cb_types.GetSelection()]
  953 + except KeyError:
  954 + return self.alg_types[0]
  955 +
  956 + def GetOptions(self):
  957 + if self.GetAlgorithmSelected() == self.alg_types[0]:
  958 + options = {'angle': self.ca_options.angle.GetValue(),
  959 + 'max distance': self.ca_options.max_distance.GetValue(),
  960 + 'min weight': self.ca_options.min_weight.GetValue(),
  961 + 'steps': self.ca_options.steps.GetValue()}
  962 + else:
  963 + options = {}
  964 + return options
  965 +
  966 +
invesalius/gui/task_slice.py
@@ -25,6 +25,7 @@ import wx.lib.platebtn as pbtn @@ -25,6 +25,7 @@ import wx.lib.platebtn as pbtn
25 import wx.lib.pubsub as ps 25 import wx.lib.pubsub as ps
26 26
27 import data.mask as mask 27 import data.mask as mask
  28 +import data.slice_ as slice_
28 import constants as const 29 import constants as const
29 import gui.dialogs as dlg 30 import gui.dialogs as dlg
30 import gui.widgets.gradient as grad 31 import gui.widgets.gradient as grad
@@ -143,10 +144,20 @@ class InnerTaskPanel(wx.Panel): @@ -143,10 +144,20 @@ class InnerTaskPanel(wx.Panel):
143 144
144 def OnButtonNextTask(self, evt): 145 def OnButtonNextTask(self, evt):
145 overwrite = self.check_box.IsChecked() 146 overwrite = self.check_box.IsChecked()
  147 + algorithm = ''
  148 + options = {}
146 if self.GetMaskSelected() != -1: 149 if self.GetMaskSelected() != -1:
  150 + sl = slice_.Slice()
  151 + if sl.current_mask.was_edited:
  152 + dlgs = dlg.SurfaceDialog()
  153 + if dlgs.ShowModal() == wx.ID_OK:
  154 + algorithm = dlgs.GetAlgorithmSelected()
  155 + options = dlgs.GetOptions()
  156 + else:
  157 + return
147 ps.Publisher().sendMessage('Create surface from index', 158 ps.Publisher().sendMessage('Create surface from index',
148 (self.GetMaskSelected(), 159 (self.GetMaskSelected(),
149 - overwrite)) 160 + overwrite, algorithm, options))
150 ps.Publisher().sendMessage('Fold surface task') 161 ps.Publisher().sendMessage('Fold surface task')
151 else: 162 else:
152 dlg.InexistentMask() 163 dlg.InexistentMask()