niivue / niivue

a WebGL2 based medical image viewer. Supports over 30 formats of volumes and meshes.
https://niivue.github.io/niivue/
BSD 2-Clause "Simplified" License
266 stars 66 forks source link

opening saved labelmap with nibabel fails #481

Closed haehn closed 1 year ago

haehn commented 1 year ago

Hi,

I saved a labelmap with nv.saveImage('image.nii', true) but when I load the file with nibabel it fails:

nib.load('image.nii')
>> raise ImageFileError(f'Cannot work out file type of "{filename}"')
>> nibabel.filebasedimages.ImageFileError: Cannot work out file type of "image.nii"
nib.Nifti1Image.from_filename('image.nii')
>> nibabel.spatialimages.HeaderDataError: magic string "" is not valid
nib.Nifti2Image.from_filename('image.nii')
>> nibabel.spatialimages.HeaderDataError: data code 0 not supported

Does anybody observe the same? Slicedrop.com loads the file properly but is also not very strict regarding magic strings etc.

Thanks, Daniel

haehn commented 1 year ago

on a side note, image.nii even being just the segmentation is 50+ MB. that is unusual - should be very small

haehn commented 1 year ago

if i load image.nii in Slicer and then store it as .nrrd the file is 320kB

neurolabusc commented 1 year ago

@haehn I am unable to replicate. I opened this demo and chose the Save Drawing item from the File menu which executes nv1.saveImage('draw.nii', true). This creates a 2777952 byte file, which one would expect with a UINT8 volume with 124x160x140 rows/columns/slices and the 352 byte NIfTI header. It opens fine, with the correct n+1 magic string.

import nibabel as nib
img = nib.load('draw.nii')
print(img.header)
sizeof_hdr      : 348
data_type       : b''
db_name         : b''
extents         : 0
session_error   : 0
regular         : b''
dim_info        : 0
dim             : [  3 124 160 140   1   1   1   1]
intent_p1       : 0.0
intent_p2       : 0.0
intent_p3       : 0.0
intent_code     : none
datatype        : uint8
bitpix          : 8
slice_start     : 0
pixdim          : [1. 1. 1. 1. 5. 0. 0. 0.]
vox_offset      : 0.0
scl_slope       : nan
scl_inter       : nan
slice_end       : 0
slice_code      : unknown
xyzt_units      : 10
cal_max         : 0.0
cal_min         : 0.0
slice_duration  : 0.0
toffset         : 0.0
glmax           : 0
glmin           : 0
descrip         : b'spm - realigned'
aux_file        : b''
qform_code      : aligned
sform_code      : aligned
quatern_b       : -0.15573758
quatern_c       : 0.054097265
quatern_d       : -0.016657775
qoffset_x       : -64.89824
qoffset_y       : -110.431145
qoffset_z       : -27.925539
srow_x          : [ 9.93592024e-01  1.60050392e-02  1.11887276e-01 -6.48982391e+01]
srow_y          : [-4.97049093e-02  9.50936675e-01  3.05366874e-01 -1.10431145e+02]
srow_z          : [ -0.10151029  -0.3089714    0.9456386  -27.925539  ]
intent_name     : b''
magic           : b'n+1'

I suspect the issue is with the volume you are opening. Maybe we need to ensure that voxel-based volumes loaded in non-NIfTI formats get the proper NIfTI details when opened. Can you share your source image with my institutional email?

neurolabusc commented 1 year ago

@haehn I think the issue is that this.hdr = new nifti.NIFTI1(); does not set the NIfTI1 magic signature. Therefore, all works well when a user loads a NIfTI image (where the magic is copied), but when one imports an image from a different format (e.g. NRRD) writing assumes it is a Analyze format image (the forerunner of NIfTI, which is incompatible with nibabel).

neurolabusc commented 1 year ago

@haehn I have added a kludge: the NIfTI magic should be a string, but creating a new NIFTI1 image with the library sets the magic to 1. Therefore, when NiiVue loads any image format, a numeric header is set to the magic for an embedded NIfTI header (n+1). We should explore whether the library should be changed. Perhaps the intention was that the spatial transforms are unknown, so a full NIfTI header is underspecified, or perhaps the expectation is that developers should explicitly request a embedded (n+1) or detached (ni1) header.

With regards to file size, note that with the demo changing

nv1.saveImage('draw.nii', true)

to read

nv1.saveImage('draw.nii.gz', true)

will reduce the file size from 2778 kb to 11kb: a x245 reduction. The discrete segmentation maps with long runs of the same color are ideal for compression strategies. Uncompressed voxelwise data files tend to be large: 2D bitmaps grow with the area, while 3D voxels grow with the volume. This is a clear advantage of surface based meshes versus voxels as spatial resolution increases. Just be aware that some tools (e.g. the popular SPM12) do not support compressed NIfTI data (though AFNI, FSL, MRIcroGL, NiiVue, FreeSurfer, etc do).

haehn commented 1 year ago

OK great it works with the fix!