diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6769e21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/.idea/crystalfly.iml b/.idea/crystalfly.iml index d0876a7..fc4b484 100644 --- a/.idea/crystalfly.iml +++ b/.idea/crystalfly.iml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 1d62595..5bf8748 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/.idea/poetry.xml b/.idea/poetry.xml new file mode 100644 index 0000000..2097c01 --- /dev/null +++ b/.idea/poetry.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/crystalfly/main.py b/crystalfly/main.py index 1de6a4e..de66de9 100644 --- a/crystalfly/main.py +++ b/crystalfly/main.py @@ -1,15 +1,18 @@ +import ctypes import sys from PySide6.QtWidgets import QApplication from crystalfly.ui.main_window import MainWindow +ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("crystalfly") + def main(): app = QApplication(sys.argv) window = MainWindow() window.show() - app.exec() + sys.exit(app.exec()) if __name__ == '__main__': diff --git a/crystalfly/test.py b/crystalfly/test.py new file mode 100644 index 0000000..aa670b2 --- /dev/null +++ b/crystalfly/test.py @@ -0,0 +1,52 @@ +import sys + +from PySide6.QtCore import QThread, Signal, Slot +from PySide6.QtWidgets import QWidget, QPushButton, QApplication, QLabel, QVBoxLayout +from vtkmodules.vtkIOImage import vtkImageReader2 + + +class TestThread(QThread): + timeSignal = Signal(str) + + def __init__(self): + super().__init__() + + def run(self): + reader = vtkImageReader2() + reader.SetFileName("D:/Downloads/christmas_tree_512x499x512_uint16.raw") + reader.SetFileDimensionality(3) + reader.SetDataSpacing(0.1, 0.1, 0.1) + reader.SetDataExtent(0, 511, 0, 498, 0, 511) + reader.SetDataScalarTypeToUnsignedShort() + reader.Update() + self.timeSignal.emit("success") + # for i in range(10): + # print(i) + # self.timeSignal.emit(str(i)) + # time.sleep(1) + + +class MainView(QWidget): + def __init__(self): + super().__init__() + self.resize(800, 600) + self.test_thread = TestThread() + main_layout = QVBoxLayout() + self.label = QLabel("0") + self.button = QPushButton('TEST') + self.button.clicked.connect(self.button_clicked) + main_layout.addWidget(self.label) + main_layout.addWidget(self.button) + self.setLayout(main_layout) + self.test_thread.timeSignal.connect(self.label.setText) + + @Slot() + def button_clicked(self): + self.test_thread.start() + + +if __name__ == '__main__': + app = QApplication(sys.argv) + window = MainView() + window.show() + app.exec() diff --git a/crystalfly/test_volumn.py b/crystalfly/test_volumn.py new file mode 100644 index 0000000..43bdf67 --- /dev/null +++ b/crystalfly/test_volumn.py @@ -0,0 +1,58 @@ +import vtk +from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget +from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor + + +class VolumeRenderingWindow(QMainWindow): + def __init__(self): + super().__init__() + + reader = vtk.vtkImageReader2() + reader.SetDataExtent(0, 511, 0, 499, 0, 511) + reader.SetDataByteOrderToLittleEndian() + reader.SetDataScalarTypeToUnsignedShort() + reader.SetFileName("D:/Downloads/christmas_tree_512x499x512_uint16.raw") # Replace with your file path + reader.Update() + + volume_mapper = vtk.vtkGPUVolumeRayCastMapper() # You can also use vtkFixedPointVolumeRayCastMapper + volume_mapper.SetInputData(reader.GetOutput()) + + volume_property = vtk.vtkVolumeProperty() + color_transfer_function = vtk.vtkColorTransferFunction() + color_transfer_function.AddRGBPoint(0, 0, 0, 0) # Black + color_transfer_function.AddRGBPoint(255, 1, 1, 1) # White + volume_property.SetColor(color_transfer_function) + + gradient_opacity_transfer_function = vtk.vtkPiecewiseFunction() + gradient_opacity_transfer_function.AddPoint(0, 0.0) + gradient_opacity_transfer_function.AddPoint(90, 0.5) + gradient_opacity_transfer_function.AddPoint(100, 1.0) + volume_property.SetGradientOpacity(gradient_opacity_transfer_function) + + volume_actor = vtk.vtkVolume() + volume_actor.SetMapper(volume_mapper) + volume_actor.SetProperty(volume_property) + + renderer = vtk.vtkRenderer() + renderer.AddVolume(volume_actor) + + # Create a PySide6 widget to embed the VTK rendering window + vtk_widget = QVTKRenderWindowInteractor() + vtk_widget.SetRenderWindow(renderer.GetRenderWindow()) + + # Set up the main window layout + central_widget = QWidget() + layout = QVBoxLayout() + layout.addWidget(vtk_widget) + central_widget.setLayout(layout) + self.setCentralWidget(central_widget) + + # Set background color + renderer.SetBackground(0.1, 0.2, 0.4) + + +if __name__ == "__main__": + app = QApplication([]) + window = VolumeRenderingWindow() + window.show() + app.exec() diff --git a/crystalfly/thread/__pycache__/image_reader.cpython-311.pyc b/crystalfly/thread/__pycache__/image_reader.cpython-311.pyc index 258e047..898b501 100644 Binary files a/crystalfly/thread/__pycache__/image_reader.cpython-311.pyc and b/crystalfly/thread/__pycache__/image_reader.cpython-311.pyc differ diff --git a/crystalfly/thread/image_reader.py b/crystalfly/thread/image_reader.py index d922f59..39112f4 100644 --- a/crystalfly/thread/image_reader.py +++ b/crystalfly/thread/image_reader.py @@ -6,11 +6,13 @@ from vtkmodules.vtkIOImage import vtkMedicalImageReader2 from crystalfly.model.image_reader import image_reader_model +# from vtkmodules.vtkCommonCore import vtkCommand, VTK_INT + class ImageReaderThread(QThread): loadedSignal = Signal(Any) - def __init__(self, file_data): - QThread.__init__(self) + def __init__(self, file_data, parent=None): + QThread.__init__(self, parent) self.file_data = file_data def run(self): @@ -28,7 +30,7 @@ class ImageReaderThread(QThread): self.file_data["size"]["z"] - 1, ) # self.file_data["data_type"] - reader.SetDataScalarTypeToUnsignedShort() + reader.SetDataScalarType(self.file_data["data_type"]) reader.Update() image_reader_model.loaded(reader.GetOutput()) print("success") diff --git a/crystalfly/ui/__pycache__/import_file.cpython-311.pyc b/crystalfly/ui/__pycache__/import_file.cpython-311.pyc index afbd5e9..b626cef 100644 Binary files a/crystalfly/ui/__pycache__/import_file.cpython-311.pyc and b/crystalfly/ui/__pycache__/import_file.cpython-311.pyc differ diff --git a/crystalfly/ui/__pycache__/main_window.cpython-311.pyc b/crystalfly/ui/__pycache__/main_window.cpython-311.pyc index 45fd67c..e507c55 100644 Binary files a/crystalfly/ui/__pycache__/main_window.cpython-311.pyc and b/crystalfly/ui/__pycache__/main_window.cpython-311.pyc differ diff --git a/crystalfly/ui/__pycache__/volume_viewer.cpython-311.pyc b/crystalfly/ui/__pycache__/volume_viewer.cpython-311.pyc new file mode 100644 index 0000000..325d9bb Binary files /dev/null and b/crystalfly/ui/__pycache__/volume_viewer.cpython-311.pyc differ diff --git a/crystalfly/ui/__pycache__/vtk_viewer.cpython-311.pyc b/crystalfly/ui/__pycache__/vtk_viewer.cpython-311.pyc index 0affa06..cf38fd9 100644 Binary files a/crystalfly/ui/__pycache__/vtk_viewer.cpython-311.pyc and b/crystalfly/ui/__pycache__/vtk_viewer.cpython-311.pyc differ diff --git a/crystalfly/ui/ctk.cpp b/crystalfly/ui/ctk.cpp new file mode 100644 index 0000000..2d8c4f3 --- /dev/null +++ b/crystalfly/ui/ctk.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +//VTK_MODULE_INIT( vtkRenderingOpenGL2 ); +//VTK_MODULE_INIT( vtkRenderingVolumeOpenGL2 ); +int main() +{ + + //raw data reader + + vtkSmartPointer reader = vtkSmartPointer::New(); + + reader->SetFileName("/home/wjm/code/p_volume_rendering/data/marschner_lobb_41x41x41_uint8.raw"); + reader->SetFileDimensionality(3);//设置显示图像的维数 + reader->SetDataScalarType(VTK_UNSIGNED_CHAR);//VTK_UNSIGNED_short将数据转换为unsigned char型 + reader->SetDataExtent(0, 40, 0, 40, 0, 40); + reader->SetDataSpacing(1, 1, 1); //设置像素间间距 + //reader->SetDataOrigin(0.0, 0.0, 0.0);//设置基准点,(一般没有用)做虚拟切片时可能会用的上 + reader->Update(); + + + + + //visualize the raw data + + + // properties options + vtkNew volumeProperty; + volumeProperty->ShadeOn(); + volumeProperty->SetInterpolationType(VTK_LINEAR_INTERPOLATION); + + // get the real range in hounsfield + vtkDataArray *arr = reader->GetOutput()->GetPointData()->GetScalars(); + double range[2]; + arr->GetRange(range); + + // 1D transfer functions + vtkNew colorTF; + colorTF->AddRGBPoint(-200, 0.0, 0.0, 0.0); + colorTF->AddRGBPoint(110, 0.4, 0.4, 1.0); + colorTF->AddRGBPoint(512, 1.0, 1.0, 1.0); + colorTF->AddRGBPoint(range[1], 0.9, 0.1, 0.3); + + vtkNew scalarTF; + //scalarTF->AddPoint(-200, 0.00); + scalarTF->AddPoint(-255, 0.00); + scalarTF->AddPoint(110, 0.00); + scalarTF->AddPoint(512, 0.5); + scalarTF->AddPoint(range[1], 0.9); + + vtkNew gradientTF; + gradientTF->AddPoint(-200, 0.0); + gradientTF->AddPoint(range[1] / 4.0, 1.0); + + volumeProperty->SetScalarOpacity(scalarTF); + volumeProperty->SetGradientOpacity(gradientTF); + volumeProperty->SetColor(colorTF); + + // setup rendering context + vtkNew renderWindow; + renderWindow->SetSize(512, 512); + renderWindow->SetMultiSamples(0); + + // mapping data + vtkNew mapper; + mapper->SetInputConnection(reader->GetOutputPort()); + mapper->SetBlendModeToComposite(); + mapper->SetUseJittering(1); + + // renderer and volume + vtkNew renderer; + renderWindow->AddRenderer(renderer); + renderer->SetBackground(0.03, 0.33, 0.33); + + vtkNew volume; + volume->SetMapper(mapper); + volume->SetProperty(volumeProperty); + renderer->AddVolume(volume); + + renderer->ResetCamera(); + renderer->GetActiveCamera()->Zoom(1.3); + + vtkNew interactor; + interactor->SetRenderWindow(renderWindow); + + vtkNew style; + interactor->SetInteractorStyle(style); + + renderWindow->Render(); + + interactor->Start(); + + + return 0; + +} diff --git a/crystalfly/ui/import_file.py b/crystalfly/ui/import_file.py index 718710e..e4d17ed 100644 --- a/crystalfly/ui/import_file.py +++ b/crystalfly/ui/import_file.py @@ -3,6 +3,8 @@ from pathlib import Path from PySide6.QtCore import Slot, QEvent from PySide6.QtUiTools import loadUiType from PySide6.QtWidgets import QDialog, QFileDialog +from vtkmodules.vtkCommonCore import VTK_INT, VTK_SHORT, VTK_LONG, VTK_FLOAT, VTK_UNSIGNED_SHORT, VTK_UNSIGNED_INT, \ + VTK_UNSIGNED_LONG from crystalfly.thread.image_reader import ImageReaderThread @@ -12,7 +14,7 @@ ImportFileType, _ = loadUiType(str(Path(__file__).parent.joinpath("import_file.u class ImportFile(QDialog, ImportFileType): def __init__(self): super(ImportFile, self).__init__() - self.image_read_thread = None + # self.image_read_thread = None self.setupUi(self) self.browse_file.clicked.connect(self.browse_file_handler) @@ -30,19 +32,34 @@ class ImportFile(QDialog, ImportFileType): @Slot() def load_image_file(self): + data_type_text = self.data_type_combobox.currentText() + data_type = None + if data_type_text == "int 8": + data_type = VTK_INT + elif data_type_text == "uint 8": + data_type = VTK_UNSIGNED_INT + elif data_type_text == "int 16": + data_type = VTK_SHORT + elif data_type_text == "uint 16": + data_type = VTK_UNSIGNED_SHORT + elif data_type_text == "int 32": + data_type = VTK_LONG + elif data_type_text == "uint 32": + data_type = VTK_UNSIGNED_LONG + elif data_type_text == "float": + data_type = VTK_FLOAT file_data = { "file_path": self.file_path_input.text(), - "data_type": self.data_type_combobox.currentText(), + "data_type": data_type, "size": { "x": self.size_x_spin.value(), "y": self.size_y_spin.value(), "z": self.size_z_spin.value(), } } - self.image_read_thread = ImageReaderThread(file_data) + image_read_thread = ImageReaderThread(file_data, self) # self.image_read_thread.loadedSignal.connect(self.set_image) - self.image_read_thread.start() - + image_read_thread.start() @Slot() def browse_file_handler(self): diff --git a/crystalfly/ui/main_window.py b/crystalfly/ui/main_window.py index 721cb27..bd375e6 100644 --- a/crystalfly/ui/main_window.py +++ b/crystalfly/ui/main_window.py @@ -3,8 +3,10 @@ from pathlib import Path from PySide6.QtCore import Slot from PySide6.QtUiTools import loadUiType from PySide6.QtWidgets import QMainWindow +from vtkmodules.vtkRenderingOpenGL2 import vtkGenericOpenGLRenderWindow from crystalfly.ui.import_file import ImportFile +from crystalfly.ui.volume_viewer import VolumeViewer from crystalfly.ui.vtk_viewer import VTKImageViewer MainWindowType, _ = loadUiType(str(Path(__file__).parent.joinpath("main_window.ui"))) @@ -13,22 +15,60 @@ MainWindowType, _ = loadUiType(str(Path(__file__).parent.joinpath("main_window.u class MainWindow(QMainWindow, MainWindowType): def __init__(self): super(MainWindow, self).__init__() + self.volume = None + self.transverse = None + self.sagittal = None + self.coronal = None + self.vtk_render_window = vtkGenericOpenGLRenderWindow() self.setupUi(self) self.file_open_dialog = ImportFile() self.file_open.triggered.connect(self.handle_file_open) self.init_four_pane() + self.is_pane_maximum = False def init_four_pane(self): - coronal = VTKImageViewer() - coronal.image_viewer.SetSliceOrientationToYZ() - sagittal = VTKImageViewer() - sagittal.image_viewer.SetSliceOrientationToXZ() - transverse = VTKImageViewer() - transverse.image_viewer.SetSliceOrientationToXY() + self.coronal = VTKImageViewer(1) + self.sagittal = VTKImageViewer(3) + self.transverse = VTKImageViewer(4) + self.volume = VolumeViewer(2) + self.coronal.image_viewer.SetSliceOrientationToYZ() + self.sagittal.image_viewer.SetSliceOrientationToXZ() + self.transverse.image_viewer.SetSliceOrientationToXY() + + self.volume.doubleClicked.connect(self.cell_max) + self.coronal.doubleClicked.connect(self.cell_max) + self.sagittal.doubleClicked.connect(self.cell_max) + self.transverse.doubleClicked.connect(self.cell_max) # self.gridLayout.addWidget() - self.gridLayout_2.addWidget(coronal) - self.gridLayout_3.addWidget(sagittal) - self.gridLayout_4.addWidget(transverse) + self.volume_grid_layout.addWidget(self.volume) + self.coronal_grid_layout.addWidget(self.coronal) + self.gridLayout_3.addWidget(self.sagittal) + self.gridLayout_4.addWidget(self.transverse) + + @Slot(int) + def cell_max(self, quadrant: int): + print(quadrant) + if self.is_pane_maximum: + self.widget_3d.show() + self.widget_coronal.show() + self.widget_sagittal.show() + self.widget_transverse.show() + self.is_pane_maximum = False + else: + self.widget_3d.hide() + self.widget_coronal.hide() + self.widget_sagittal.hide() + self.widget_transverse.hide() + print(quadrant) + if quadrant == 1: + self.widget_coronal.show() + elif quadrant == 2: + self.widget_3d.show() + elif quadrant == 3: + self.widget_sagittal.show() + elif quadrant == 4: + self.widget_transverse.show() + self.is_pane_maximum = True @Slot() def cell_click(self): diff --git a/crystalfly/ui/main_window.ui b/crystalfly/ui/main_window.ui index d74ebec..04d9314 100644 --- a/crystalfly/ui/main_window.ui +++ b/crystalfly/ui/main_window.ui @@ -40,11 +40,11 @@ 0 - + border: 1px solid rgb(195, 195, 195); - + 1 @@ -61,12 +61,12 @@ - + border: 1px solid rgb(195, 195, 195); margin-left: -1px; - + 1 @@ -83,7 +83,7 @@ margin-left: -1px; - + border: 1px solid rgb(195, 195, 195); margin-left: -1px; @@ -106,7 +106,7 @@ margin-top: -1px; - + border: 1px solid rgb(195, 195, 195); margin-top: -1px; diff --git a/crystalfly/ui/volume_viewer.py b/crystalfly/ui/volume_viewer.py new file mode 100644 index 0000000..190c1e7 --- /dev/null +++ b/crystalfly/ui/volume_viewer.py @@ -0,0 +1,77 @@ +# noinspection PyUnresolvedReferences +import vtkmodules.vtkRenderingContextOpenGL2 +from PySide6.QtCore import Signal +from PySide6.QtGui import QMouseEvent +from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor +from vtkmodules.vtkCommonDataModel import vtkPiecewiseFunction +from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera +from vtkmodules.vtkRenderingCore import vtkVolume, vtkVolumeProperty, vtkRenderer, vtkColorTransferFunction +from vtkmodules.vtkRenderingVolume import vtkGPUVolumeRayCastMapper +# noinspection PyUnresolvedReferences +from vtkmodules.vtkRenderingVolumeOpenGL2 import vtkSmartVolumeMapper + +from crystalfly.model.image_reader import image_reader_model + + +class VolumeViewer(QVTKRenderWindowInteractor): + doubleClicked = Signal(int) + + def __init__(self, quadrant: int): + super().__init__() + self.quadrant = quadrant + self.render_window = self.GetRenderWindow() + self.render_window.SetMultiSamples(0) + + self.render_window.AddRenderer(vtkRenderer()) + self.renderer = self.render_window.GetRenderers().GetFirstRenderer() + # self.iren = vtkRenderWindowInteractor() + # self.iren.SetRenderWindow(self.render_window) + + self.volume_mapper = vtkGPUVolumeRayCastMapper() + self.volume_mapper.SetBlendModeToComposite() + self.volume_mapper.SetUseJittering(1) + + self.colorTF = vtkColorTransferFunction() + self.colorTF.AddRGBPoint(-200, 0.0, 0.0, 0.0) + self.colorTF.AddRGBPoint(110, 0.4, 0.4, 1.0) + self.colorTF.AddRGBPoint(512, 1.0, 1.0, 1.0) + + self.scalarTF = vtkPiecewiseFunction() + self.scalarTF.AddPoint(-255, 0.00) + self.scalarTF.AddPoint(110, 0.00) + self.scalarTF.AddPoint(512, 0.5) + + self.volume_property = vtkVolumeProperty() + self.volume_property.ShadeOn() + self.volume_property.SetInterpolationTypeToLinear() + self.volume_property.SetColor(self.colorTF) + self.volume_property.SetScalarOpacity(self.scalarTF) + + self.volume = vtkVolume() + self.volume.SetMapper(self.volume_mapper) + self.volume.SetProperty(self.volume_property) + + self.renderer.ResetCamera() + self.renderer.GetActiveCamera().Zoom(1.3) + + self.interactor_style = vtkInteractorStyleTrackballCamera() + self.iren = self.render_window.GetInteractor() + # self.iren.SetRenderWindow(self.render_window) + self.iren.SetInteractorStyle(self.interactor_style) + self.iren.Initialize() + + self.renderer.AddVolume(self.volume) + image_reader_model.loadedSignal.connect(self.set_image) + self.renderer.SetBackground(0.3, 0.3, 0.3) + self.renderer.SetBackgroundAlpha(0.4) + + def set_image(self, image): + self.volume_mapper.SetInputData(image) + self.iren.SetRenderWindow(self.render_window) + self.render_window.Render() + self.volume.Update() + # self.volume_mapper.Render() + + def mouseDoubleClickEvent(self, evt: QMouseEvent): + print(evt, self.quadrant) + self.doubleClicked.emit(self.quadrant) diff --git a/crystalfly/ui/vtk_viewer.py b/crystalfly/ui/vtk_viewer.py index fe14dd9..b2e1a17 100644 --- a/crystalfly/ui/vtk_viewer.py +++ b/crystalfly/ui/vtk_viewer.py @@ -1,21 +1,27 @@ # noinspection PyUnresolvedReferences -# import vtkmodules.vtkRenderingContextOpenGL2 -from PySide6.QtGui import QCloseEvent +import vtkmodules.vtkRenderingContextOpenGL2 +from PySide6.QtCore import Signal +from PySide6.QtGui import QCloseEvent, QMouseEvent from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor -from vtkmodules.vtkInteractionImage import vtkImageViewer2 +from vtkmodules.vtkInteractionImage import vtkResliceImageViewer, vtkImageViewer2 from vtkmodules.vtkInteractionStyle import vtkInteractorStyleImage +from vtkmodules.vtkInteractionWidgets import vtkResliceCursorLineRepresentation, vtkImagePlaneWidget from crystalfly.model.image_reader import image_reader_model class VTKImageViewer(QVTKRenderWindowInteractor): - def __init__(self): + doubleClicked = Signal(int) + + def __init__(self, quadrant: int): super().__init__() + self.quadrant = quadrant + # self.render_window = render_window self.render_window = self.GetRenderWindow() # image viewer + # self.image_viewer = vtkResliceImageViewer() self.image_viewer = vtkImageViewer2() - # self.image_viewer.SetRenderWindow(self.render_window) # 交互 self.interactor_style = vtkInteractorStyleImage() @@ -25,6 +31,14 @@ class VTKImageViewer(QVTKRenderWindowInteractor): self.interactor_style.AddObserver("MouseWheelBackwardEvent", self.mouse_wheel_backward_event) self.iren.Initialize() + # rep = vtkResliceCursorLineRepresentation.SafeDownCast( + # self.image_viewer.GetResliceCursorWidget().GetRepresentation()) + # # self.image_viewer. + # rep.GetResliceCursorActor().GetCursorAlgorithm().SetReslicePlaneNormal(0) + # + # plane_widget = vtkImagePlaneWidget() + # plane_widget.SetInteractor(self.iren) + # plane_widget.On() image_reader_model.loadedSignal.connect(self.set_image) def set_image(self, image): @@ -49,3 +63,6 @@ class VTKImageViewer(QVTKRenderWindowInteractor): def closeEvent(self, evt: QCloseEvent): super().closeEvent(evt) self.Finalize() + + def mouseDoubleClickEvent(self, evt: QMouseEvent): + self.doubleClicked.emit(self.quadrant)