Questions:
Update 14-SEP-2021: Simplified problem even further to a smaller MRE. After some analysis, it doesn't seem Qt threading is the culprit, so corresponding Qt code was removed.
pyvista
does not plot my volume along the correct axis and the output is garbled.ParaView
on the other hand plots things properly. How can I fix this?(NOTE: I cannot share the actual data because it is confidential. However, below you can see
pyvista
orients my data along thez-axis
, when in fact it should be along thex-axis
, and that it is garbled. I show the bounding box in ParaView.The results are the same regardless if I use the
fixed_point
vs.smart
volume mappers. I usefixed_point
since I am on Windows.)
pyvista:
ParaView:
Plotting volumes in
pyvista
is much slower than inParaView
. Is there some way I can make this faster?The time for my code with
pyvista
vs.ParaView
isMy Code: ~13 minutes, 9 seconds ParaView 5.9.1 (installed pre-built binary): ~24 seconds
I've used
cProfile
to help identify problem areas (please see below).
Setup:
Data
No. of DICOM Files: 1,172
DICOM File Size: 5.96 MB
Total Scan Size: 7GB
DICOM Image Dimensions: 2402 x 1301 pixels
System / Hardware
OS: Windows 10 Professional x64-bit, Build 1909
CPU: 2x Intel(R) Xeon(R) Gold 6248R
Disk: 2TB NVMe M.2 SSD
RAM: 192 GB DDR4
Compute GPUs: 2x NVIDIA Quadro RTX8000
Display GPU: 1x NVIDIA Quadro RTX4000
Software
Python: 3.8.10 x64-bit
pyvista: 0.32.1
VTK: 9.0.3
ParaView: 5.9.1
IDE: VSCode 1.59.0
Code:
import cProfile
import io
import os
import pstats
import numpy as np
import pyvista as pv
import SimpleITK as sitk
from SimpleITK import ImageSeriesReader
from trimesh import points
pv.rcParams["volume_mapper"] = "fixed_point" # Windows
folder = "C:\\path\\to\\DICOM\\stack\\folder"
def profile(fnc):
"""Wrapper for cProfile"""
def inner(*args, **kwargs):
pr = cProfile.Profile()
pr.enable()
retval = fnc(*args, **kwargs)
pr.disable()
s = io.StringIO()
sortby = "cumulative"
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats()
print(s.getvalue())
return retval
return inner
@profile
def plot_volume(folder):
p = pv.Plotter()
dicom_reader = ImageSeriesReader()
dicom_files = dicom_reader.GetGDCMSeriesFileNames(folder)
dicom_reader.SetFileNames(dicom_files)
scan = dicom_reader.Execute()
origin = scan.GetOrigin()
spacing = scan.GetSpacing()
direction = scan.GetDirection()
data = sitk.GetArrayFromImage(scan)
data = (data // 256).astype(np.uint8) # Cast 16-bit to 8-bit
volume = pv.UniformGrid(data.shape)
volume.origin = origin
volume.spacing = spacing
volume.direction = direction
volume.point_data["Values"] = data.flatten(order="F")
volume.set_active_scalars("Values")
p.add_volume(
volume,
opacity="sigmoid",
reset_camera=True,
)
p.add_axes()
p.show()
if __name__ == "__main__":
plot_volume(folder)
Output:
WARNING: In d:\a\1\sitk-build\itk-prefix\include\itk-5.2\itkImageSeriesReader.hxx, line 480
ImageSeriesReader (0000021B082D3360): Non uniform sampling or missing slices detected, maximum nonuniformity:7.39539e-07
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
1 11.220 11.220 772.300 772.300 gui\main.py:61(plot_volume)
1 86.881 86.881 648.445 648.445 .venv\lib\site-packages\pyvista\plotting\plotting.py:2271(add_volume)
1 0.000 0.000 373.896 373.896 .venv\lib\site-packages\pyvista\core\filters\data_set.py:2022(cell_data_to_point_data)
1 0.001 0.001 371.802 371.802 .venv\lib\site-packages\pyvista\core\filters\__init__.py:30(_update_alg)
2 371.802 185.901 371.802 185.901 {method 'Update' of 'vtkmodules.vtkCommonExecutionModel.vtkAlgorithm' objects}
606/273 8.916 0.015 134.346 0.492 {built-in method numpy.core._multiarray_umath.implement_array_function}
3 17.923 5.974 101.495 33.832 .venv\lib\site-packages\numpy\lib\nanfunctions.py:68(_replace_nan)
693 85.541 0.123 85.541 0.123 {built-in method numpy.array}
2 0.001 0.000 74.715 37.358 <__array_function__ internals>:2(nanmin)
2 0.718 0.359 69.822 34.911 .venv\lib\site-packages\numpy\lib\nanfunctions.py:228(nanmin)
57 46.992 0.824 46.992 0.824 {method 'astype' of 'numpy.ndarray' objects}
2 45.969 22.985 45.969 22.985 {method 'flatten' of 'numpy.ndarray' objects}
1 0.000 0.000 45.027 45.027 <__array_function__ internals>:2(nanmax)
1 0.253 0.253 42.448 42.448 .venv\lib\site-packages\numpy\lib\nanfunctions.py:343(nanmax)
1 0.000 0.000 25.705 25.705 .venv\lib\site-packages\pyvista\plotting\plotting.py:4634(show)
3 0.000 0.000 20.822 6.941 .venv\lib\site-packages\pyvista\core\datasetattributes.py:539(set_array)
3 0.000 0.000 18.391 6.130 .venv\lib\site-packages\pyvista\core\datasetattributes.py:730(_prepare_array)
11 0.000 0.000 18.391 1.672 .venv\lib\site-packages\pyvista\utilities\helpers.py:132(convert_array)
4 0.001 0.000 18.391 4.598 .venv\lib\site-packages\vtkmodules\util\numpy_support.py:104(numpy_to_vtk)
1 17.685 17.685 17.685 17.685 {method 'DeepCopy' of 'vtkmodules.vtkCommonCore.vtkDataArray' objects}
1 0.000 0.000 16.113 16.113 .venv\lib\site-packages\SimpleITK\SimpleITK.py:7854(Execute)
1 16.113 16.113 16.113 16.113 {built-in method SimpleITK._SimpleITK.ImageSeriesReader_Execute}
1 0.000 0.000 15.542 15.542 .venv\lib\site-packages\pyvista\plotting\render_window_interactor.py:615(start)
1 15.542 15.542 15.542 15.542 {method 'Start' of 'vtkmodules.vtkRenderingCore.vtkRenderWindowInteractor' objects}
1 0.000 0.000 14.598 14.598 <__array_function__ internals>:2(percentile)
1 0.000 0.000 14.598 14.598 .venv\lib\site-packages\numpy\lib\function_base.py:3724(percentile)
1 0.000 0.000 14.598 14.598 .venv\lib\site-packages\numpy\lib\function_base.py:3983(_quantile_unchecked)
1 0.235 0.235 14.598 14.598 .venv\lib\site-packages\numpy\lib\function_base.py:3513(_ureduce)
1 0.000 0.000 14.362 14.362 .venv\lib\site-packages\numpy\lib\function_base.py:4018(_quantile_ureduce_func)
1 12.671 12.671 12.671 12.671 {method 'partition' of 'numpy.ndarray' objects}
2 0.000 0.000 10.132 5.066 .venv\lib\site-packages\pyvista\plotting\plotting.py:1185(render)
1 10.132 10.132 10.132 10.132 {method 'Render' of 'vtkmodules.vtkRenderingOpenGL2.vtkOpenGLRenderWindow' objects}
61 0.000 0.000 9.805 0.161 .venv\lib\site-packages\numpy\core\fromnumeric.py:69(_wrapreduction)
63 9.804 0.156 9.805 0.156 {method 'reduce' of 'numpy.ufunc' objects}
2 0.000 0.000 6.170 3.085 <__array_function__ internals>:2(amin)
2 0.000 0.000 6.170 3.085 .venv\lib\site-packages\numpy\core\fromnumeric.py:2763(amin)
2 0.000 0.000 6.170 3.085 {method 'min' of 'numpy.ndarray' objects}
2 0.000 0.000 6.170 3.085 .venv\lib\site-packages\numpy\core\_methods.py:42(_amin)
1 0.000 0.000 6.073 6.073 .venv\lib\site-packages\SimpleITK\SimpleITK.py:7828(GetGDCMSeriesFileNames)
1 6.073 6.073 6.073 6.073 {built-in method SimpleITK._SimpleITK.ImageSeriesReader_GetGDCMSeriesFileNames}
1 0.000 0.000 3.413 3.413 .venv\lib\site-packages\SimpleITK\extra.py:252(GetArrayFromImage)
1 0.000 0.000 3.358 3.358 <__array_function__ internals>:2(amax)
1 0.000 0.000 3.358 3.358 .venv\lib\site-packages\numpy\core\fromnumeric.py:2638(amax)
1 0.000 0.000 3.358 3.358 {method 'max' of 'numpy.ndarray' objects}
1 0.000 0.000 3.358 3.358 .venv\lib\site-packages\numpy\core\_methods.py:38(_amax)
2 0.000 0.000 2.807 1.403 .venv\lib\site-packages\pyvista\core\datasetattributes.py:212(__setitem__)
1 0.000 0.000 2.764 2.764 .venv\lib\site-packages\pyvista\core\dataset.py:1637(__setitem__)
3 2.430 0.810 2.430 0.810 {method 'AddArray' of 'vtkmodules.vtkCommonDataModel.vtkFieldData' objects}
2 2.290 1.145 2.290 1.145 .venv\lib\site-packages\pyvista\core\pyvista_ndarray.py:53(__setitem__)
1 0.000 0.000 2.093 2.093 .venv\lib\site-packages\pyvista\core\filters\__init__.py:39(_get_output)
2 0.000 0.000 2.093 1.046 .venv\lib\site-packages\pyvista\core\grid.py:291(__init__)
1 0.000 0.000 2.092 2.092 .venv\lib\site-packages\pyvista\utilities\helpers.py:797(wrap)
1 0.000 0.000 2.092 2.092 .venv\lib\site-packages\pyvista\core\dataobject.py:53(deep_copy)
1 2.092 2.092 2.092 2.092 {method 'DeepCopy' of 'vtkmodules.vtkCommonDataModel.vtkImageData' objects}
40 0.000 0.000 1.444 0.036 <__array_function__ internals>:2(copyto)
4 0.591 0.148 0.591 0.148 {method 'SetVoidArray' of 'vtkmodules.vtkCommonCore.vtkAbstractArray' objects}
3 0.000 0.000 0.277 0.092 <__array_function__ internals>:2(all)
3 0.000 0.000 0.277 0.092 .venv\lib\site-packages\numpy\core\fromnumeric.py:2367(all)
3 0.000 0.000 0.277 0.092 {method 'all' of 'numpy.ndarray' objects}
3 0.000 0.000 0.277 0.092 .venv\lib\site-packages\numpy\core\_methods.py:60(_all)
80/4 0.001 0.000 0.219 0.055 <frozen importlib._bootstrap>:986(_find_and_load)
76/4 0.001 0.000 0.219 0.055 <frozen importlib._bootstrap>:956(_find_and_load_unlocked)
73/2 0.001 0.000 0.214 0.107 <frozen importlib._bootstrap>:650(_load_unlocked)
66/2 0.000 0.000 0.214 0.107 <frozen importlib._bootstrap_external>:842(exec_module)
78/11 0.027 0.000 0.213 0.019 {built-in method builtins.exec}
104/2 0.000 0.000 0.213 0.106 <frozen importlib._bootstrap>:211(_call_with_frames_removed)
2 0.000 0.000 0.193 0.096 .venv\lib\site-packages\pyvista\plotting\plotting.py:43(_has_matplotlib)
1 0.001 0.001 0.190 0.190 .venv\lib\site-packages\matplotlib\__init__.py:1(<module>)
104/27 0.000 0.000 0.146 0.005 <frozen importlib._bootstrap>:1017(_handle_fromlist)
32/9 0.000 0.000 0.145 0.016 {built-in method builtins.__import__}
1 0.001 0.001 0.119 0.119 .venv\lib\site-packages\matplotlib\rcsetup.py:1(<module>)
4 0.113 0.028 0.113 0.028 {method 'SetNumberOfTuples' of 'vtkmodules.vtkCommonCore.vtkAbstractArray' objects}
1 0.037 0.037 0.038 0.038 .venv\lib\site-packages\pyvista\plotting\mapper.py:4(make_mapper)
1 0.000 0.000 0.037 0.037 .venv\lib\site-packages\matplotlib\animation.py:19(<module>)
1 0.000 0.000 0.037 0.037 .venv\lib\site-packages\matplotlib\fontconfig_pattern.py:1(<module>)
2 0.005 0.002 0.036 0.018 .venv\lib\site-packages\matplotlib\__init__.py:709(_rc_params_in_file)
76 0.001 0.000 0.035 0.000 <frozen importlib._bootstrap>:890(_find_spec)
66 0.002 0.000 0.034 0.001 <frozen importlib._bootstrap_external>:914(get_code)
75 0.000 0.000 0.033 0.000 <frozen importlib._bootstrap_external>:1399(find_spec)
75 0.001 0.000 0.033 0.000 <frozen importlib._bootstrap_external>:1367(_get_spec)
612 0.002 0.000 0.033 0.000 .venv\lib\site-packages\matplotlib\__init__.py:574(__setitem__)
1 0.000 0.000 0.031 0.031 .venv\lib\site-packages\matplotlib\colors.py:1(<module>)
153 0.004 0.000 0.030 0.000 <frozen importlib._bootstrap_external>:1498(find_spec)
381/346 0.012 0.000 0.029 0.000 {built-in method builtins.__build_class__}
1 0.001 0.001 0.028 0.028 .venv\lib\site-packages\pyparsing.py:27(<module>)
1 0.000 0.000 0.027 0.027 .venv\lib\site-packages\pyvista\plotting\render_window_interactor.py:627(process_events)
1 0.027 0.027 0.027 0.027 {method 'ProcessEvents' of 'vtkmodules.vtkRenderingUI.vtkWin32RenderWindowInteractor' objects}
1 0.000 0.000 0.026 0.026 .venv\lib\site-packages\pyvista\plotting\colors.py:397(get_cmap_safe)
1 0.000 0.000 0.024 0.024 .venv\lib\site-packages\PIL\Image.py:27(<module>)
1 0.000 0.000 0.022 0.022 .venv\lib\site-packages\matplotlib\cm.py:1(<module>)
355 0.022 0.000 0.022 0.000 {built-in method nt.stat}
2 0.000 0.000 0.021 0.011 .venv\lib\site-packages\matplotlib\rcsetup.py:164(_validate_date_converter)
324 0.000 0.000 0.020 0.000 <frozen importlib._bootstrap_external>:135(_path_stat)
1 0.000 0.000 0.020 0.020 .venv\lib\site-packages\matplotlib\dates.py:1(<module>)
73 0.000 0.000 0.014 0.000 <frozen importlib._bootstrap>:549(module_from_spec)
66 0.002 0.000 0.014 0.000 <frozen importlib._bootstrap_external>:1034(get_data)
1 0.000 0.000 0.014 0.014 .venv\lib\site-packages\matplotlib\scale.py:1(<module>)
1 0.000 0.000 0.013 0.013 .venv\lib\site-packages\matplotlib\cm.py:32(_gen_cmap_registry)
1 0.000 0.000 0.012 0.012 .venv\lib\site-packages\dateutil\parser\__init__.py:2(<module>)
259 0.000 0.000 0.012 0.000 C:\Program Files\Python38\lib\re.py:289(_compile)
66 0.000 0.000 0.012 0.000 <frozen importlib._bootstrap_external>:638(_compile_bytecode)
66 0.011 0.000 0.011 0.000 {built-in method marshal.loads}
26 0.000 0.000 0.011 0.000 C:\Program Files\Python38\lib\sre_compile.py:759(compile)
1 0.000 0.000 0.011 0.011 .venv\lib\site-packages\pyparsing.py:6398(pyparsing_common)
1 0.000 0.000 0.010 0.010 .venv\lib\site-packages\matplotlib\ticker.py:1(<module>)
1 0.000 0.000 0.010 0.010 .venv\lib\site-packages\PIL\PngImagePlugin.py:34(<module>)
5 0.000 0.000 0.010 0.002 <frozen importlib._bootstrap_external>:1164(create_module)
5 0.010 0.002 0.010 0.002 {built-in method _imp.create_dynamic}
48 0.000 0.000 0.010 0.000 C:\Program Files\Python38\lib\re.py:250(compile)
1 0.000 0.000 0.009 0.009 .venv\lib\site-packages\matplotlib\__init__.py:138(_check_versions)
32 0.001 0.000 0.009 0.000 .venv\lib\site-packages\matplotlib\colors.py:915(from_list)
66 0.009 0.000 0.009 0.000 {built-in method io.open_code}
1 0.000 0.000 0.009 0.009 .venv\lib\site-packages\dateutil\parser\_parser.py:2(<module>)
754 0.006 0.000 0.008 0.000 <frozen importlib._bootstrap_external>:91(_path_join)
6 0.000 0.000 0.008 0.001 C:\Program Files\Python38\lib\importlib\__init__.py:109(imp
Update 14-SEP-2021 #2:
Interestingly, when trying to print out the shapes of data for debugging purposes as follows:
data_flattened = data.flatten(order="F")
volume.point_data["Values"] = data_flattened
volume.set_active_scalars("Values")
print(f"Points Shape: {volume.points.shape}")
print(f"Data Shape: {data.shape}")
print(f"Flattened Data Shape: {data_flattened.shape}")
I get the following error:
Error:
numpy.core._exceptions.MemoryError: Unable to allocate 81.9 GiB for an array with shape (3662502344, 3) and data type float64
Output:
Traceback (most recent call last):
File "C:\Program Files\Python38\lib\runpy.py", line 194, in _run_module_as_main
return _run_code(code, main_globals, None,
File "C:\Program Files\Python38\lib\runpy.py", line 87, in _run_code
exec(code, run_globals)
File "c:\Users\user\.vscode\extensions\ms-python.python-2021.9.1191016588\pythonFiles\lib\python\debugpy\__main__.py", line 45, in <module>
cli.main()
File "c:\Users\user\.vscode\extensions\ms-python.python-2021.9.1191016588\pythonFiles\lib\python\debugpy/..\debugpy\server\cli.py", line 444, in main
run()
File "c:\Users\user\.vscode\extensions\ms-python.python-2021.9.1191016588\pythonFiles\lib\python\debugpy/..\debugpy\server\cli.py", line 285, in run_file
runpy.run_path(target_as_str, run_name=compat.force_str("__main__"))
File "C:\Program Files\Python38\lib\runpy.py", line 265, in run_path
return _run_module_code(code, init_globals, run_name,
File "C:\Program Files\Python38\lib\runpy.py", line 97, in _run_module_code
_run_code(code, mod_globals, init_globals,
File "C:\Program Files\Python38\lib\runpy.py", line 87, in _run_code
exec(code, run_globals)
File "c:\Users\user\Code\gui\gui\main.py", line 81, in <module>
plot_volume(folder)
File "c:\Users\user\Code\gui\gui\main.py", line 22, in inner
retval = fnc(*args, **kwargs)
File "c:\Users\user\Code\gui\gui\main.py", line 65, in plot_volume
print(f"Points Shape: {volume.points.shape}")
File "c:\Users\user\Code\gui\.venv\lib\site-packages\pyvista\core\grid.py", line 368, in points
return np.c_[xx.ravel(order='F'), yy.ravel(order='F'), zz.ravel(order='F')]
File "c:\Users\user\Code\gui\.venv\lib\site-packages\numpy\lib\index_tricks.py", line 413, in __getitem__
res = self.concatenate(tuple(objs), axis=axis)
File "<__array_function__ internals>", line 5, in concatenate
numpy.core._exceptions.MemoryError: Unable to allocate 81.9 GiB for an array with shape (3662502344, 3) and data type float64