├── SimpleITKSnap ├── utils │ ├── __init__.py │ ├── ImageUtils3D.py │ ├── ImageIO.py │ └── ImageUtils.py ├── __init__.py ├── ExtensionTools.py ├── Extension.py ├── ViewModel.py └── View.py ├── demo.gif ├── logo.png ├── demoCode.png ├── requirements.txt ├── .gitignore ├── setup.py ├── simpleITK-Snap.py ├── LICENSE ├── example.py ├── README.md └── CODE_OF_CONDUCT.md /SimpleITKSnap/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SimpleITKSnap/__init__.py: -------------------------------------------------------------------------------- 1 | from .View import imshow, fileshow 2 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesQFreeman/simpleITK-Snap/HEAD/demo.gif -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesQFreeman/simpleITK-Snap/HEAD/logo.png -------------------------------------------------------------------------------- /demoCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesQFreeman/simpleITK-Snap/HEAD/demoCode.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy~=1.18.5 2 | opencv-python~=4.1.2.30 3 | matplotlib~=3.2.2 4 | pyqt5~=5.15.0 5 | simpleitk~=1.2.4 -------------------------------------------------------------------------------- /SimpleITKSnap/utils/ImageUtils3D.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | 4 | from numpy import ndarray 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test.ipynb 2 | .vscode/ 3 | test.nii.gz 4 | SimpleITKSnap/utils/__pycache__/ 5 | .DS_Store 6 | CTA.nii.gz 7 | venv/ 8 | .idea/ 9 | __pycache__/ 10 | dist/ 11 | build/ 12 | SimpleITKSnap.egg-info/ -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="SimpleITKSnap", # Replace with your own username 8 | version="0.1.1-alpha", 9 | author="JamesQFreeman", 10 | author_email="wsheng@sjtu.edu.cn", 11 | description="A Qt-based 3D medical image visualization tool.", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/JamesQFreeman/simpleITK-Snap", 15 | packages=setuptools.find_packages(), 16 | classifiers=[ 17 | "Programming Language :: Python :: 3", 18 | "License :: OSI Approved :: MIT License", 19 | "Operating System :: OS Independent", 20 | ], 21 | python_requires='>=3.5', 22 | ) 23 | -------------------------------------------------------------------------------- /SimpleITKSnap/utils/ImageIO.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | import cv2 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | from PyQt5.QtGui import QImage, QPixmap 7 | from numpy import ndarray 8 | 9 | 10 | def createQPixmapFromArray(img: ndarray, fmt=QImage.Format_Grayscale8) -> QPixmap: 11 | height, width = img.shape[:2] 12 | pixelSize = 1 if fmt == QImage.Format_Grayscale8 else 3 13 | qImg = QImage(img.data, width, height, pixelSize * width, fmt) 14 | return QPixmap(qImg) 15 | 16 | 17 | def getArrayFromFig(fig) -> ndarray: 18 | plt.tight_layout() 19 | buf = io.BytesIO() 20 | fig.savefig(buf, format="png", dpi=100) 21 | plt.close() 22 | buf.seek(0) 23 | imgArr = np.frombuffer(buf.getvalue(), dtype=np.uint8) 24 | buf.close() 25 | img = cv2.imdecode(imgArr, 1) 26 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 27 | 28 | return img 29 | -------------------------------------------------------------------------------- /simpleITK-Snap.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | 4 | from PyQt5.QtWidgets import QApplication 5 | 6 | from SimpleITKSnap.Extension import FFT 7 | from SimpleITKSnap.View import MainWindow 8 | from SimpleITKSnap.ViewModel import FileView3D 9 | 10 | 11 | def getArgs(): 12 | parser = argparse.ArgumentParser(description='Visualize a 3D image file') 13 | parser.add_argument('-f', '--file', dest='file', type=str, default=False, 14 | help='Load image from a file') 15 | return parser.parse_args() 16 | 17 | 18 | if __name__ == '__main__': 19 | args = getArgs() 20 | # if not args.file: 21 | # raise Exception("No File") 22 | app = QApplication([]) 23 | main = MainWindow(FileView3D('CTA.nii.gz', (400, 400)), FFT) 24 | # main = MainWindow(view=FileView3D(args.file, (400, 400))) 25 | main.show() 26 | sys.exit(app.exec_()) 27 | -------------------------------------------------------------------------------- /SimpleITKSnap/ExtensionTools.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | import matplotlib.pyplot as plt 4 | import numpy as np 5 | from cv2 import cvtColor, COLOR_GRAY2RGB 6 | from .utils.ImageUtils import toGray8 7 | from SimpleITKSnap.utils.ImageIO import getArrayFromFig 8 | 9 | 10 | def pltExtension(extension): 11 | @wraps(extension) 12 | def wrapper(*args): 13 | fig = plt.figure(figsize=(4, 4)) 14 | text = extension(*args) 15 | return getArrayFromFig(fig), text 16 | 17 | return wrapper 18 | 19 | 20 | def imgExtension(extension): 21 | @wraps(extension) 22 | def wrapper(*args): 23 | img, text = extension(*args) 24 | img = toGray8(img) 25 | # if img.dtype != np.uint8: 26 | # img = (255 * (img - img.min()) / (img.max() - img.min())).astype(np.uint8) 27 | # if len(img.shape) == 2: 28 | # img = cvtColor(img, COLOR_GRAY2RGB) 29 | return img, text 30 | 31 | return wrapper 32 | -------------------------------------------------------------------------------- /SimpleITKSnap/Extension.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | import matplotlib.pyplot as plt 4 | import numpy as np 5 | from numpy import ndarray 6 | from numpy.fft import fft2, fftshift 7 | 8 | from SimpleITKSnap.ExtensionTools import imgExtension, pltExtension 9 | 10 | 11 | @pltExtension 12 | def histogram(array: ndarray, x: int, *_) -> str: 13 | plt.hist(array[x].flatten(), 64) 14 | return "Histogram of the {}th slice".format(x + 1) 15 | 16 | 17 | @imgExtension 18 | def defaultBlank(array: ndarray, x: int, *_) -> Tuple[ndarray, str]: 19 | s = np.zeros((400, 400), dtype=np.uint8) 20 | return s, "Magnitude spectrum of the {}th slice".format(x + 1) 21 | 22 | 23 | def bone(array: ndarray, x: int, y: int, z: int) -> Tuple[ndarray, str]: 24 | pass 25 | 26 | 27 | @imgExtension 28 | def FFT(array: ndarray, x: int, *_) -> Tuple[ndarray, str]: 29 | s = np.log(np.abs(fftshift(fft2(array[x])))) 30 | return s, "Magnitude spectrum of the {}th slice".format(x + 1) 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sheng Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SimpleITKSnap/utils/ImageUtils.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | import cv2 4 | import numpy as np 5 | from numpy import ndarray 6 | 7 | 8 | def padImage(image: ndarray, newShape: Tuple[int, int]) -> ndarray: 9 | shape = image.shape 10 | delta_h = newShape[0] - shape[0] 11 | delta_w = newShape[1] - shape[1] 12 | top, bottom = delta_h // 2, delta_h - (delta_h // 2) 13 | left, right = delta_w // 2, delta_w - (delta_w // 2) 14 | return cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, 15 | value=0) 16 | 17 | 18 | def resizeBySpacing(image: ndarray, shape: Tuple[int, int], spacing: Tuple[float, float]) -> ndarray: 19 | physicalRatio = (image.shape[0] * spacing[0]) / (image.shape[1] * spacing[1]) 20 | orgShape = (image.shape[0], image.shape[1] * physicalRatio) 21 | zoomRatio = min(shape[0] / orgShape[0], shape[1] / orgShape[0]) 22 | shapeBeforePad = (int(zoomRatio * orgShape[0]), int(zoomRatio * orgShape[1])) 23 | resized = cv2.resize(image, (shapeBeforePad[1], shapeBeforePad[0])) 24 | return padImage(resized, shape) 25 | 26 | 27 | def toGray8(img: ndarray): 28 | result = img.copy() 29 | cv2.normalize(img, result, 0, 255, cv2.NORM_MINMAX) 30 | # print(result.shape) 31 | return result.astype(np.uint8) 32 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | from numpy.fft import fft2, fftshift 5 | 6 | import SimpleITKSnap as sis 7 | from SimpleITKSnap.Extension import pltExtension, imgExtension 8 | from SimpleITKSnap.utils.ImageUtils3D import normalizeToGrayScale8 9 | 10 | 11 | # FEATURE 1: Extension, in a single line! 12 | # Support both matplotlib and numpy array, 13 | # everything you have in 2D, use it in 3D without modification. 14 | @imgExtension 15 | def FFT(array, x, *_): 16 | s = np.log(np.abs(fftshift(fft2(array[x])))) 17 | return s, "Spectrum" 18 | 19 | 20 | @pltExtension 21 | def histogram(array, x, *_): 22 | plt.hist(array[x].flatten(), 64) 23 | return "Histogram" 24 | 25 | 26 | # edges = cv2.Canny(img,100,200) -> If it is a 2D image 27 | @imgExtension 28 | def Canny(img, x, y, z): 29 | img = normalizeToGrayScale8(img[:, y, :]) 30 | edges = cv2.Canny(img, 100, 200) 31 | return edges, "Canny" 32 | 33 | 34 | sis.fileshow('CTA.nii.gz', Canny) 35 | 36 | 37 | # FEATURE 2: In-place 3D imshow, Why not use 3D like 2D 38 | # You never have to save use SimpleITK, then open ITK-SNAP, then, 39 | # find your file in dozens of other images, finally, open it. 40 | # myArray = np.arange(0, 256 * 256 * 256).reshape(256, 256, 256) 41 | # sis.imshow(myArray, histogram) 42 | -------------------------------------------------------------------------------- /SimpleITKSnap/ViewModel.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | from typing import Tuple 3 | 4 | import SimpleITK as sitk 5 | import numpy as np 6 | from cv2 import resize 7 | from numpy import ndarray 8 | 9 | from SimpleITKSnap.utils.ImageUtils import resizeBySpacing, toGray8 10 | 11 | 12 | class View3D: 13 | def __init__(self, array: ndarray, displaySize: Tuple[int, int], 14 | spacing: Tuple[float, float, float] = (1, 1, 1)) -> None: 15 | self.data = array 16 | self.grayScale8 = toGray8(self.data) 17 | self.displaySize = displaySize 18 | self.spacing = spacing 19 | 20 | @lru_cache(maxsize=128) 21 | def getXSlice(self, x: int) -> ndarray: 22 | return resizeBySpacing(self.grayScale8[x, :, :], self.displaySize, (self.spacing[0], self.spacing[1])) 23 | 24 | @lru_cache(maxsize=128) 25 | def getYSlice(self, y: int) -> ndarray: 26 | return resizeBySpacing(self.grayScale8[:, y, :], self.displaySize, (self.spacing[0], self.spacing[2])) 27 | 28 | @lru_cache(maxsize=128) 29 | def getZSlice(self, z: int) -> ndarray: 30 | return resizeBySpacing(self.grayScale8[:, :, z], self.displaySize, (self.spacing[1], self.spacing[2])) 31 | 32 | @lru_cache(maxsize=128) 33 | def getExtensionInfo(self, extensionFunc, x: int, y: int, z: int) -> Tuple[ndarray, str]: 34 | img, s = extensionFunc(self.data, x, y, z) 35 | return resize(img, self.displaySize), s 36 | 37 | 38 | class FileView3D(View3D): 39 | def __init__(self, imgDir: str, displaySize: Tuple[int, int]) -> None: 40 | sitkImg = sitk.ReadImage(imgDir) 41 | spacing = sitkImg.GetSpacing() 42 | array = sitk.GetArrayFromImage(sitkImg) 43 | # print(array.dtype) 44 | array = np.flip(array, 0) 45 | super().__init__(array, displaySize, spacing) 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 | 8 | ## Overview 9 | SimpleITKSnap is a 3D-image visualization tool. SimpleITKSnap is developed to bridge the gap between 3D-image process 10 | programming and its visualization. Comparing to the existing method, SimpleITKSnap have these advantage: 11 | 12 | 13 | 14 | 1. Matplotlib-style display: ```SimpleITKSnap.imshow(yourArray)```. 15 | You don't need to save your processed result to disk, open ITK-SNAP, find the file and load it again. 16 | 2. Extension-based design, meet your visualization demand by writing your own extension. 17 | The extension development requires minimal code and is super easy to √evelop. 18 | 19 | 20 | ## Install 21 | First, clone this repo to your local environment: 22 | 23 | ```bash 24 | git clone https://github.com/JamesQFreeman/simpleITK-Snap.git 25 | ``` 26 | 27 | Then use pip to install the dependency package: 28 | 29 | ```bash 30 | pip install -r requirements.txt 31 | ``` 32 | 33 | Then you are ready to go! 34 | 35 | ## Usage 36 | 37 | ### In-place Mode 38 | You can open 3D image in python code. 39 | ```python 40 | import SimpleITKSnap as sis 41 | from SimpleITKSnap.Extension import histogram 42 | array = np.arange(0,256*256*256).reshape(256,256,256) 43 | sis.imshow(array, histogram) 44 | ``` 45 | 46 | ### Application Mode 47 | Let's see an example of a brain CT image: 48 | To open an image, simply type: 49 | ```bash 50 | python simpleITK-Snap -f YourFile.nii.gz 51 | ``` 52 | 53 | ![A CTA image opened in simpleITK-Snap](./demo.gif) 54 | 55 | ## Extension 56 | You can develop your own extension in two ways: 57 | - Matplotlib-style: 58 | ```python 59 | @pltExtension 60 | def yourExtension(array3d:ndarray, x:int, y:int, z:int) -> str: 61 | plt.whateverYouWant() 62 | return "Extention display test at {}, {}, {}".format(x,y,z) 63 | ``` 64 | 65 | - Array-style: 66 | ```python 67 | @imgExtension 68 | def yourExtention(array3d:ndarray, x:int, y:int, z:int) -> Tuple[ndarray,str]: 69 | processed_2d_image = whateverYouWant() 70 | return processed_2d_image, "Extention display test at {}, {}, {}".format(x,y,z) 71 | ``` 72 | 73 | ## Dependency & Compatibility 74 | 75 | SimpleITKSnap is based on: 76 | - python3 77 | - SimpleITK 78 | - numpy 79 | - opencv-python 80 | - PyQt5 81 | 82 | Compatibility Test Status: 83 | 84 | | | Windows 10 | OS X | Linux | 85 | |-------|-------------|------|-------| 86 | | Build | ![Build Status](https://img.shields.io/badge/Build-Pass-green) | ![Build Status](https://img.shields.io/badge/Build-Pass-green) | ![Build Status](https://img.shields.io/badge/Build-Pass-green) | 87 | | PyPI | | ![Build Status](https://img.shields.io/badge/Build-Pass-green) | | 88 | 89 | ## Release note and what's coming next 90 | 91 | At release 0.1.3, jupyter notebook is supported 92 | #### release 0.1.4 93 | - Pypi support 94 | 95 | #### release 0.1.5 96 | - A doc 97 | - More extension examples 98 | 99 | ## Developer 100 | This project is started by JamesQFreeman(wsheng@sjtu.edu.cn) and supported by SJTU MIC lab. -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at wsheng@sjtu.edu.cn. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /SimpleITKSnap/View.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from PyQt5 import QtCore 4 | from PyQt5.QtCore import Qt 5 | from PyQt5.QtGui import QImage 6 | from PyQt5.QtWidgets import (QApplication, QGridLayout, QGroupBox, QDialog, 7 | QLabel, QSlider, QVBoxLayout) 8 | from numpy import ndarray 9 | 10 | from SimpleITKSnap.Extension import histogram, defaultBlank 11 | from SimpleITKSnap.ViewModel import View3D, FileView3D 12 | from SimpleITKSnap.utils.ImageIO import createQPixmapFromArray 13 | 14 | 15 | class MainWindow(QDialog): 16 | def __init__(self, view: View3D, extensionFunc=histogram): 17 | super().__init__() 18 | # load image 19 | self.imageData = view 20 | self.imageShape = self.imageData.data.shape 21 | 22 | self.x, self.y, self.z = 0, 0, 0 23 | self.extensionFunc = extensionFunc 24 | self.createExtensionGroupBox() 25 | self.createXViewGroupBox() 26 | self.createYViewGroupBox() 27 | self.createZViewGroupBox() 28 | 29 | mainLayout = QGridLayout() 30 | mainLayout.addWidget(self.XViewGroupBox, 1, 0) 31 | mainLayout.addWidget(self.YViewGroupBox, 1, 1) 32 | mainLayout.addWidget(self.ZViewGroupBox, 2, 0) 33 | mainLayout.addWidget(self.extensionGroupBox, 2, 1) 34 | self.setLayout(mainLayout) 35 | self.setWindowTitle("Simple-ITKSnap") 36 | 37 | def refreshExtension(self): 38 | image, text = self.imageData.getExtensionInfo( 39 | self.extensionFunc, self.x, self.y, self.z) 40 | self.extensionImageLabel.setPixmap( 41 | createQPixmapFromArray(image, fmt=QImage.Format_RGB888)) 42 | self.extensionTextLabel.setText(text) 43 | 44 | def setX(self, x): 45 | self.x = x 46 | # IMAGE 47 | image = self.imageData.getXSlice(self.x) 48 | self.imLabelX.setPixmap(createQPixmapFromArray(image)) 49 | # INDEX 50 | self.idxLabelX.setText("{}/{}".format(self.x + 1, self.imageShape[0])) 51 | # self.refreshExtension() 52 | 53 | def setY(self, y): 54 | self.y = y 55 | # IMAGE 56 | image = self.imageData.getYSlice(self.y) 57 | self.imLabelY.setPixmap(createQPixmapFromArray(image)) 58 | # INDEX 59 | self.idxLabelY.setText("{}/{}".format(self.y + 1, self.imageShape[1])) 60 | # self.refreshExtension() 61 | 62 | def setZ(self, z): 63 | self.z = z 64 | # IMAGE 65 | image = self.imageData.getZSlice(self.z) 66 | self.imLabelZ.setPixmap(createQPixmapFromArray(image)) 67 | # INDEX 68 | self.idxLabelZ.setText("{}/{}".format(self.z + 1, self.imageShape[2])) 69 | # self.refreshExtension() 70 | 71 | def createXViewGroupBox(self): 72 | self.XViewGroupBox = QGroupBox("Horizontal plane") 73 | # IMAGE 74 | self.imLabelX = QLabel() 75 | # SLIDER 76 | slider = QSlider(Qt.Horizontal, self.XViewGroupBox) 77 | slider.setMinimum(0) 78 | slider.setMaximum(self.imageShape[0] - 1) 79 | slider.valueChanged.connect(self.setX) 80 | # INDEX 81 | self.idxLabelX = QLabel() 82 | 83 | # initialization 84 | self.setX(0) 85 | # LAYOUT 86 | layout = QVBoxLayout() 87 | layout.addWidget(self.idxLabelX) 88 | layout.addWidget(self.imLabelX) 89 | layout.addWidget(slider) 90 | layout.addStretch(1) 91 | self.XViewGroupBox.setLayout(layout) 92 | 93 | def createYViewGroupBox(self): 94 | self.YViewGroupBox = QGroupBox("Coronal plane") 95 | # IMAGE 96 | self.imLabelY = QLabel() 97 | # SLIDER 98 | slider = QSlider(Qt.Horizontal, self.YViewGroupBox) 99 | slider.setMinimum(0) 100 | slider.setMaximum(self.imageShape[1] - 1) 101 | slider.valueChanged.connect(self.setY) 102 | # INDEX 103 | self.idxLabelY = QLabel() 104 | 105 | # initialization 106 | self.setY(0) 107 | # LAYOUT 108 | layout = QVBoxLayout() 109 | layout.addWidget(self.idxLabelY) 110 | layout.addWidget(self.imLabelY) 111 | layout.addWidget(slider) 112 | layout.addStretch(1) 113 | self.YViewGroupBox.setLayout(layout) 114 | 115 | def createZViewGroupBox(self): 116 | self.ZViewGroupBox = QGroupBox("Sagittal plane") 117 | # IMAGE 118 | self.imLabelZ = QLabel() 119 | # SLIDER 120 | slider = QSlider(Qt.Horizontal, self.ZViewGroupBox) 121 | slider.setMinimum(0) 122 | slider.setMaximum(self.imageShape[2] - 1) 123 | slider.valueChanged.connect(self.setZ) 124 | # INDEX 125 | self.idxLabelZ = QLabel() 126 | 127 | # initialization 128 | self.setZ(0) 129 | # LAYOUT 130 | layout = QVBoxLayout() 131 | layout.addWidget(self.idxLabelZ) 132 | layout.addWidget(self.imLabelZ) 133 | layout.addWidget(slider) 134 | layout.addStretch(1) 135 | self.ZViewGroupBox.setLayout(layout) 136 | 137 | def createExtensionGroupBox(self): 138 | self.extensionGroupBox = QGroupBox("Extension") 139 | 140 | self.extensionImageLabel = QLabel() 141 | self.extensionTextLabel = QLabel() 142 | 143 | self.timer = QtCore.QTimer(self) 144 | # Throw event timeout with an interval of 500 milliseconds 145 | self.timer.setInterval(100) 146 | self.timer.timeout.connect(self.refreshExtension) 147 | self.timer.start() 148 | 149 | layout = QVBoxLayout() 150 | layout.addWidget(self.extensionTextLabel) 151 | layout.addWidget(self.extensionImageLabel) 152 | self.extensionGroupBox.setLayout(layout) 153 | 154 | 155 | def imshow(array: ndarray, extensionFunc=defaultBlank): 156 | if not QApplication.instance(): 157 | app = QApplication(sys.argv) 158 | else: 159 | app = QApplication.instance() 160 | main = MainWindow(View3D(array, (400, 400)), extensionFunc) 161 | main.show() 162 | app.exec_() 163 | # sys.exit() 164 | 165 | 166 | def fileshow(file: str, extensionFunc): 167 | if not QApplication.instance(): 168 | app = QApplication(sys.argv) 169 | else: 170 | app = QApplication.instance() 171 | main = MainWindow(FileView3D(file, (400, 400)), extensionFunc) 172 | main.show() 173 | app.exec_() 174 | # sys.exit() 175 | --------------------------------------------------------------------------------