From 0c8cb7b4301553070b0ef2f17c34190897dfcfd0 Mon Sep 17 00:00:00 2001 From: quantulr <35954003+quantulr@users.noreply.github.com> Date: Fri, 19 Apr 2024 17:08:06 +0800 Subject: [PATCH] update --- .gitignore | 160 ++++++++++++++++++ .idea/crystalfly.iml | 2 +- .idea/misc.xml | 2 +- .idea/poetry.xml | 10 ++ .idea/vcs.xml | 6 + crystalfly/main.py | 5 +- crystalfly/test.py | 52 ++++++ crystalfly/test_volumn.py | 58 +++++++ .../__pycache__/image_reader.cpython-311.pyc | Bin 2228 -> 2282 bytes crystalfly/thread/image_reader.py | 8 +- .../__pycache__/import_file.cpython-311.pyc | Bin 3875 -> 4565 bytes .../__pycache__/main_window.cpython-311.pyc | Bin 3135 -> 5719 bytes .../__pycache__/volume_viewer.cpython-311.pyc | Bin 0 -> 5886 bytes .../ui/__pycache__/vtk_viewer.cpython-311.pyc | Bin 4280 -> 5041 bytes crystalfly/ui/ctk.cpp | 112 ++++++++++++ crystalfly/ui/import_file.py | 27 ++- crystalfly/ui/main_window.py | 58 ++++++- crystalfly/ui/main_window.ui | 12 +- crystalfly/ui/volume_viewer.py | 77 +++++++++ crystalfly/ui/vtk_viewer.py | 27 ++- 20 files changed, 585 insertions(+), 31 deletions(-) create mode 100644 .gitignore create mode 100644 .idea/poetry.xml create mode 100644 .idea/vcs.xml create mode 100644 crystalfly/test.py create mode 100644 crystalfly/test_volumn.py create mode 100644 crystalfly/ui/__pycache__/volume_viewer.cpython-311.pyc create mode 100644 crystalfly/ui/ctk.cpp create mode 100644 crystalfly/ui/volume_viewer.py 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 258e0472a89047105d64dc6140277309bfcd27ba..898b5017ba204f3669f24a6bf69122211d3ab409 100644 GIT binary patch delta 442 zcmX|6yGjF55Z$}k$4%nKY)C{x9xLjC+K5`3h!7N;6voO%7x%^(6AgExC`7Rc2zD}! zl@IWNSSYCY2R1f|q*}1C5?ebf@AY+xGn_eRW|%wuUDqx&%_lJ6Ofp~C*IYET|JIY1 zY_X1J728u6W>p*Igg)8Tr^?nMnI@J^=uCmKnqBbl(O*cca{ZBpNfgI5@mv&yD-nt) zhAv?jC8Hi!a!4spHI{Ofibcsv)!`!sJfJLU14T?G0!%vwqaEP|x3_TTLHJ<3iOm)^ z8`y+#(M0pN1@R08YC@4+_<`+%YcURnw2l05Bt_V=duhbywXLc(SKX`-c#{%H163Xm z={Zu(uzqgADp_=fn@e(cgdcVl{uiMRUu+mO2q$t<2{GZs;8A|+FrKk-96n9l_*-40 zO`K}sR0F5rP#N>;EJgi{;8hvxZR5&hbScnpC&(M#KjJwj0~2aLEUO8m!=Abk9pLVN Z_!EzoSM9YDNz*}y;d$hPEj%o7{|g{HXqx~4 delta 441 zcmaDQxJ8h6IWI340}vS4$fa>kDD78AyZhXPe1hjIwOY7#SE=GfvKDlws7I+`}j@$aIUPI5j7& zhzF>q7^HFXQAR%-ZXlyd3d3Z*`1s7c%#!$cuxdG=q6UV$9Q;=}BxZ zSY;V`CR?#ODk=o0mbfIABnBrZ<|Gz{R2HO$2KczG$)vic)au7!X NBirQ79L6jlc>t7@X;A-EPP+kAQXh;1MSlj0CUAdv`7MF?1pj8sfoxl*^5cS%gV*qUAP zfsAOS960n4G#4u6KoyBLLh7ZL_EPm9Xk{!a&`L;1RqsUYsp_F`)*mfOt{v^Xee?Uh zH#7U@H~aF!ul>HSMbV9*wI{>N)V1vk;lH;B=Te$C9aJ zQfe_dmzup@5A>hI*ero_2%Ce>;omS%FL6t_Ov6Ga-Q(JY#%`#=MikY!r3{SapLSZ_^gr6MuzYh~nsXUpSuQ z0E^Mt2>=V0T%;q;F5E@uo#mb=0J-Mrz<`HtgZ@X5qo>aC%?+%e4X%t8{E)4Hh+s4- z0iz9WbrCqO$+7f$18x>LTayzSjFmSDoW03eYOC>(7YEK^aY?qI_grE(kbE=Ica9T* zbJjVh;r}m3dDp1F%)y~}8t|?cB-lnvoX-ca_;$Ftz`xN6MR;LTTrX@JHY41~La1yj zqdh3nzEvoOZ`jNBU4MO%;7!o5JGJh}uNsl->-<{%&0bd~)}7WE9G(GJ$LheB+4TqOKZg^@%}TfDpDz!iq@PXAt&@gei!y+L1UI({8ZnOU!jV z$`;mC@($xhm|>Lyks4WI#2uEXOrMf9va<2Cq{>#}1cY2RUCW?RnNemSuVwG6GEuXN zY&>8u-2pKX)@nlQrN6ksn@nPQA870|bSm{8N~1^8R8^Wfkfsl%=`G)JTWHI5?2&dp zta=72o`I84cq_S;JQllmo>axbia2-@8hQhXHjt-Pai}5=orH!PNHZ&9A(JpsGbr8& zdR!G_6)|=af{CnMjT}j1RcY)%8b6fAkEOm-dd1yON8Lf1c1!Wz#5eix+YWlB4|}F7 zf^;fKM?$zNgb#%1p%AS!9ulQrx_`t&^dnEtHLK`OUA0arZmU(5SMu3H@xCdrI+n7= z1D##AX2rVcD^EYZMSu2uila2)9Zs?T17UR~F=j3@!!97{Wu_a9DavxLkk$2^S}4eJ z922HV>%!lvodf&rSpOa$-Se(=^f>y?5SZ6C#CxCJJL4EIMPuT)mz|XrnTmJQW$}|6 zp-09;t*EROR6POiOi|N{t20HS)?{ZX&JvrYGfR7x6kF&|;sEy3-^J@Ow)`tZthpoL z|0PV-rko~AFa%{F{Tik-zK`WI4&#n9^nVy0;hXd;U-HHzo;X8qn&+K6iPsGO^DU&c G`F{Yam!?7h delta 1075 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 E2gkOg)c^nh delta 1312 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 literal 0 HcmV?d00001 diff --git a/crystalfly/ui/__pycache__/vtk_viewer.cpython-311.pyc b/crystalfly/ui/__pycache__/vtk_viewer.cpython-311.pyc index 0affa0639e928a91085e6378c92c91f4e71fac82..cf38fd90fe3f692ed6b827f6c3b8cf3463b03631 100644 GIT binary patch delta 2086 zcma)6Uu+ab7@ygHw|9Gc2lQ&M*FRiKDPAnsKoNu#trRSV|CC^|FPnp%)`h*hI=cth zG-)3itxAH;VCs{(2SteSfshyQjD~|Wa@oWLAK|Hr5Tg(J%^ufs*hFW)`R#mv zX1;H}`DVWCy1ps)sjf#5v~Ay{$KKNBW69}vfm=Wpvao{;41d89@}ePPE)yLoFB@`R zF_eIpoKRjh)L<+-;e5o1~Z2c_G$8JJ~vKI+T=W;^Ivb?CHUPdy5SOai}avfAjioBB`&Oz^Xy{5ZvvZO z;!!78Ag|fhIPvE1_f5YJ5xfmqAwfnAK|n+#(iIHxLa#MLwlI;#ggG>ZEnzllD3&;j zjF2S(s+J5GwiFV1Y!Lzr4fyDoV5zf`q0gZU*oeV~!+z}Ki33l>?`PCEN;!+>3f^g< z?hgJEbe^pT`$75vUO*mZ*VQCz4fiB4n!;1aTsEIVWs$wC^+ZiRD`=hu)~TqeEDPdX zkTli0U<#dCwl0VH^rRV73pW7e^0=(AZ*Mmez*Q5tO0uuSoh=Dpc1sh4`l@N#1=}-C8V6$*lDZN+ z6hCV7QZUa2wLrscP`ZSp><6j0L-Zw=IAejmA2!X?PR?~r)8$D`v#`8>Bmtwzmd4}A z*8QvjrTZJeA2aA@RevXRF?2^w-d2;h)YPJys;H@IGCe0<)bFx+Ik8qn9euajH!rqt zo|6{U)`trFLVizvm_XsS8+(;~SZRT!XfwMMYR1p7PeL17H-NyGgYaE`lIb+v$ZfjW zFQE?H$NmmY;@%sR>I}v$?5}X!wmv>V9VOl;mOJ=>P}rj&1{J zbkoVck7l)@IbSTfWbewf;Gvo9d8!UpwHgMeZeJJR463S8sDnG|`rGRIvh=ZjOU*8-*-D)# z-%lpaw!}6L@Ll+#UGV4-j3}==+RcF<67A%`uOi*eLFTZBO~jg~gM{%nXsDL17*}!7 zp}XG!KylW;Y9jUiD<2NbAGmyA=FpOql811)X$kTFf8YzUoLWN55R>FhO9+77i|v|L zvD|;PZ3)5ee?YM7AjC|<-v|$GzG#&k;%0;QxnL1$7siK+1&>_tj!clk$ic0CWW~&n z9es1uw#fGEF>km?2^CvmlfJ$p9VywhT*>T2Jc|`}HQpHwo>LkEQ>ucXM+x&;Y4&-% zHCB7)UCN&zs<9vA8>XXt$kTWY>XncAz57!gti}2wJq|3-y89Erl87=8v4^zMSCiZR7F;0HM)xiD~+_I?7(m~|AtC)|F2y9d!sHA I*akcQ2Tf@O;{X5v delta 1442 zcmZ`&UuYaf7@yg{bGO;eU0S)#d3U|Gx!9xLmC%}pQfjEwD8*pB5@EqEF*}h9dl#L( zBy9;KLePjp2&3jr0)nOZB<+I_KIpSAml9}M6#AeK5rh-1h!4eY_OfXXb#CT&-=Fz@ z^L^jUe)Y`P$E{nYnM5#_FBWF!$fjklTmx+jdC0>)YG4OrAxpm0kR3UaWnXEijvC2| zuQhZ>k7U&+jf9iHNJ8Vt)7FruQ~3)Sp)X-|hm-V34V4pnLM`Lan0YsAZ+H8TE zot(P5xo2a{2XTeJi%+kN!M^t)9f}<6AxBzAv)IFH#F0H|4LOP@1FD_^sCg>YXS+MJ zwLXWeqwCm7z;SwLOge=khhcIJgUHcov+GI zW1U}<3z~<)(8s@x4U#AEZ{%aflQ0XyPGm7^6=GI|=2Ee|osL-tfYs(MQTdi~Zv057 z1`BUf782L3H|s6e4g1Gw>owZ+D0{u$^yZgBlSP`l6sb&98TQph3*E)2hfL!cbptZJ zr4DDLPzk6%8zx+L#-9!X*A1YVi2tI#L@f|*W_mB68Xw#T&Op3|zgE$Y#QZ@2NWV|) zd&J%$*wfXk2I8jb|+Wa%~gax_=xb&v=8N^a(R&p zJ10OqlKtq_ZoQ-YJF9?4_(SVCtq9(7k{42?6>()OCm&J>}aR3-9i6` Q_ETqm6Xrku!xts`4?ST{NdN!< 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)