Coder Social home page Coder Social logo

brudfors / unires Goto Github PK

View Code? Open in Web Editor NEW
66.0 8.0 11.0 34.7 MB

Model-based super-resolution of medical images in PyTorch.

License: MIT License

Python 97.11% MATLAB 0.74% Dockerfile 2.08% Shell 0.06%
computed-tomography magnetic-resonance-imaging super-resolution medical-imaging

unires's Introduction

Unified Super-Resolution in PyTorch

This repository implements a unified model for super-resolving medical images (MRI and CT scans), which combines: super-resolution with a multi-channel denoising prior, rigid registration and a correction for interleaved slice acquisition. The archetype use-case is when having multiple scans of the same subject (e.g., T1w, T2w and FLAIR MRIs) and an analysis requires these scans to be represented on the same grid (i.e., having the same image size, affine matrix and voxel size). By default, the model reconstructs 1 mm isotropic images with a field-of-view that contains all input scans; however, this voxel size can be customised with the possibility of sub-millimetric reconstuctions. The model additionally supports multiple repeats of each MR sequence. There is an option that makes images registered and defined on the same grid, across subjects, where the grid size is optimal from a CNN fitting perspective. The implementation is written in PyTorch and should therefore execute fast on the GPU. The software can be run either through Docker -- which ensures the correct library and OS versions are used, plus requires no compilation -- or directly by interfacing with the Python code.

An installation-free demo of UniRes is available in Colab:

Open In Colab

1. Python

1.1. Installation

Clone UniRes:

git clone https://github.com/brudfors/UniRes

Then cd into the UniRes folder and install it by:

pip install .

OBS: The algorithm runs much faster if the compiled backend is used:

NI_COMPILED_BACKEND="C" pip install --no-build-isolation .

However, for running on the GPU, this only works if you ensure that the PyTorch installation uses the same CUDA version that is on your system; therefore, it might be worth installing PyTorch beforehand, i.e.:

pip install torch==1.9.0+cu111
NI_COMPILED_BACKEND="C" pip install --no-build-isolation .

where the PyTorch CUDA version matches the output of nvcc --version.

1.2. Example usage

Running UniRes is straight forward. Let's say you have three MR images: T1.nii.gz, T2.nii.gz and PD.nii.gz, then simply run unires in the terminal as:

unires T1.nii.gz T2.nii.gz PD.nii.gz

Three 1 mm isotropic images are written to the same folder as the input data, prefixed 'ur_'.

Algorithm options can be displayed by:

unires --help

As an example, the voxel size of the super-resolved data is here set to 1.5 mm isotropic:

unires --vx 1.5 T1.nii.gz T2.nii.gz PD.nii.gz

There is also an option that makes images registered and defined on the same grid, across subjects, where the grid size is optimal from a CNN fitting perspective:

unires --common_output T1.nii.gz T2.nii.gz PD.nii.gz

As the unified super-resolution can take a few minutes (on a fast GPU ;), it is possible to instead use a trilinear reslice, this is enabled by:

unires --linear --common_output T1.nii.gz T2.nii.gz PD.nii.gz

2. Running through NVIDIA Docker

This section describes setting up UniRes to run using NVIDIA's Docker engine on Ubuntu. As long as the NVIDIA Docker engine can be installed on MS Windows or Mac OSX there is no reason that these operating systems could not also be used. However, setting it up for Windows and OSX is not described here.

2.1. Install NVIDIA driver and Docker

Make sure that you have installed the NVIDIA driver and Docker engine for your Linux distribution (you do not need to install the CUDA Toolkit on the host system). These commands should install Docker:

curl https://get.docker.com | sh
sudo systemctl start docker && sudo systemctl enable docker

Regarding the NVIDIA driver, I personally like the installation guide in [1] (step 2). Although this guide is targeted at Ubuntu 19.04, it should generalise to other Debian/Ubuntu versions (I used it for Ubuntu 18.04).

2.2. Install the NVIDIA Docker engine

Execute the following commands to install the NVIDIA Docker engine:

distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update
sudo apt-get install -y nvidia-docker2

Next, edit/create /etc/docker/daemon.json with content (e.g., by sudo vim /etc/docker/daemon.json):

{
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
         } 
    },
    "default-runtime": "nvidia" 
}

then do:

sudo systemctl restart docker

Finally, test that it works by starting nvidia-smi in a Docker container:

sudo docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi

2.3. Build UniRes Docker image

cd to the content of the docker folder and run the following command to build the UniRes image:

docker build --rm --tag unires:1.0 .

If there are permission issues, the following can help:

sudo chmod 666 /var/run/docker.sock

Now you can run UniRes via Docker containers started from the unires:1.0 image!

2.4. Process MRI scans through Docker container

Let's say you have a folder named data in your current working directory, which contains two MR images of the same subject: T1.nii.gz, PD.nii.gz. You can then process these two scans with UniRes by executing:

docker run -it --rm -v $PWD/data:/home/docker/app/data unires:1.0 data/T1.nii.gz data/PD.nii.gz

When the algorithm has finished, you will find the processed scans in the same data folder, prefixed 'ur_'.

3. References

@inproceedings{brudfors2018mri,
  title={MRI super-resolution using multi-channel total variation},
  author={Brudfors, Mikael and Balbastre, Ya{\"e}l and Nachev, Parashkev and Ashburner, John},
  booktitle={Annual Conference on Medical Image Understanding and Analysis},
  pages={217--228},
  year={2018},
  organization={Springer}
}

@article{brudfors2019tool,
  title={A Tool for Super-Resolving Multimodal Clinical MRI},
  author={Brudfors, Mikael and Balbastre, Yael and Nachev, Parashkev and Ashburner, John},
  journal={arXiv preprint arXiv:1909.01140},
  year={2019}
}

unires's People

Contributors

balbasty avatar brudfors avatar chrisfoulon avatar liamchalcroft avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

unires's Issues

Question about how to create low-resolution images from high-resolution images to run on UniRes

Hi @brudfors ,

Thanks for this wonderful work! I am working on a project which solves a similar problem to UniRes and uses UniRes as a comparison method. However, I think there is something wrong with my code (in creating a low-resolution image or scaling before computing the SSIM metric) that I cannot achieve a similar result in your paper. I would really appreciate it if you could help me check whether anything I did was wrong.

I use the following code to generate a low-resolution image (4mm thick axial sliced) from a high-resolution one ($1\times1\times1 mm^3$):

import nibabel as nib
from nibabel.affines import rescale_affine
from nilearn.image import resample_img

img = nib.load("HR_mri.nii.gz")
new_zooms = np.array(img.header.get_zooms()) * [1, 1, 4]
new_shape = np.array(img.shape) // [1, 1, 4]
new_affine = rescale_affine(img.affine, img.shape, new_zooms, new_shape)
new_img = resample_img(
    img,
    target_affine=new_affine,
    target_shape=new_shape,
    interpolation="nearest",
)
near_new_img = resample_img(
    new_img,
    target_affine=img.affine,
    target_shape=img.affine,
    interpolation="nearest",
)
new_nib = nib.Nifti1Image(near_new_img.get_fdata(), img.affine)
nib.save(
    new_nib,
    "LR_mri.nii.gz",
)

Then I let UniRes run on the LR_mri.nii.gz to generate a high-resolution one. However, this is the result I get: (Input, UniRes)
image.
To draw this image, I cropped out an area ($160\times224\times160$) around the brain. I compute the SSIM value on this area after scaling both volumes into [0, 1]. The SSIM I computed is only around 0.48, which is lower than the value reported in the paper.

It also would be really helpful if you could show me the code for how you create low-resolution images.

Thank you for this wonderful project again!

Jueqi

preprocessing with Label

Hello thanks for publishing fantastic algorithm !
I have prostate dataset that I want to use for segmentation - my question is Can I use your tool with labelled dataset?
I have ground truth labels sampled as t2w image, so in order to be able to use it I need to be able to apply all transformations identically to my labels and t2w - How to achieve it?

Inconsistent results when passing single vs multi-modalities

Hello, I'm running UniRes to super-resolve a series of scans from multiple subjects, typically consisting of a FLAIR, spin-echo, and MPRAGE each. When I pass all three scans to UniRes using default parameters, i.e.:

unires MPRAGE.nii.gz SE.nii.gz FLAIR.nii.gz

Then, bizarrely, the SE scan will look super-resolved, the FLAIR will look downsampled further, and the MPRAGE will look like a complete blur, regardless of permutation. When I pass the FLAIR and MPRAGE images individually through unires, the images are super-resolved and look fine. The images are already co-registered, and the issue persists using the --common-output flag. I've included some examples below detailing this:

Example input MPRAGE:
image

Example output SR-MPRAGE when passing multiple modalities:
image

Example output SR-MPRAGE when passing only the MPRAGE:
image

Add release/tag and dependencies version

Hi Mikael,

Congratulation on the great repository! I just wanted to make some suggestions; please, consider using Github tags or releases. It would make it easier to follow the latest updates/fixes of the tool. Also, it would be great to have specified the nitorch version that the unires uses to avoid having recent nitorch updates break any function from unires. Finally, it would be great to see the unires available at pypi to be easier to install and define it in the requirements. I would be more than happy to help with any of these suggestions; just let me know what you think. Cheers

Boolean values are difficult to set in the command-line tool

It seems that ArgumentParser has a weird way of converting values to bool as only "" is mapped to False (False and 0 map to True).
It makes it a struggle to deactivate steps (e.g., --crop or --do_atlas_align).
Maybe something along those lines can be used to make it easier for the user to pass boolean values?

(Also, it would be super useful to have the default values written in the command-line help)

Thanks!

Pre-conditioning CG

Hi Mikael,

I think you can get CG to converge dramatically faster by using a relatively simple preconditioner.
From what I understand, CG is really just gradient descent, except that you don't descend along directions that you have already visited (you project the current gradient on the subspace orthogonal to all previous gradients). This means that without preconditioning, we can take very tiny steps, and preconditioning can be seen as using an approximation of the Hessian to take larger (better) steps. I found that using a diagonal preconditioner that is more positive definite than the true Hessian makes a very good conditioner. In your case, your Hessian (the system that you are solving) is t*A'A + lam*D'D, so your preconditioner can be inv(t*diag(A'A*1) + lam*diag(D'D)). Since D'D is just a convolution, its diagonal is constant and equal to 2/sum(vx ** 2) (see the Dartel paper: that is the central weight in the membrane energy).

So at each step, preconditioning just means dividing voxel-wise by t*A'A*1 + 2/sum(vx ** 2)

Happy Christmas!

Error when trying to run

I encount this problem, no matter how I install the enviroment. Any idea what can cause it?

15/09/2023 12:58:30 | GPU: NVIDIA RTX A6000, CUDA: True, PyTorch: 2.0.1+cu117

Input
c=0, n=0 | fname=data/pd_icbm_normal_1mm_pn0_rf0.nii.gz
c=1, n=0 | fname=data/t1_icbm_normal_1mm_pn0_rf0.nii.gz

Estimating model hyper-parameters... completed in 0.78400 seconds:
c=0 | tau= 0.000452 | sd= 47.04 | mu= 4314 | ct=False
c=1 | tau= 0.1775 | sd= 2.373 | mu= 427.9 | ct=False

Performing multi-channel (N=2) alignment...<class 'torch.Tensor'>
<class 'torch.Tensor'>
<class 'torch.Tensor'>
<class 'torch.Tensor'>
Traceback (most recent call last):
File "/home/sophie/miniconda3/envs/UniRes_cpu/bin/unires", line 8, in
sys.exit(run())
^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/unires/_cli.py", line 289, in run
_preproc(**vars(args))
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/unires/_cli.py", line 75, in _preproc
dat_y, mat_y, pth_y = preproc(pth, s)
^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/unires/run.py", line 313, in preproc
x, y, sett = init(data, sett)
^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/unires/run.py", line 265, in init
x, sett = _init_reg(x, sett)
^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/unires/_core.py", line 330, in _init_reg
mat_a = affine_align(imgs, **sett.coreg_params, fix=fix, device=sett.device)[1]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/nitorch/tools/preproc.py", line 166, in affine_align
mat_a, mat_fix, dim_fix, _ = _affine_align(dat, mat,
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/nitorch/tools/affine_reg/_align.py", line 150, in _affine_align
q, args = _fit_q(q, dat_fix, grid, mat_fix, dat, mat, mov,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/nitorch/tools/affine_reg/_core.py", line 199, in _fit_q
q[m, ...] = _do_optimisation(q[m, ...], args, s, opt, dim)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/nitorch/tools/affine_reg/_core.py", line 139, in _do_optimisation
res = minimize(_compute_cost, q, args, method='Powell',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/scipy/optimize/_minimize.py", line 701, in minimize
res = _minimize_powell(fun, x0, args, callback, bounds, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/scipy/optimize/_optimize.py", line 3507, in _minimize_powell
fval, x, direc1 = _linesearch_powell(func, x, direc1,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/scipy/optimize/_optimize.py", line 3195, in _linesearch_powell
res = _minimize_scalar_bounded(myfunc, bound, xatol=tol / 100)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/scipy/optimize/_optimize.py", line 2285, in _minimize_scalar_bounded
fx = func(x, args)
^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/scipy/optimize/_optimize.py", line 3176, in myfunc
return func(p + alphaxi)
^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/scipy/optimize/_optimize.py", line 620, in function_wrapper
fx = function(np.copy(x), *(wrapper_args + args))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/nitorch/tools/affine_reg/_costs.py", line 86, in _compute_cost
dat_new = grid_pull(dat[m], grid, bound='dft', extrapolate=True, interpolation=1)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/nitorch/spatial/_grid.py", line 201, in grid_pull
out = GridPull.apply(input, grid, interpolation, bound, extrapolate)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/torch/autograd/function.py", line 506, in apply
return super().apply(*args, **kwargs) # type: ignore[misc]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/torch/cuda/amp/autocast_mode.py", line 106, in decorate_fwd
return fwd(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/nitorch/_C/grid.py", line 252, in forward
output = grid_pull(input, grid, *opt)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sophie/miniconda3/envs/UniRes_cpu/lib/python3.11/site-packages/nitorch/_C/_ts/pushpull.py", line 44, in grid_pull
return iso1.pull3d(inp, grid, bound_fn, extrapolate)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: r INTERNAL ASSERT FAILED at "../aten/src/ATen/core/jit_type_base.h":549, please report a bug to PyTorch.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.