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

View File

@ -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__':

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

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.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):

View File

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

View File

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