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)