Commit 00952d8a152e7758dccbbc8d1cc2d5cde54fefeb
1 parent
390a84c8
Exists in
master
and in
67 other branches
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
Showing
7 changed files
with
322 additions
and
90 deletions
Show diff stats
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_=""): | @@ -852,3 +853,114 @@ def ExportPicture(type_=""): | ||
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() |