├── 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 |
3 |
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 | 
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 |  |  |  |
87 | | PyPI | |  | |
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 |
--------------------------------------------------------------------------------