Skip to content

qform not saved #463

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mrjeffs opened this issue Jul 13, 2016 · 15 comments · Fixed by #478
Closed

qform not saved #463

mrjeffs opened this issue Jul 13, 2016 · 15 comments · Fixed by #478

Comments

@mrjeffs
Copy link

mrjeffs commented Jul 13, 2016

hi matthew et al, just converted both some of my parrec data and then the nibabel/test/data and found no qform in the nii files. the sform is there but no qform. it is in the pr_hdr and the nhdr object. i can access it using the get_qform() method but it does not make it into the actual saved file. This is an issue because some packages do not fall back on the sform when the qform is missing and without offsets things get a little unpredictable for example with ANTS.
can you confirm this behavior and offer a solution?
jeff

@mrjeffs
Copy link
Author

mrjeffs commented Jul 13, 2016

FYI found a thread on FSL listserve that says for FSL the sform is primary and qform is fall back, so it depends. j

@matthew-brett
Copy link
Member

Could you say more about what exactly you are doing? I guess loading into memory as PARREC format, and then converting to nifti, using a script? Could you give a minimal script that demonstrates the problem?

@mrjeffs
Copy link
Author

mrjeffs commented Jul 13, 2016

sure. here is nii header dump straight out of parrec2nii:
toddr@scotty:/...f_control/hbm_group_data/qT1/scs317/source_nii$ fslhd -x SCS_317_WIP_T1_MAP_20B_SENSE_9_1.nii
<nifti_image
nifti_type = 'NIFTI-1+'
header_filename = 'SCS_317_WIP_T1_MAP_20B_SENSE_9_1.nii'
image_filename = 'SCS_317_WIP_T1_MAP_20B_SENSE_9_1.nii'
image_offset = '39824'
ndim = '3'
nx = '240'
ny = '240'
nz = '116'
dx = '1'
dy = '1'
dz = '1.1'
datatype = '512'
datatype_name = 'UINT16'
nvox = '6681600'
nbyper = '2'
byteorder = 'LSB_FIRST'
cal_min = '0'
cal_max = '4.09248e+06'
scl_slope = '2532.47'
scl_inter = '0'
xyz_units = '2'
xyz_units_name = 'mm'
sform_code = '2'
sform_code_name = 'Aligned Anat'
sto_xyz_matrix = '-1 0 0 115.319 0 1 0 -101.889 0 0 1.1 -59.98 0 0 0 1'
sto_ijk matrix = '-1 -0 -0 115.319 -0 1 -0 101.889 -0 -0 0.909091 54.5273 0 0 0 1'
sform_i_orientation = 'Right-to-Left'
sform_j_orientation = 'Posterior-to-Anterior'
sform_k_orientation = 'Inferior-to-Superior'
num_ext = '1'
/>

you see only sform data in the header.
here is header with both qform and sform data present:

toddr@scotty:/...ntrol/hbm_group_data/ants_template/template_v1$ fslhd -x SCS353_WIP_MEMP_VBM_SENSE_11_1_rms.nii.gz
<nifti_image
nifti_type = 'NIFTI-1+'
header_filename = 'SCS353_WIP_MEMP_VBM_SENSE_11_1_rms.nii.gz'
image_filename = 'SCS353_WIP_MEMP_VBM_SENSE_11_1_rms.nii.gz'
image_offset = '352'
ndim = '3'
nx = '210'
ny = '352'
nz = '352'
dx = '0.8'
dy = '0.682'
dz = '0.682'
datatype = '16'
datatype_name = 'FLOAT32'
nvox = '26019840'
nbyper = '4'
byteorder = 'LSB_FIRST'
xyz_units = '2'
xyz_units_name = 'mm'
time_units = '8'
time_units_name = 's'
descrip = 'FreeSurfer May 13 2013'
qform_code = '1'
qform_code_name = 'Scanner Anat'
qto_xyz_matrix = '-0.794786 0.0593181 0.0502433 61.8522 0.0693908 0.679415 -0.00438754 -110.569 0.0591607 9.24452e-07 0.680133 -102.282 0 0 0 1'
qto_ijk_matrix = '-1.24185 0.108423 0.0924386 98.2543 0.127532 1.46072 1.98699e-06 153.622 0.108021 -0.00943306 1.46226 141.838 0 0 0 1'
quatern_b = '-0.0434998'
quatern_c = '-0.998368'
quatern_d = '-0.00161131'
qoffset_x = '61.8522'
qoffset_y = '-110.569'
qoffset_z = '-102.282'
qfac = '-1'
qform_i_orientation = 'Right-to-Left'
qform_j_orientation = 'Posterior-to-Anterior'
qform_k_orientation = 'Inferior-to-Superior'
sform_code = '1'
sform_code_name = 'Scanner Anat'
sto_xyz_matrix = '-0.794786 0.0593181 0.0502431 61.8522 0.0693908 0.679415 -0.00438753 -110.569 0.0591604 9.33106e-07 0.680133 -102.282 0 0 0 1'
sto_ijk matrix = '-1.24185 0.108423 0.0924382 98.2542 0.127532 1.46072 2.00531e-06 153.622 0.108021 -0.00943304 1.46226 141.838 0 0 0 1'
sform_i_orientation = 'Right-to-Left'
sform_j_orientation = 'Posterior-to-Anterior'
sform_k_orientation = 'Inferior-to-Superior'
num_ext = '0'
/>

@mrjeffs
Copy link
Author

mrjeffs commented Jul 13, 2016

here is the command with print statements inserted in parrec2nii to expose affine and qform values:
toddr@scotty:~/Software/nibabel/nibabel/tests/data$ parrec2nii -o parrec2nii_test --overwrite --scaling='fp' phantom_varscale.REC
orig affine = [[ -3.64994708 0. 1.83564171 123.66276611]
[ 0. -3.75 0. 115.617 ]
[ 0.86045705 0. 7.78655376 -27.91161211]
[ 0. 0. 0. 1. ]]

ornt after LAS dot affine = [[ -3.64994708 0. 1.83564171 123.66276611]
[ 0. -3.75 0. 115.617 ]
[ 0.86045705 0. 7.78655376 -27.91161211]
[ 0. 0. 0. 1. ]]
affine after LAS+ = [[ -3.64994708 0. 1.83564171 123.66276611]
[ 0. 3.75 0. -120.633 ]
[ 0.86045705 0. 7.78655376 -27.91161211]
[ 0. 0. 0. 1. ]]
affine at save = [[ -3.64994708 0. 1.83564171 123.66276611]
[ 0. 3.75 0. -120.633 ]
[ 0.86045705 0. 7.78655376 -27.91161211]
[ 0. 0. 0. 1. ]]
nhdr getqform = [[ -3.64994711 0. 1.83564145 123.6627655 ]
[ 0. 3.75 0. -120.63300323]
[ 0.86045693 0. 7.78655383 -27.91161156]
[ 0. 0. 0. 1. ]]

toddr@scotty:/Software/nibabel/nibabel/tests/data$ fslorient -getqform parrec2nii_test/phantom_varscale.nii
3.75 0 0 0 0 3.75 0 0 0 0 8 0 0 0 0 1
toddr@scotty:
/Software/nibabel/nibabel/tests/data$ fslhd -x parrec2nii_test/phantom_varscale.nii
<nifti_image
nifti_type = 'NIFTI-1+'
header_filename = 'parrec2nii_test/phantom_varscale.nii'
image_filename = 'parrec2nii_test/phantom_varscale.nii'
image_offset = '352'
ndim = '4'
nx = '64'
ny = '64'
nz = '9'
nt = '3'
dx = '3.75'
dy = '3.75'
dz = '8'
dt = '2'
datatype = '64'
datatype_name = 'FLOAT64'
nvox = '110592'
nbyper = '8'
byteorder = 'LSB_FIRST'
cal_min = '-10516.8'
cal_max = '645360'
scl_slope = '1'
scl_inter = '0'
xyz_units = '2'
xyz_units_name = 'mm'
time_units = '16'
time_units_name = 'ms'
sform_code = '2'
sform_code_name = 'Aligned Anat'
sto_xyz_matrix = '-3.64995 0 1.83564 123.663 0 3.75 0 -120.633 0.860457 0 7.78655 -27.9116 0 0 0 1'
sto_ijk matrix = '-0.259552 -0 0.0611881 33.8047 -0 0.266667 -0 32.1688 0.0286819 -0 0.121665 -0.15102 0 0 0 1'
sform_i_orientation = 'Right-to-Left'
sform_j_orientation = 'Posterior-to-Anterior'
sform_k_orientation = 'Inferior-to-Superior'
num_ext = '0'
/>

@mrjeffs
Copy link
Author

mrjeffs commented Jul 13, 2016

i have a fix which uses fslorient -copysform2qform in a subshell. its inelegant but needs must.
the qform is in the nimg object when saved as you can see from nhdr.get_qform() from last print stmnt middle of last post. somewhere in nifti1.save it is discarded and only sform is written to header.

@mrjeffs
Copy link
Author

mrjeffs commented Jul 14, 2016

Having now looked in detail at nifti1.py header_dtd definitions there is no qform matrix definition (or sform matrix for that matter), just the qform code, quaternions, and qoffsets but they don't seem to survive writing to file. the sform matrix gets generated presumably from that, tho i havn't figured out how yet. seems possible the qform matrix could be easily generated as well, then written.
I personally vote to include the qform matrix in the nii header when its appropriate in your development cycle.

@matthew-brett
Copy link
Member

The qform matrix derives from the translations, composed on the quaternion rotations composed with the zooms with an extra flip - description here : http://nifti.nimh.nih.gov/nifti-1/documentation/nifti1fields/nifti1fields_pages/qsform.html - implemented at https://github.com/nipy/nibabel/blob/master/nibabel/nifti1.py#L889

I'm guessing that the problem here is that, when you create a new blank NIfTI header, and ask to set an affine matrix into the header, the default qform has code 0 ('unknown') - see https://github.com/nipy/nibabel/blob/master/nibabel/nifti1.py#L1738 . I guess you want the affine code to be something else, such as 2 'aligned'. This will then show up with (for example) the FSL output you are looking at.

@mrjeffs
Copy link
Author

mrjeffs commented Jul 16, 2016

yes indeed. how could than implemented without new code? do
nhdr.set_qform(affine, code=2) after setting up nifti1 object and before saving?
i will check if qform code = 0 eventho qform matrix was present. i had assumes it was 2 since the affine was there.

@matthew-brett
Copy link
Member

Right - set_qform(affine, code=2) should do it.

@mrjeffs
Copy link
Author

mrjeffs commented Jul 18, 2016

thanks matthew, that worked. problem solved. i would recommend the one line added to the parrec2nii binary script at line 225 in a pull request. is it faster for you or shall i?

# Make corresponding NIfTI image
nimg = nifti1.Nifti1Image(in_data, affine, pr_hdr)
nhdr = nimg.header
nhdr.set_data_dtype(out_dtype)
nhdr.set_slope_inter(slope, intercept)
nhdr.set_qform(affine, code=2)                       #line 225 added to pull request

@matthew-brett
Copy link
Member

Would you mind doing it? If you have time, it would be good to have a test too.

@mrjeffs
Copy link
Author

mrjeffs commented Jul 18, 2016

no worries. i'll give it a go.

@matthew-brett
Copy link
Member

@mrjeffs - any progress? Can I help?

@matthew-brett
Copy link
Member

We're getting to close to a release - any chance of PR for this in the next couple of days?

@mrjeffs
Copy link
Author

mrjeffs commented Aug 10, 2016

congrats to jasper for doing the heavy lifting on building the tests.

matthew-brett added a commit that referenced this issue Aug 15, 2016
MRG: parrec2nii sets qform code to 2

Set qform code to 2 instead of 0 when writing out parrec2nii images.

Closes #463
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants