Skip to content

Commit

Permalink
update doc strings: nifti morph info
Browse files Browse the repository at this point in the history
  • Loading branch information
dfsp-spirit committed May 13, 2020
1 parent c38819c commit 1951b93
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 14 deletions.
28 changes: 15 additions & 13 deletions R/nifti2mgh.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# "The world coordinate system is assumed to be ras: +x is Right, +y is Anterior and +z is Superior"


#' @title Turn an `oro.nifti` instance into an `fs.volume` instance with complete header.
#' @title Turn a 3D or 4D `oro.nifti` instance into an `fs.volume` instance with complete header.
#'
#' @description This is work in progress. This function takes an `oro.nifti` instance and computes the MGH header fields from the NIFTI header data, allowing for proper orientation of the contained image data (see \code{\link[freesurferformats]{mghheader.vox2ras}} and related functions). Currently only few datatypes are supported, and the `sform` header field needs to be present in the NIFTI instance.
#'
Expand All @@ -17,6 +17,8 @@
#'
#' @seealso \code{\link[oro.nifti]{readNIfTI}}, \code{\link[freesurferformats]{read.fs.mgh}}
#'
#' @note This is not supposed to be used to read 1D morphometry data from NIFTI files generated by FreeSurfer (e.g., by converting `lh.thickness` to NIFTI using `mri_convert`): the FreeSurfer NIFTI hack is not supported by oro.nifti.
#'
#' @references \href{https://nifti.nimh.nih.gov/nifti-1/}{NIfTI-1 data format spec}
#'
#' @examples
Expand All @@ -37,7 +39,6 @@ read.fs.volume.nii <- function(filepath, flatten = FALSE, with_header=FALSE, dro
nifti_img = filepath;

if (requireNamespace("oro.nifti", quietly = TRUE)) {
message(sprintf("fs.volume.from.oro.nifti: This is an experimental WIP function. Do NOT use this in production.\n"));
if(is.character(nifti_img)) {
nifti_img = oro.nifti::readNIfTI(nifti_img);
}
Expand Down Expand Up @@ -65,9 +66,9 @@ read.fs.volume.nii <- function(filepath, flatten = FALSE, with_header=FALSE, dro
# The intent code describes how to interprete the data (e.g., that the values describe a certain distribution or whatever).
# See https://brainder.org/2012/09/23/the-nifti-file-format/ or the NIFTI spec for details.
# There is no field for this in MGH header afaict, so we ignore it.
if(nifti_img@intent_code != 0L) {
message("NIFTI intent_code '%d' ignored.\n", nifti_img@intent_code);
}
#if(nifti_img@intent_code != 0L) {
# message("NIFTI intent_code '%d' ignored.\n", nifti_img@intent_code);
#}

## -----Check the datatype ------
MRI_UCHAR = translate.mri.dtype("MRI_UCHAR");
Expand All @@ -90,7 +91,7 @@ read.fs.volume.nii <- function(filepath, flatten = FALSE, with_header=FALSE, dro

bytes_per_voxel = mri_dtype_numbytes(dtype); # Note that we store the size in **bytes** per voxel, while the Nifti header uses **bits**.

message(sprintf("Nifti header: Nifti datatype=%d with %d bitpix. MRI datatype '%s' (code %d), with %d bytes per voxel.\n", nifti_img@datatype, nifti_img@bitpix, translate.mri.dtype(dtype), dtype, bytes_per_voxel));
#message(sprintf("Nifti header: Nifti datatype=%d with %d bitpix. MRI datatype '%s' (code %d), with %d bytes per voxel.\n", nifti_img@datatype, nifti_img@bitpix, translate.mri.dtype(dtype), dtype, bytes_per_voxel));

## ----- Check image dimensions -----
num_used_dimensions = nifti_img@dim_[1];
Expand All @@ -114,7 +115,7 @@ read.fs.volume.nii <- function(filepath, flatten = FALSE, with_header=FALSE, dro
# Check for FreeSurfer hack in number of columns (negative column count, true column count stored in @glmin field).
# Loading these non-standard NIFTI files will not work anyways with oro.nifti I guess, so this may never happen.
if(nifti_img@dim_[2] < 0L) {
message(sprintf("Nifti image with FreeSurfer hack detected, assuming %d columns.\n", nifti_img@glmin));
#message(sprintf("Nifti image with FreeSurfer hack detected, assuming %d columns.\n", nifti_img@glmin));
ncols = nifti_img@glmin;
} else {
ncols = nifti_img@dim_[2];
Expand All @@ -126,7 +127,7 @@ read.fs.volume.nii <- function(filepath, flatten = FALSE, with_header=FALSE, dro
is_ico7 = (ncols * nifti_img@dim_[3] * nifti_img@dim_[4] == ico7_num_vertices);

ico7_state_string = ifelse(is_ico7, "looks like", "does NOT look like");
message(sprintf("Nifti header: Image has %d used dimensions. It %s ico7 morphometry data.\n", nifti_img@dim_[1], ico7_state_string));
#message(sprintf("Nifti header: Image has %d used dimensions. It %s ico7 morphometry data.\n", nifti_img@dim_[1], ico7_state_string));

## Compute space and time unit factors from @xyzt_units.
# NIFTI can store in different units, while MGH uses fixed units. Depending on the unit used in the NIFTI, we may need to
Expand All @@ -135,8 +136,8 @@ read.fs.volume.nii <- function(filepath, flatten = FALSE, with_header=FALSE, dro
space_unit_factor = space_info$scaling;
time_info = nifti.time.info(nifti_img@xyzt_units);
time_unit_factor = time_info$scaling;
message(sprintf("Space data in NIFTI file is stored in unit '%s', using scaling factor %f to match FreeSurfer unit 'mm'.\n", space_info$name, space_info$scaling));
message(sprintf("Time data in NIFTI file is stored in unit '%s', using scaling factor %f to match FreeSurfer unit 'ms'.\n", time_info$name, time_info$scaling));
#message(sprintf("Space data in NIFTI file is stored in unit '%s', using scaling factor %f to match FreeSurfer unit 'mm'.\n", space_info$name, space_info$scaling));
#message(sprintf("Time data in NIFTI file is stored in unit '%s', using scaling factor %f to match FreeSurfer unit 'ms'.\n", time_info$name, time_info$scaling));


header = mghheader(c(ncols, nifti_img@dim_[3], nifti_img@dim_[4], num_frames), dtype);
Expand All @@ -152,7 +153,7 @@ read.fs.volume.nii <- function(filepath, flatten = FALSE, with_header=FALSE, dro
# Extract vox2ras matrix from NIFTI sform, then init the MGH header from the vox2ras matrix.
vox2ras = rbind(nifti_img@srow_x, nifti_img@srow_y, nifti_img@srow_z, c(0, 0, 0, 1));
header = mghheader.update.from.vox2ras(header, vox2ras);
message(sprintf("Computed transformation matrix into '%s' using sform header data.\n", nifti.transform.type.name(nifti_img@sform_code)));
#message(sprintf("Computed transformation matrix into '%s' using sform header data.\n", nifti.transform.type.name(nifti_img@sform_code)));
header$ras_good_flag = 1L;
} else if(nifti_img@qform_code != 0L) {
# The qform transform is based on the 3 qoffset fields, qfac, and 4 quaternions. 3 of them are in the following header fields, the 4th one needs to be
Expand Down Expand Up @@ -221,7 +222,7 @@ read.fs.volume.nii <- function(filepath, flatten = FALSE, with_header=FALSE, dro
header$ras_good_flag = 1L;


message(sprintf("Computed transformation matrix into '%s' using qform header data.\n", nifti.transform.type.name(nifti_img@qform_code)));
#message(sprintf("Computed transformation matrix into '%s' using qform header data.\n", nifti.transform.type.name(nifti_img@qform_code)));
} else {
warning("Nifti image does not contain valid sform or qform, orientation cannot be derived and is arbitrary.");
# Fill in more or less random orientation values, scale voxel values by xsize, ysize, zsize.
Expand Down Expand Up @@ -273,7 +274,8 @@ read.fs.volume.nii <- function(filepath, flatten = FALSE, with_header=FALSE, dro
header$internal$slice_direction_name = orientation_info$direction_name;

data = nifti_img@.Data;
# TODO: Check in which storage ordering the data is saved in the NIFTI image and rotate/permute the array accordingly.

# Check in which storage ordering the data is saved in the NIFTI image and rotate/permute the array accordingly.
# See https://brainder.org/2012/09/23/the-nifti-file-format/ and the official NIFTI standard.
data = rotate3D(drop(data), axis=1L, degrees = 90L);

Expand Down
5 changes: 4 additions & 1 deletion man/read.fs.volume.nii.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1951b93

Please sign in to comment.