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 | 464 | filename = filename.replace("/", "") #Fix problem case other/Skull_DICOM |
| 465 | 465 | |
| 466 | 466 | dirpath = session.CreateProject(filename) |
| 467 | - proj.SavePlistProject(dirpath, filename) | |
| 467 | + #proj.SavePlistProject(dirpath, filename) | |
| 468 | 468 | |
| 469 | 469 | def OnOpenDicomGroup(self, pubsub_evt): |
| 470 | 470 | group, interval, file_range = pubsub_evt.data | ... | ... |
invesalius/data/mask.py
invesalius/data/slice_.py
| ... | ... | @@ -285,6 +285,7 @@ class Slice(object): |
| 285 | 285 | mask = self.buffer_slices[orientation].mask |
| 286 | 286 | image = self.buffer_slices[orientation].image |
| 287 | 287 | thresh_min, thresh_max = self.current_mask.edition_threshold_range |
| 288 | + self.current_mask.was_edited = True | |
| 288 | 289 | |
| 289 | 290 | if hasattr(position, '__iter__'): |
| 290 | 291 | py, px = position |
| ... | ... | @@ -515,7 +516,9 @@ class Slice(object): |
| 515 | 516 | If slice_number is None then all the threshold is calculated for all |
| 516 | 517 | slices, otherwise only to indicated slice. |
| 517 | 518 | """ |
| 519 | + self.current_mask.was_edited = False | |
| 518 | 520 | thresh_min, thresh_max = threshold_range |
| 521 | + print "Threshold" | |
| 519 | 522 | |
| 520 | 523 | if self.current_mask.index == index: |
| 521 | 524 | # TODO: find out a better way to do threshold |
| ... | ... | @@ -588,23 +591,26 @@ class Slice(object): |
| 588 | 591 | #--------------------------------------------------------------------------- |
| 589 | 592 | |
| 590 | 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 | 596 | proj = Project() |
| 595 | 597 | mask = proj.mask_dict[mask_index] |
| 596 | 598 | |
| 597 | 599 | # This is very important. Do not use masks' imagedata. It would mess up |
| 598 | 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 | 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 | 615 | def GetOutput(self): |
| 610 | 616 | return self.blend_filter.GetOutput() | ... | ... |
invesalius/data/surface.py
| ... | ... | @@ -21,8 +21,6 @@ import multiprocessing |
| 21 | 21 | import os |
| 22 | 22 | import plistlib |
| 23 | 23 | import random |
| 24 | -import sys | |
| 25 | -import tempfile | |
| 26 | 24 | |
| 27 | 25 | import vtk |
| 28 | 26 | import wx.lib.pubsub as ps |
| ... | ... | @@ -35,6 +33,7 @@ import session as ses |
| 35 | 33 | import surface_process |
| 36 | 34 | import utils as utl |
| 37 | 35 | import vtk_utils as vu |
| 36 | +import data.ca_smoothing as ca_smoothing | |
| 38 | 37 | |
| 39 | 38 | class Surface(): |
| 40 | 39 | """ |
| ... | ... | @@ -363,9 +362,11 @@ class SurfaceManager(): |
| 363 | 362 | """ |
| 364 | 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 | 366 | min_value, max_value = mask.threshold_range |
| 368 | - fill_holes = True | |
| 367 | + fill_holes = False | |
| 368 | + | |
| 369 | + mask.matrix.flush() | |
| 369 | 370 | |
| 370 | 371 | #if len(surface_data) == 5: |
| 371 | 372 | #imagedata, colour, [min_value, max_value], \ |
| ... | ... | @@ -382,7 +383,7 @@ class SurfaceManager(): |
| 382 | 383 | |
| 383 | 384 | mode = 'CONTOUR' # 'GRAYSCALE' |
| 384 | 385 | quality=_('Optimal *') |
| 385 | - keep_largest = True | |
| 386 | + keep_largest = False | |
| 386 | 387 | surface_name = "" |
| 387 | 388 | colour = mask.colour |
| 388 | 389 | |
| ... | ... | @@ -411,8 +412,6 @@ class SurfaceManager(): |
| 411 | 412 | |
| 412 | 413 | language = ses.Session().language |
| 413 | 414 | |
| 414 | - overwrite = 0 | |
| 415 | - | |
| 416 | 415 | if (prj.Project().original_orientation == const.CORONAL): |
| 417 | 416 | flip_image = False |
| 418 | 417 | else: |
| ... | ... | @@ -422,7 +421,7 @@ class SurfaceManager(): |
| 422 | 421 | |
| 423 | 422 | pipe_in, pipe_out = multiprocessing.Pipe() |
| 424 | 423 | o_piece = 1 |
| 425 | - piece_size = 20 | |
| 424 | + piece_size = 2000 | |
| 426 | 425 | |
| 427 | 426 | n_pieces = int(round(matrix.shape[0] / piece_size + 0.5, 0)) |
| 428 | 427 | |
| ... | ... | @@ -432,10 +431,18 @@ class SurfaceManager(): |
| 432 | 431 | p = [] |
| 433 | 432 | for i in xrange(n_processors): |
| 434 | 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 | 446 | p.append(sp) |
| 440 | 447 | sp.start() |
| 441 | 448 | |
| ... | ... | @@ -471,28 +478,65 @@ class SurfaceManager(): |
| 471 | 478 | polydata_append.AddInput(reader.GetOutput()) |
| 472 | 479 | t -= 1 |
| 473 | 480 | |
| 481 | + polydata_append.Update() | |
| 474 | 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 | 541 | if keep_largest: |
| 498 | 542 | conn = vtk.vtkPolyDataConnectivityFilter() |
| ... | ... | @@ -500,17 +544,19 @@ class SurfaceManager(): |
| 500 | 544 | conn.SetExtractionModeToLargestRegion() |
| 501 | 545 | conn.AddObserver("ProgressEvent", lambda obj,evt: |
| 502 | 546 | UpdateProgress(obj, _("Generating 3D surface..."))) |
| 547 | + conn.Update() | |
| 503 | 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 | 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 | 553 | if fill_holes: |
| 509 | 554 | filled_polydata = vtk.vtkFillHolesFilter() |
| 510 | 555 | filled_polydata.SetInput(polydata) |
| 511 | 556 | filled_polydata.SetHoleSize(300) |
| 512 | 557 | filled_polydata.AddObserver("ProgressEvent", lambda obj,evt: |
| 513 | 558 | UpdateProgress(obj, _("Generating 3D surface..."))) |
| 559 | + filled_polydata.Update() | |
| 514 | 560 | polydata = filled_polydata.GetOutput() |
| 515 | 561 | |
| 516 | 562 | normals = vtk.vtkPolyDataNormals() |
| ... | ... | @@ -572,7 +618,7 @@ class SurfaceManager(): |
| 572 | 618 | |
| 573 | 619 | # The following lines have to be here, otherwise all volumes disappear |
| 574 | 620 | measured_polydata = vtk.vtkMassProperties() |
| 575 | - measured_polydata.SetInput(smoother.GetOutput()) | |
| 621 | + measured_polydata.SetInput(to_measure) | |
| 576 | 622 | volume = measured_polydata.GetVolume() |
| 577 | 623 | surface.volume = volume |
| 578 | 624 | self.last_surface_index = surface.index | ... | ... |
invesalius/data/surface_process.py
| ... | ... | @@ -11,9 +11,11 @@ from scipy import ndimage |
| 11 | 11 | |
| 12 | 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 | 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 | 20 | multiprocessing.Process.__init__(self) |
| 19 | 21 | self.pipe = pipe |
| ... | ... | @@ -31,10 +33,26 @@ class SurfaceProcess(multiprocessing.Process): |
| 31 | 33 | self.q_out = q_out |
| 32 | 34 | self.dtype = dtype |
| 33 | 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 | 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 | 56 | while 1: |
| 39 | 57 | roi = self.q_in.get() |
| 40 | 58 | if roi is None: |
| ... | ... | @@ -46,59 +64,95 @@ class SurfaceProcess(multiprocessing.Process): |
| 46 | 64 | self.pipe.send([prog, msg]) |
| 47 | 65 | |
| 48 | 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 | 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 | 94 | flip = vtk.vtkImageFlip() |
| 53 | 95 | flip.SetInput(image) |
| 54 | 96 | flip.SetFilteredAxis(1) |
| 55 | 97 | flip.FlipAboutOriginOn() |
| 56 | 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 | 108 | # Create vtkPolyData from vtkImageData |
| 58 | 109 | #print "Generating Polydata" |
| 59 | - if self.mode == "CONTOUR": | |
| 110 | + #if self.mode == "CONTOUR": | |
| 60 | 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 | 117 | contour.SetValue(0, self.min_value) # initial threshold |
| 64 | 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 | 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 | 157 | self.pipe.send(None) |
| 104 | 158 | |
| ... | ... | @@ -108,4 +162,6 @@ class SurfaceProcess(multiprocessing.Process): |
| 108 | 162 | writer.SetFileName(filename) |
| 109 | 163 | writer.Write() |
| 110 | 164 | |
| 165 | + print "Writing piece", roi, "to", filename | |
| 166 | + | |
| 111 | 167 | self.q_out.put(filename) | ... | ... |
invesalius/gui/dialogs.py
| ... | ... | @@ -24,6 +24,7 @@ import sys |
| 24 | 24 | |
| 25 | 25 | import wx |
| 26 | 26 | from wx.lib import masked |
| 27 | +from wx.lib.agw import floatspin | |
| 27 | 28 | from wx.lib.wordwrap import wordwrap |
| 28 | 29 | import wx.lib.pubsub as ps |
| 29 | 30 | |
| ... | ... | @@ -852,3 +853,114 @@ def ExportPicture(type_=""): |
| 852 | 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 | 25 | import wx.lib.pubsub as ps |
| 26 | 26 | |
| 27 | 27 | import data.mask as mask |
| 28 | +import data.slice_ as slice_ | |
| 28 | 29 | import constants as const |
| 29 | 30 | import gui.dialogs as dlg |
| 30 | 31 | import gui.widgets.gradient as grad |
| ... | ... | @@ -143,10 +144,20 @@ class InnerTaskPanel(wx.Panel): |
| 143 | 144 | |
| 144 | 145 | def OnButtonNextTask(self, evt): |
| 145 | 146 | overwrite = self.check_box.IsChecked() |
| 147 | + algorithm = '' | |
| 148 | + options = {} | |
| 146 | 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 | 158 | ps.Publisher().sendMessage('Create surface from index', |
| 148 | 159 | (self.GetMaskSelected(), |
| 149 | - overwrite)) | |
| 160 | + overwrite, algorithm, options)) | |
| 150 | 161 | ps.Publisher().sendMessage('Fold surface task') |
| 151 | 162 | else: |
| 152 | 163 | dlg.InexistentMask() | ... | ... |