This commit is contained in:
quantulr
2024-04-19 17:08:06 +08:00
parent 0c8e503d2f
commit 0c8cb7b430
20 changed files with 585 additions and 31 deletions

160
.gitignore vendored Normal file
View File

@ -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/

2
.idea/crystalfly.iml generated
View File

@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" /> <orderEntry type="jdk" jdkName="Poetry (crystalfly) (2)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

2
.idea/misc.xml generated
View File

@ -3,5 +3,5 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="Poetry (crystalfly)" /> <option name="sdkName" value="Poetry (crystalfly)" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Poetry (crystalfly)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Poetry (crystalfly) (2)" project-jdk-type="Python SDK" />
</project> </project>

10
.idea/poetry.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PoetryConfigService">
<option name="poetryVirtualenvPaths">
<set>
<option value="$PROJECT_DIR$/../../../ApplicationData/pypoetry/Cache/virtualenvs/crystalfly-e61ASWpP-py3.11/Scripts/python.exe" />
</set>
</option>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,15 +1,18 @@
import ctypes
import sys import sys
from PySide6.QtWidgets import QApplication from PySide6.QtWidgets import QApplication
from crystalfly.ui.main_window import MainWindow from crystalfly.ui.main_window import MainWindow
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("crystalfly")
def main(): def main():
app = QApplication(sys.argv) app = QApplication(sys.argv)
window = MainWindow() window = MainWindow()
window.show() window.show()
app.exec() sys.exit(app.exec())
if __name__ == '__main__': if __name__ == '__main__':

52
crystalfly/test.py Normal file
View File

@ -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()

58
crystalfly/test_volumn.py Normal file
View File

@ -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()

View File

@ -6,11 +6,13 @@ from vtkmodules.vtkIOImage import vtkMedicalImageReader2
from crystalfly.model.image_reader import image_reader_model from crystalfly.model.image_reader import image_reader_model
# from vtkmodules.vtkCommonCore import vtkCommand, VTK_INT
class ImageReaderThread(QThread): class ImageReaderThread(QThread):
loadedSignal = Signal(Any) loadedSignal = Signal(Any)
def __init__(self, file_data): def __init__(self, file_data, parent=None):
QThread.__init__(self) QThread.__init__(self, parent)
self.file_data = file_data self.file_data = file_data
def run(self): def run(self):
@ -28,7 +30,7 @@ class ImageReaderThread(QThread):
self.file_data["size"]["z"] - 1, self.file_data["size"]["z"] - 1,
) )
# self.file_data["data_type"] # self.file_data["data_type"]
reader.SetDataScalarTypeToUnsignedShort() reader.SetDataScalarType(self.file_data["data_type"])
reader.Update() reader.Update()
image_reader_model.loaded(reader.GetOutput()) image_reader_model.loaded(reader.GetOutput())
print("success") print("success")

112
crystalfly/ui/ctk.cpp Normal file
View File

@ -0,0 +1,112 @@
#include <vtkImageData.h>
#include <vtkDICOMImageReader.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkVolumeProperty.h>
#include <vtkPiecewiseFunction.h>
#include <vtkColorTransferFunction.h>
#include <vtkGPUVolumeRayCastMapper.h>
#include <vtkPointData.h>
#include <vtkCamera.h>
#include <vtkNew.h>
#include <vtkFloatArray.h>
#include <vtkImageReader.h>
#include <fstream>
#include <string>
//#include <vtkAutoInit.h>
//VTK_MODULE_INIT( vtkRenderingOpenGL2 );
//VTK_MODULE_INIT( vtkRenderingVolumeOpenGL2 );
int main()
{
//raw data reader
vtkSmartPointer<vtkImageReader> reader = vtkSmartPointer<vtkImageReader>::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<vtkVolumeProperty> 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<vtkColorTransferFunction> 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<vtkPiecewiseFunction> 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<vtkPiecewiseFunction> 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<vtkRenderWindow> renderWindow;
renderWindow->SetSize(512, 512);
renderWindow->SetMultiSamples(0);
// mapping data
vtkNew<vtkGPUVolumeRayCastMapper> mapper;
mapper->SetInputConnection(reader->GetOutputPort());
mapper->SetBlendModeToComposite();
mapper->SetUseJittering(1);
// renderer and volume
vtkNew<vtkRenderer> renderer;
renderWindow->AddRenderer(renderer);
renderer->SetBackground(0.03, 0.33, 0.33);
vtkNew<vtkVolume> volume;
volume->SetMapper(mapper);
volume->SetProperty(volumeProperty);
renderer->AddVolume(volume);
renderer->ResetCamera();
renderer->GetActiveCamera()->Zoom(1.3);
vtkNew<vtkRenderWindowInteractor> interactor;
interactor->SetRenderWindow(renderWindow);
vtkNew<vtkInteractorStyleTrackballCamera> style;
interactor->SetInteractorStyle(style);
renderWindow->Render();
interactor->Start();
return 0;
}

View File

@ -3,6 +3,8 @@ from pathlib import Path
from PySide6.QtCore import Slot, QEvent from PySide6.QtCore import Slot, QEvent
from PySide6.QtUiTools import loadUiType from PySide6.QtUiTools import loadUiType
from PySide6.QtWidgets import QDialog, QFileDialog 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 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): class ImportFile(QDialog, ImportFileType):
def __init__(self): def __init__(self):
super(ImportFile, self).__init__() super(ImportFile, self).__init__()
self.image_read_thread = None # self.image_read_thread = None
self.setupUi(self) self.setupUi(self)
self.browse_file.clicked.connect(self.browse_file_handler) self.browse_file.clicked.connect(self.browse_file_handler)
@ -30,19 +32,34 @@ class ImportFile(QDialog, ImportFileType):
@Slot() @Slot()
def load_image_file(self): 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_data = {
"file_path": self.file_path_input.text(), "file_path": self.file_path_input.text(),
"data_type": self.data_type_combobox.currentText(), "data_type": data_type,
"size": { "size": {
"x": self.size_x_spin.value(), "x": self.size_x_spin.value(),
"y": self.size_y_spin.value(), "y": self.size_y_spin.value(),
"z": self.size_z_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.loadedSignal.connect(self.set_image)
self.image_read_thread.start() image_read_thread.start()
@Slot() @Slot()
def browse_file_handler(self): def browse_file_handler(self):

View File

@ -3,8 +3,10 @@ from pathlib import Path
from PySide6.QtCore import Slot from PySide6.QtCore import Slot
from PySide6.QtUiTools import loadUiType from PySide6.QtUiTools import loadUiType
from PySide6.QtWidgets import QMainWindow from PySide6.QtWidgets import QMainWindow
from vtkmodules.vtkRenderingOpenGL2 import vtkGenericOpenGLRenderWindow
from crystalfly.ui.import_file import ImportFile from crystalfly.ui.import_file import ImportFile
from crystalfly.ui.volume_viewer import VolumeViewer
from crystalfly.ui.vtk_viewer import VTKImageViewer from crystalfly.ui.vtk_viewer import VTKImageViewer
MainWindowType, _ = loadUiType(str(Path(__file__).parent.joinpath("main_window.ui"))) 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): class MainWindow(QMainWindow, MainWindowType):
def __init__(self): def __init__(self):
super(MainWindow, self).__init__() 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.setupUi(self)
self.file_open_dialog = ImportFile() self.file_open_dialog = ImportFile()
self.file_open.triggered.connect(self.handle_file_open) self.file_open.triggered.connect(self.handle_file_open)
self.init_four_pane() self.init_four_pane()
self.is_pane_maximum = False
def init_four_pane(self): def init_four_pane(self):
coronal = VTKImageViewer() self.coronal = VTKImageViewer(1)
coronal.image_viewer.SetSliceOrientationToYZ() self.sagittal = VTKImageViewer(3)
sagittal = VTKImageViewer() self.transverse = VTKImageViewer(4)
sagittal.image_viewer.SetSliceOrientationToXZ() self.volume = VolumeViewer(2)
transverse = VTKImageViewer() self.coronal.image_viewer.SetSliceOrientationToYZ()
transverse.image_viewer.SetSliceOrientationToXY() 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.addWidget()
self.gridLayout_2.addWidget(coronal) self.volume_grid_layout.addWidget(self.volume)
self.gridLayout_3.addWidget(sagittal) self.coronal_grid_layout.addWidget(self.coronal)
self.gridLayout_4.addWidget(transverse) 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() @Slot()
def cell_click(self): def cell_click(self):

View File

@ -40,11 +40,11 @@
<number>0</number> <number>0</number>
</property> </property>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QWidget" name="widget_2" native="true"> <widget class="QWidget" name="widget_3d" native="true">
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">border: 1px solid rgb(195, 195, 195);</string> <string notr="true">border: 1px solid rgb(195, 195, 195);</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="volume_grid_layout">
<property name="leftMargin"> <property name="leftMargin">
<number>1</number> <number>1</number>
</property> </property>
@ -61,12 +61,12 @@
</widget> </widget>
</item> </item>
<item row="0" column="2"> <item row="0" column="2">
<widget class="QWidget" name="widget_3" native="true"> <widget class="QWidget" name="widget_coronal" native="true">
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">border: 1px solid rgb(195, 195, 195); <string notr="true">border: 1px solid rgb(195, 195, 195);
margin-left: -1px;</string> margin-left: -1px;</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="coronal_grid_layout">
<property name="leftMargin"> <property name="leftMargin">
<number>1</number> <number>1</number>
</property> </property>
@ -83,7 +83,7 @@ margin-left: -1px;</string>
</widget> </widget>
</item> </item>
<item row="1" column="2"> <item row="1" column="2">
<widget class="QWidget" name="widget_5" native="true"> <widget class="QWidget" name="widget_transverse" native="true">
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">border: 1px solid rgb(195, 195, 195); <string notr="true">border: 1px solid rgb(195, 195, 195);
margin-left: -1px; margin-left: -1px;
@ -106,7 +106,7 @@ margin-top: -1px;</string>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QWidget" name="widget_4" native="true"> <widget class="QWidget" name="widget_sagittal" native="true">
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">border: 1px solid rgb(195, 195, 195); <string notr="true">border: 1px solid rgb(195, 195, 195);
margin-top: -1px;</string> margin-top: -1px;</string>

View File

@ -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)

View File

@ -1,21 +1,27 @@
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
# import vtkmodules.vtkRenderingContextOpenGL2 import vtkmodules.vtkRenderingContextOpenGL2
from PySide6.QtGui import QCloseEvent from PySide6.QtCore import Signal
from PySide6.QtGui import QCloseEvent, QMouseEvent
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from vtkmodules.vtkInteractionImage import vtkImageViewer2 from vtkmodules.vtkInteractionImage import vtkResliceImageViewer, vtkImageViewer2
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleImage from vtkmodules.vtkInteractionStyle import vtkInteractorStyleImage
from vtkmodules.vtkInteractionWidgets import vtkResliceCursorLineRepresentation, vtkImagePlaneWidget
from crystalfly.model.image_reader import image_reader_model from crystalfly.model.image_reader import image_reader_model
class VTKImageViewer(QVTKRenderWindowInteractor): class VTKImageViewer(QVTKRenderWindowInteractor):
def __init__(self): doubleClicked = Signal(int)
def __init__(self, quadrant: int):
super().__init__() super().__init__()
self.quadrant = quadrant
# self.render_window = render_window
self.render_window = self.GetRenderWindow() self.render_window = self.GetRenderWindow()
# image viewer # image viewer
# self.image_viewer = vtkResliceImageViewer()
self.image_viewer = vtkImageViewer2() self.image_viewer = vtkImageViewer2()
# self.image_viewer.SetRenderWindow(self.render_window)
# 交互 # 交互
self.interactor_style = vtkInteractorStyleImage() self.interactor_style = vtkInteractorStyleImage()
@ -25,6 +31,14 @@ class VTKImageViewer(QVTKRenderWindowInteractor):
self.interactor_style.AddObserver("MouseWheelBackwardEvent", self.mouse_wheel_backward_event) self.interactor_style.AddObserver("MouseWheelBackwardEvent", self.mouse_wheel_backward_event)
self.iren.Initialize() 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) image_reader_model.loadedSignal.connect(self.set_image)
def set_image(self, image): def set_image(self, image):
@ -49,3 +63,6 @@ class VTKImageViewer(QVTKRenderWindowInteractor):
def closeEvent(self, evt: QCloseEvent): def closeEvent(self, evt: QCloseEvent):
super().closeEvent(evt) super().closeEvent(evt)
self.Finalize() self.Finalize()
def mouseDoubleClickEvent(self, evt: QMouseEvent):
self.doubleClicked.emit(self.quadrant)