ANTsPy icon indicating copy to clipboard operation
ANTsPy copied to clipboard

to_nibabel and from_nibabel missing from version 0.5.3 api

Open LelandBarnard opened this issue 1 year ago • 10 comments

Describe the bug The functions ants.to_nibabel(ants_img) and ants.from_nibabel(nib_img) appear to be missing from the 0.5.3 release. Have they been removed intentionally?

To reproduce With antspyx==0.5.3: ants.from_nibabel? now returns Object `ants.from_nibabel` not found. The same behavior is observed for ants.to_nibabel?

Expected behavior

Screenshots

ANTsPy installation (please complete the following information):

  • Hardware [ Kaggle kernel]
  • OS: [Ubuntu ]
  • System details [ Linux 5.15.154+ x86_64 ]
  • Sub-system: [ ]
  • ANTsPy version: [ 0.5.3 ]
  • Installation type: [ pip ]

Additional context Add any other context about the problem here. Many issues are specific to particular data so please include example data if possible.

LelandBarnard avatar Aug 07 '24 21:08 LelandBarnard

I'm not sure why but it looks like the file ants/utils/convert_nibabel.py was renamed to ants/utils/nifti_to_ants.py in #637

The new function is nifti_to_ants(img)

cookpa avatar Aug 07 '24 22:08 cookpa

The old functions used to explicitly use nibabel

def to_nibabel(image):
    """
    Convert an ANTsImage to a Nibabel image
    """
    import nibabel as nib

    fd, tmpfile = mkstemp(suffix=".nii.gz")
    image.to_filename(tmpfile)
    new_img = nib.load(tmpfile)
    os.close(fd)
    # os.remove(tmpfile) ## Don't remove tmpfile as nibabel lazy loads the data.
    return new_img


def from_nibabel(nib_image):
    """
    Convert a nibabel image to an ANTsImage
    """
    fd, tmpfile = mkstemp(suffix=".nii.gz")
    nib_image.to_filename(tmpfile)
    new_img = iio2.image_read(tmpfile)
    os.close(fd)
    os.remove(tmpfile)
    return new_img

cookpa avatar Aug 07 '24 22:08 cookpa

I'm not sure why but it looks like the file ants/utils/convert_nibabel.py was renamed to ants/utils/nifti_to_ants.py in #637

The new function is nifti_to_ants(img)

I see, thanks! Is there a replacement for the other direction as well? (i.e., ants_to_nifti)

LelandBarnard avatar Aug 08 '24 21:08 LelandBarnard

I'm not sure why that wasn't added, perhaps to remove a dependency on nibabel? Looking at the code, it didn't actually do anything except write the nifti file to disk, then call nibabel.load.

cookpa avatar Aug 09 '24 01:08 cookpa

This change crashes our downstream. And the replaced function does not work. The function uses get_data() and has been deprecated since version 3 (current version 5) and crashes.

It should be nib_image.get_fdata() instead of nib_image.get_data().astype( np.float )

The other way around could also be implemented without saving a temporary file.

def get_ras_affine(rotation, spacing, origin) -> np.ndarray:
    #Source: https://github.com/fepegar/torchio/blob/5983f83f0e7f13f9c5056e25f8753b03426ae18a/src/torchio/data/io.py#L357
    rotation_zoom = rotation * spacing
    translation_ras = rotation.dot(origin)
    affine = np.eye(4)
    affine[:3, :3] = rotation_zoom
    affine[:3, 3] = translation_ras
    return affine

def to_nibabel(img: "ants.core.ants_image.ANTsImage",header=None):
    try:
        from nibabel.nifti1 import Nifti1Image
    except ModuleNotFoundError as e:
        raise ModuleNotFoundError(
            "Could not import nibabel, for conversion to nibabel. Install nibabel with pip install nibabel"
        ) from e
    affine = get_ras_affine(rotation=img.direction, spacing=img.spacing, origin=img.origin)
    return Nifti1Image(img.numpy(), affine, header)

robert-graf avatar Aug 13 '24 10:08 robert-graf

Thanks for the suggestion of nib_image.get_fdata() - this avoids the lazy loading issues.

It seems we've had some problems with orientations during nibabel conversions, eg #64, #52.

@ncullen93 @stnava what do you think of the proposed solution above? To test we would need to have nibabel as a dependency.

cookpa avatar Aug 13 '24 13:08 cookpa

Oh, you probably use the SimpleITK definition of affines. Then the above code is not yet functioning. As you already figured out in the other issues, you must mirror the first two directions. As can be seen here:
https://github.com/fepegar/torchio/blob/5983f83f0e7f13f9c5056e25f8753b03426ae18a/src/torchio/data/io.py#L357

robert-graf avatar Aug 13 '24 13:08 robert-graf

Yeah, now I am remembering how complicated this is.

The other limitation is that software conventions differ on whether to use the sform or qform transform. It looks like nibabel's algorithm uses the sform if the sform_code is > 0. ITK will only use the sform if it describes a rigid transform. You should be good to go if the sform is rigid and the sform_code == 1. But it may diverge from nibabel's behavior in other circumstances.

cookpa avatar Aug 13 '24 14:08 cookpa

Yes, it was removed because it was just calling nibabel in a very simple way and we really shouldn't have nibabel as a dependency. The proposed solution looks OK to me though.

ncullen93 avatar Aug 14 '24 13:08 ncullen93

I don't use nibabel directly so would need some help crafting a solution that works across different image orientations. Happy to review PRs

cookpa avatar Aug 14 '24 17:08 cookpa