These were some meditations about splitting the image into two API parts.
The first part would be the lower level IO implementation. This part is rather like a fusion of the Header and ArrayProxy objects in current nibabel. It takes care of lower level details like i/o data dtype, shape, offset, and it might help with slicing to get the data. On top of that would be a high level interface implementing load, save, filename, data. The top-level image also had the novel idea of a mode parameter which, if 'r', would raise an error on attempting to save.
An image houses the association of the:
These are straightforward attributes, and have no necessary relationship to stuff on disk.
By ‘’disk’‘, we mean, file-like objects - not necessarily on disk.
The io implementation manages the relationship of images and stuff on disk.
Specifically, it manages load of images from disk, and save of images to disk.
The user does not see the io implementation unless they ask to. In standard use of images they will not need to do this.
By use case.
Creating array image, saving
>>> import tempfile
>>> from nibabel.images import Image
>>> from nibabel import load, save
>>> fp, fname = tempfile.mkstemp('.nii')
>>> data = np.arange(24).reshape((2,3,4))
>>> img = Image(data)
>>> img.filename is None
True
>>> img.save()
Traceback (most recent call last):
...
ImageError: no filespec to save to
>>> save(img)
Traceback (most recent call last):
...
ImageError: no filespec to save to
>>> img2 = save(img, 'some_image.nii') # type guessed from filename
>>> img2.filename == fname
True
>>> img.filename is None # still
True
>>> img.filename = 'some_filename.nii' # read only property
Traceback (most recent call last):
...
AttributeError: can't set attribute
Load, futz, save
>>> img3 = load(fname, mode='r')
>>> img3.filename == fname
True
>>> np.all(img3.data == data)
True
>>> img3.data[0,0] = 99
>>> img3.save()
Traceback (most recent call last):
...
ImageError: trying to write to read only image
>>> img3.mode = 'rw'
>>> img3.save()
>>> load(img4)
>>> img4.mode # 'r' is the default
'r'
>>> mod_data = data.copy()
>>> mod_data[0,0] = 99
>>> np.all(img4.data = mod_data)
True
Prepare image for later writing
>>> img5 = Image(np.zeros(2,3,4))
>>> fp, fname2 = tempfile.mkstemp('.nii')
>>> img5.set_filespec(fname2)
>>> # then do some things to the image
>>> img5.save()
This is an example where you do need the io API
>>> from nibabel.ioimps import guessed_imp
>>> fp, fname3 = tempfile.mkstemp('.nii')
>>> ioimp = guessed_imp(fname3)
>>> ioimp.set_data_dtype(np.float)
>>> ioimp.set_data_shape((2,3,4)) # set_data_shape method
>>> slice_def = (slice(None), slice(None), 0)
>>> ioimp.write_slice(data[slice_def], slice_def) # write_slice method
>>> slice_def = (2, 3, 1)
>>> ioimp.write_slice(data[slice_def], slice_def) # write_slice method
Traceback (most recent call last):
...
ImageIOError: data write is not contiguous