These are some work-in-progress notes in the hope that they will help adding a new image format to NiBabel.
As usual, the general idea is to make your image as explicit and transparent as possible.
From the Zen of Python (import this), these guys spring to mind:
So far we have tried to make the nibabel version of the image as close as possible to the way the user of the particular format is expecting to see it.
For example, the NIfTI format documents describe the image with the first dimension of the image data array being the fastest varying in memory (and on disk). Numpy defaults to having the last dimension of the array being the fastest varying in memory. We chose to have the first dimension vary fastest in memory to match the conventions in the NIfTI specification.
You are likely to know the image format much much better than the rest of us do, but to help you with the code, we will need to learn. The following will really help us get up to speed:
Read-only access to a format is better than no access to a format, and often much better. For example, we can read but not write PAR / REC and MINC files. Having the code to read the files makes it easier to work with these files in Python, and easier for someone else to add the ability to write the format later.
An image should conform to the image API. See the module docstring for spatialimages for a description of the API.
You should test whether your image does conform to the API by adding a test class for your image in nibabel.tests.test_image_api. For example, the API test for the PAR / REC image format looks like:
class TestPARRECAPI(LoadImageAPI):
def loader(self, fname):
return parrec.load(fname)
example_images = PARREC_EXAMPLE_IMAGES
where your work is to define the EXAMPLE_IMAGES list — see the nibabel.tests.test_parrec file for the PAR / REC example images definition.
There is no API requirement that a new image format inherit from the general SpatialImage class, but in fact all our image formats do inherit from this class. We strongly suggest you do the same, to get many simple methods implemented for free. You can always override the ones you don’t want.
There is also a generic header class you might consider building on to contain your image metadata — Header. See that class for the header API.
The API does not require it, but if it is possible, it may be good to implement the image data as loaded from disk as an array proxy. See the docstring of arrayproxy for a description of the API, and see the module code for an implementation of the API. You may be able to use the unmodified ArrayProxy class for your image type.
If you write a new array proxy class, add tests for the API of the class in nibabel.tests.test_proxy_api. See TestPARRECAPI for an example.
A nibabel image is the association of:
Your new image constructor may well be the default from SpatialImage, which looks like this:
def __init__(self, dataobj, affine, header=None,
extra=None, file_map=None):
Your job when loading a file is to create:
You will likely implement this logic in the from_file_map method of the image class. See PARRECImage for an example.