# reference: https://blog.csdn.net/weixin_46579211/article/details/118279231 import typing import numpy as np from qtpy.QtWidgets import QWidget, QVBoxLayout # 只有调用convert_vtk才会加载vtk相关的并把这个控件转换为真正的vtk控件 vtk = None QVTKRenderWindowInteractor = None vtkImageImportFromArray = None class VTKWidget(QWidget): def __init__(self, parent: typing.Optional["QWidget"]) -> None: super().__init__(parent) self.setObjectName("vtkWidget") self.vlayer = QVBoxLayout(self) # default setting self.smoothing_iterations = 10 self.pass_band = 0.005 self.feature_angle = 120 self.import_vtk = False def convert_vtk(self) -> bool: try: import vtk from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor from vtkmodules.util.vtkImageImportFromArray import vtkImageImportFromArray global vtk global QVTKRenderWindowInteractor global vtkImageImportFromArray self.import_vtk = True vtk.vtkFileOutputWindow().SetGlobalWarningDisplay(0) self.init() except: self.import_vtk = False finally: return self.import_vtk def init(self, clear: bool=True) -> None: if self.import_vtk is False: return # remove item = self.vlayer.itemAt(0) self.vlayer.removeItem(item) if item is not None and item.widget(): item.widget().deleteLater() # add self.renderer = vtk.vtkRenderer() self.interactor = QVTKRenderWindowInteractor(self) self.interactor.GetRenderWindow().AddRenderer(self.renderer) self.vlayer.addWidget(self.interactor) if clear: # set background self.renderer.SetBackground(vtk.vtkNamedColors().GetColor3d( "black")) self.interactor.Start() def show_array(self, data: np.ndarray, spacing: typing.Tuple, color_map: typing.List) -> None: if self.import_vtk is False: return print("color_map:", color_map) self.num_block = len(np.unique(data)) print("num_block:", self.num_block) self.reader = vtkImageImportFromArray() self.reader.SetArray(data) self.reader.SetDataSpacing(spacing) mbds = vtk.vtkMultiBlockDataSet() mbds.SetNumberOfBlocks(self.num_block - 1) for iter in range(1, self.num_block + 1): contour = self._get_mc_contour(iter) smoother = self._smoothing(self.smoothing_iterations, self.pass_band, self.feature_angle, contour) mbds.SetBlock(iter, smoother.GetOutput()) self._multidisplay(mbds, color_map) def _get_mc_contour(self, setvalue: int) -> typing.Any: contour = vtk.vtkDiscreteMarchingCubes() contour.SetInputConnection(self.reader.GetOutputPort()) contour.ComputeNormalsOn() contour.SetValue(0, setvalue) return contour def _smoothing(self, smoothing_iterations: int, pass_band: float, feature_angle: int, contour: typing.Any) -> typing.Any: smoother = vtk.vtkWindowedSincPolyDataFilter() smoother.SetInputConnection(contour.GetOutputPort()) smoother.SetNumberOfIterations(smoothing_iterations) smoother.BoundarySmoothingOff() smoother.FeatureEdgeSmoothingOff() smoother.SetFeatureAngle(feature_angle) smoother.SetPassBand(pass_band) smoother.NonManifoldSmoothingOn() smoother.NormalizeCoordinatesOn() smoother.Update() return smoother def _multidisplay(self, obj: typing.Any, color_map: typing.List) -> None: self.init(False) mapper = vtk.vtkCompositePolyDataMapper2() mapper.SetInputDataObject(obj) cdsa = vtk.vtkCompositeDataDisplayAttributes() mapper.SetCompositeDataDisplayAttributes(cdsa) # 上色 color_map.insert(0, [0., 0., 0.]) for i in range(1, self.num_block + 1): r, g, b = color_map[i - 1] mapper.SetBlockColor(i, r / 255., g / 255., b / 255.) actor = vtk.vtkActor() actor.SetMapper(mapper) actor.RotateX(180) # 翻转一下才对 # Enable user interface interactor. self.renderer.AddActor(actor) self.interactor.Start()