Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Very Slow Loading of NIfTI (.nii.gz) Files Compared to SimpleITK #8107

Open
ivezakis opened this issue Sep 24, 2024 · 1 comment
Open

Very Slow Loading of NIfTI (.nii.gz) Files Compared to SimpleITK #8107

ivezakis opened this issue Sep 24, 2024 · 1 comment

Comments

@ivezakis
Copy link

ivezakis commented Sep 24, 2024

Describe the bug
There is a significant performance gap between MONAI and SimpleITK when loading NIfTI (.nii.gz) files.
In my own benchmark I have found:

  • MONAI average load time: 2.0321 seconds
  • SimpleITK average load time: 0.1135 seconds

Using cProfile I notice that for monai, the bulk of the loading time is consumed by numpy.asanyarray. Results are the following when running a loop with monai.transforms.LoadImage 10 times:

MONAI Profiling Results

ncalls tottime percall cumtime percall filename:lineno(function)
10 0.000 0.000 20.231 2.023 benchmark.py:11(load_with_monai)
10 0.016 0.002 20.212 2.021 monai/transforms/io/array.py:229(call)
10 0.000 0.000 19.959 1.996 monai/data/image_reader.py:927(get_data)
10 0.000 0.000 19.956 1.996 monai/data/image_reader.py:1018(_get_array_data)
60/40 17.903 0.298 19.956 0.499 {built-in method numpy.asanyarray}
10 0.000 0.000 2.053 0.205 nibabel/arrayproxy.py:436(array)
10 0.000 0.000 2.053 0.205 nibabel/arrayproxy.py:413(_get_scaled)
10 0.000 0.000 2.052 0.205 nibabel/arrayproxy.py:389(_get_unscaled)
10 0.041 0.004 2.050 0.205 nibabel/volumeutils.py:392(array_from_file)
10 0.052 0.005 2.008 0.201 {method 'readinto' of '_io._BufferedIOBase' objects}

SimpleITK Profiling Results

ncalls tottime percall cumtime percall filename:lineno(function)
10 0.000 0.000 1.139 0.114 benchmark.py:15(load_with_sitk)
10 0.000 0.000 1.076 0.108 SimpleITK/extra.py:340(ReadImage)
10 0.062 0.006 0.062 0.006 {built-in method numpy.array}
10 0.000 0.000 0.001 0.000 SimpleITK/extra.py:259(GetArrayViewFromImage)
10 0.000 0.000 0.001 0.000 SimpleITK/SimpleITK.py:8443(init)
10 0.001 0.000 0.001 0.000 {built-in method SimpleITK._SimpleITK.new_ImageFileReader}
10 0.000 0.000 0.000 0.000 SimpleITK/extra.py:156(_get_numpy_dtype)

Note: Times are in seconds. 'ncalls' is the number of calls, 'tottime' is the total time spent in the function (excluding time in subfunctions), 'percall' is the average time spent per call, and 'cumtime' is the cumulative time spent in the function and all subfunctions.

As shown in the profiling results, the majority of time in MONAI is spent in numpy operations, particularly numpy.asanyarray, which takes about 19.956 seconds cumulatively. In contrast, SimpleITK's ReadImage function takes only 1.076 seconds.

To Reproduce

import time
import os
import numpy as np
import monai
import SimpleITK as sitk

def load_with_monai(file_path):
    start_time = time.time()
    image = monai.transforms.LoadImage()(file_path)
    end_time = time.time()
    return image, end_time - start_time

def load_with_sitk(file_path):
    start_time = time.time()
    image = sitk.ReadImage(file_path)
    array = sitk.GetArrayFromImage(image)
    end_time = time.time()
    return array, end_time - start_time

def run_benchmark(file_path, num_iterations=10):
    monai_times = []
    sitk_times = []

    for _ in range(num_iterations):
        _, monai_time = load_with_monai(file_path)
        monai_times.append(monai_time)

        _, sitk_time = load_with_sitk(file_path)
        sitk_times.append(sitk_time)

    print(f"MONAI average load time: {np.mean(monai_times):.4f} seconds")
    print(f"SimpleITK average load time: {np.mean(sitk_times):.4f} seconds")

if __name__ == "__main__":
    file_path = "path/to/file.nii.gz"
    if not os.path.exists(file_path):
        print(f"File not found: {file_path}")
    else:
        run_benchmark(file_path)

Environment

MONAI version: 1.3.2
Numpy version: 1.26.4
Pytorch version: 2.4.1
MONAI flags: HAS_EXT = False, USE_COMPILED = False, USE_META_DICT = False
MONAI rev id: 59a7211070538586369afd4a01eca0a7fe2e742e
MONAI __file__: C:\Users\<username>\miniconda3\envs\panorama\Lib\site-packages\monai\__init__.py

Optional dependencies:
Pytorch Ignite version: NOT INSTALLED or UNKNOWN VERSION.
ITK version: NOT INSTALLED or UNKNOWN VERSION.
Nibabel version: 5.2.1
scikit-image version: NOT INSTALLED or UNKNOWN VERSION.
scipy version: 1.13.1
Pillow version: 10.4.0
Tensorboard version: NOT INSTALLED or UNKNOWN VERSION.
gdown version: NOT INSTALLED or UNKNOWN VERSION.
TorchVision version: 0.19.1
tqdm version: 4.66.5
lmdb version: NOT INSTALLED or UNKNOWN VERSION.
psutil version: NOT INSTALLED or UNKNOWN VERSION.
pandas version: NOT INSTALLED or UNKNOWN VERSION.
einops version: NOT INSTALLED or UNKNOWN VERSION.
transformers version: NOT INSTALLED or UNKNOWN VERSION.
mlflow version: NOT INSTALLED or UNKNOWN VERSION.
pynrrd version: NOT INSTALLED or UNKNOWN VERSION.
clearml version: NOT INSTALLED or UNKNOWN VERSION.

For details about installing the optional dependencies, please visit:
    https://docs.monai.io/en/latest/installation.html#installing-the-recommended-dependencies


================================
Printing system config...
================================
`psutil` required for `print_system_info`

================================
Printing GPU config...
================================
Num GPUs: 1
Has CUDA: True
CUDA version: 12.4
cuDNN enabled: True
NVIDIA_TF32_OVERRIDE: None
TORCH_ALLOW_TF32_CUBLAS_OVERRIDE: None
cuDNN version: 90100
Current device: 0
Library compiled for CUDA architectures: ['sm_50', 'sm_60', 'sm_61', 'sm_70', 'sm_75', 'sm_80', 'sm_86', 'sm_90']
GPU 0 Name: NVIDIA GeForce RTX 4070
GPU 0 Is integrated: False
GPU 0 Is multi GPU board: False
GPU 0 Multi processor count: 46
GPU 0 Total memory (GB): 12.0
GPU 0 CUDA capability (maj.min): 8.9

Additional context
Add any other context about the problem here.

@kartikshastrakar
Copy link

import time
import os
import numpy as np
import monai
import SimpleITK as sitk
import cProfile

Custom LoadImage with prefetching

class FastMONAILoadImage(monai.transforms.LoadImage):
def call(self, filename):
data = super().call(filename)
# Directly extract the image array to bypass unnecessary numpy operations
return np.asarray(data[0]) # Only return the image array

def load_with_monai(file_path):
start_time = time.time()
image = FastMONAILoadImage()(file_path) # Use the optimized loader
end_time = time.time()
return image, end_time - start_time

def load_with_sitk(file_path):
start_time = time.time()
image = sitk.ReadImage(file_path)
array = sitk.GetArrayFromImage(image)
end_time = time.time()
return array, end_time - start_time

def run_benchmark(file_path, num_iterations=10):
monai_times = []
sitk_times = []

# Profile the MONAI loading process
cProfile.runctx('load_with_monai(file_path)', globals(), locals(), filename="monai_profile.prof")

for _ in range(num_iterations):
    _, monai_time = load_with_monai(file_path)
    monai_times.append(monai_time)

    _, sitk_time = load_with_sitk(file_path)
    sitk_times.append(sitk_time)

print(f"MONAI average load time: {np.mean(monai_times):.4f} seconds")
print(f"SimpleITK average load time: {np.mean(sitk_times):.4f} seconds")

if name == "main":
file_path = "path/to/file.nii.gz"
if not os.path.exists(file_path):
print(f"File not found: {file_path}")
else:
run_benchmark(file_path)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants