├── .covrignore ├── src ├── .gitignore ├── Makevars.win ├── Makevars.in ├── dcm2niix │ ├── nii_foreign.h │ ├── nii_ortho.h │ ├── jpg_0XC3.h │ ├── print.h │ ├── nifti1_io_core.h │ ├── nii_dicom_batch.h │ ├── tinydir.h │ ├── nii_ortho.cpp │ ├── nii_dicom.h │ ├── nii_foreign.cpp │ ├── jpg_0XC3.cpp │ └── nifti1_io_core.cpp ├── ujpeg │ └── ujpeg.h ├── ImageList.h └── main.cpp ├── .gitignore ├── cleanup ├── codecov.yml ├── LICENCE ├── inst ├── extdata │ ├── jpeg │ │ ├── 01j.dcm │ │ └── 02j.dcm │ ├── jpl │ │ ├── 01jl.dcm │ │ └── 02jl.dcm │ └── raw │ │ ├── 01.dcm │ │ ├── 02.dcm │ │ ├── 03.dcm │ │ └── 04.dcm ├── tinytest │ ├── attrib_names.rds │ ├── attributes.rds │ ├── test-15-sort.R │ ├── test-10-jpeg.R │ ├── test-20-qa.R │ └── test-05-read.R └── COPYRIGHTS ├── tests └── tinytest.R ├── .Rbuildignore ├── .gitmodules ├── NAMESPACE ├── man ├── reexports.Rd ├── divest.capabilities.Rd └── readDicom.Rd ├── DESCRIPTION ├── R ├── zzz.R ├── menu.R └── read.R ├── configure.ac ├── .github └── workflows │ └── ci.yaml ├── README.Rmd ├── README.md └── NEWS /.covrignore: -------------------------------------------------------------------------------- 1 | src/dcm2niix 2 | src/ujpeg 3 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | /Makevars 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /config.* 3 | /compile_commands.json 4 | /.cache 5 | -------------------------------------------------------------------------------- /cleanup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -f src/Makevars src/*.o src/dcm2niix/*.o src/ujpeg/ujpeg.o 4 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | codecov: 3 | token: 80c1a88b-0ce6-4a3e-8b13-e318d3cb9638 4 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | YEAR: 2024 2 | COPYRIGHT HOLDER: Jon Clayden 3 | ORGANIZATION: University College London 4 | -------------------------------------------------------------------------------- /inst/extdata/jpeg/01j.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonclayden/divest/HEAD/inst/extdata/jpeg/01j.dcm -------------------------------------------------------------------------------- /inst/extdata/jpeg/02j.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonclayden/divest/HEAD/inst/extdata/jpeg/02j.dcm -------------------------------------------------------------------------------- /inst/extdata/jpl/01jl.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonclayden/divest/HEAD/inst/extdata/jpl/01jl.dcm -------------------------------------------------------------------------------- /inst/extdata/jpl/02jl.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonclayden/divest/HEAD/inst/extdata/jpl/02jl.dcm -------------------------------------------------------------------------------- /inst/extdata/raw/01.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonclayden/divest/HEAD/inst/extdata/raw/01.dcm -------------------------------------------------------------------------------- /inst/extdata/raw/02.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonclayden/divest/HEAD/inst/extdata/raw/02.dcm -------------------------------------------------------------------------------- /inst/extdata/raw/03.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonclayden/divest/HEAD/inst/extdata/raw/03.dcm -------------------------------------------------------------------------------- /inst/extdata/raw/04.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonclayden/divest/HEAD/inst/extdata/raw/04.dcm -------------------------------------------------------------------------------- /inst/tinytest/attrib_names.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonclayden/divest/HEAD/inst/tinytest/attrib_names.rds -------------------------------------------------------------------------------- /inst/tinytest/attributes.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonclayden/divest/HEAD/inst/tinytest/attributes.rds -------------------------------------------------------------------------------- /tests/tinytest.R: -------------------------------------------------------------------------------- 1 | 2 | if ( requireNamespace("tinytest", quietly=TRUE) ){ 3 | tinytest::test_package("divest") 4 | } 5 | 6 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^\.covrignore$ 2 | ^\.travis\.yml$ 3 | ^appveyor\.yml$ 4 | ^codecov\.yml$ 5 | ^\.github$ 6 | ^README.Rmd$ 7 | ^compile_commands.json$ 8 | ^\.cache$ 9 | ^inst/tinytest/dcm_qa 10 | -------------------------------------------------------------------------------- /src/Makevars.win: -------------------------------------------------------------------------------- 1 | PKG_CPPFLAGS = -DUSING_R -DmyDisableOpenJPEG -I. -Idcm2niix -Iujpeg 2 | 3 | OBJECTS_LIBS = ujpeg/ujpeg.o 4 | 5 | OBJECTS_DCM2NIIX = dcm2niix/jpg_0XC3.o dcm2niix/nifti1_io_core.o dcm2niix/nii_dicom.o dcm2niix/nii_dicom_batch.o dcm2niix/nii_ortho.o 6 | 7 | OBJECTS = main.o $(OBJECTS_DCM2NIIX) $(OBJECTS_LIBS) 8 | -------------------------------------------------------------------------------- /src/Makevars.in: -------------------------------------------------------------------------------- 1 | PKG_CPPFLAGS = -DUSING_R -DmyDisableMiniZ @JPEG2K_FLAGS@ @FUNCDEFS@ -I. -Idcm2niix -Iujpeg 2 | PKG_LIBS = @JPEG2K_LIBS@ 3 | 4 | OBJECTS_LIBS = ujpeg/ujpeg.o 5 | 6 | OBJECTS_DCM2NIIX = dcm2niix/jpg_0XC3.o dcm2niix/nifti1_io_core.o dcm2niix/nii_dicom.o dcm2niix/nii_dicom_batch.o dcm2niix/nii_ortho.o 7 | 8 | OBJECTS = main.o $(OBJECTS_DCM2NIIX) $(OBJECTS_LIBS) 9 | -------------------------------------------------------------------------------- /inst/COPYRIGHTS: -------------------------------------------------------------------------------- 1 | The "dcm2niix" tool, upon which the "divest" R package is built, is copyright (C) 2014-2016 Chris Rorden (BSD licence). 2 | 3 | The included NanoJPEG library is copyright (C) 2009-2016 Martin J. Fiedler (MIT licence). 4 | 5 | The included TinyDir library is copyright (C) 2013-2014 Cong Xu (BSD licence). 6 | 7 | The included memmem implementation is copyright (C) 2005 Pascal Gloor (BSD licence). 8 | -------------------------------------------------------------------------------- /src/dcm2niix/nii_foreign.h: -------------------------------------------------------------------------------- 1 | // Attempt to open non-DICOM image 2 | 3 | #ifndef _NII_FOREIGN_ 4 | #define _NII_FOREIGN_ 5 | 6 | #include "nii_dicom_batch.h" 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | // #include "nii_dicom_batch.h" 13 | 14 | // int open_foreign (const char *fn); 15 | int convert_foreign(const char *fn, struct TDCMopts opts); 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/dcm2niix/nii_ortho.h: -------------------------------------------------------------------------------- 1 | #ifndef _NIFTI_ORTHO_CORE_ 2 | #define _NIFTI_ORTHO_CORE_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #ifndef USING_R 9 | #include "nifti1.h" 10 | #endif 11 | 12 | void mat2sForm(struct nifti_1_header *h, mat44 s); 13 | bool isMat44Canonical(mat44 R); 14 | unsigned char *nii_setOrtho(unsigned char *img, struct nifti_1_header *h); 15 | #ifdef __cplusplus 16 | } 17 | #endif 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dcm_qa"] 2 | path = inst/tinytest/dcm_qa 3 | url = https://github.com/neurolabusc/dcm_qa.git 4 | [submodule "dcm_qa_nih"] 5 | path = inst/tinytest/dcm_qa_nih 6 | url = https://github.com/neurolabusc/dcm_qa_nih.git 7 | [submodule "tests/testthat/dcm_qa_uih"] 8 | path = inst/tinytest/dcm_qa_uih 9 | url = https://github.com/neurolabusc/dcm_qa_uih.git 10 | [submodule "tests/dcm_qa_stc"] 11 | path = inst/tinytest/dcm_qa_stc 12 | url = https://github.com/neurolabusc/dcm_qa_stc.git 13 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("imageAttributes<-") 4 | export(convertDicom) 5 | export(divest.capabilities) 6 | export(fromBidsJson) 7 | export(imageAttributes) 8 | export(readDicom) 9 | export(scanDicom) 10 | export(sortDicom) 11 | export(toBidsJson) 12 | import(RNifti) 13 | importFrom(RNifti,"imageAttributes<-") 14 | importFrom(RNifti,fromBidsJson) 15 | importFrom(RNifti,imageAttributes) 16 | importFrom(RNifti,toBidsJson) 17 | importFrom(Rcpp,evalCpp) 18 | useDynLib(divest, .registration = TRUE, .fixes = "C_") 19 | -------------------------------------------------------------------------------- /man/reexports.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/zzz.R 3 | \docType{import} 4 | \name{reexports} 5 | \alias{reexports} 6 | \alias{imageAttributes} 7 | \alias{imageAttributes<-} 8 | \alias{fromBidsJson} 9 | \alias{toBidsJson} 10 | \title{Objects exported from other packages} 11 | \keyword{internal} 12 | \description{ 13 | These objects are imported from other packages. Follow the links 14 | below to see their documentation. 15 | 16 | \describe{ 17 | \item{RNifti}{\code{\link[RNifti:bidsJson]{fromBidsJson}}, \code{\link[RNifti]{imageAttributes}}, \code{\link[RNifti:imageAttributes]{imageAttributes<-}}, \code{\link[RNifti:bidsJson]{toBidsJson}}} 18 | }} 19 | 20 | -------------------------------------------------------------------------------- /inst/tinytest/test-15-sort.R: -------------------------------------------------------------------------------- 1 | origPath <- system.file("extdata", "raw", package="divest") 2 | tempPath <- tempdir() 3 | file.copy(origPath, tempPath, recursive=TRUE) 4 | tempPath <- file.path(tempPath, "raw") 5 | 6 | expect_stdout(sortDicom(tempPath), "Renamed 4 DICOM") 7 | expect_equal(length(list.files(tempPath)), 2L) 8 | expect_true(all(c("T0_N_S8","T0_N_S9") %in% list.files(tempPath))) 9 | expect_stdout(readDicom(file.path(tempPath,"T0_N_S8"),interactive=FALSE), "Found 2 DICOM") 10 | 11 | unlink(tempPath, recursive=TRUE) 12 | 13 | dir.create(tempPath) 14 | setwd(tempPath) 15 | 16 | expect_stdout(sortDicom(origPath, labelFormat="T%t/S%s/%4r.ima", nested=FALSE, keepUnsorted=TRUE), "Renamed 4 DICOM") 17 | expect_true(all(c("S8","S9") %in% list.files(file.path(tempPath,"T0")))) 18 | 19 | unlink(tempPath, recursive=TRUE) 20 | -------------------------------------------------------------------------------- /src/dcm2niix/jpg_0XC3.h: -------------------------------------------------------------------------------- 1 | // Decode DICOM Transfer Syntax 1.2.840.10008.1.2.4.70 and 1.2.840.10008.1.2.4.57 2 | // JPEG Lossless, Nonhierarchical 3 | // see ISO/IEC 10918-1 / ITU T.81 4 | // specifically, format with 'Start of Frame' (SOF) code 0xC3 5 | // http://www.w3.org/Graphics/JPEG/itu-t81.pdf 6 | // This code decodes data with 1..16 bits per pixel 7 | // It appears unique to medical imaging, and is not supported by most JPEG libraries 8 | // http://www.dicomlibrary.com/dicom/transfer-syntax/ 9 | // https://en.wikipedia.org/wiki/Lossless_JPEG#Lossless_mode_of_operation 10 | #ifndef _JPEG_SOF_0XC3_ 11 | #define _JPEG_SOF_0XC3_ 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | unsigned char *decode_JPEG_SOF_0XC3(const char *fn, int skipBytes, bool verbose, int *dimX, int *dimY, int *bits, int *frames, int diskBytes); 18 | 19 | #ifdef __cplusplus 20 | } 21 | #endif 22 | 23 | #endif -------------------------------------------------------------------------------- /inst/tinytest/test-10-jpeg.R: -------------------------------------------------------------------------------- 1 | options(divest.bidsAttributes=FALSE) 2 | 3 | rawpath <- system.file("extdata", "raw", package="divest") 4 | r <- readDicom(rawpath, interactive=FALSE, verbosity=-2) 5 | i <- which(sapply(r,RNifti::ndim) == 3L) 6 | 7 | path <- system.file("extdata", "jpeg", package="divest") 8 | expect_stdout(d <- readDicom(path,interactive=FALSE), "Found 2 DICOM") 9 | 10 | expect_identical(length(d), 1L) 11 | expect_equal(dim(d[[1]]), c(2,224,256)) 12 | expect_equal(attr(d[[1]],"flipAngle"), 15) 13 | expect_equal(scale(as.array(d[[1]])), scale(as.array(r[[i]])), check.attributes=FALSE, tolerance=0.1) 14 | 15 | path <- system.file("extdata", "jpl", package="divest") 16 | expect_stdout(d <- readDicom(path,interactive=FALSE), "Found 2 DICOM") 17 | 18 | expect_identical(length(d), 1L) 19 | expect_equal(dim(d[[1]]), c(2,224,256)) 20 | expect_equal(attr(d[[1]],"flipAngle"), 15) 21 | expect_equal(as.array(d[[1]]), as.array(r[[i]])) 22 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: divest 2 | Version: 1.2.0 3 | Date: 2025-03-04 4 | Title: Get Images Out of DICOM Format Quickly 5 | Authors@R: c(person("Jon", "Clayden", email="code@clayden.org", role=c("aut","cre"), comment=c(ORCID="0000-0002-6608-0619")), 6 | person("Chris", "Rorden", role="aut"), 7 | person("Martin J", "Fiedler", role="cph"), 8 | person("Cong", "Xu", role="cph"), 9 | person("Pascal", "Gloor", role="cph")) 10 | Maintainer: Jon Clayden 11 | Depends: R (>= 3.5.0) 12 | Imports: Rcpp, RNifti (>= 1.8.0) 13 | LinkingTo: Rcpp, RNifti 14 | Suggests: jsonlite, tinytest, covr 15 | Description: Provides tools to sort DICOM-format medical image files, and 16 | convert them to NIfTI-1 format. 17 | License: BSD_3_clause + file LICENCE 18 | URL: https://github.com/jonclayden/divest 19 | BugReports: https://github.com/jonclayden/divest/issues 20 | Encoding: UTF-8 21 | RoxygenNote: 7.3.2 22 | Roxygen: list(old_usage=TRUE, markdown=TRUE) 23 | -------------------------------------------------------------------------------- /man/divest.capabilities.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/zzz.R 3 | \name{divest.capabilities} 4 | \alias{divest.capabilities} 5 | \title{Report package capabilities} 6 | \usage{ 7 | divest.capabilities(what = NULL) 8 | } 9 | \arguments{ 10 | \item{what}{A character vector of components to extract, or \code{NULL}, 11 | the default, which indicates the full set of capabilities.} 12 | } 13 | \value{ 14 | A named logical vector, indicating whether plain JPEG, JPEG-LS and 15 | JPEG2000 DICOM transfer syntaxes are supported by the current build of the 16 | package, and also whether \code{zlib} is available to produce compressed NIfTI 17 | output files from \code{\link[=convertDicom]{convertDicom()}}. 18 | } 19 | \description{ 20 | This function summarises the capabilities of the package as compiled for the 21 | current machine, analogously to the \code{base} function \code{\link[=capabilities]{capabilities()}}. It 22 | identifies the support available for various input and output formats. 23 | } 24 | \examples{ 25 | divest.capabilities() 26 | } 27 | \seealso{ 28 | \code{\link[=readDicom]{readDicom()}} 29 | } 30 | \author{ 31 | Jon Clayden \href{mailto:code@clayden.org}{code@clayden.org} 32 | } 33 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | #' @import RNifti 2 | #' @importFrom Rcpp evalCpp 3 | #' @useDynLib divest, .registration = TRUE, .fixes = "C_" 4 | NULL 5 | 6 | #' Report package capabilities 7 | #' 8 | #' This function summarises the capabilities of the package as compiled for the 9 | #' current machine, analogously to the `base` function [capabilities()]. It 10 | #' identifies the support available for various input and output formats. 11 | #' 12 | #' @param what A character vector of components to extract, or `NULL`, 13 | #' the default, which indicates the full set of capabilities. 14 | #' @return A named logical vector, indicating whether plain JPEG, JPEG-LS and 15 | #' JPEG2000 DICOM transfer syntaxes are supported by the current build of the 16 | #' package, and also whether `zlib` is available to produce compressed NIfTI 17 | #' output files from [convertDicom()]. 18 | #' 19 | #' @seealso [readDicom()] 20 | #' @examples 21 | #' divest.capabilities() 22 | #' @author Jon Clayden 23 | #' @export 24 | divest.capabilities <- function (what = NULL) 25 | { 26 | caps <- .Call(C_getCapabilities) 27 | if (!is.null(what)) 28 | caps <- caps[charmatch(what, names(caps), 0L)] 29 | return (caps) 30 | } 31 | 32 | #' @export 33 | RNifti::imageAttributes 34 | #' @export 35 | RNifti::`imageAttributes<-` 36 | #' @export 37 | RNifti::fromBidsJson 38 | #' @export 39 | RNifti::toBidsJson 40 | -------------------------------------------------------------------------------- /R/menu.R: -------------------------------------------------------------------------------- 1 | # Wrapper function to allow mocking in tests 2 | .readline <- function (...) base::readline(...) 3 | 4 | # Similar to utils::menu(), but defaults to selecting everything, and allows 5 | # comma separation and ranges (using colons or hyphens) 6 | # Currently lacks column formatting, since strings are generally quite long 7 | .menu <- function (choices) 8 | { 9 | choices <- as.character(choices) 10 | nChoices <- length(choices) 11 | 12 | if (nChoices < 1L) 13 | return (integer(0)) 14 | 15 | digits <- as.integer(floor(log10(nChoices)) + 1) 16 | numbers <- sprintf(paste0("%",digits,"d: "), seq_len(nChoices)) 17 | 18 | cat(paste0("\n", numbers, choices)) 19 | cat("\n\nType to select everything, 0 for nothing, or indices separated by spaces or commas") 20 | selection <- .readline("\nSelection: ") 21 | 22 | if (selection == "") 23 | selection <- seq_len(nChoices) 24 | else if (selection == "0") 25 | selection <- integer(0) 26 | else 27 | { 28 | # Split into elements separated by commas or spaces, and resolve ranges 29 | parts <- unlist(strsplit(selection, "[, ]+", perl=TRUE)) 30 | selection <- as.integer(unlist(lapply(parts, function (str) { 31 | str <- sub("(\\d)-(\\d)", "\\1:\\2", str, perl=TRUE) 32 | eval(parse(text=str)) 33 | }))) 34 | } 35 | 36 | return (selection) 37 | } 38 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # -*- Autoconf -*- 2 | # Process this file with autoconf to produce a configure script. 3 | 4 | AC_PREREQ([2.71]) 5 | AC_INIT([divest], [1.2.0], [code@clayden.org]) 6 | # AC_CONFIG_SRCDIR([ImageList.h]) 7 | 8 | # Get compiler flags from R 9 | : ${R_HOME=`R RHOME`} 10 | if test -z "${R_HOME}"; then 11 | AC_MSG_ERROR([Could not determine R_HOME.]) 12 | fi 13 | CXX=`"${R_HOME}/bin/R" CMD config CXX` 14 | CXXFLAGS=`"${R_HOME}/bin/R" CMD config CXXFLAGS` 15 | CPPFLAGS=`"${R_HOME}/bin/R" CMD config CPPFLAGS` 16 | 17 | AC_LANG(C++) 18 | 19 | # Checks for programs. 20 | AC_PROG_CXX 21 | AC_PATH_PROG([PKGCONFIG], [pkg-config], [none]) 22 | AC_ARG_VAR([PKGCONFIG], [path to pkg-config]) 23 | AC_CHECK_INCLUDES_DEFAULT 24 | 25 | # Checks for functions. 26 | FUNCDEFS="" 27 | AC_CHECK_FUNC([fmemopen], [FUNCDEFS="-DHAVE_FMEMOPEN"]) 28 | 29 | # Checks for OpenJPEG. 30 | JPEG2K_FLAGS="-DmyDisableOpenJPEG" 31 | JPEG2K_LIBS="" 32 | 33 | if ( test -z "${PKGCONFIG}" || test "${PKGCONFIG}" = "none" ); then 34 | AC_SEARCH_LIBS([opj_version], [openjp2], [ 35 | AC_CHECK_HEADER([openjpeg.h], [ 36 | JPEG2K_FLAGS="" 37 | JPEG2K_LIBS="-lopenjp2" 38 | AC_MSG_NOTICE(Using OpenJPEG for JPEG2000 support) 39 | ]) 40 | ]) 41 | elif ${PKGCONFIG} --exists libopenjp2; then 42 | JPEG2K_FLAGS=`"${PKGCONFIG}" --cflags libopenjp2` 43 | JPEG2K_LIBS=`"${PKGCONFIG}" --libs libopenjp2` 44 | AC_MSG_NOTICE(Using OpenJPEG for JPEG2000 support) 45 | fi 46 | 47 | # Checks for JasPer (if OpenJPEG was not found). 48 | if test -z "${JPEG2K_LIBS}"; then 49 | AC_SEARCH_LIBS([jas_getversion], [jasper], [ 50 | AC_CHECK_HEADER([jasper/jasper.h], [ 51 | JPEG2K_FLAGS="-DmyEnableJasper -DmyDisableOpenJPEG" 52 | JPEG2K_LIBS="-ljasper" 53 | AC_MSG_NOTICE(Using JasPer for JPEG2000 support) 54 | ]) 55 | ]) 56 | fi 57 | 58 | AC_SUBST(FUNCDEFS) 59 | AC_SUBST(JPEG2K_FLAGS) 60 | AC_SUBST(JPEG2K_LIBS) 61 | 62 | AC_CONFIG_FILES([src/Makevars]) 63 | AC_OUTPUT 64 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [ push, pull_request ] 4 | 5 | defaults: 6 | run: 7 | shell: bash 8 | 9 | jobs: 10 | # This workflow contains a single job called "check" 11 | check: 12 | # Build and check on Linux and Windows 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: [ ubuntu-latest, windows-latest ] 17 | 18 | # The type of runner that this job runs on 19 | runs-on: ${{ matrix.os }} 20 | 21 | # Run non-CRAN-friendly tests 22 | env: 23 | TT_AT_HOME: 'TRUE' 24 | 25 | # Steps represent a sequence of tasks that will be executed as part of the job 26 | steps: 27 | # Check out the repository under $GITHUB_WORKSPACE 28 | - uses: actions/checkout@v4 29 | with: 30 | submodules: true 31 | 32 | # Install and set up R 33 | - uses: r-lib/actions/setup-r@v2 34 | 35 | # The curl package (upon which covr depends) requires libcurl 36 | - name: Install upstream system dependencies 37 | if: runner.os == 'Linux' 38 | run: sudo apt-get install -y libcurl4-openssl-dev 39 | 40 | - name: Parse and install dependencies 41 | run: | 42 | deps <- read.dcf("DESCRIPTION", c("Depends","Imports","LinkingTo","Suggests","Enhances")) 43 | deps <- na.omit(unlist(strsplit(deps, "\\s*,\\s*", perl=TRUE))) 44 | deps <- setdiff(unique(sub("\\s*\\([^\\)]+\\)\\s*$", "", deps, perl=TRUE)), c("R", rownames(installed.packages()))) 45 | install.packages(deps) 46 | shell: Rscript {0} 47 | 48 | - name: Build package 49 | run: R CMD build . 50 | 51 | - name: Check package 52 | run: R CMD check --no-manual *tar.gz || { mv *.Rcheck Rcheck; false; } 53 | 54 | # Upload the check directory as an artefact on failure 55 | - name: Upload check results 56 | if: failure() 57 | uses: actions/upload-artifact@v4 58 | with: 59 | name: ${{ matrix.os }}-results 60 | path: Rcheck 61 | 62 | - name: Update test coverage 63 | if: runner.os == 'Linux' 64 | run: Rscript -e 'covr::codecov()' 65 | -------------------------------------------------------------------------------- /src/ujpeg/ujpeg.h: -------------------------------------------------------------------------------- 1 | #ifndef _NANOJPEG_H 2 | #define _NANOJPEG_H 3 | 4 | // nj_result_t: Result codes for njDecode(). 5 | typedef enum _nj_result { 6 | NJ_OK = 0, // no error, decoding successful 7 | NJ_NO_JPEG, // not a JPEG file 8 | NJ_UNSUPPORTED, // unsupported format 9 | NJ_OUT_OF_MEM, // out of memory 10 | NJ_INTERNAL_ERR, // internal error 11 | NJ_SYNTAX_ERROR, // syntax error 12 | __NJ_FINISHED // used internally, will never be reported 13 | } nj_result_t; 14 | 15 | // njInit: Initialize NanoJPEG. 16 | // For safety reasons, this should be called at least one time before using 17 | // using any of the other NanoJPEG functions. 18 | void njInit(void); 19 | 20 | // njDecode: Decode a JPEG image. 21 | // Decodes a memory dump of a JPEG file into internal buffers. 22 | // Parameters: 23 | // jpeg = The pointer to the memory dump. 24 | // size = The size of the JPEG file. 25 | // Return value: The error code in case of failure, or NJ_OK (zero) on success. 26 | nj_result_t njDecode(const void *jpeg, const int size); 27 | 28 | // njGetWidth: Return the width (in pixels) of the most recently decoded 29 | // image. If njDecode() failed, the result of njGetWidth() is undefined. 30 | int njGetWidth(void); 31 | 32 | // njGetHeight: Return the height (in pixels) of the most recently decoded 33 | // image. If njDecode() failed, the result of njGetHeight() is undefined. 34 | int njGetHeight(void); 35 | 36 | // njIsColor: Return 1 if the most recently decoded image is a color image 37 | // (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result 38 | // of njGetWidth() is undefined. 39 | int njIsColor(void); 40 | 41 | // njGetImage: Returns the decoded image data. 42 | // Returns a pointer to the most recently image. The memory layout it byte- 43 | // oriented, top-down, without any padding between lines. Pixels of color 44 | // images will be stored as three consecutive bytes for the red, green and 45 | // blue channels. This data format is thus compatible with the PGM or PPM 46 | // file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8. 47 | // If njDecode() failed, the result of njGetImage() is undefined. 48 | unsigned char *njGetImage(void); 49 | 50 | // njGetImageSize: Returns the size (in bytes) of the image data returned 51 | // by njGetImage(). If njDecode() failed, the result of njGetImageSize() is 52 | // undefined. 53 | int njGetImageSize(void); 54 | 55 | // njDone: Uninitialize NanoJPEG. 56 | // Resets NanoJPEG's internal state and frees all memory that has been 57 | // allocated at run-time by NanoJPEG. It is still possible to decode another 58 | // image after a njDone() call. 59 | void njDone(void); 60 | 61 | #endif //_NANOJPEG_H 62 | -------------------------------------------------------------------------------- /inst/tinytest/test-20-qa.R: -------------------------------------------------------------------------------- 1 | options(divest.bidsAttributes=TRUE) 2 | 3 | ignoreFields <- c("ConversionSoftwareVersion", "BodyPartExamined", "ImageType") 4 | ignoreImages <- c("dti_tra_dir16_PA_6_134431") 5 | 6 | test_battery <- function (root, labelFormat = "%p_%s") 7 | { 8 | if (!file.exists(root)) 9 | return (invisible(NULL)) 10 | 11 | images <- readDicom(file.path(root,"In"), interactive=FALSE, labelFormat=labelFormat, verbosity=-2) 12 | labels <- unlist(images) 13 | 14 | refStems <- file.path(root, "Ref", labels) 15 | refFiles <- list(image=paste(refStems,"nii",sep="."), 16 | metadata=paste(refStems,"json",sep="."), 17 | bval=paste(refStems,"bval",sep="."), 18 | bvec=paste(refStems,"bvec",sep=".")) 19 | refFilesPresent <- lapply(refFiles, file.exists) 20 | 21 | # expect_setequal(list.files(file.path(root,"Ref"), "\\.nii$"), basename(refFiles$image)) 22 | 23 | for (i in seq_along(images)) 24 | { 25 | if (labels[i] %in% ignoreImages) 26 | next 27 | 28 | if (refFilesPresent$image[i]) 29 | { 30 | refImage <- RNifti::readNifti(refFiles$image[i], internal=TRUE) 31 | expect_equal(RNifti::niftiHeader(refImage), RNifti::niftiHeader(images[[i]]), info=labels[i], tolerance=1e-5) 32 | } 33 | 34 | if (refFilesPresent$metadata[i]) 35 | { 36 | metadata <- attributes(images[[i]]) 37 | refMetadata <- jsonlite::read_json(refFiles$metadata[i], simplifyVector=TRUE) 38 | fields <- setdiff(names(refMetadata), ignoreFields) 39 | # Check we have all the metadata we expect 40 | # If not, this test will fail but we remove the names to avoid a subsequent error 41 | missingFields <- setdiff(fields, names(metadata)) 42 | expect_length(missingFields, 0L, info=missingFields) 43 | fields <- intersect(fields, names(metadata)) 44 | expect_equal(refMetadata[fields], metadata[fields], info=labels[i], tolerance=1e-4) 45 | } 46 | 47 | # These files only apply to diffusion sequences 48 | if (refFilesPresent$bval[i] && refFilesPresent$bvec[i]) 49 | { 50 | bValues <- drop(as.matrix(read.table(refFiles$bval[i]))) 51 | bVectors <- t(as.matrix(read.table(refFiles$bvec[i]))) 52 | expect_equivalent(metadata$bValues, bValues, info=labels[i], tolerance=1e-4) 53 | expect_equivalent(metadata$bVectors, bVectors, info=labels[i], tolerance=1e-4) 54 | } 55 | } 56 | } 57 | 58 | # Main QA test battery 59 | test_battery("dcm_qa") 60 | 61 | # NIH QA battery 62 | test_battery("dcm_qa_nih") 63 | 64 | # UIH QA battery 65 | test_battery("dcm_qa_uih", "%p_%s_%t") 66 | 67 | # Slice timing QA battery 68 | test_battery("dcm_qa_stc", "%v_%p_%s") 69 | -------------------------------------------------------------------------------- /inst/tinytest/test-05-read.R: -------------------------------------------------------------------------------- 1 | options(divest.bidsAttributes=FALSE) 2 | 3 | caps <- divest.capabilities() 4 | expect_equal(names(caps), c("jpeg","jpegLS","jpeg2000","zlib")) 5 | 6 | # Read all 7 | path <- system.file("extdata", "raw", package="divest") 8 | expect_stdout(d <- readDicom(path,interactive=FALSE), "Found 4 DICOM") 9 | expect_stdout(readDicom(path,interactive=FALSE,verbosity=-1), "WARNING") 10 | 11 | # Check results 12 | expect_length(d, 2L) 13 | i <- which(sapply(d,RNifti::ndim) == 3L) 14 | expect_equal(dim(d[[i]]), c(2,224,256)) 15 | expect_equal(attr(d[[i]],"flipAngle"), 15) 16 | 17 | # Check all attributes 18 | # NB. String sort order is locale-dependent, so use stored names directly for indexing 19 | attributes <- imageAttributes(d[[i]]) 20 | attrNames <- setdiff(names(attributes), "conversionSoftwareVersion") 21 | storedAttrNames <- readRDS("attrib_names.rds") 22 | expect_true(setequal(attrNames, storedAttrNames)) 23 | expect_equivalent_to_reference(attributes[storedAttrNames], "attributes.rds") 24 | 25 | # Setting `imageAttributes<-`(x,NULL) should scrub all but basic RNifti metadata 26 | copy <- d[[i]] 27 | imageAttributes(copy) <- NULL 28 | expect_equal(attr(d[[i]],"pixdim"), attr(copy,"pixdim")) 29 | expect_null(attr(copy, "echoTime")) 30 | 31 | origin <- RNifti::worldToVoxel(c(0,0,0), d[[i]]) 32 | expect_equal(round(origin), c(-16,95,135)) 33 | 34 | # Straight to file conversion 35 | tempDir <- tempdir() 36 | expect_stdout(d <- readDicom(path,output=tempDir), "Found 4 DICOM") 37 | expect_length(d, 2L) 38 | expect_inherits(d, "character") 39 | expect_equal(list.files(tempDir, "\\.nii"), c("T0_N_S8.nii.gz","T0_N_S9.nii.gz")) 40 | 41 | # Cropping 42 | expect_stdout(d <- readDicom(path,interactive=FALSE,crop=TRUE), "Cropping") 43 | i <- which(sapply(d,RNifti::ndim) == 3L) 44 | expect_equal(dim(d[[i]]), c(2,224,170)) 45 | 46 | # Subsets 47 | expect_stdout(d <- scanDicom(path), "Found 4 DICOM") 48 | expect_equal(d$repetitionTime, c(4100,11)) 49 | expect_stdout(readDicom(d,repetitionTime==4100), "Found 2 DICOM") 50 | expect_stdout(readDicom(path,repetitionTime==4100,interactive=FALSE), "Found 2 DICOM") 51 | 52 | # Single files 53 | expect_stdout(d <- readDicom(file.path(path,"01.dcm"),interactive=FALSE), "Convert 1 DICOM") 54 | expect_equal(dim(d[[1]]), c(224,256,1)) 55 | expect_warning(readDicom(file.path(path,"nonsense"),interactive=FALSE), "does not exist") 56 | 57 | # Depth argument 58 | expect_stdout(d <- readDicom(file.path(path,".."),depth=0L,interactive=FALSE), "No valid DICOM files") 59 | expect_length(d, 0L) 60 | 61 | if (at_home()) 62 | { 63 | # Monkey-patch the .readline function to simulate interactivity 64 | ns <- getNamespace("divest") 65 | unlockBinding(".readline", ns) 66 | assign(".readline", function(...) "1", envir=ns) 67 | 68 | # (Pseudo-)interactivity 69 | expect_stdout(d <- readDicom(path,interactive=TRUE), "Found 2 DICOM") 70 | expect_equal(unlist(d), "T0_N_S8") 71 | } 72 | -------------------------------------------------------------------------------- /src/ImageList.h: -------------------------------------------------------------------------------- 1 | #ifndef _IMAGE_LIST_H_ 2 | #define _IMAGE_LIST_H_ 3 | 4 | #ifdef USING_R 5 | 6 | #define STRICT_R_HEADERS 7 | #include 8 | 9 | #include "RNifti.h" 10 | 11 | class ImageList 12 | { 13 | private: 14 | // BIDS JSON buffer size limit per image, currently 4 MiB 15 | const size_t jsonBufferSize = 0x400000; 16 | char *jsonBuffer; 17 | FILE *jsonHandle_; 18 | 19 | std::vector paths_; 20 | 21 | Rcpp::List list; 22 | Rcpp::List deferredAttributes; 23 | 24 | public: 25 | ImageList () : jsonBuffer(NULL), jsonHandle_(NULL) {} 26 | 27 | operator SEXP () 28 | { 29 | return list; 30 | } 31 | 32 | Rcpp::CharacterVector pathVector () 33 | { 34 | return Rcpp::wrap(paths_); 35 | } 36 | 37 | FILE * jsonHandle () 38 | { 39 | #ifdef HAVE_FMEMOPEN 40 | if (jsonHandle_ == NULL) 41 | { 42 | // If the system supports it, use fmemopen to stream into a string buffer 43 | // This is C style, but dcm2niix expects a FILE* 44 | jsonBuffer = R_alloc(jsonBufferSize, 1); 45 | jsonHandle_ = fmemopen(jsonBuffer, jsonBufferSize, "w"); 46 | } 47 | #endif 48 | return jsonHandle_; 49 | } 50 | 51 | void append (nifti_image * const image, const std::string &name) 52 | { 53 | RNifti::NiftiImage wrapper(image, true); 54 | Rcpp::RObject pointer = wrapper.toPointer(name); 55 | std::vector classStrings = pointer.attr("class"); 56 | classStrings.insert(classStrings.begin(), "divestImage"); 57 | pointer.attr("class") = classStrings; 58 | 59 | if (deferredAttributes.size() > 0) 60 | { 61 | std::vector attributeNames = deferredAttributes.names(); 62 | for (int i = 0; i < deferredAttributes.size(); i++) 63 | pointer.attr(attributeNames[i]) = deferredAttributes[i]; 64 | deferredAttributes = Rcpp::List(); 65 | } 66 | 67 | if (jsonHandle_ != NULL) 68 | { 69 | // dcm2niix handles closing the file handle 70 | pointer.attr(".bidsJson") = const_cast(jsonBuffer); 71 | jsonHandle_ = NULL; 72 | } 73 | 74 | list.push_back(pointer); 75 | } 76 | 77 | template 78 | void addDeferredAttribute (const std::string &name, const ValueType &value) 79 | { 80 | deferredAttributes[name] = value; 81 | } 82 | 83 | template 84 | void addDeferredAttribute (const std::string &name, const ValueType &value, const int nRows, const int nCols) 85 | { 86 | Rcpp::RObject wrappedValue = Rcpp::wrap(value); 87 | wrappedValue.attr("dim") = Rcpp::Dimension(nRows, nCols); 88 | deferredAttributes[name] = wrappedValue; 89 | } 90 | 91 | void appendPath (const std::string &path) 92 | { 93 | paths_.push_back(path); 94 | } 95 | }; 96 | 97 | #endif 98 | 99 | #endif 100 | -------------------------------------------------------------------------------- /src/dcm2niix/print.h: -------------------------------------------------------------------------------- 1 | // This unit allows us to re-direct text messages 2 | // For standard C programs send text messages to the console via "printf" 3 | // The XCode project shows how you can re-direct these messages to a NSTextView 4 | // For QT programs, we can sent text messages to the cout buffer 5 | // The QT project shows how you can re-direct these to a Qtextedit 6 | // For R programs, we can intercept these messages. 7 | 8 | #ifndef _R_PRINT_H_ 9 | #define _R_PRINT_H_ 10 | #include 11 | #ifdef USING_R 12 | #define R_USE_C99_IN_CXX 13 | #include 14 | #define printMessage(...) \ 15 | do { \ 16 | Rprintf("[dcm2niix info] "); \ 17 | Rprintf(__VA_ARGS__); \ 18 | } while (0) 19 | #define printWarning(...) \ 20 | do { \ 21 | Rprintf("[dcm2niix WARNING] "); \ 22 | Rprintf(__VA_ARGS__); \ 23 | } while (0) 24 | #define printError(...) \ 25 | do { \ 26 | Rprintf("[dcm2niix ERROR] "); \ 27 | Rprintf(__VA_ARGS__); \ 28 | } while (0) 29 | #define printProgress(frac) \ 30 | do { \ 31 | Rprintf("[dcm2niix PROGRESS] %g", frac); \ 32 | } while (0) 33 | #else 34 | #ifdef myUseCOut 35 | // for piping output to Qtextedit 36 | // printf and cout buffers are not the same 37 | // #define printMessage(...) ({fprintf(stdout,__VA_ARGS__);}) 38 | #include 39 | template 40 | void printMessage(const char *format, Args... args) { 41 | // std::printf( format, args... ); 42 | // fprintf(stdout,"Short read on %s: Expected 512, got %zd\n",path, bytes_read); 43 | int length = std::snprintf(nullptr, 0, format, args...); 44 | if (length <= 0) 45 | return; 46 | char *buf = new char[length + 1]; 47 | std::snprintf(buf, length + 1, format, args...); 48 | std::cout << buf; 49 | delete[] buf; 50 | } 51 | #define printError(...) \ 52 | do { \ 53 | printMessage("Error: "); \ 54 | printMessage(__VA_ARGS__); \ 55 | } while (0) 56 | #define printProgress(frac) \ 57 | do { \ 58 | printMessage("Progress: %g\n", frac); \ 59 | } while (0) 60 | #else 61 | #include 62 | #define printMessage printf 63 | // #define printMessageError(...) fprintf (stderr, __VA_ARGS__) 64 | #define printProgress(frac) \ 65 | do { \ 66 | printMessage("Progress: %g\n", frac); \ 67 | } while (0) 68 | #ifdef myErrorStdOut // for XCode MRIcro project, pipe errors to stdout not stderr 69 | #define printError(...) \ 70 | do { \ 71 | printMessage("Error: "); \ 72 | printMessage(__VA_ARGS__); \ 73 | } while (0) 74 | #else 75 | #define printError(...) \ 76 | do { \ 77 | fprintf(stderr, "Error: "); \ 78 | fprintf(stderr, __VA_ARGS__); \ 79 | } while (0) 80 | #endif 81 | #endif // myUseCOut 82 | // n.b. use ({}) for multi-line macros http://www.geeksforgeeks.org/multiline-macros-in-c/ 83 | // these next lines work on GCC but not _MSC_VER 84 | // #define printWarning(...) ({printMessage("Warning: "); printMessage(__VA_ARGS__);}) 85 | // #define printError(...) ({ printMessage("Error: "); printMessage(__VA_ARGS__);}) 86 | #define printWarning(...) \ 87 | do { \ 88 | printMessage("Warning: "); \ 89 | printMessage(__VA_ARGS__); \ 90 | } while (0) 91 | 92 | #endif // USING_R 93 | #endif //_R_PRINT_H_ 94 | -------------------------------------------------------------------------------- /src/dcm2niix/nifti1_io_core.h: -------------------------------------------------------------------------------- 1 | // this minimal set of nifti routines is based on nifti1_io without the dependencies (zlib) and a few extra functions 2 | // http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.h 3 | // http://niftilib.sourceforge.net 4 | #ifndef _NIFTI_IO_CORE_HEADER_ 5 | #define _NIFTI_IO_CORE_HEADER_ 6 | 7 | #ifdef USING_R 8 | #define STRICT_R_HEADERS 9 | #include "RNifti.h" 10 | #include 11 | #else 12 | #include "nifti1.h" 13 | #include 14 | #endif 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | #include //requires VS 2015 or later 21 | 22 | #include 23 | 24 | #ifndef USING_R 25 | typedef struct { /** 3x3 matrix struct **/ 26 | float m[3][3]; 27 | } mat33; 28 | typedef struct { /** 4x4 matrix struct **/ 29 | float m[4][4]; 30 | } mat44; 31 | #endif 32 | typedef struct { /** x4 vector struct **/ 33 | float v[4]; 34 | } vec4; 35 | typedef struct { /** x3 vector struct **/ 36 | float v[3]; 37 | } vec3; 38 | typedef struct { /** x4 vector struct INTEGER**/ 39 | int v[3]; 40 | } ivec3; 41 | 42 | #define LOAD_MAT33(AA, a11, a12, a13, a21, a22, a23, a31, a32, a33) \ 43 | (AA.m[0][0] = a11, AA.m[0][1] = a12, AA.m[0][2] = a13, \ 44 | AA.m[1][0] = a21, AA.m[1][1] = a22, AA.m[1][2] = a23, \ 45 | AA.m[2][0] = a31, AA.m[2][1] = a32, AA.m[2][2] = a33) 46 | 47 | #define LOAD_MAT44(AA, a11, a12, a13, a14, a21, a22, a23, a24, a31, a32, a33, a34) \ 48 | (AA.m[0][0] = a11, AA.m[0][1] = a12, AA.m[0][2] = a13, AA.m[0][3] = a14, \ 49 | AA.m[1][0] = a21, AA.m[1][1] = a22, AA.m[1][2] = a23, AA.m[1][3] = a24, \ 50 | AA.m[2][0] = a31, AA.m[2][1] = a32, AA.m[2][2] = a33, AA.m[2][3] = a34, \ 51 | AA.m[3][0] = AA.m[3][1] = AA.m[3][2] = 0.0f, AA.m[3][3] = 1.0f) 52 | 53 | #undef ASSIF // assign v to *p, if possible 54 | #define ASSIF(p, v) \ 55 | if ((p) != NULL) \ 56 | *(p) = (v) 57 | float dotProduct(vec3 u, vec3 v); 58 | float nifti_mat33_determ(mat33 R); 59 | int isSameFloat(float a, float b); 60 | int isSameDouble(double a, double b); 61 | bool littleEndianPlatform(); 62 | 63 | vec3 nifti_mat33_eig3(double bxx, double bxy, double bxz, double byy, double byz, double bzz); 64 | mat33 nifti_mat33_inverse(mat33 R); 65 | mat33 nifti_mat33_mul(mat33 A, mat33 B); 66 | mat33 nifti_mat33_transpose(mat33 A); 67 | mat44 nifti_dicom2mat(float orient[7], float patientPosition[4], float xyzMM[4]); 68 | mat44 nifti_mat44_inverse(mat44 R); 69 | mat44 nifti_mat44_mul(mat44 A, mat44 B); 70 | vec3 crossProduct(vec3 u, vec3 v); 71 | vec3 nifti_vect33_norm(vec3 v); 72 | vec4 nifti_vect44_norm(vec4 v); 73 | vec3 nifti_vect33mat33_mul(vec3 v, mat33 m); 74 | ivec3 setiVec3(int x, int y, int z); 75 | vec3 setVec3(float x, float y, float z); 76 | vec4 setVec4(float x, float y, float z); 77 | #ifndef USING_R 78 | #ifndef USING_MGH_NIFTI_IO 79 | // This declaration differs from the equivalent function in the current nifti1_io.h, so avoid the clash 80 | void swap_nifti_header(struct nifti_1_header *h); 81 | #else 82 | void swap_nifti_header(struct nifti_1_header *h, int is_nifti); 83 | #endif 84 | #endif 85 | vec4 nifti_vect44mat44_mul(vec4 v, mat44 m); 86 | void nifti_swap_2bytes(size_t n, void *ar); // 2 bytes at a time 87 | void nifti_swap_4bytes(size_t n, void *ar); // 4 bytes at a time 88 | void nifti_swap_8bytes(size_t n, void *ar); // 8 bytes at a time 89 | void nifti_mat44_to_quatern(mat44 R, 90 | float *qb, float *qc, float *qd, 91 | float *qx, float *qy, float *qz, 92 | float *dx, float *dy, float *dz, float *qfac); 93 | mat44 nifti_quatern_to_mat44(float qb, float qc, float qd, 94 | float qx, float qy, float qz, 95 | float dx, float dy, float dz, float qfac); 96 | 97 | #ifdef __cplusplus 98 | } 99 | #endif 100 | 101 | #endif /* _NIFTI_IO_CORE_HEADER_ */ 102 | -------------------------------------------------------------------------------- /src/dcm2niix/nii_dicom_batch.h: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | 4 | #ifndef MRIpro_nii_batch_h 5 | #define MRIpro_nii_batch_h 6 | 7 | #ifdef USING_DCM2NIIXFSWRAPPER 8 | #include "nifti1.h" 9 | #include "nii_dicom.h" 10 | #include 11 | 12 | struct MRIFSSTRUCT { 13 | struct nifti_1_header hdr0; 14 | 15 | size_t imgsz; 16 | unsigned char *imgM; 17 | 18 | char pulseSequenceDetails[kDICOMStr]; 19 | struct TDICOMdata tdicomData; 20 | char namePostFixes[256]; 21 | char *dicomfile; 22 | 23 | int nDcm; 24 | char **dicomlst; 25 | 26 | struct TDTI *tdti; 27 | int numDti; 28 | }; 29 | 30 | MRIFSSTRUCT *nii_getMrifsStruct(); 31 | void nii_clrMrifsStruct(); 32 | 33 | std::vector *nii_getMrifsStructVector(); 34 | void nii_clrMrifsStructVector(); 35 | 36 | void dcmListDump(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts); 37 | #endif 38 | 39 | #ifdef __cplusplus 40 | extern "C" { 41 | #endif 42 | 43 | #include //requires VS 2015 or later 44 | #include 45 | #ifndef USING_R 46 | #include "nifti1.h" 47 | #endif 48 | #include "nii_dicom.h" 49 | 50 | #ifdef USING_R 51 | struct TDicomSeries { 52 | std::string name; 53 | TDICOMdata representativeData; 54 | std::vector files; 55 | }; 56 | #endif 57 | 58 | #define kNAME_CONFLICT_SKIP 0 // 0 = write nothing for a file that exists with desired name 59 | #define kNAME_CONFLICT_OVERWRITE 1 // 1 = overwrite existing file with same name 60 | #define kNAME_CONFLICT_ADD_SUFFIX 2 // default 2 = write with new suffix as a new file 61 | 62 | #define kMaximize16BitRange_False 0 // e.g. raw UINT16 values 0..4095 saved as INT16 (e.g. AFNI preserves INT16 "short", converts UINT16 to float32) 63 | #define kMaximize16BitRange_True 1 // e.g. raw UINT16 values 0..4095 saved as 0..61425 UINT16 (SPM free precision) 64 | #define kMaximize16BitRange_Raw 2 // e.g. raw UINT16 values 0..4095 saved as UINT16 (retains raw data type, AFNI would convert to float32) 65 | 66 | #define kSaveFormatNIfTI 0 67 | #define kSaveFormatNRRD 1 68 | #define kSaveFormatMGH 2 69 | #define kSaveFormatJNII 3 70 | #define kSaveFormatBNII 4 71 | 72 | #define MAX_NUM_SERIES 16 73 | #define kOptsStr 512 74 | 75 | struct TDCMopts { 76 | bool isDumpNotConvert; 77 | bool isIgnoreTriggerTimes, isTestx0021x105E, isAddNamePostFixes, isSaveNativeEndian, isOneDirAtATime, isRenameNotConvert, isSave3D, isGz, isPipedGz, isFlipY, isCreateBIDS, isSortDTIbyBVal, isAnonymizeBIDS, isOnlyBIDS, isCreateText, isForceOnsetTimes, isIgnoreDerivedAnd2D, isPhilipsFloatNotDisplayScaling, isTiltCorrect, isRGBplanar, isOnlySingleFile, isForceStackDCE, isIgnoreSeriesInstanceUID, isRotate3DAcq, isCrop, isGuessBidsFilename; 78 | int saveFormat, isMaximize16BitRange, isForceStackSameSeries, nameConflictBehavior, isVerbose, isProgress, compressFlag, dirSearchDepth, onlySearchDirForDICOM, gzLevel, diffCyclingModeGE; // support for compressed data 0=none, 79 | char filename[kOptsStr], outdir[kOptsStr], indir[kOptsStr], pigzname[kOptsStr], optsname[kOptsStr], indirParent[kOptsStr], imageComments[24], bidsSubject[kOptsStr], bidsSession[kOptsStr]; 80 | double seriesNumber[MAX_NUM_SERIES]; // requires double must store -1 (report but do not convert) as well as seriesUidCrc (uint32) 81 | long numSeries; 82 | #ifdef USING_R 83 | bool isScanOnly, isImageInMemory; 84 | void *imageList; 85 | std::vector series; 86 | 87 | // Used when sorting a directory 88 | std::vector sourcePaths; 89 | std::vector targetPaths; 90 | std::vector ignoredPaths; 91 | #endif 92 | }; 93 | void saveIniFile(struct TDCMopts opts); 94 | void setDefaultOpts(struct TDCMopts *opts, const char *argv[]); // either "setDefaultOpts(opts,NULL)" or "setDefaultOpts(opts,argv)" where argv[0] is path to search 95 | void readIniFile(struct TDCMopts *opts, const char *argv[]); 96 | int nii_saveNIIx(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts); 97 | int nii_loadDir(struct TDCMopts *opts); 98 | int nii_loadDirCore(char *indir, struct TDCMopts *opts); 99 | void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char *filename); 100 | int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts opts); 101 | void nii_createDummyFilename(char *niiFilename, struct TDCMopts opts); 102 | #ifdef __cplusplus 103 | } 104 | #endif 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | ```{r, echo=FALSE} 2 | knitr::opts_chunk$set(collapse = TRUE) 3 | ``` 4 | 5 | [![CRAN version](https://www.r-pkg.org/badges/version/divest)](https://cran.r-project.org/package=divest) [![CI Status](https://github.com/jonclayden/divest/actions/workflows/ci.yaml/badge.svg)](https://github.com/jonclayden/divest/actions/workflows/ci.yaml) [![codecov](https://codecov.io/gh/jonclayden/divest/graph/badge.svg?token=515zW7eMSl)](https://app.codecov.io/gh/jonclayden/divest) [![Dependencies](https://tinyverse.netlify.app/badge/divest)](https://tinyverse.netlify.app) 6 | 7 | # An R interface to dcm2niix 8 | 9 | [DICOM](https://www.dicomstandard.org), for Digital Imaging and Communications in Medicine, is the highly complex standard by which medical imaging devices such as magnetic resonance (MR) and computed tomography (CT) scanners communicate. Importantly for medical imaging research, DICOM defines the format in which images are first created when a subject is scanned. The complexity of DICOM, and the high degree of variation in how it is implemented by hardware vendors, makes it difficult and error-prone to work with. The NIfTI-1 file format has emerged as a simpler, more interoperable standard for medical images, and generally researchers want to convert their images to this format [as soon as possible](https://doi.org/10.1016/j.jneumeth.2016.03.001). 10 | 11 | > *divest*, **v.**: rid oneself of something that one no longer wants or requires 12 | 13 | The `divest` package is an alternative interface to Chris Rorden's excellent [`dcm2niix` DICOM-to-NIfTI conversion tool](https://github.com/rordenlab/dcm2niix). Code has been contributed to `dcm2niix` to support an in-memory interface that links that tool's speed and reliability to the R-native NIfTI tools provided by the [`RNifti` package](https://github.com/jonclayden/RNifti). 14 | 15 | The package is [on CRAN](https://cran.r-project.org/package=divest), and the latest development version of the package can always be installed from GitHub using the `remotes` package. 16 | 17 | ```{r, eval=FALSE} 18 | # install.packages("remotes") 19 | remotes::install_github("jonclayden/divest") 20 | ``` 21 | 22 | **Please note that, like `dcm2niix`, the `divest` package is to be used for research purposes only, and is not a clinical tool. It comes with no warranty.** 23 | 24 | ## Usage 25 | 26 | The package's key function is `readDicom`, which scans a directory containing DICOM files, stacks related data into merged 3D or 4D images where appropriate, and returns a list of `niftiImage` objects. For example, 27 | 28 | ```{r, results="hide"} 29 | library(divest) 30 | path <- system.file("extdata", "raw", package="divest") 31 | images <- readDicom(path, interactive=FALSE, verbosity=-1) 32 | ``` 33 | 34 | The conversion is interactive by default, prompting the user to select which series to convert, but here we simply convert everything non-interactively. The minimal test dataset provided with the package contains two images from each of two acquisitions. (It is incomplete, hence the warnings.) We can see the basic properties of a converted composite image by printing it. 35 | 36 | ```{r} 37 | # Extract the image with a fourth dimension 38 | i <- which(sapply(images, RNifti::ndim) == 4) 39 | images[[i]] 40 | ``` 41 | 42 | Additional properties of the scanning sequence, such as the magnetic field strength used, are stored in attributes if they can be deduced from the DICOM files. 43 | 44 | ```{r} 45 | imageAttributes(images[[i]]) 46 | ``` 47 | 48 | If desired, functions from the `RNifti` package can be used to inspect and modify the details of the converted NIfTI image, or to write it to file. 49 | 50 | ```{r} 51 | library(RNifti) 52 | niftiHeader(images[[i]]) 53 | ``` 54 | ```{r, eval=FALSE} 55 | writeNifti(images[[i]], "stack") 56 | ``` 57 | 58 | It is also possible to obtain information about the available DICOM series without actually performing the conversion. The `scanDicom` function returns a data frame containing certain information about each series. 59 | 60 | ```{r} 61 | names(scanDicom(path)) 62 | ``` 63 | 64 | Elements of this data frame which can't be determined from the DICOM metadata, for example due to anonymisation, will take the conventional `NA` value to indicate missing data. 65 | 66 | DICOM files can be converted to NIfTI files on disk, rather than in memory, by using `convertDicom` rather than `readDicom` (or just setting the `output` option): 67 | 68 | ```{r, results="hide"} 69 | paths <- convertDicom(path, output=".", interactive=FALSE, verbosity=-1) 70 | ``` 71 | ```{r} 72 | list.files(pattern="\\.nii") 73 | ``` 74 | -------------------------------------------------------------------------------- /man/readDicom.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/read.R 3 | \name{readDicom} 4 | \alias{readDicom} 5 | \alias{convertDicom} 6 | \alias{sortDicom} 7 | \alias{scanDicom} 8 | \title{Read one or more DICOM directories} 9 | \usage{ 10 | readDicom(path = ".", subset = NULL, flipY = TRUE, crop = FALSE, 11 | forceStack = FALSE, verbosity = 0L, labelFormat = "T\%t_N\%n_S\%s", 12 | depth = 5L, interactive = base::interactive(), output = NULL) 13 | 14 | convertDicom(path = ".", subset = NULL, flipY = TRUE, crop = FALSE, 15 | forceStack = FALSE, verbosity = 0L, labelFormat = "T\%t_N\%n_S\%s", 16 | depth = 5L, interactive = base::interactive(), output = path) 17 | 18 | sortDicom(path = ".", forceStack = FALSE, verbosity = 0L, 19 | labelFormat = "T\%t_N\%n_S\%s/\%b", depth = 5L, nested = NA, 20 | keepUnsorted = FALSE, output = path) 21 | 22 | scanDicom(path = ".", forceStack = FALSE, verbosity = 0L, 23 | labelFormat = "T\%t_N\%n_S\%s", depth = 5L) 24 | } 25 | \arguments{ 26 | \item{path}{A character vector of paths to scan for DICOM files. Each will 27 | examined in turn. The default is the current working directory. 28 | \code{readDicom} (only) will accept paths to individual DICOM files, 29 | rather than directories. Alternatively, for \code{readDicom} and 30 | \code{sortDicom}, a data frame like the one returned by \code{scanDicom}, 31 | from which file paths will be read.} 32 | 33 | \item{subset}{If \code{path} is a data frame, an expression which will be 34 | evaluated in the context of the data frame to determine which series to 35 | convert. Should evaluate to a logical vector. If \code{path} is a 36 | character vector, \code{scanDicom} is called on the path(s) first to 37 | produce the data frame. If this is specified, and does not evaluate to 38 | \code{NULL}, the read will be noninteractive, irrespective of the value of 39 | the \code{interactive} argument.} 40 | 41 | \item{flipY}{If \code{TRUE}, the default, then images will be flipped in the 42 | Y-axis. This is usually desirable, given the difference between 43 | orientation conventions in the DICOM and NIfTI-1 formats.} 44 | 45 | \item{crop}{If \code{TRUE}, then \code{dcm2niix} will attempt to crop excess 46 | neck slices from brain images.} 47 | 48 | \item{forceStack}{If \code{TRUE}, images with the same series number will 49 | always be stacked together as long as their dimensions are compatible. If 50 | \code{FALSE}, the default, images will be separated if they differ in 51 | echo, coil or exposure number, echo time, protocol name or orientation.} 52 | 53 | \item{verbosity}{Integer value between -2 and 3, controlling the amount of 54 | output generated during the conversion. A value of -1 will suppress all 55 | output from \code{dcm2niix} except warnings and errors; -2 also suppresses 56 | warnings.} 57 | 58 | \item{labelFormat}{A \code{\link{sprintf}}-style string specifying the 59 | format to use for the final image labels or paths. See Details.} 60 | 61 | \item{depth}{The maximum subdirectory depth in which to search for DICOM 62 | files, relative to each \code{path}.} 63 | 64 | \item{interactive}{If \code{TRUE}, the default in interactive sessions, the 65 | requested paths will first be scanned and a list of DICOM series will be 66 | presented. You may then choose which series to convert.} 67 | 68 | \item{output}{The directory to write converted or copied NIfTI files to, or 69 | \code{NULL}. In the latter case, which isn't valid for \code{sortDicom}, 70 | images are converted in memory and returned as R objects.} 71 | 72 | \item{nested}{For \code{sortDicom}, should the sorted files be created 73 | within the source directory (\code{TRUE}), or in the current working 74 | directory (\code{FALSE})? Now soft-deprecated in favour of \code{output}, 75 | which is more flexible.} 76 | 77 | \item{keepUnsorted}{For \code{sortDicom}, should the unsorted files be left 78 | in place, or removed after they are copied into their new locations? The 79 | default, \code{FALSE}, corresponds to a move rather than a copy. If 80 | creating new files fails then the old ones will not be deleted.} 81 | } 82 | \value{ 83 | \code{readDicom} and \code{convertDicom} return a list of 84 | \code{niftiImage} objects if \code{output} is \code{NULL}; otherwise 85 | (invisibly) a vector of paths to NIfTI-1 files created in the target 86 | directory. Returned images typically have attributes containing additional 87 | metadata extracted from the DICOM headers, either in a JSON string or (if 88 | the \code{jsonlite} package is available), in individually parsed 89 | elements. The \code{scanDicom} function returns a data frame containing 90 | information about each DICOM series found. \code{sortDicom} is mostly 91 | called for its side-effect, but also (invisibly) returns a list detailing 92 | source and target paths. 93 | } 94 | \description{ 95 | These functions are R wrappers around the DICOM-to-NIfTI conversion routines 96 | provided by \code{dcm2niix}. They scan directories containing DICOM files, 97 | potentially pertaining to more than one image series, read them and/or merge 98 | them into a list of \code{niftiImage} objects. 99 | } 100 | \details{ 101 | The \code{scanDicom} function parses directories full of DICOM files and 102 | returns information about the acquisition series they contain. 103 | \code{readDicom} reads these files and converts them to (internal) NIfTI 104 | images (whose pixel data can be extracted using \code{as.array}). 105 | \code{convertDicom} performs the same conversion but writes to NIfTI files 106 | by default, instead of retaining the images in memory. \code{sortDicom} 107 | renames the files, but does not convert them. 108 | 109 | The \code{labelFormat} argument describes the string format used for image 110 | labels and sorted files. Valid codes, each escaped with a percentage sign, 111 | include \code{a} for coil number, \code{b} for the source file base name, 112 | \code{c} for image comments, \code{d} for series description, \code{e} for 113 | echo number, \code{f} for the source directory, \code{i} for patient ID, 114 | \code{j} for the series instance UID, \code{k} for the study instance UID, 115 | \code{l} for the procedure step description, \code{m} for manufacturer, 116 | \code{n} for patient name, \code{p} for protocol name, \code{q} for 117 | scanning sequence, \code{r} for instance number, \code{s} for series number, 118 | \code{t} for the date and time, \code{u} for acquisition number, \code{v} 119 | for vendor, \code{x} for study ID and \code{z} for sequence name. For 120 | \code{sortDicom} the label forms the new file path, and may include one or 121 | more slashes to create subdirectories. A ".dcm" suffix will be added to file 122 | names if no extension is specified. 123 | } 124 | \examples{ 125 | path <- system.file("extdata", "raw", package="divest") 126 | scanDicom(path) 127 | readDicom(path, interactive=FALSE) 128 | } 129 | \author{ 130 | Jon Clayden \href{mailto:code@clayden.org}{code@clayden.org} 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![CRAN version](https://www.r-pkg.org/badges/version/divest)](https://cran.r-project.org/package=divest) [![CI Status](https://github.com/jonclayden/divest/actions/workflows/ci.yaml/badge.svg)](https://github.com/jonclayden/divest/actions/workflows/ci.yaml) [![codecov](https://codecov.io/gh/jonclayden/divest/graph/badge.svg?token=515zW7eMSl)](https://app.codecov.io/gh/jonclayden/divest) [![Dependencies](https://tinyverse.netlify.app/badge/divest)](https://tinyverse.netlify.app) 4 | 5 | # An R interface to dcm2niix 6 | 7 | [DICOM](https://www.dicomstandard.org), for Digital Imaging and Communications in Medicine, is the highly complex standard by which medical imaging devices such as magnetic resonance (MR) and computed tomography (CT) scanners communicate. Importantly for medical imaging research, DICOM defines the format in which images are first created when a subject is scanned. The complexity of DICOM, and the high degree of variation in how it is implemented by hardware vendors, makes it difficult and error-prone to work with. The NIfTI-1 file format has emerged as a simpler, more interoperable standard for medical images, and generally researchers want to convert their images to this format [as soon as possible](https://doi.org/10.1016/j.jneumeth.2016.03.001). 8 | 9 | > *divest*, **v.**: rid oneself of something that one no longer wants or requires 10 | 11 | The `divest` package is an alternative interface to Chris Rorden's excellent [`dcm2niix` DICOM-to-NIfTI conversion tool](https://github.com/rordenlab/dcm2niix). Code has been contributed to `dcm2niix` to support an in-memory interface that links that tool's speed and reliability to the R-native NIfTI tools provided by the [`RNifti` package](https://github.com/jonclayden/RNifti). 12 | 13 | The package is [on CRAN](https://cran.r-project.org/package=divest), and the latest development version of the package can always be installed from GitHub using the `remotes` package. 14 | 15 | 16 | ``` r 17 | # install.packages("remotes") 18 | remotes::install_github("jonclayden/divest") 19 | ``` 20 | 21 | **Please note that, like `dcm2niix`, the `divest` package is to be used for research purposes only, and is not a clinical tool. It comes with no warranty.** 22 | 23 | ## Usage 24 | 25 | The package's key function is `readDicom`, which scans a directory containing DICOM files, stacks related data into merged 3D or 4D images where appropriate, and returns a list of `niftiImage` objects. For example, 26 | 27 | 28 | ``` r 29 | library(divest) 30 | path <- system.file("extdata", "raw", package="divest") 31 | images <- readDicom(path, interactive=FALSE, verbosity=-1) 32 | ``` 33 | 34 | The conversion is interactive by default, prompting the user to select which series to convert, but here we simply convert everything non-interactively. The minimal test dataset provided with the package contains two images from each of two acquisitions. (It is incomplete, hence the warnings.) We can see the basic properties of a converted composite image by printing it. 35 | 36 | 37 | ``` r 38 | # Extract the image with a fourth dimension 39 | i <- which(sapply(images, RNifti::ndim) == 4) 40 | images[[i]] 41 | ## Internal image: "T0_N_S8" 42 | ## - 96 x 96 x 1 x 2 voxels 43 | ## - 2.5 x 2.5 x 5 mm x 4.1 s per voxel 44 | ``` 45 | 46 | Additional properties of the scanning sequence, such as the magnetic field strength used, are stored in attributes if they can be deduced from the DICOM files. 47 | 48 | 49 | ``` r 50 | imageAttributes(images[[i]]) 51 | ## $bValues 52 | ## [1] 2000 2000 53 | ## 54 | ## $bVectors 55 | ## [,1] [,2] [,3] 56 | ## [1,] 0.5508 0.4258 0.7177 57 | ## [2,] 0.1110 0.2640 0.9581 58 | ## 59 | ## $modality 60 | ## [1] "MR" 61 | ## 62 | ## $fieldStrength 63 | ## [1] 1.494 64 | ## 65 | ## $imagingFrequency 66 | ## [1] 63.68285 67 | ## 68 | ## $patientPosition 69 | ## [1] "HFS" 70 | ## 71 | ## $MRAcquisitionType 72 | ## [1] "2D" 73 | ## 74 | ## $seriesDescription 75 | ## [1] "DTIb3000s5" 76 | ## 77 | ## $protocolName 78 | ## [1] "DTIb3000s5" 79 | ## 80 | ## $scanningSequence 81 | ## [1] "SE\\EP" 82 | ## 83 | ## $sequenceVariant 84 | ## [1] "SK\\SP" 85 | ## 86 | ## $scanOptions 87 | ## [1] "FS" 88 | ## 89 | ## $sequenceName 90 | ## [1] "ep_b2000#16" 91 | ## 92 | ## $imageType 93 | ## [1] "ORIGINAL" "PRIMARY" "M" "ND" "NORM" 94 | ## 95 | ## $nonlinearGradientCorrection 96 | ## [1] FALSE 97 | ## 98 | ## $seriesNumber 99 | ## [1] 8 100 | ## 101 | ## $acquisitionNumber 102 | ## [1] 1 103 | ## 104 | ## $sliceThickness 105 | ## [1] 5 106 | ## 107 | ## $sliceSpacing 108 | ## [1] 5 109 | ## 110 | ## $SAR 111 | ## [1] 0.0973325 112 | ## 113 | ## $numberOfAverages 114 | ## [1] 2 115 | ## 116 | ## $echoTime 117 | ## [1] 112 118 | ## 119 | ## $repetitionTime 120 | ## [1] 4100 121 | ## 122 | ## $spoilingState 123 | ## [1] TRUE 124 | ## 125 | ## $flipAngle 126 | ## [1] 90 127 | ## 128 | ## $percentPhaseFOV 129 | ## [1] 100 130 | ## 131 | ## $percentSampling 132 | ## [1] 100 133 | ## 134 | ## $phaseEncodingSteps 135 | ## [1] 72 136 | ## 137 | ## $acquisitionMatrixPE 138 | ## [1] 96 139 | ## 140 | ## $reconMatrixPE 141 | ## [1] 96 142 | ## 143 | ## $pixelBandwidth 144 | ## [1] 900 145 | ## 146 | ## $phaseEncodingDirection 147 | ## [1] "j" 148 | ## 149 | ## $phaseEncodingSign 150 | ## [1] -1 151 | ## 152 | ## $imageOrientationPatientDICOM 153 | ## [1] 1 0 0 0 1 0 154 | ## 155 | ## $inPlanePhaseEncodingDirectionDICOM 156 | ## [1] "COL" 157 | ## 158 | ## $conversionSoftware 159 | ## [1] "dcm2niix" 160 | ## 161 | ## $conversionSoftwareVersion 162 | ## [1] "v1.0.20241001" 163 | ``` 164 | 165 | If desired, functions from the `RNifti` package can be used to inspect and modify the details of the converted NIfTI image, or to write it to file. 166 | 167 | 168 | ``` r 169 | library(RNifti) 170 | niftiHeader(images[[i]]) 171 | ## NIfTI-1 header 172 | ## sizeof_hdr: 348 173 | ## dim_info: 57 174 | ## dim: 4 96 96 1 2 1 1 1 175 | ## intent_p1: 0 176 | ## intent_p2: 0 177 | ## intent_p3: 0 178 | ## intent_code: 0 (Unknown) 179 | ## datatype: 4 (INT16) 180 | ## bitpix: 16 181 | ## slice_start: 0 182 | ## pixdim: -1.0 2.5 2.5 5.0 4.1 0.0 0.0 0.0 183 | ## vox_offset: 352 184 | ## scl_slope: 1 185 | ## scl_inter: 0 186 | ## slice_end: 0 187 | ## slice_code: 0 (Unknown) 188 | ## xyzt_units: 10 189 | ## cal_max: 0 190 | ## cal_min: 0 191 | ## slice_duration: 0 192 | ## toffset: 0 193 | ## descrip: TE=1.1e+02;Time=0.000;phase=1 194 | ## aux_file: 195 | ## qform_code: 1 (Scanner Anat) 196 | ## sform_code: 1 (Scanner Anat) 197 | ## quatern_b: 0 198 | ## quatern_c: 1 199 | ## quatern_d: 0 200 | ## qoffset_x: 122.0339 201 | ## qoffset_y: -101.2288 202 | ## qoffset_z: -55.42373 203 | ## srow_x: -2.5000 0.0000 0.0000 122.0339 204 | ## srow_y: 0.0000 2.5000 0.0000 -101.2288 205 | ## srow_z: 0.00000 0.00000 5.00000 -55.42373 206 | ## intent_name: 207 | ## magic: n+1 208 | ``` 209 | 210 | ``` r 211 | writeNifti(images[[i]], "stack") 212 | ``` 213 | 214 | It is also possible to obtain information about the available DICOM series without actually performing the conversion. The `scanDicom` function returns a data frame containing certain information about each series. 215 | 216 | 217 | ``` r 218 | names(scanDicom(path)) 219 | ## [dcm2niix info] Found 4 DICOM file(s) 220 | ## [1] "label" "rootPath" "files" 221 | ## [4] "seriesNumber" "seriesDescription" "patientName" 222 | ## [7] "studyDate" "echoTime" "repetitionTime" 223 | ## [10] "echoNumber" "phase" "diffusion" 224 | ``` 225 | 226 | Elements of this data frame which can't be determined from the DICOM metadata, for example due to anonymisation, will take the conventional `NA` value to indicate missing data. 227 | 228 | DICOM files can be converted to NIfTI files on disk, rather than in memory, by using `convertDicom` rather than `readDicom` (or just setting the `output` option): 229 | 230 | 231 | ``` r 232 | paths <- convertDicom(path, output=".", interactive=FALSE, verbosity=-1) 233 | ``` 234 | 235 | ``` r 236 | list.files(pattern="\\.nii") 237 | ## [1] "T0_N_S8.nii.gz" "T0_N_S9.nii.gz" 238 | ``` 239 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "RNifti.h" 4 | #include "RNiftiAPI.h" 5 | 6 | #include "ImageList.h" 7 | #include "nii_dicom_batch.h" 8 | #include "nii_dicom.h" 9 | 10 | using namespace Rcpp; 11 | 12 | bool isEmptyString (const char *str) 13 | { 14 | if (strlen(str) == 0) 15 | return true; 16 | 17 | const char *ptr = str; 18 | while (*ptr != '\0') 19 | { 20 | // Strings composed entirely of spaces are considered empty 21 | if (*ptr != ' ') 22 | return false; 23 | ptr++; 24 | } 25 | return true; 26 | } 27 | 28 | RcppExport SEXP readDirectory (SEXP path_, SEXP flipY_, SEXP crop_, SEXP forceStack_, SEXP verbosity_, SEXP labelFormat_, SEXP singleFile_, SEXP depth_, SEXP task_, SEXP outputDir_) 29 | { 30 | BEGIN_RCPP 31 | const std::string path = as(path_); 32 | const std::string labelFormat = as(labelFormat_); 33 | const std::string task = as(task_); 34 | 35 | const bool willConvert = (task == "read" || task == "convert"); 36 | 37 | TDCMopts options; 38 | setDefaultOpts(&options, NULL); 39 | options.isGz = true; 40 | options.gzLevel = 6; 41 | options.isFlipY = as(flipY_); 42 | options.isCreateBIDS = willConvert; 43 | options.isImageInMemory = (task == "read"); 44 | options.isCreateText = false; 45 | options.isSortDTIbyBVal = false; 46 | options.isForceStackSameSeries = as(forceStack_); 47 | options.isCrop = as(crop_); 48 | options.isOnlySingleFile = as(singleFile_); 49 | options.isScanOnly = (task == "scan"); 50 | options.isRenameNotConvert = (task == "sort"); 51 | options.isVerbose = as(verbosity_); 52 | options.dirSearchDepth = as(depth_); 53 | options.compressFlag = kCompressYes; 54 | strcpy(options.indir, path.c_str()); 55 | strcpy(options.filename, labelFormat.c_str()); 56 | if (!Rf_isNull(outputDir_)) 57 | { 58 | const std::string outputDir = as(outputDir_); 59 | strcpy(options.outdir, outputDir.c_str()); 60 | } 61 | 62 | ImageList images; 63 | options.imageList = (void *) &images; 64 | 65 | // Scan the directory of interest, and create NiftiImage objects if required 66 | int returnValue = nii_loadDir(&options); 67 | if (returnValue == EXIT_SUCCESS || returnValue == kEXIT_SOME_OK_SOME_BAD) 68 | { 69 | if (options.isScanOnly) 70 | { 71 | // Construct a data frame containing information about each series 72 | // A vector of descriptive strings is also built, and attached as an attribute 73 | const size_t n = options.series.size(); 74 | CharacterVector label(n,NA_STRING), seriesDescription(n,NA_STRING), patientName(n,NA_STRING), descriptions(n); 75 | DateVector studyDate(n); 76 | NumericVector echoTime(n,NA_REAL), repetitionTime(n,NA_REAL); 77 | IntegerVector files(n,NA_INTEGER), seriesNumber(n,NA_INTEGER), echoNumber(n,NA_INTEGER); 78 | LogicalVector phase(n,NA_LOGICAL), diffusion(n,false); 79 | List paths(n); 80 | for (size_t i = 0; i < n; i++) 81 | { 82 | const TDICOMdata &data = options.series[i].representativeData; 83 | std::ostringstream description; 84 | description << "Series " << data.seriesNum; 85 | seriesNumber[i] = data.seriesNum; 86 | if (!isEmptyString(data.seriesDescription)) 87 | { 88 | description << " \"" << data.seriesDescription << "\""; 89 | seriesDescription[i] = data.seriesDescription; 90 | } 91 | else if (!isEmptyString(data.sequenceName)) 92 | description << " \"" << data.sequenceName << "\""; 93 | else if (!isEmptyString(data.protocolName)) 94 | description << " \"" << data.protocolName << "\""; 95 | if (!isEmptyString(data.patientName)) 96 | { 97 | description << ", patient \"" << data.patientName << "\""; 98 | patientName[i] = data.patientName; 99 | } 100 | if (strlen(data.studyDate) >= 8 && strncmp(data.studyDate,"00000000",8) != 0) 101 | { 102 | description << ", acquired on " << std::string(data.studyDate,4) << "-" << std::string(data.studyDate+4,2) << "-" << std::string(data.studyDate+6,2); 103 | studyDate[i] = Date(data.studyDate, "%Y%m%d"); 104 | } 105 | else 106 | studyDate[i] = Date(NA_REAL); 107 | if (data.TE > 0.0) 108 | { 109 | description << ", TE " << data.TE << " ms"; 110 | echoTime[i] = data.TE; 111 | } 112 | if (data.TR > 0.0) 113 | { 114 | description << ", TR " << data.TR << " ms"; 115 | repetitionTime[i] = data.TR; 116 | } 117 | if (data.echoNum > 0) 118 | echoNumber[i] = data.echoNum; 119 | if (data.echoNum > 1) 120 | description << ", echo " << data.echoNum; 121 | if (data.isHasPhase) 122 | description << ", phase"; 123 | if (data.CSA.numDti > 0) 124 | diffusion[i] = true; 125 | 126 | // The name is stored with leading path components, which we remove here 127 | if (options.series[i].name.length() > 0) 128 | { 129 | #if defined(_WIN32) || defined(_WIN64) 130 | size_t pathSeparator = options.series[i].name.find_last_of("\\/"); 131 | #else 132 | size_t pathSeparator = options.series[i].name.find_last_of('/'); 133 | #endif 134 | if (pathSeparator == std::string::npos) 135 | label[i] = options.series[i].name; 136 | else 137 | label[i] = options.series[i].name.substr(pathSeparator+1); 138 | } 139 | 140 | phase[i] = data.isHasPhase; 141 | descriptions[i] = description.str(); 142 | files[i] = options.series[i].files.size(); 143 | paths[i] = wrap(options.series[i].files); 144 | } 145 | 146 | DataFrame info = DataFrame::create(Named("label")=label, Named("rootPath")=path, Named("files")=files, Named("seriesNumber")=seriesNumber, Named("seriesDescription")=seriesDescription, Named("patientName")=patientName, Named("studyDate")=studyDate, Named("echoTime")=echoTime, Named("repetitionTime")=repetitionTime, Named("echoNumber")=echoNumber, Named("phase")=phase, Named("diffusion")=diffusion, Named("stringsAsFactors")=false); 147 | info.attr("descriptions") = descriptions; 148 | info.attr("paths") = paths; 149 | info.attr("class") = CharacterVector::create("divestListing","data.frame"); 150 | return info; 151 | } 152 | else if (options.isRenameNotConvert) 153 | return List::create(Named("source")=options.sourcePaths, Named("target")=options.targetPaths, Named("ignored")=options.ignoredPaths); 154 | else if (!options.isImageInMemory) 155 | return images.pathVector(); 156 | else 157 | return images; 158 | } 159 | else if (returnValue == kEXIT_NO_VALID_FILES_FOUND) 160 | Rprintf("No valid DICOM files found\n"); 161 | else 162 | Rf_error("DICOM scan failed"); 163 | END_RCPP 164 | } 165 | 166 | RcppExport SEXP getCapabilities () 167 | { 168 | BEGIN_RCPP 169 | bool jpeg = false, jpegLS = false, jpeg2000 = false; 170 | 171 | #ifndef myDisableClassicJPEG 172 | jpeg = true; 173 | #endif 174 | 175 | #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) 176 | jpegLS = true; 177 | #endif 178 | 179 | #if !defined(myDisableOpenJPEG) || defined(myEnableJasper) 180 | jpeg2000 = true; 181 | #endif 182 | 183 | return LogicalVector::create(Named("jpeg")=jpeg, Named("jpegLS")=jpegLS, Named("jpeg2000")=jpeg2000, Named("zlib")=nifti_compiled_with_zlib()); 184 | END_RCPP 185 | } 186 | 187 | static const R_CallMethodDef callMethods[] = { 188 | { "readDirectory", (DL_FUNC) &readDirectory, 10 }, 189 | { "getCapabilities", (DL_FUNC) &getCapabilities, 0 }, 190 | { NULL, NULL, 0 } 191 | }; 192 | 193 | extern "C" { 194 | 195 | void R_init_divest (DllInfo *info) 196 | { 197 | R_registerRoutines(info, NULL, callMethods, NULL, NULL); 198 | R_useDynamicSymbols(info, FALSE); 199 | R_forceSymbols(info, TRUE); 200 | 201 | niftilib_register_all(); 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /src/dcm2niix/tinydir.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013-2014, Cong Xu 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | #ifndef TINYDIR_H 26 | #define TINYDIR_H 27 | 28 | #include 29 | #include 30 | #include 31 | #ifdef _MSC_VER 32 | #define WIN32_LEAN_AND_MEAN 33 | #include 34 | #pragma warning(disable : 4996) 35 | #else 36 | #include 37 | #include 38 | #endif 39 | 40 | /* types */ 41 | 42 | #define _TINYDIR_PATH_MAX 4096 43 | #ifdef _MSC_VER 44 | /* extra chars for the "\\*" mask */ 45 | #define _TINYDIR_PATH_EXTRA 2 46 | #else 47 | #define _TINYDIR_PATH_EXTRA 0 48 | #endif 49 | #define _TINYDIR_FILENAME_MAX 256 50 | 51 | #ifdef _MSC_VER 52 | #define _TINYDIR_FUNC static __inline 53 | #else 54 | #define _TINYDIR_FUNC static __inline__ 55 | #endif 56 | 57 | typedef struct 58 | { 59 | char path[_TINYDIR_PATH_MAX]; 60 | char name[_TINYDIR_FILENAME_MAX]; 61 | int is_dir; 62 | int is_reg; 63 | 64 | #ifdef _MSC_VER 65 | #else 66 | struct stat _s; 67 | #endif 68 | } tinydir_file; 69 | 70 | typedef struct 71 | { 72 | char path[_TINYDIR_PATH_MAX]; 73 | int has_next; 74 | size_t n_files; 75 | 76 | tinydir_file *_files; 77 | #ifdef _MSC_VER 78 | HANDLE _h; 79 | WIN32_FIND_DATA _f; 80 | #else 81 | DIR *_d; 82 | struct dirent *_e; 83 | #endif 84 | } tinydir_dir; 85 | 86 | /* declarations */ 87 | 88 | _TINYDIR_FUNC 89 | int tinydir_open(tinydir_dir *dir, const char *path); 90 | _TINYDIR_FUNC 91 | int tinydir_open_sorted(tinydir_dir *dir, const char *path); 92 | _TINYDIR_FUNC 93 | void tinydir_close(tinydir_dir *dir); 94 | 95 | _TINYDIR_FUNC 96 | int tinydir_next(tinydir_dir *dir); 97 | _TINYDIR_FUNC 98 | int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file); 99 | _TINYDIR_FUNC 100 | int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i); 101 | _TINYDIR_FUNC 102 | int tinydir_open_subdir_n(tinydir_dir *dir, size_t i); 103 | 104 | _TINYDIR_FUNC 105 | int _tinydir_file_cmp(const void *a, const void *b); 106 | 107 | /* definitions*/ 108 | 109 | _TINYDIR_FUNC 110 | int tinydir_open(tinydir_dir *dir, const char *path) { 111 | if (dir == NULL || path == NULL || strlen(path) == 0) { 112 | errno = EINVAL; 113 | return -1; 114 | } 115 | if (strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) { 116 | errno = ENAMETOOLONG; 117 | return -1; 118 | } 119 | 120 | /* initialise dir */ 121 | dir->_files = NULL; 122 | #ifdef _MSC_VER 123 | dir->_h = INVALID_HANDLE_VALUE; 124 | #else 125 | dir->_d = NULL; 126 | #endif 127 | tinydir_close(dir); 128 | 129 | strcpy(dir->path, path); 130 | #ifdef _MSC_VER 131 | strcat(dir->path, "\\*"); 132 | dir->_h = FindFirstFile(dir->path, &dir->_f); 133 | dir->path[strlen(dir->path) - 2] = '\0'; 134 | if (dir->_h == INVALID_HANDLE_VALUE) 135 | #else 136 | dir->_d = opendir(path); 137 | if (dir->_d == NULL) 138 | #endif 139 | { 140 | errno = ENOENT; 141 | goto bail; 142 | } 143 | /* read first file */ 144 | dir->has_next = 1; 145 | #ifndef _MSC_VER 146 | dir->_e = readdir(dir->_d); 147 | if (dir->_e == NULL) { 148 | dir->has_next = 0; 149 | } 150 | #endif 151 | 152 | return 0; 153 | 154 | bail: 155 | tinydir_close(dir); 156 | return -1; 157 | } 158 | 159 | _TINYDIR_FUNC 160 | int tinydir_open_sorted(tinydir_dir *dir, const char *path) { 161 | /* Count the number of files first, to pre-allocate the files array */ 162 | size_t n_files = 0; 163 | if (tinydir_open(dir, path) == -1) { 164 | return -1; 165 | } 166 | while (dir->has_next) { 167 | n_files++; 168 | if (tinydir_next(dir) == -1) { 169 | goto bail; 170 | } 171 | } 172 | tinydir_close(dir); 173 | 174 | if (tinydir_open(dir, path) == -1) { 175 | return -1; 176 | } 177 | 178 | dir->n_files = 0; 179 | dir->_files = (tinydir_file *)malloc(sizeof *dir->_files * n_files); 180 | if (dir->_files == NULL) { 181 | errno = ENOMEM; 182 | goto bail; 183 | } 184 | while (dir->has_next) { 185 | tinydir_file *p_file; 186 | dir->n_files++; 187 | 188 | p_file = &dir->_files[dir->n_files - 1]; 189 | if (tinydir_readfile(dir, p_file) == -1) { 190 | goto bail; 191 | } 192 | 193 | if (tinydir_next(dir) == -1) { 194 | goto bail; 195 | } 196 | 197 | /* Just in case the number of files has changed between the first and 198 | second reads, terminate without writing into unallocated memory */ 199 | if (dir->n_files == n_files) { 200 | break; 201 | } 202 | } 203 | 204 | qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp); 205 | 206 | return 0; 207 | 208 | bail: 209 | tinydir_close(dir); 210 | return -1; 211 | } 212 | 213 | _TINYDIR_FUNC 214 | void tinydir_close(tinydir_dir *dir) { 215 | if (dir == NULL) { 216 | return; 217 | } 218 | 219 | memset(dir->path, 0, sizeof(dir->path)); 220 | dir->has_next = 0; 221 | dir->n_files = 0; 222 | if (dir->_files != NULL) { 223 | free(dir->_files); 224 | } 225 | dir->_files = NULL; 226 | #ifdef _MSC_VER 227 | if (dir->_h != INVALID_HANDLE_VALUE) { 228 | FindClose(dir->_h); 229 | } 230 | dir->_h = INVALID_HANDLE_VALUE; 231 | #else 232 | if (dir->_d) { 233 | closedir(dir->_d); 234 | } 235 | dir->_d = NULL; 236 | dir->_e = NULL; 237 | #endif 238 | } 239 | 240 | _TINYDIR_FUNC 241 | int tinydir_next(tinydir_dir *dir) { 242 | if (dir == NULL) { 243 | errno = EINVAL; 244 | return -1; 245 | } 246 | if (!dir->has_next) { 247 | errno = ENOENT; 248 | return -1; 249 | } 250 | 251 | #ifdef _MSC_VER 252 | if (FindNextFile(dir->_h, &dir->_f) == 0) 253 | #else 254 | dir->_e = readdir(dir->_d); 255 | if (dir->_e == NULL) 256 | #endif 257 | { 258 | dir->has_next = 0; 259 | #ifdef _MSC_VER 260 | if (GetLastError() != ERROR_SUCCESS && 261 | GetLastError() != ERROR_NO_MORE_FILES) { 262 | tinydir_close(dir); 263 | errno = EIO; 264 | return -1; 265 | } 266 | #endif 267 | } 268 | 269 | return 0; 270 | } 271 | 272 | _TINYDIR_FUNC 273 | int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file) { 274 | if (dir == NULL || file == NULL) { 275 | errno = EINVAL; 276 | return -1; 277 | } 278 | #ifdef _MSC_VER 279 | if (dir->_h == INVALID_HANDLE_VALUE) 280 | #else 281 | if (dir->_e == NULL) 282 | #endif 283 | { 284 | errno = ENOENT; 285 | return -1; 286 | } 287 | if (strlen(dir->path) + 288 | strlen( 289 | #ifdef _MSC_VER 290 | dir->_f.cFileName 291 | #else 292 | dir->_e->d_name 293 | #endif 294 | ) + 295 | 1 + _TINYDIR_PATH_EXTRA >= 296 | _TINYDIR_PATH_MAX) { 297 | /* the path for the file will be too long */ 298 | errno = ENAMETOOLONG; 299 | return -1; 300 | } 301 | if (strlen( 302 | #ifdef _MSC_VER 303 | dir->_f.cFileName 304 | #else 305 | dir->_e->d_name 306 | #endif 307 | ) >= _TINYDIR_FILENAME_MAX) { 308 | errno = ENAMETOOLONG; 309 | return -1; 310 | } 311 | 312 | strcpy(file->path, dir->path); 313 | strcat(file->path, "/"); 314 | strcpy(file->name, 315 | #ifdef _MSC_VER 316 | dir->_f.cFileName 317 | #else 318 | dir->_e->d_name 319 | #endif 320 | ); 321 | /* Limit the number of bytes copied to the maximum length of the name, 322 | to avoid spurious compiler warnings about possible overlap */ 323 | strncat(file->path, file->name, _TINYDIR_FILENAME_MAX); 324 | #ifndef _MSC_VER 325 | if (stat(file->path, &file->_s) == -1) { 326 | return -1; 327 | } 328 | #endif 329 | file->is_dir = 330 | #ifdef _MSC_VER 331 | !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); 332 | #else 333 | S_ISDIR(file->_s.st_mode); 334 | #endif 335 | file->is_reg = 336 | #ifdef _MSC_VER 337 | !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || 338 | (!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) && 339 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && 340 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) && 341 | #ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM 342 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) && 343 | #endif 344 | #ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA 345 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) && 346 | #endif 347 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) && 348 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)); 349 | #else 350 | S_ISREG(file->_s.st_mode); 351 | #endif 352 | 353 | return 0; 354 | } 355 | 356 | _TINYDIR_FUNC 357 | int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i) { 358 | if (dir == NULL || file == NULL) { 359 | errno = EINVAL; 360 | return -1; 361 | } 362 | if (i >= dir->n_files) { 363 | errno = ENOENT; 364 | return -1; 365 | } 366 | 367 | memcpy(file, &dir->_files[i], sizeof(tinydir_file)); 368 | 369 | return 0; 370 | } 371 | 372 | _TINYDIR_FUNC 373 | int tinydir_open_subdir_n(tinydir_dir *dir, size_t i) { 374 | char path[_TINYDIR_PATH_MAX]; 375 | if (dir == NULL) { 376 | errno = EINVAL; 377 | return -1; 378 | } 379 | if (i >= dir->n_files || !dir->_files[i].is_dir) { 380 | errno = ENOENT; 381 | return -1; 382 | } 383 | 384 | strcpy(path, dir->_files[i].path); 385 | tinydir_close(dir); 386 | if (tinydir_open_sorted(dir, path) == -1) { 387 | return -1; 388 | } 389 | 390 | return 0; 391 | } 392 | 393 | _TINYDIR_FUNC 394 | int _tinydir_file_cmp(const void *a, const void *b) { 395 | const tinydir_file *fa = (const tinydir_file *)a; 396 | const tinydir_file *fb = (const tinydir_file *)b; 397 | if (fa->is_dir != fb->is_dir) { 398 | return -(fa->is_dir - fb->is_dir); 399 | } 400 | return strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX); 401 | } 402 | 403 | #endif 404 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | Significant changes to the divest package are laid out below for each release. 2 | 3 | =============================================================================== 4 | 5 | VERSION 1.2.0 6 | 7 | - `imageAttributes()`, and other functions for extracting and manipulating 8 | extended image attributes, have now been moved to the `RNifti` package. They 9 | are still exported by `divest` for convenience. 10 | - If the `jsonlite` package is not available then `readDicom()` will not 11 | attempt to convert JSON to separate image attributes. Instead the `.bidsJson` 12 | attribute will be retained as a JSON string. 13 | - Success in converting some DICOM files but not others now produces a warning 14 | rather than an error from `dcm2niix`. This allows the successfully converted 15 | images to be recovered. 16 | - Some `bash`-specific syntax has been removed from the package's `configure` 17 | script. 18 | 19 | =============================================================================== 20 | 21 | VERSION 1.1.1 22 | 23 | - If an output directory is specified to `readDicom()` or `convertDicom()`, it 24 | will now be created if it doesn't already exist. (Previously `dcm2niix` would 25 | emit warnings about file permissions and nothing would be written.) The 26 | vector of files written will also be returned invisibly. 27 | - The `miniz` library included with `dcm2niix` is no longer used except on 28 | Windows, as `zlib` should otherwise be available. 29 | - The package configuration script will no longer try to interpret "none" as a 30 | path to `pkg-config`. 31 | - Several small issues identified by compiler sanitisers and `valgrind` have 32 | been resolved. 33 | 34 | =============================================================================== 35 | 36 | VERSION 1.1.0 37 | 38 | - The new `divest.capabilities()` function reports on the package build's 39 | input/output capabilities, by analogy with `capabilities()` from base R. 40 | - The package's configuration script now uses `pkg-config` (if available) to 41 | find the correct flags for the OpenJPEG library. This should improve the 42 | range of systems where it is properly detected. 43 | - Upstream improvements to v1.0.20241001, including reductions to stack usage. 44 | 45 | =============================================================================== 46 | 47 | VERSION 1.0.0 48 | 49 | - It is now possible to use divest more analogously to dcm2niix, as a purely 50 | on-disk file-format converter, although the option to convert in memory is 51 | still available. The new convertDicom() function is a variant of readDicom() 52 | that defaults to this behaviour. In this case BIDS JSON sidecar files are 53 | written alongside the NIfTI image files. 54 | - There is now less duplicated logic in divest's codepaths, relative to the 55 | mainline dcm2niix binary, improving consistency with dcm2niix's BIDS output. 56 | - There are now helper functions to extract and replace extended image 57 | attributes, and for converting to and from BIDS JSON. 58 | - The BIDS naming convention, rather than divest's own, can be used for image 59 | attributes by setting the "divest.bidsAttributes" option to TRUE. 60 | - The "nested" argument to sortDicom() has been soft-deprecated in favour of 61 | the more flexible "output", which is also more consistent with readDicom(). 62 | - R version 3.5.0 or later is now explicitly required. 63 | 64 | =============================================================================== 65 | 66 | VERSION 0.10.3 67 | 68 | - Uses of the deprecated C function sprintf() have now been circumvented. 69 | 70 | =============================================================================== 71 | 72 | VERSION 0.10.2 73 | 74 | - Upstream improvements, to v1.0.20211006 and beyond. 75 | 76 | =============================================================================== 77 | 78 | VERSION 0.10.1 79 | 80 | - Fixes for new compiler warnings on certain platforms. 81 | 82 | =============================================================================== 83 | 84 | VERSION 0.10.0 85 | 86 | - Upstream improvements to v1.0.20210317, including support for Siemens XA20 87 | and XA30. 88 | - Somewhat generalised OpenJPEG support. 89 | 90 | =============================================================================== 91 | 92 | VERSION 0.9.0 93 | 94 | - Upstream improvements have been integrated, including for diffusion-weighted 95 | series containing only b=0 volumes. 96 | - Passing a data frame to readDicom() without specifying a "subset" argument 97 | should now convert all files as intended, rather than doing nothing. 98 | - Test behaviour has been tweaked for full compatibility with RNifti v1.2.0. 99 | 100 | =============================================================================== 101 | 102 | VERSION 0.8.2 103 | 104 | - Fixes for minor compiler warnings. 105 | 106 | =============================================================================== 107 | 108 | VERSION 0.8.1 109 | 110 | - The C++ file sorting implementation has been revised to avoid the possibility 111 | of an infinite loop in which the same files are repeatedly resorted. 112 | - Some tweaks have been made to the package tests. 113 | 114 | =============================================================================== 115 | 116 | VERSION 0.8.0 117 | 118 | - The output format used by sortDicom() is now more flexible, allowing multiple 119 | levels of subdirectories to be created (as requested in issue #3). Most of 120 | the work is now done in C++ code, and the "labelFormat" argument default has 121 | changed to reflect the full filename, not just the directory component. (This 122 | is now more consistent with dcm2niix.) The function's behaviour should be 123 | broadly the same as before, but there will be slight differences due to the 124 | quite different implementation. 125 | - The three core R functions gain a "depth" argument, which controls the 126 | maximum subdirectory search depth when scanning for DICOM files. 127 | - The QA battery tests have been moved into the package's main test set. These 128 | are (still) skipped in CRAN builds, however, due to the large sizes of the 129 | associated datasets. 130 | - Failing to find any valid DICOM files now produces a simple message, rather 131 | than an error. 132 | - Slice timing metadata should no longer include spurious zero values. 133 | - Various upstream improvements have been merged, as usual. 134 | 135 | =============================================================================== 136 | 137 | VERSION 0.7.2 138 | 139 | - ASAN/UBSAN and Valgrind warnings have been resolved. 140 | 141 | =============================================================================== 142 | 143 | VERSION 0.7.1 144 | 145 | - There is no longer a failure if the user does not have write access to the 146 | DICOM directory being read from. 147 | - R's temporary directory, rather than a hidden directory within the source 148 | folder, is now used for temporary copies of files when reading subsets. 149 | 150 | =============================================================================== 151 | 152 | VERSION 0.7.0 153 | 154 | - Additional attributes for series number and description, sequence name, 155 | protocol name, slice thickness and slice spacing are now captured from DICOM 156 | files during conversion. 157 | - readDicom() now accepts a "subset" argument, even when a path is given rather 158 | than a data frame. In this case scanDicom() is called first, and then the 159 | requested subset is converted. 160 | - The package is now compatible with an upcoming change in the RNifti package. 161 | - Further upstream improvements. 162 | 163 | =============================================================================== 164 | 165 | VERSION 0.6.1 166 | 167 | - The C++ compiler configuration is now properly detected by the configure 168 | script. 169 | - Warnings from GCC8 and Valgrind have been addressed. 170 | 171 | =============================================================================== 172 | 173 | VERSION 0.6.0 174 | 175 | - Upstream improvements, particularly for Philips data, have been integrated. 176 | - The package's continuous integration tests now check it against the dcm_qa 177 | dataset (see https://github.com/neurolabusc/dcm_qa). 178 | - A workaround has been added for certain undefined behaviour. 179 | 180 | =============================================================================== 181 | 182 | VERSION 0.5.0 183 | 184 | - The new sortDicom() function can be used to sort a directory of DICOM files 185 | into subdirectories corresponding to individual acquisitions. 186 | - It is now possible to read and convert individual files to NIfTI-1 format. 187 | This will happen whenever an argument to readDicom() is a file rather than a 188 | directory. 189 | - JPEG-encoded versions of the test files are now provided, to test the package 190 | more thoroughly. 191 | - Additional attributes, such as inversion time, patient weight and slice 192 | timing, are now captured, while the calculations for some others have 193 | changed, to match the current version of dcm2niix. Echo train duration and 194 | the "EPI factor" have been removed (see 195 | https://github.com/rordenlab/dcm2niix/issues/127). 196 | - The scanDicom() function now properly concatenates attributes when called 197 | with multiple paths. 198 | 199 | =============================================================================== 200 | 201 | VERSION 0.4.1 202 | 203 | - Fixes for Solaris and OpenJPEG compatibility. 204 | 205 | =============================================================================== 206 | 207 | VERSION 0.4.0 208 | 209 | - The data frame returned from scanDicom() can now be passed to readDicom(), 210 | and the latter gains a "subset" argument, which can be used to select a 211 | subset of the series to convert. 212 | - The results of scanDicom() now also include the number of files associated 213 | with each series, and whether or not diffusion direction metadata is 214 | available. 215 | - Additional attributes containing information about the acquisition are now 216 | extracted from the ASCII CSA header in Siemens EPI files. 217 | 218 | =============================================================================== 219 | 220 | VERSION 0.3.0 221 | 222 | - A configure script has been added to detect OpenJPEG or JasPer libraries. If 223 | either is available, the package will be able to read DICOM data encoded 224 | using the JPEG2000 codec. 225 | - The readDicom() function gains a "labelFormat" option, which allows the 226 | format of image labels to be customised. 227 | - A new "verbosity" level of -1 is now supported, which filters all but warning 228 | and error messages from dcm2niix's output. 229 | - Spurious date and time information should no longer be returned. 230 | 231 | =============================================================================== 232 | 233 | VERSION 0.2.0 234 | 235 | - The readDicom() function is now interactive in suitable sessions, allowing 236 | the user to choose which DICOM series to convert. 237 | - The new scanDicom() function allows the DICOM files in a directory to be 238 | scanned without performing any conversion. It returns a data frame 239 | containing information about the available scan series. 240 | - Both functions now search the current working directory by default. 241 | - Diffusion b-values and gradient vectors, and patient information, are now 242 | additionally stored as attributes in returned image objects. 243 | - Image cropping and forced stacking options from dcm2niix are now exposed to 244 | the R interface. 245 | - Gantry tilt correction is now applied to CT images where needed. (Reported 246 | by John Muschelli.) 247 | - Phase encoding attributes are now handled more robustly. 248 | 249 | =============================================================================== 250 | 251 | VERSION 0.1.2 252 | 253 | - The package now behaves correctly on big-endian systems. 254 | 255 | =============================================================================== 256 | 257 | VERSION 0.1.1 258 | 259 | - A test has been made more robust. 260 | 261 | =============================================================================== 262 | 263 | VERSION 0.1.0 264 | 265 | - First public release. 266 | 267 | =============================================================================== 268 | -------------------------------------------------------------------------------- /src/dcm2niix/nii_ortho.cpp: -------------------------------------------------------------------------------- 1 | #ifndef USING_R 2 | #include "nifti1.h" 3 | #endif 4 | #include "nifti1_io_core.h" 5 | #include "nii_ortho.h" 6 | #include 7 | #include 8 | #include 9 | #include //requires VS 2015 or later 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | // #include 16 | #include 17 | #ifndef _MSC_VER 18 | 19 | #include 20 | 21 | #endif 22 | // #define MY_DEBUG //verbose text reporting 23 | 24 | #include "print.h" 25 | 26 | typedef struct { 27 | int v[3]; 28 | } vec3i; 29 | 30 | mat33 matDotMul33(mat33 a, mat33 b) 31 | // in Matlab: ret = a'.*b 32 | { 33 | mat33 ret; 34 | for (int i = 0; i < 3; i++) { 35 | for (int j = 0; j < 3; j++) { 36 | ret.m[i][j] = a.m[i][j] * b.m[j][i]; 37 | } 38 | } 39 | return ret; 40 | } 41 | 42 | mat33 matMul33(mat33 a, mat33 b) 43 | // mult = a * b 44 | { 45 | mat33 mult; 46 | for (int i = 0; i < 3; i++) { 47 | for (int j = 0; j < 3; j++) { 48 | mult.m[j][i] = 0; 49 | for (int k = 0; k < 3; k++) 50 | mult.m[j][i] += a.m[j][k] * b.m[k][i]; 51 | } 52 | } 53 | return mult; 54 | } 55 | 56 | float getOrthoResidual(mat33 orig, mat33 transform) { 57 | mat33 mat = matDotMul33(orig, transform); 58 | float ret = 0; 59 | for (int i = 0; i < 3; i++) { 60 | for (int j = 0; j < 3; j++) { 61 | ret = ret + (mat.m[i][j]); 62 | } 63 | } 64 | return ret; 65 | } 66 | 67 | mat33 getBestOrient(mat44 R, vec3i flipVec) 68 | // flipVec reports flip: [1 1 1]=no flips, [-1 1 1] flip X dimension 69 | { 70 | mat33 ret, newmat, orig; 71 | LOAD_MAT33(orig, R.m[0][0], R.m[0][1], R.m[0][2], 72 | R.m[1][0], R.m[1][1], R.m[1][2], 73 | R.m[2][0], R.m[2][1], R.m[2][2]); 74 | float best = 0; // FLT_MAX; 75 | float newval; 76 | for (int rot = 0; rot < 6; rot++) { // 6 rotations 77 | switch (rot) { 78 | case 0: 79 | LOAD_MAT33(newmat, flipVec.v[0], 0, 0, 0, flipVec.v[1], 0, 0, 0, flipVec.v[2]); 80 | break; 81 | case 1: 82 | LOAD_MAT33(newmat, flipVec.v[0], 0, 0, 0, 0, flipVec.v[1], 0, flipVec.v[2], 0); 83 | break; 84 | case 2: 85 | LOAD_MAT33(newmat, 0, flipVec.v[0], 0, flipVec.v[1], 0, 0, 0, 0, flipVec.v[2]); 86 | break; 87 | case 3: 88 | LOAD_MAT33(newmat, 0, flipVec.v[0], 0, 0, 0, flipVec.v[1], flipVec.v[2], 0, 0); 89 | break; 90 | case 4: 91 | LOAD_MAT33(newmat, 0, 0, flipVec.v[0], flipVec.v[1], 0, 0, 0, flipVec.v[2], 0); 92 | break; 93 | case 5: 94 | LOAD_MAT33(newmat, 0, 0, flipVec.v[0], 0, flipVec.v[1], 0, flipVec.v[2], 0, 0); 95 | break; 96 | } 97 | newval = getOrthoResidual(orig, newmat); 98 | if (newval > best) { 99 | best = newval; 100 | ret = newmat; 101 | } 102 | } 103 | return ret; 104 | } 105 | 106 | bool isMat44Canonical(mat44 R) 107 | // returns true if diagonals >0 and all others =0 108 | // no rotation is necessary - already in perfect orthogonal alignment 109 | { 110 | for (int i = 0; i < 3; i++) { 111 | for (int j = 0; j < 3; j++) { 112 | if ((i == j) && (R.m[i][j] <= 0)) 113 | return false; 114 | if ((i != j) && (R.m[i][j] != 0)) 115 | return false; 116 | } // j 117 | } // i 118 | return true; 119 | } 120 | 121 | vec3i setOrientVec(mat33 m) 122 | // Assumes isOrthoMat NOT computed on INVERSE, hence return INVERSE of solution... 123 | // e.g. [-1,2,3] means reflect x axis, [2,1,3] means swap x and y dimensions 124 | { 125 | vec3i ret = {{0, 0, 0}}; 126 | // mat33 m = {-1,0,0, 0,1,0, 0,0,1}; 127 | for (int i = 0; i < 3; i++) { 128 | for (int j = 0; j < 3; j++) { 129 | if (m.m[i][j] > 0) 130 | ret.v[j] = i + 1; 131 | if (m.m[i][j] < 0) 132 | ret.v[j] = -(i + 1); 133 | } // j 134 | } // i 135 | return ret; 136 | } 137 | 138 | mat44 setMat44Vec(mat33 m33, vec3 Translations) 139 | // convert a 3x3 rotation matrix to a 4x4 matrix where the last column stores translations and the last row is 0 0 0 1 140 | { 141 | mat44 m44; 142 | for (int i = 0; i < 3; i++) { 143 | for (int j = 0; j < 3; j++) { 144 | m44.m[i][j] = m33.m[i][j]; 145 | } 146 | } 147 | m44.m[0][3] = Translations.v[0]; 148 | m44.m[1][3] = Translations.v[1]; 149 | m44.m[2][3] = Translations.v[2]; 150 | m44.m[3][0] = 0; 151 | m44.m[3][1] = 0; 152 | m44.m[3][2] = 0; 153 | m44.m[3][3] = 1; 154 | return m44; 155 | } 156 | 157 | mat44 sFormMat(struct nifti_1_header *h) { 158 | mat44 s; 159 | s.m[0][0] = h->srow_x[0]; 160 | s.m[0][1] = h->srow_x[1]; 161 | s.m[0][2] = h->srow_x[2]; 162 | s.m[0][3] = h->srow_x[3]; 163 | s.m[1][0] = h->srow_y[0]; 164 | s.m[1][1] = h->srow_y[1]; 165 | s.m[1][2] = h->srow_y[2]; 166 | s.m[1][3] = h->srow_y[3]; 167 | s.m[2][0] = h->srow_z[0]; 168 | s.m[2][1] = h->srow_z[1]; 169 | s.m[2][2] = h->srow_z[2]; 170 | s.m[2][3] = h->srow_z[3]; 171 | s.m[3][0] = 0; 172 | s.m[3][1] = 0; 173 | s.m[3][2] = 0; 174 | s.m[3][3] = 1; 175 | return s; 176 | } 177 | 178 | void mat2sForm(struct nifti_1_header *h, mat44 s) { 179 | h->srow_x[0] = s.m[0][0]; 180 | h->srow_x[1] = s.m[0][1]; 181 | h->srow_x[2] = s.m[0][2]; 182 | h->srow_x[3] = s.m[0][3]; 183 | h->srow_y[0] = s.m[1][0]; 184 | h->srow_y[1] = s.m[1][1]; 185 | h->srow_y[2] = s.m[1][2]; 186 | h->srow_y[3] = s.m[1][3]; 187 | h->srow_z[0] = s.m[2][0]; 188 | h->srow_z[1] = s.m[2][1]; 189 | h->srow_z[2] = s.m[2][2]; 190 | h->srow_z[3] = s.m[2][3]; 191 | } 192 | 193 | size_t *orthoOffsetArray(int dim, int stepBytesPerVox) { 194 | // return lookup table of length dim with values incremented by stepBytesPerVox 195 | // e.g. if Dim=10 and stepBytes=2: 0,2,4..18, is stepBytes=-2 18,16,14...0 196 | size_t *lut = (size_t *)malloc(dim * sizeof(size_t)); 197 | if (stepBytesPerVox > 0) 198 | lut[0] = 0; 199 | else 200 | lut[0] = -stepBytesPerVox * (dim - 1); 201 | if (dim > 1) 202 | for (int i = 1; i < dim; i++) 203 | lut[i] = lut[i - 1] + (size_t)stepBytesPerVox; 204 | return lut; 205 | } // orthoOffsetArray() 206 | 207 | // void reOrientImg( unsigned char * restrict img, vec3i outDim, vec3i outInc, int bytePerVox, int nvol) { 208 | void reOrientImg(unsigned char *img, vec3i outDim, vec3i outInc, int bytePerVox, int nvol) { 209 | // reslice data to new orientation 210 | // generate look up tables 211 | size_t *xLUT = orthoOffsetArray(outDim.v[0], bytePerVox * outInc.v[0]); 212 | size_t *yLUT = orthoOffsetArray(outDim.v[1], bytePerVox * outInc.v[1]); 213 | size_t *zLUT = orthoOffsetArray(outDim.v[2], bytePerVox * outInc.v[2]); 214 | // convert data 215 | size_t bytePerVol = bytePerVox * outDim.v[0] * outDim.v[1] * outDim.v[2]; // number of voxels in spatial dimensions [1,2,3] 216 | size_t o = 0; // output address 217 | uint8_t *inbuf = (uint8_t *)malloc(bytePerVol); // we convert 1 volume at a time 218 | uint8_t *outbuf = (uint8_t *)img; // source image 219 | for (int vol = 0; vol < nvol; vol++) { 220 | memcpy(&inbuf[0], &outbuf[vol * bytePerVol], bytePerVol); // copy source volume 221 | for (int z = 0; z < outDim.v[2]; z++) 222 | for (int y = 0; y < outDim.v[1]; y++) 223 | for (int x = 0; x < outDim.v[0]; x++) { 224 | memcpy(&outbuf[o], &inbuf[xLUT[x] + yLUT[y] + zLUT[z]], bytePerVox); 225 | o = o + bytePerVox; 226 | } // for each x 227 | } // for each volume 228 | // free arrays 229 | free(inbuf); 230 | free(xLUT); 231 | free(yLUT); 232 | free(zLUT); 233 | } // reOrientImg 234 | 235 | unsigned char *reOrient(unsigned char *img, struct nifti_1_header *h, vec3i orientVec, mat33 orient, vec3 minMM) 236 | // e.g. [-1,2,3] means reflect x axis, [2,1,3] means swap x and y dimensions 237 | { 238 | size_t nvox = h->dim[1] * h->dim[2] * h->dim[3]; 239 | if (nvox < 1) 240 | return img; 241 | vec3i outDim = {{0, 0, 0}}; 242 | vec3i outInc = {{0, 0, 0}}; 243 | for (int i = 0; i < 3; i++) { // set dimension, pixdim and 244 | outDim.v[i] = h->dim[abs(orientVec.v[i])]; 245 | if (abs(orientVec.v[i]) == 1) 246 | outInc.v[i] = 1; 247 | if (abs(orientVec.v[i]) == 2) 248 | outInc.v[i] = h->dim[1]; 249 | if (abs(orientVec.v[i]) == 3) 250 | outInc.v[i] = h->dim[1] * h->dim[2]; 251 | if (orientVec.v[i] < 0) 252 | outInc.v[i] = -outInc.v[i]; // flip 253 | } // for each dimension 254 | int nvol = 1; // convert all non-spatial volumes from source to destination 255 | for (int vol = 4; vol < 8; vol++) { 256 | if (h->dim[vol] > 1) 257 | nvol = nvol * h->dim[vol]; 258 | } 259 | reOrientImg(img, outDim, outInc, h->bitpix / 8, nvol); 260 | // now change the header.... 261 | vec3 outPix = {{h->pixdim[abs(orientVec.v[0])], h->pixdim[abs(orientVec.v[1])], h->pixdim[abs(orientVec.v[2])]}}; 262 | for (int i = 0; i < 3; i++) { 263 | h->dim[i + 1] = outDim.v[i]; 264 | h->pixdim[i + 1] = outPix.v[i]; 265 | } 266 | mat44 s = sFormMat(h); 267 | mat33 mat; // computer transform 268 | LOAD_MAT33(mat, s.m[0][0], s.m[0][1], s.m[0][2], 269 | s.m[1][0], s.m[1][1], s.m[1][2], 270 | s.m[2][0], s.m[2][1], s.m[2][2]); 271 | mat = matMul33(mat, orient); 272 | s = setMat44Vec(mat, minMM); // add offset 273 | mat2sForm(h, s); 274 | h->qform_code = h->sform_code; // apply to the quaternion as well 275 | float dumdx, dumdy, dumdz; 276 | nifti_mat44_to_quatern(s, &h->quatern_b, &h->quatern_c, &h->quatern_d, &h->qoffset_x, &h->qoffset_y, &h->qoffset_z, &dumdx, &dumdy, &dumdz, &h->pixdim[0]); 277 | return img; 278 | } // reOrient() 279 | 280 | float getDistance(vec3 v, vec3 min) 281 | // scalar distance between two 3D points - Pythagorean theorem 282 | { 283 | return sqrt(pow((v.v[0] - min.v[0]), 2) + pow((v.v[1] - min.v[1]), 2) + pow((v.v[2] - min.v[2]), 2)); 284 | } 285 | 286 | vec3 xyz2mm(mat44 R, vec3 v) { 287 | vec3 ret; 288 | for (int i = 0; i < 3; i++) { 289 | ret.v[i] = ((R.m[i][0] * v.v[0]) + (R.m[i][1] * v.v[1]) + (R.m[i][2] * v.v[2]) + R.m[i][3]); 290 | } 291 | return ret; 292 | } 293 | 294 | vec3 minCornerFlip(struct nifti_1_header *h, vec3i *flipVec) 295 | // orthogonal rotations and reflections applied as 3x3 matrices will cause the origin to shift 296 | // a simple solution is to first compute the most left, posterior, inferior voxel in the source image 297 | // this voxel will be at location i,j,k = 0,0,0, so we can simply use this as the offset for the final 4x4 matrix... 298 | { 299 | int i, j, minIndex; 300 | vec3i flipVecs[8]; 301 | vec3 corner[8], min; 302 | mat44 s = sFormMat(h); 303 | for (int i = 0; i < 8; i++) { 304 | if (i & 1) 305 | flipVecs[i].v[0] = -1; 306 | else 307 | flipVecs[i].v[0] = 1; 308 | if (i & 2) 309 | flipVecs[i].v[1] = -1; 310 | else 311 | flipVecs[i].v[1] = 1; 312 | if (i & 4) 313 | flipVecs[i].v[2] = -1; 314 | else 315 | flipVecs[i].v[2] = 1; 316 | corner[i] = setVec3(0, 0, 0); // assume no reflections 317 | if ((flipVecs[i].v[0]) < 1) 318 | corner[i].v[0] = h->dim[1] - 1; // reflect X 319 | if ((flipVecs[i].v[1]) < 1) 320 | corner[i].v[1] = h->dim[2] - 1; // reflect Y 321 | if ((flipVecs[i].v[2]) < 1) 322 | corner[i].v[2] = h->dim[3] - 1; // reflect Z 323 | corner[i] = xyz2mm(s, corner[i]); 324 | } 325 | // find extreme edge from ALL corners.... 326 | min = corner[0]; 327 | for (i = 1; i < 8; i++) { 328 | for (j = 0; j < 3; j++) { 329 | if (corner[i].v[j] < min.v[j]) 330 | min.v[j] = corner[i].v[j]; 331 | } 332 | } 333 | float dx; // observed distance from corner 334 | float min_dx = getDistance(corner[0], min); 335 | minIndex = 0; // index of corner closest to min 336 | // see if any corner is closer to absmin than the first one... 337 | for (i = 1; i < 8; i++) { 338 | dx = getDistance(corner[i], min); 339 | if (dx < min_dx) { 340 | min_dx = dx; 341 | minIndex = i; 342 | } 343 | } 344 | min = corner[minIndex]; // this is the single corner closest to min from all 345 | *flipVec = flipVecs[minIndex]; 346 | return min; 347 | } 348 | 349 | #ifdef MY_DEBUG 350 | void reportMat44o(char *str, mat44 A) { 351 | printMessage("%s = [%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1]\n", str, 352 | A.m[0][0], A.m[0][1], A.m[0][2], A.m[0][3], 353 | A.m[1][0], A.m[1][1], A.m[1][2], A.m[1][3], 354 | A.m[2][0], A.m[2][1], A.m[2][2], A.m[2][3]); 355 | } 356 | #endif 357 | 358 | unsigned char *nii_setOrtho(unsigned char *img, struct nifti_1_header *h) { 359 | if ((h->dim[1] < 1) || (h->dim[2] < 1) || (h->dim[3] < 1)) 360 | return img; 361 | if ((h->sform_code == NIFTI_XFORM_UNKNOWN) && (h->qform_code != NIFTI_XFORM_UNKNOWN)) { // only q-form provided 362 | mat44 q = nifti_quatern_to_mat44(h->quatern_b, h->quatern_c, h->quatern_d, 363 | h->qoffset_x, h->qoffset_y, h->qoffset_z, 364 | h->pixdim[1], h->pixdim[2], h->pixdim[3], h->pixdim[0]); 365 | mat2sForm(h, q); // convert q-form to s-form 366 | h->sform_code = h->qform_code; 367 | } 368 | if (h->sform_code == NIFTI_XFORM_UNKNOWN) { 369 | #ifdef MY_DEBUG 370 | printMessage("No Q or S spatial transforms - assuming canonical orientation"); 371 | #endif 372 | return img; 373 | } 374 | mat44 s = sFormMat(h); 375 | if (isMat44Canonical(s)) { 376 | #ifdef MY_DEBUG 377 | printMessage("Image in perfect alignment: no need to reorient"); 378 | #endif 379 | return img; 380 | } 381 | vec3i flipV; 382 | vec3 minMM = minCornerFlip(h, &flipV); 383 | mat33 orient = getBestOrient(s, flipV); 384 | vec3i orientVec = setOrientVec(orient); 385 | if ((orientVec.v[0] == 1) && (orientVec.v[1] == 2) && (orientVec.v[2] == 3)) { 386 | #ifdef MY_DEBUG 387 | printMessage("Image already near best orthogonal alignment: no need to reorient\n"); 388 | #endif 389 | return img; 390 | } 391 | bool is24 = false; 392 | if (h->bitpix == 24) { // RGB stored as planar data. treat as 3 8-bit slices 393 | return img; 394 | /*is24 = true; 395 | h->bitpix = 8; 396 | h->dim[3] = h->dim[3] * 3;*/ 397 | } 398 | img = reOrient(img, h, orientVec, orient, minMM); 399 | if (is24) { 400 | h->bitpix = 24; 401 | h->dim[3] = h->dim[3] / 3; 402 | } 403 | #ifdef MY_DEBUG 404 | printMessage("NewRotation= %d %d %d\n", orientVec.v[0], orientVec.v[1], orientVec.v[2]); 405 | printMessage("MinCorner= %.2f %.2f %.2f\n", minMM.v[0], minMM.v[1], minMM.v[2]); 406 | reportMat44o((char *)"input", s); 407 | s = sFormMat(h); 408 | reportMat44o((char *)"output", s); 409 | #endif 410 | return img; 411 | } 412 | -------------------------------------------------------------------------------- /R/read.R: -------------------------------------------------------------------------------- 1 | tempDirectory <- function () 2 | { 3 | # Don't overwrite an existing temporary directory 4 | originalTempDirectory <- tempDirectory <- file.path(tempdir(), paste("divest",Sys.getpid(),sep="_")) 5 | suffix <- 1 6 | while (file.exists(tempDirectory)) 7 | { 8 | tempDirectory <- paste(originalTempDirectory, as.character(suffix), sep="_") 9 | suffix <- suffix + 1 10 | } 11 | 12 | dir.create(tempDirectory, recursive=TRUE) 13 | return (tempDirectory) 14 | } 15 | 16 | resolvePaths <- function (path, subset = NULL, dirsOnly = FALSE) 17 | { 18 | # Data frame case (caller should handle subsets with character path) 19 | if (is.data.frame(path)) 20 | { 21 | if (!is.null(subset)) 22 | paths <- unlist(attr(path,"paths")[subset]) 23 | else 24 | paths <- unlist(attr(path,"paths")) 25 | 26 | tempDirectory <- tempDirectory() 27 | success <- file.symlink(paths, tempDirectory) 28 | if (!all(success)) 29 | { 30 | unlink(tempDirectory, recursive=TRUE) 31 | dir.create(tempDirectory, recursive=TRUE) 32 | success <- file.copy(paths, tempDirectory) 33 | } 34 | 35 | if (all(success)) 36 | return (structure(tempDirectory, temporary=TRUE)) 37 | else 38 | stop("Cannot symlink or copy files into temporary directory") 39 | } 40 | 41 | # Path should now only be a character vector 42 | for (i in seq_along(path)) 43 | { 44 | if (!file.exists(path[i])) 45 | { 46 | warning(paste0("Path \"", path[i], "\" does not exist")) 47 | path <- path[-i] 48 | } 49 | else if (dirsOnly && !file.info(path[i])$isdir) 50 | { 51 | warning(paste0("Path \"", path[i], "\" does not point to a directory")) 52 | path <- path[-i] 53 | } 54 | else 55 | path[i] <- path.expand(path[i]) 56 | } 57 | return (path) 58 | } 59 | 60 | sortInfoTable <- function (table) 61 | { 62 | ordering <- with(table, order(patientName,studyDate,seriesNumber,echoNumber,phase)) 63 | return (structure(table[ordering,], descriptions=attr(table,"descriptions")[ordering], paths=attr(table,"paths")[ordering], ordering=ordering, class=c("divestListing","data.frame"))) 64 | } 65 | 66 | readPath <- function (path, flipY, crop, forceStack, verbosity, labelFormat, singleFile, depth, task = c("read","convert","scan","sort"), outputDir = NULL) 67 | { 68 | task <- match.arg(task) 69 | if (verbosity < 0L) 70 | { 71 | output <- NULL 72 | connection <- textConnection("output", "w", local=TRUE) 73 | sink(connection) 74 | on.exit({ 75 | sink() 76 | cat(paste(c(grep(ifelse(verbosity < -1L, "ERROR", "WARNING|ERROR"), output, ignore.case=TRUE, perl=TRUE, value=TRUE), ""), collapse="\n")) 77 | }) 78 | } 79 | 80 | if (is.null(outputDir)) 81 | outputDir <- tempDirectory() 82 | else if (!file.exists(outputDir)) 83 | dir.create(outputDir) 84 | results <- .Call(C_readDirectory, path, flipY, crop, forceStack, verbosity, labelFormat, singleFile, depth, task, outputDir) 85 | 86 | if (task == "read") 87 | { 88 | convertAttributes <- !isTRUE(getOption("divest.bidsAttributes")) 89 | addAttributes <- function (im) 90 | { 91 | attribs <- attributes(im) 92 | jsonPath <- file.path(outputDir, paste(as.character(im),"json",sep=".")) 93 | 94 | if (is.null(attribs$.bidsJson) && file.exists(jsonPath)) 95 | attribs$.bidsJson <- paste(readLines(jsonPath, encoding="UTF-8"), collapse="\n") 96 | 97 | if (!is.null(attribs$.bidsJson)) 98 | { 99 | attribs <- c(attribs, fromBidsJson(attribs$.bidsJson, rename=convertAttributes)) 100 | attribs$.bidsJson <- NULL 101 | attributes(im) <- attribs 102 | } 103 | 104 | return (im) 105 | } 106 | if (requireNamespace("jsonlite", quietly=TRUE)) 107 | results <- lapply(results, addAttributes) 108 | } 109 | 110 | return (results) 111 | } 112 | 113 | #' Read one or more DICOM directories 114 | #' 115 | #' These functions are R wrappers around the DICOM-to-NIfTI conversion routines 116 | #' provided by \code{dcm2niix}. They scan directories containing DICOM files, 117 | #' potentially pertaining to more than one image series, read them and/or merge 118 | #' them into a list of \code{niftiImage} objects. 119 | #' 120 | #' The \code{scanDicom} function parses directories full of DICOM files and 121 | #' returns information about the acquisition series they contain. 122 | #' \code{readDicom} reads these files and converts them to (internal) NIfTI 123 | #' images (whose pixel data can be extracted using \code{as.array}). 124 | #' \code{convertDicom} performs the same conversion but writes to NIfTI files 125 | #' by default, instead of retaining the images in memory. \code{sortDicom} 126 | #' renames the files, but does not convert them. 127 | #' 128 | #' The \code{labelFormat} argument describes the string format used for image 129 | #' labels and sorted files. Valid codes, each escaped with a percentage sign, 130 | #' include \code{a} for coil number, \code{b} for the source file base name, 131 | #' \code{c} for image comments, \code{d} for series description, \code{e} for 132 | #' echo number, \code{f} for the source directory, \code{i} for patient ID, 133 | #' \code{j} for the series instance UID, \code{k} for the study instance UID, 134 | #' \code{l} for the procedure step description, \code{m} for manufacturer, 135 | #' \code{n} for patient name, \code{p} for protocol name, \code{q} for 136 | #' scanning sequence, \code{r} for instance number, \code{s} for series number, 137 | #' \code{t} for the date and time, \code{u} for acquisition number, \code{v} 138 | #' for vendor, \code{x} for study ID and \code{z} for sequence name. For 139 | #' \code{sortDicom} the label forms the new file path, and may include one or 140 | #' more slashes to create subdirectories. A ".dcm" suffix will be added to file 141 | #' names if no extension is specified. 142 | #' 143 | #' @param path A character vector of paths to scan for DICOM files. Each will 144 | #' examined in turn. The default is the current working directory. 145 | #' \code{readDicom} (only) will accept paths to individual DICOM files, 146 | #' rather than directories. Alternatively, for \code{readDicom} and 147 | #' \code{sortDicom}, a data frame like the one returned by \code{scanDicom}, 148 | #' from which file paths will be read. 149 | #' @param subset If \code{path} is a data frame, an expression which will be 150 | #' evaluated in the context of the data frame to determine which series to 151 | #' convert. Should evaluate to a logical vector. If \code{path} is a 152 | #' character vector, \code{scanDicom} is called on the path(s) first to 153 | #' produce the data frame. If this is specified, and does not evaluate to 154 | #' \code{NULL}, the read will be noninteractive, irrespective of the value of 155 | #' the \code{interactive} argument. 156 | #' @param flipY If \code{TRUE}, the default, then images will be flipped in the 157 | #' Y-axis. This is usually desirable, given the difference between 158 | #' orientation conventions in the DICOM and NIfTI-1 formats. 159 | #' @param crop If \code{TRUE}, then \code{dcm2niix} will attempt to crop excess 160 | #' neck slices from brain images. 161 | #' @param forceStack If \code{TRUE}, images with the same series number will 162 | #' always be stacked together as long as their dimensions are compatible. If 163 | #' \code{FALSE}, the default, images will be separated if they differ in 164 | #' echo, coil or exposure number, echo time, protocol name or orientation. 165 | #' @param verbosity Integer value between -2 and 3, controlling the amount of 166 | #' output generated during the conversion. A value of -1 will suppress all 167 | #' output from \code{dcm2niix} except warnings and errors; -2 also suppresses 168 | #' warnings. 169 | #' @param labelFormat A \code{\link{sprintf}}-style string specifying the 170 | #' format to use for the final image labels or paths. See Details. 171 | #' @param depth The maximum subdirectory depth in which to search for DICOM 172 | #' files, relative to each \code{path}. 173 | #' @param interactive If \code{TRUE}, the default in interactive sessions, the 174 | #' requested paths will first be scanned and a list of DICOM series will be 175 | #' presented. You may then choose which series to convert. 176 | #' @param output The directory to write converted or copied NIfTI files to, or 177 | #' \code{NULL}. In the latter case, which isn't valid for \code{sortDicom}, 178 | #' images are converted in memory and returned as R objects. 179 | #' @param nested For \code{sortDicom}, should the sorted files be created 180 | #' within the source directory (\code{TRUE}), or in the current working 181 | #' directory (\code{FALSE})? Now soft-deprecated in favour of \code{output}, 182 | #' which is more flexible. 183 | #' @param keepUnsorted For \code{sortDicom}, should the unsorted files be left 184 | #' in place, or removed after they are copied into their new locations? The 185 | #' default, \code{FALSE}, corresponds to a move rather than a copy. If 186 | #' creating new files fails then the old ones will not be deleted. 187 | #' @return \code{readDicom} and \code{convertDicom} return a list of 188 | #' \code{niftiImage} objects if \code{output} is \code{NULL}; otherwise 189 | #' (invisibly) a vector of paths to NIfTI-1 files created in the target 190 | #' directory. Returned images typically have attributes containing additional 191 | #' metadata extracted from the DICOM headers, either in a JSON string or (if 192 | #' the \code{jsonlite} package is available), in individually parsed 193 | #' elements. The \code{scanDicom} function returns a data frame containing 194 | #' information about each DICOM series found. \code{sortDicom} is mostly 195 | #' called for its side-effect, but also (invisibly) returns a list detailing 196 | #' source and target paths. 197 | #' 198 | #' @examples 199 | #' path <- system.file("extdata", "raw", package="divest") 200 | #' scanDicom(path) 201 | #' readDicom(path, interactive=FALSE) 202 | #' @author Jon Clayden 203 | #' @export readDicom 204 | readDicom <- function (path = ".", subset = NULL, flipY = TRUE, crop = FALSE, forceStack = FALSE, verbosity = 0L, labelFormat = "T%t_N%n_S%s", depth = 5L, interactive = base::interactive(), output = NULL) 205 | { 206 | if (!is.data.frame(path) && !missing(subset)) 207 | path <- scanDicom(path, forceStack, verbosity, labelFormat) 208 | if (is.data.frame(path)) 209 | subset <- eval(substitute(subset), path) 210 | 211 | path <- resolvePaths(path, subset) 212 | task <- ifelse(is.null(output), "read", "convert") 213 | 214 | if (any(attr(path, "temporary"))) 215 | on.exit(unlink(path[attr(path,"temporary")], recursive=TRUE)) 216 | 217 | processPath <- function(p) 218 | { 219 | if (!file.info(p)$isdir) 220 | readPath(p, flipY, crop, forceStack, verbosity, labelFormat, TRUE, depth, task, output) 221 | else if (interactive && is.null(subset)) 222 | { 223 | info <- sortInfoTable(readPath(p, flipY, crop, forceStack, min(0L,verbosity), labelFormat, FALSE, depth, "scan")) 224 | 225 | selection <- .menu(attr(info,"descriptions")) 226 | if (identical(selection, seq_len(nrow(info)))) 227 | { 228 | allResults <- readPath(p, flipY, crop, forceStack, verbosity, labelFormat, FALSE, depth, task, output) 229 | return (allResults[attr(info,"ordering")]) 230 | } 231 | else 232 | { 233 | selectedResults <- lapply(resolvePaths(info,selection), readPath, flipY, crop, forceStack, verbosity, labelFormat, FALSE, depth, task, output) 234 | return (do.call(c, selectedResults)) 235 | } 236 | } 237 | else 238 | readPath(p, flipY, crop, forceStack, verbosity, labelFormat, FALSE, depth, task, output) 239 | } 240 | 241 | result <- do.call(c, lapply(path,processPath)) 242 | if (is.character(result)) 243 | return (invisible(result)) 244 | else 245 | return (result) 246 | } 247 | 248 | # Create and then monkey-patch convertDicom() so that it differs from 249 | # readDicom() only in the default "output" parameter 250 | 251 | #' @rdname readDicom 252 | #' @export 253 | convertDicom <- readDicom 254 | formals(convertDicom)$output <- as.symbol("path") 255 | 256 | #' @rdname readDicom 257 | #' @export 258 | sortDicom <- function (path = ".", forceStack = FALSE, verbosity = 0L, labelFormat = "T%t_N%n_S%s/%b", depth = 5L, nested = NA, keepUnsorted = FALSE, output = path) 259 | { 260 | if (isFALSE(nested)) 261 | output <- "." 262 | info <- readPath(path, FALSE, FALSE, forceStack, verbosity, labelFormat, FALSE, depth, "sort", output) 263 | 264 | if (!keepUnsorted && length(info$source) == length(info$target)) 265 | { 266 | inPlace <- (normalizePath(info$source) == normalizePath(info$target)) 267 | unlink(info$source[!inPlace]) 268 | } 269 | 270 | invisible(info) 271 | } 272 | 273 | #' @rdname readDicom 274 | #' @export 275 | scanDicom <- function (path = ".", forceStack = FALSE, verbosity = 0L, labelFormat = "T%t_N%n_S%s", depth = 5L) 276 | { 277 | results <- lapply(path, function(p) { 278 | if (file.info(p)$isdir) 279 | readPath(path.expand(p), TRUE, FALSE, forceStack, verbosity, labelFormat, FALSE, depth, "scan") 280 | else 281 | warning(paste0("Path \"", p, "\" does not point to a directory")) 282 | }) 283 | 284 | sortInfoTable(Reduce(function(x,y) structure(rbind(x,y), descriptions=c(attr(x,"descriptions"),attr(y,"descriptions")), paths=c(attr(x,"paths"),attr(y,"paths"))), results)) 285 | } 286 | -------------------------------------------------------------------------------- /src/dcm2niix/nii_dicom.h: -------------------------------------------------------------------------------- 1 | #include "nifti1_io_core.h" 2 | #include //requires VS 2015 or later 3 | #include 4 | #include 5 | #ifndef USING_R 6 | #include "nifti1.h" 7 | #endif 8 | 9 | #ifndef MRIpro_nii_dcm_h 10 | 11 | #define MRIpro_nii_dcm_h 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | #define STR_HELPER(x) #x 18 | #define STR(x) STR_HELPER(x) 19 | 20 | #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) 21 | #define kLSsuf " (JP-LS:CharLS)" 22 | #else 23 | #define kLSsuf "" 24 | #endif 25 | #ifdef myEnableJasper 26 | #define kJP2suf " (JP2:JasPer)" 27 | #else 28 | #ifdef myDisableOpenJPEG 29 | #define kJP2suf "" 30 | #else 31 | #define kJP2suf " (JP2:OpenJPEG)" 32 | #endif 33 | #endif 34 | #if defined(__ICC) || defined(__INTEL_COMPILER) 35 | #define kCCsuf " IntelCC" STR(__INTEL_COMPILER) 36 | #elif defined(_MSC_VER) 37 | #define kCCsuf " MSC" STR(_MSC_VER) 38 | #elif defined(__clang__) 39 | #define kCCsuf " Clang" STR(__clang_major__) "." STR(__clang_minor__) "." STR(__clang_patchlevel__) 40 | #elif defined(__GNUC__) || defined(__GNUG__) 41 | #define kCCsuf " GCC" STR(__GNUC__) "." STR(__GNUC_MINOR__) "." STR(__GNUC_PATCHLEVEL__) 42 | #else 43 | #define kCCsuf " CompilerNA" // unknown compiler! 44 | #endif 45 | #if defined(__arm__) || defined(__ARM_ARCH) 46 | #define kCPUsuf " ARM" 47 | #elif defined(__x86_64) 48 | #define kCPUsuf " x86-64" 49 | #else 50 | #define kCPUsuf " " // unknown CPU 51 | #endif 52 | 53 | #define kDCMdate "v1.0.20241001" 54 | #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf 55 | 56 | static const int kMaxEPI3D = 1024; // maximum number of EPI images in Siemens Mosaic 57 | 58 | #if defined(__linux__) // Unix users must use setrlimit 59 | static const int kMaxSlice2D = 65535; // issue460 maximum number of 2D slices in 4D (Philips) images 60 | #else 61 | static const int kMaxSlice2D = 131070; // 65535; //issue460 maximum number of 2D slices in 4D (Philips) images 62 | #endif 63 | static const int kMaxDTI4D = kMaxSlice2D; // issue460: maximum number of DTI directions for 4D (Philips) images, also maximum number of 2D slices for Enhanced DICOM and PAR/REC 64 | 65 | #define kDICOMStr 66 // 64 characters plus NULL https://github.com/rordenlab/dcm2niix/issues/268 66 | #define kDICOMStrLarge 256 67 | #define kDICOMStrExtraLarge 65536 // for Siemens WipMemBlock only 68 | 69 | #define kMANUFACTURER_UNKNOWN 0 70 | #define kMANUFACTURER_SIEMENS 1 71 | #define kMANUFACTURER_GE 2 72 | #define kMANUFACTURER_PHILIPS 3 73 | #define kMANUFACTURER_TOSHIBA 4 74 | #define kMANUFACTURER_UIH 5 75 | #define kMANUFACTURER_BRUKER 6 76 | #define kMANUFACTURER_HITACHI 7 77 | #define kMANUFACTURER_CANON 8 78 | #define kMANUFACTURER_MEDISO 9 79 | #define kMANUFACTURER_MRSOLUTIONS 10 80 | #define kMANUFACTURER_HYPERFINE 11 81 | 82 | // note: note a complete modality list, e.g. XA,PX, etc 83 | #define kMODALITY_UNKNOWN 0 84 | #define kMODALITY_CR 1 85 | #define kMODALITY_CT 2 86 | #define kMODALITY_MR 3 87 | #define kMODALITY_PT 4 88 | #define kMODALITY_US 5 89 | 90 | // PartialFourierDirection 0018,9036 91 | #define kPARTIAL_FOURIER_DIRECTION_UNKNOWN 0 92 | #define kPARTIAL_FOURIER_DIRECTION_PHASE 1 93 | #define kPARTIAL_FOURIER_DIRECTION_FREQUENCY 2 94 | #define kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT 3 95 | #define kPARTIAL_FOURIER_DIRECTION_COMBINATION 4 96 | 97 | // GE EPI settings 98 | #define kGE_EPI_UNKNOWN -1 99 | #define kGE_EPI_EPI 0 100 | #define kGE_EPI_EPIRT 1 101 | #define kGE_EPI_EPI2 2 102 | #define kGE_EPI_PEPOLAR_FWD 3 103 | #define kGE_EPI_PEPOLAR_REV 4 104 | #define kGE_EPI_PEPOLAR_REV_FWD 5 105 | #define kGE_EPI_PEPOLAR_FWD_REV 6 106 | #define kGE_EPI_PEPOLAR_REV_FWD_FLIP 7 107 | #define kGE_EPI_PEPOLAR_FWD_REV_FLIP 8 108 | 109 | // GE Diff Gradient Cycling Mode 110 | #define kGE_DIFF_CYCLING_UNKNOWN -1 111 | #define kGE_DIFF_CYCLING_OFF 0 112 | #define kGE_DIFF_CYCLING_ALLTR 1 113 | #define kGE_DIFF_CYCLING_2TR 2 114 | #define kGE_DIFF_CYCLING_3TR 3 115 | #define kGE_DIFF_CYCLING_SPOFF 100 116 | 117 | // GE phase encoding 118 | #define kGE_PHASE_ENCODING_POLARITY_UNKNOWN -1 119 | #define kGE_PHASE_ENCODING_POLARITY_UNFLIPPED 0 120 | #define kGE_PHASE_ENCODING_POLARITY_FLIPPED 4 121 | #define kGE_SLICE_ORDER_UNKNOWN -1 122 | #define kGE_SLICE_ORDER_TOP_DOWN 0 123 | #define kGE_SLICE_ORDER_BOTTOM_UP 2 124 | 125 | // #define kGE_PHASE_DIRECTION_CENTER_OUT_REV 3 126 | // #define kGE_PHASE_DIRECTION_CENTER_OUT 4 127 | 128 | // EXIT_SUCCESS 0 129 | // EXIT_FAILURE 1 130 | #define kEXIT_NO_VALID_FILES_FOUND 2 131 | #define kEXIT_REPORT_VERSION 3 132 | #define kEXIT_CORRUPT_FILE_FOUND 4 133 | #define kEXIT_INPUT_FOLDER_INVALID 5 134 | #define kEXIT_OUTPUT_FOLDER_INVALID 6 135 | #define kEXIT_OUTPUT_FOLDER_READ_ONLY 7 136 | #define kEXIT_SOME_OK_SOME_BAD 8 137 | #define kEXIT_RENAME_ERROR 9 138 | #define kEXIT_INCOMPLETE_VOLUMES_FOUND 10 // issue 515 139 | #define kEXIT_NOMINAL 11 // did not expect to convert files 140 | 141 | // 0043,10A3 ---: PSEUDOCONTINUOUS 142 | // 0043,10A4 ---: 3D pulsed continuous ASL technique 143 | #define kASL_FLAG_NONE 0 144 | #define kASL_FLAG_GE_3DPCASL 1 145 | #define kASL_FLAG_GE_3DCASL 2 146 | #define kASL_FLAG_GE_PSEUDOCONTINUOUS 4 147 | #define kASL_FLAG_GE_CONTINUOUS 8 148 | #define kASL_FLAG_PHILIPS_CONTROL 16 149 | #define kASL_FLAG_PHILIPS_LABEL 32 150 | #define kASL_FLAG_GE_PULSED 64 151 | 152 | // for spoiling 0018,9016 153 | #define kSPOILING_UNKOWN -1 154 | #define kSPOILING_NONE 0 155 | #define kSPOILING_RF 1 156 | #define kSPOILING_GRADIENT 2 157 | #define kSPOILING_RF_AND_GRADIENT 3 158 | 159 | static const int kSliceOrientUnknown = 0; 160 | static const int kSliceOrientTra = 1; 161 | static const int kSliceOrientSag = 2; 162 | static const int kSliceOrientCor = 3; 163 | static const int kSliceOrientMosaicNegativeDeterminant = 4; 164 | static const int kCompressNone = 0; 165 | static const int kCompressYes = 1; 166 | static const int kCompressC3 = 2; // obsolete JPEG lossless 167 | static const int kCompress50 = 3; // obsolete JPEG lossy 168 | static const int kCompressRLE = 4; // run length encoding 169 | static const int kCompressPMSCT_RLE1 = 5; // see rle2img: Philips/ELSCINT1 run-length compression 07a1,1011= PMSCT_RLE1 170 | static const int kCompressJPEGLS = 6; // LoCo JPEG-LS 171 | static const int kMaxOverlay = 16; // even group values 0x6000..0x601E 172 | #ifdef myEnableJasper 173 | static const int kCompressSupport = kCompressYes; // JASPER for JPEG2000 174 | #else 175 | #ifdef myDisableOpenJPEG 176 | static const int kCompressSupport = kCompressNone; // no decompressor 177 | #else 178 | static const int kCompressSupport = kCompressYes; // OPENJPEG for JPEG2000 179 | #endif 180 | #endif 181 | 182 | // Maximum number of dimensions for .dimensionIndexValues, i.e. possibly the 183 | // number of axes in the output .nii. 184 | static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; 185 | 186 | // Maximum supported number of entries in DeidentificationMethodCodeSequence 187 | // Any additional will be ignored 188 | static const uint8_t MAX_DEID_CS = 10; 189 | struct TDeIDCodeSequence { 190 | char CodeValue[kDICOMStr]; 191 | char CodeMeaning[kDICOMStrLarge]; 192 | char CodingSchemeDesignator[kDICOMStr]; 193 | char CodingSchemeVersion[kDICOMStr]; 194 | }; 195 | 196 | struct TDTI { 197 | float V[4]; 198 | // int totalSlicesIn4DOrder; 199 | }; 200 | struct TDTI4D { 201 | struct TDTI S[kMaxDTI4D]; 202 | int sliceOrder[kMaxSlice2D]; // [7,3,2] means the first slice on disk should be moved to 7th position 203 | int gradDynVol[kMaxDTI4D]; // used to parse dimensions of Philips data, e.g. file with multiple dynamics, echoes, phase+magnitude 204 | // int fragmentOffset[kMaxDTI4D], fragmentLength[kMaxDTI4D]; //for images with multiple compressed fragments 205 | float frameReferenceTime[kMaxDTI4D], frameDuration[kMaxDTI4D], decayFactor[kMaxDTI4D], volumeOnsetTime[kMaxDTI4D], triggerDelayTime[kMaxDTI4D], TE[kMaxDTI4D], TR[kMaxDTI4D], RWVScale[kMaxDTI4D], RWVIntercept[kMaxDTI4D], intenScale[kMaxDTI4D], intenIntercept[kMaxDTI4D], intenScalePhilips[kMaxDTI4D]; 206 | bool isReal[kMaxDTI4D]; 207 | bool isImaginary[kMaxDTI4D]; 208 | bool isPhase[kMaxDTI4D]; 209 | float repetitionTimeExcitation, repetitionTimeInversion; 210 | char deidentificationMethod[kDICOMStr]; 211 | struct TDeIDCodeSequence deID_CS[MAX_DEID_CS]; 212 | }; 213 | 214 | #ifdef _MSC_VER // Microsoft nomenclature for packed structures is different... 215 | #pragma pack(2) 216 | typedef struct { 217 | char name[64]; // null-terminated 218 | int32_t vm; 219 | char vr[4]; // possibly nul-term string 220 | int32_t syngodt; // ?? 221 | int32_t nitems; // number of items in CSA 222 | int32_t xx; // maybe == 77 or 205 223 | } TCSAtag; // Siemens csa tag structure 224 | typedef struct { 225 | int32_t xx1, xx2_Len, xx3_77, xx4; 226 | } TCSAitem; // Siemens csa item structure 227 | #pragma pack() 228 | #else 229 | typedef struct __attribute__((packed)) { 230 | char name[64]; // null-terminated 231 | int32_t vm; 232 | char vr[4]; // possibly nul-term string 233 | int32_t syngodt; // ?? 234 | int32_t nitems; // number of items in CSA 235 | int32_t xx; // maybe == 77 or 205 236 | } TCSAtag; // Siemens csa tag structure 237 | typedef struct __attribute__((packed)) { 238 | int32_t xx1, xx2_Len, xx3_77, xx4; 239 | } TCSAitem; // Siemens csa item structure 240 | #endif 241 | struct TCSAdata { 242 | float sliceTiming[kMaxEPI3D], dtiV[4], sliceNormV[4], bandwidthPerPixelPhaseEncode, sliceMeasurementDuration, tablePos[4]; 243 | int coilNumber, numDti, SeriesHeader_offset, SeriesHeader_length, multiBandFactor, sliceOrder, slice_start, slice_end, mosaicSlices, protocolSliceNumber1, phaseEncodingDirectionPositive; 244 | bool isPhaseMap; 245 | char bidsDataType[kDICOMStr]; // anat, func, dwi 246 | char bidsEntitySuffix[kDICOMStrLarge]; // anat, func, dwi 247 | char bidsTask[kDICOMStr]; // rest, naming40 248 | }; 249 | 250 | struct TDICOMdata { 251 | long seriesNum; 252 | int xyzDim[5]; 253 | uint32_t coilCrc, seriesUidCrc, instanceUidCrc; 254 | int overlayStart[kMaxOverlay]; 255 | int postLabelDelay, shimGradientX, shimGradientY, shimGradientZ, phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfTR, numberOfImagesInGridUIH, numberOfDiffusionT2GE, numberOfDiffusionDirectionGE, tensorFileGE, diffCyclingModeGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, frequencyEncodingSteps, phaseEncodingStepsOutOfPlane, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel, locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; 256 | float compressedSensingFactor, xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, scatterFraction, percentSampling, waterFatShift, numberOfAverages, patientSize, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4], velocityEncodeScaleGE; 257 | float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; 258 | float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; // PET ISOTOPE MODULE ATTRIBUTES (C.8-57) 259 | float frameReferenceTime, frameDuration, ecat_isotope_halflife, ecat_dosage; 260 | float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. 261 | double imagingFrequency, acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; 262 | char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], tracerRadionuclide[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr], reconstructionMethod[kDICOMStr], transferSyntax[kDICOMStr]; 263 | char prescanReuseString[kDICOMStr], imageOrientationText[kDICOMStr], pulseSequenceName[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], stationName[kDICOMStr], studyDescription[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[kDICOMStr], imageTypeText[kDICOMStr], imageType[kDICOMStr], institutionalDepartmentName[kDICOMStr], manufacturersModelName[kDICOMStr], patientID[kDICOMStr], patientOrient[kDICOMStr], patientName[kDICOMStr], accessionNumber[kDICOMStr], seriesDescription[kDICOMStr], studyID[kDICOMStr], sequenceName[kDICOMStr], protocolName[kDICOMStr], sequenceVariant[kDICOMStr], scanningSequence[kDICOMStr], patientBirthDate[kDICOMStr], patientAge[kDICOMStr], studyDate[kDICOMStr], studyTime[kDICOMStr]; 264 | char deepLearningText[kDICOMStrLarge], scanOptions[kDICOMStrLarge], institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge]; 265 | uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; 266 | int deID_CS_n; 267 | struct TCSAdata CSA; 268 | bool isDeepLearning, isVariableFlipAngle, isQuadruped, isRealIsPhaseMapHz, isPrivateCreatorRemap, isHasOverlay, isEPI, isIR, isPartialFourier, isDiffusion, isVectorFromBMatrix, isRawDataStorage, isGrayscaleSoftcopyPresentationState, isStackableSeries, isCoilVaries, isNonParallelSlices, isBVecWorldCoordinates, isSegamiOasis, isXA10A, isScaleOrTEVaries, isScaleVariesEnh, isDerived, isXRay, isMultiEcho, isValid, is3DAcq, is2DAcq, isExplicitVR, isLittleEndian, isPlanarRGB, isSigned, isHasPhase, isHasImaginary, isHasReal, isHasMagnitude, isHasMixed, isFloat, isResampled, isLocalizer; 269 | char phaseEncodingRC, patientSex; 270 | }; 271 | 272 | struct TDCMprefs { 273 | int isVerbose, compressFlag, isIgnoreTriggerTimes; 274 | }; 275 | 276 | size_t nii_ImgBytes(struct nifti_1_header hdr); 277 | void setDefaultPrefs(struct TDCMprefs *prefs); 278 | int isSameFloatGE(float a, float b); 279 | void getFileNameX(char *pathParent, const char *path, int maxLen); 280 | struct TDICOMdata readDICOMv(char *fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D); 281 | struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D *dti4D); 282 | struct TDICOMdata readDICOM(char *fname); 283 | struct TDICOMdata clear_dicom_data(void); 284 | struct TDICOMdata nii_readParRec(char *parname, int isVerbose, struct TDTI4D *dti4D, bool isReadPhase); 285 | unsigned char *nii_flipY(unsigned char *bImg, struct nifti_1_header *h); 286 | unsigned char *nii_flipImgY(unsigned char *bImg, struct nifti_1_header *hdr); 287 | unsigned char *nii_flipZ(unsigned char *bImg, struct nifti_1_header *h); 288 | //*unsigned char * nii_reorderSlices(unsigned char* bImg, struct nifti_1_header *h, struct TDTI4D *dti4D); 289 | void changeExt(char *file_name, const char *ext); 290 | unsigned char *nii_planar2rgb(unsigned char *bImg, struct nifti_1_header *hdr, int isPlanar); 291 | int isDICOMfile(const char *fname); // 0=not DICOM, 1=DICOM, 2=NOTSURE(not part 10 compliant) 292 | void setQSForm(struct nifti_1_header *h, mat44 Q44i, bool isVerbose); 293 | int headerDcm2Nii2(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose); 294 | int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeSForm); 295 | unsigned char *nii_loadImgXL(char *imgname, struct nifti_1_header *hdr, struct TDICOMdata dcm, bool iVaries, int compressFlag, int isVerbose, struct TDTI4D *dti4D); 296 | #ifdef USING_DCM2NIIXFSWRAPPER 297 | void remove_specialchars(char *buf); 298 | #endif 299 | #ifdef __cplusplus 300 | } 301 | #endif 302 | 303 | #endif 304 | -------------------------------------------------------------------------------- /src/dcm2niix/nii_foreign.cpp: -------------------------------------------------------------------------------- 1 | #include "nii_foreign.h" 2 | #include "nifti1.h" 3 | #include "nifti1_io_core.h" 4 | #include "nii_dicom.h" 5 | #include "nii_dicom_batch.h" 6 | // #include "nifti1_io_core.h" 7 | #include "print.h" 8 | #include 9 | #include //requires VS 2015 or later 10 | #include 11 | #include 12 | #include 13 | #include 14 | #ifdef _MSC_VER 15 | #include 16 | #define getcwd _getcwd 17 | #define chdir _chrdir 18 | #include "io.h" 19 | #include 20 | // #define snprintMessage _snprintMessage 21 | // #define vsnprintMessage _vsnprintMessage 22 | #define strcasecmp _stricmp 23 | #define strncasecmp _strnicmp 24 | #else 25 | #include 26 | #endif 27 | 28 | #ifndef Float32 29 | #define Float32 float 30 | #endif 31 | #ifndef uint32 32 | #define uint32 uint32_t 33 | #endif 34 | 35 | #ifdef __GNUC__ 36 | #define PACK(...) __VA_ARGS__ __attribute__((__packed__)) 37 | #else 38 | #define PACK(...) __pragma(pack(push, 1)) __VA_ARGS__ __pragma(pack(pop)) 39 | #endif 40 | 41 | /*nii_readEcat7 2017 by Chris Rorden, BSD license 42 | http://www.turkupetcentre.net/software/libdoc/libtpcimgio/ecat7_8h_source.html#l00060 43 | http://www.turkupetcentre.net/software/libdoc/libtpcimgio/ecat7r_8c_source.html#l00717 44 | http://xmedcon.sourcearchive.com/documentation/0.10.7-1build1/ecat7_8h_source.html 45 | https://github.com/BIC-MNI/minc-tools/tree/master/conversion/ecattominc 46 | https://github.com/nipy/nibabel/blob/ec4567fb09b4472c5a4bb9a13dbcc9eb0a63d875/nibabel/ecat.py 47 | */ 48 | 49 | void strClean(char *cString) { 50 | int len = (int)strlen(cString); 51 | if (len < 1) 52 | return; 53 | for (int i = 0; i < len; i++) { 54 | char c = cString[i]; 55 | if ((c < char(32)) || (c == char(127)) || (c == char(255))) 56 | cString[i] = 0; 57 | if ((c == ' ') || (c == ',') || (c == '^') || (c == '/') || (c == '\\') || (c == '%') || (c == '*')) 58 | cString[i] = '_'; 59 | } 60 | } 61 | 62 | unsigned char *readEcat7(const char *fname, struct TDICOMdata *dcm, struct nifti_1_header *hdr, struct TDCMopts opts, bool isWarnIfNotEcat) { 63 | // data type 64 | #define ECAT7_BYTE 1 65 | #define ECAT7_VAXI2 2 66 | #define ECAT7_VAXI4 3 67 | #define ECAT7_VAXR4 4 68 | #define ECAT7_IEEER4 5 69 | #define ECAT7_SUNI2 6 70 | #define ECAT7_SUNI4 7 71 | // file types 72 | // #define ECAT7_UNKNOWN 0 73 | #define ECAT7_2DSCAN 1 74 | #define ECAT7_IMAGE16 2 75 | #define ECAT7_ATTEN 3 76 | #define ECAT7_2DNORM 4 77 | #define ECAT7_POLARMAP 5 78 | #define ECAT7_VOLUME8 6 79 | #define ECAT7_VOLUME16 7 80 | #define ECAT7_PROJ 8 81 | #define ECAT7_PROJ16 9 82 | #define ECAT7_IMAGE8 10 83 | #define ECAT7_3DSCAN 11 84 | #define ECAT7_3DSCAN8 12 85 | #define ECAT7_3DNORM 13 86 | #define ECAT7_3DSCANFIT 14 87 | PACK(typedef struct { 88 | char magic[14], original_filename[32]; 89 | uint16_t sw_version, system_type, file_type; 90 | char serial_number[10]; 91 | uint32 scan_start_time; 92 | char isotope_name[8]; 93 | Float32 isotope_halflife; 94 | char radiopharmaceutical[32]; 95 | Float32 gantry_tilt, gantry_rotation, bed_elevation, intrinsic_tilt; 96 | int16_t wobble_speed, transm_source_type; 97 | Float32 distance_scanned, transaxial_fov; 98 | uint16_t angular_compression, coin_samp_mode, axial_samp_mode; 99 | Float32 ecat_calibration_factor; 100 | uint16_t calibration_unitS, calibration_units_type, compression_code; 101 | char study_type[12], patient_id[16], accession_number[16], patient_name[32], patient_sex, patient_dexterity; 102 | Float32 patient_age, patient_height, patient_weight; 103 | uint32 patient_birth_date; 104 | char physician_name[32], operator_name[32], study_description[32]; 105 | uint16_t acquisition_type, patient_orientation; 106 | char facility_name[20]; 107 | uint16_t num_planes, num_frames, num_gates, num_bed_pos; 108 | Float32 init_bed_position; 109 | Float32 bed_position[15]; 110 | Float32 plane_separation; 111 | uint16_t lwr_sctr_thres, lwr_true_thres, upr_true_thres; 112 | char user_process_code[10]; 113 | uint16_t acquisition_mode; 114 | Float32 bin_size, branching_fraction; 115 | uint32 dose_start_time; 116 | Float32 dosage, well_counter_corr_factor; 117 | char data_units[32]; 118 | uint16_t septa_state; 119 | char fill[12]; 120 | }) 121 | ecat_main_hdr; 122 | PACK(typedef struct { 123 | int16_t data_type, num_dimensions, x_dimension, y_dimension, z_dimension; 124 | Float32 x_offset, y_offset, z_offset, recon_zoom, scale_factor; 125 | int16_t image_min, image_max; 126 | Float32 x_pixel_size, y_pixel_size, z_pixel_size; 127 | int32_t frame_duration, frame_start_time; 128 | int16_t filter_code; 129 | Float32 x_resolution, y_resolution, z_resolution, num_r_elements, num_angles, z_rotation_angle, decay_corr_fctr; 130 | int32_t processing_code, gate_duration, r_wave_offset, num_accepted_beats; 131 | Float32 filter_cutoff_frequenc, filter_resolution, filter_ramp_slope; 132 | int16_t filter_order; 133 | Float32 filter_scatter_fraction, filter_scatter_slope; 134 | char annotation[40]; 135 | Float32 mtx[9], rfilter_cutoff, rfilter_resolution; 136 | int16_t rfilter_code, rfilter_order; 137 | Float32 zfilter_cutoff, zfilter_resolution; 138 | int16_t zfilter_code, zfilter_order; 139 | Float32 mtx_1_4, mtx_2_4, mtx_3_4; 140 | int16_t scatter_type, recon_type, recon_views, fill_cti[87], fill_user[49]; 141 | }) 142 | ecat_img_hdr; 143 | PACK(typedef struct { 144 | int32_t hdr[4], r[31][4]; 145 | }) 146 | ecat_list_hdr; 147 | bool swapEndian = false; 148 | size_t n; 149 | FILE *f; 150 | ecat_main_hdr mhdr; 151 | f = fopen(fname, "rb"); 152 | if (f) 153 | n = fread(&mhdr, sizeof(mhdr), 1, f); 154 | if (!f || n != 1) { 155 | printMessage("Problem reading ECAT7 file!\n"); 156 | fclose(f); 157 | return NULL; 158 | } 159 | if ((mhdr.magic[0] != 'M') || (mhdr.magic[1] != 'A') || (mhdr.magic[2] != 'T') || (mhdr.magic[3] != 'R') || (mhdr.magic[4] != 'I') || (mhdr.magic[5] != 'X')) { 160 | if (isWarnIfNotEcat) 161 | printMessage("Signature not 'MATRIX' (ECAT7): '%s'\n", fname); 162 | fclose(f); 163 | return NULL; 164 | } 165 | swapEndian = mhdr.file_type > 255; 166 | if (swapEndian) { 167 | nifti_swap_2bytes(2, &mhdr.sw_version); 168 | nifti_swap_2bytes(1, &mhdr.file_type); 169 | // nifti_swap_2bytes(1, &mhdr.num_frames); 170 | nifti_swap_4bytes(1, &mhdr.ecat_calibration_factor); 171 | nifti_swap_4bytes(1, &mhdr.isotope_halflife); 172 | nifti_swap_4bytes(2, &mhdr.dosage); 173 | } 174 | if ((mhdr.file_type < ECAT7_2DSCAN) || (mhdr.file_type > ECAT7_3DSCANFIT)) { 175 | printMessage("Unknown ECAT file type %d\n", mhdr.file_type); 176 | fclose(f); 177 | return NULL; 178 | } 179 | // read list matrix 180 | ecat_list_hdr lhdr; 181 | fseek(f, 512, SEEK_SET); 182 | size_t nRead = fread(&lhdr, sizeof(lhdr), 1, f); 183 | if (nRead != 1) { 184 | printMessage("Error reading ECAT file (list header)\n"); 185 | fclose(f); 186 | return NULL; 187 | } 188 | if (swapEndian) 189 | nifti_swap_4bytes(128, &lhdr.hdr[0]); 190 | // offset to first image 191 | int img_StartBytes = lhdr.r[0][1] * 512; 192 | // load image header for first image 193 | fseek(f, img_StartBytes - 512, SEEK_SET); // image header is block immediately before image 194 | ecat_img_hdr ihdr; 195 | nRead = fread(&ihdr, sizeof(ihdr), 1, f); 196 | if (nRead != 1) { 197 | printMessage("Error reading ECAT file (image header)\n"); 198 | fclose(f); 199 | return NULL; 200 | } 201 | if (swapEndian) { 202 | nifti_swap_2bytes(5, &ihdr.data_type); 203 | nifti_swap_4bytes(5, &ihdr.x_offset); 204 | nifti_swap_2bytes(2, &ihdr.image_min); 205 | nifti_swap_4bytes(5, &ihdr.x_pixel_size); 206 | nifti_swap_2bytes(1, &ihdr.filter_code); 207 | nifti_swap_4bytes(14, &ihdr.x_resolution); 208 | nifti_swap_2bytes(1, &ihdr.filter_order); 209 | nifti_swap_4bytes(2, &ihdr.filter_scatter_fraction); 210 | nifti_swap_4bytes(11, &ihdr.mtx); 211 | nifti_swap_2bytes(2, &ihdr.rfilter_code); 212 | nifti_swap_4bytes(2, &ihdr.zfilter_cutoff); 213 | nifti_swap_2bytes(2, &ihdr.zfilter_code); 214 | nifti_swap_4bytes(3, &ihdr.mtx_1_4); 215 | nifti_swap_2bytes(3, &ihdr.scatter_type); 216 | } 217 | if ((ihdr.data_type != ECAT7_BYTE) && (ihdr.data_type != ECAT7_SUNI2) && (ihdr.data_type != ECAT7_SUNI4)) { 218 | printMessage("Unknown or unsupported ECAT data type %d\n", ihdr.data_type); 219 | fclose(f); 220 | return NULL; 221 | } 222 | int bytesPerVoxel = 2; 223 | if (ihdr.data_type == ECAT7_BYTE) 224 | bytesPerVoxel = 1; 225 | if (ihdr.data_type == ECAT7_SUNI4) 226 | bytesPerVoxel = 4; 227 | // next: read offsets for each volume: data not saved sequentially (each volume preceded by its own ecat_img_hdr) 228 | int num_vol = 0; 229 | bool isAbort = false; 230 | bool isScaleFactorVaries = false; 231 | #define kMaxVols 16000 232 | size_t *imgOffsets = (size_t *)malloc(sizeof(size_t) * (kMaxVols)); 233 | float *imgSlopes = (float *)malloc(sizeof(float) * (kMaxVols)); 234 | ecat_img_hdr ihdrN; 235 | while ((lhdr.hdr[0] + lhdr.hdr[3]) == 31) { // while valid list 236 | if (num_vol > 0) { // read the next list 237 | fseek(f, 512 * (lhdr.hdr[1] - 1), SEEK_SET); 238 | nRead = fread(&lhdr, 512, 1, f); 239 | if (nRead != 1) { 240 | printMessage("Error reading ECAT file (yet another list header)\n"); 241 | fclose(f); 242 | return NULL; 243 | } 244 | if (swapEndian) 245 | nifti_swap_4bytes(128, &lhdr.hdr[0]); 246 | } 247 | if ((lhdr.hdr[0] + lhdr.hdr[3]) != 31) 248 | break; // if valid list 249 | if (lhdr.hdr[3] < 1) 250 | break; 251 | for (int k = 0; k < lhdr.hdr[3]; k++) { 252 | // check images' ecat_img_hdr matches first 253 | fseek(f, (lhdr.r[k][1] - 1) * 512, SEEK_SET); // image header is block immediately before image 254 | nRead = fread(&ihdrN, sizeof(ihdrN), 1, f); 255 | if (nRead != 1) { 256 | printMessage("Error reading ECAT file (yet another image header)\n"); 257 | fclose(f); 258 | return NULL; 259 | } 260 | if (swapEndian) { 261 | nifti_swap_2bytes(5, &ihdrN.data_type); 262 | nifti_swap_4bytes(5, &ihdrN.x_offset); 263 | nifti_swap_2bytes(2, &ihdrN.image_min); 264 | nifti_swap_4bytes(5, &ihdrN.x_pixel_size); 265 | nifti_swap_2bytes(1, &ihdrN.filter_code); 266 | nifti_swap_4bytes(14, &ihdrN.x_resolution); 267 | nifti_swap_2bytes(1, &ihdrN.filter_order); 268 | nifti_swap_4bytes(2, &ihdrN.filter_scatter_fraction); 269 | nifti_swap_4bytes(11, &ihdrN.mtx); 270 | nifti_swap_2bytes(2, &ihdrN.rfilter_code); 271 | nifti_swap_4bytes(2, &ihdrN.zfilter_cutoff); 272 | nifti_swap_2bytes(2, &ihdrN.zfilter_code); 273 | nifti_swap_4bytes(3, &ihdrN.mtx_1_4); 274 | nifti_swap_2bytes(3, &ihdrN.scatter_type); 275 | } 276 | if (ihdr.scale_factor != ihdrN.scale_factor) 277 | isScaleFactorVaries = true; 278 | if ((ihdr.data_type != ihdrN.data_type) || (ihdr.x_dimension != ihdrN.x_dimension) || (ihdr.y_dimension != ihdrN.y_dimension) || (ihdr.z_dimension != ihdrN.z_dimension)) { 279 | printError("Error: ECAT volumes have varying image dimensions\n"); 280 | isAbort = true; 281 | } 282 | if (num_vol < kMaxVols) { 283 | imgOffsets[num_vol] = (size_t)lhdr.r[k][1]; 284 | imgSlopes[num_vol] = ihdrN.scale_factor; 285 | } 286 | num_vol++; 287 | } 288 | if ((lhdr.hdr[0] > 0) || (isAbort)) 289 | break; // this list contains empty volumes: all lists have been read 290 | } // read all image offsets 291 | // report error reading image offsets 292 | if ((num_vol < 1) || (isAbort) || (num_vol >= kMaxVols)) { 293 | printMessage("Failure to extract ECAT7 images\n"); 294 | if (num_vol >= kMaxVols) 295 | printMessage("Increase kMaxVols"); 296 | fclose(f); 297 | free(imgOffsets); 298 | free(imgSlopes); 299 | return NULL; 300 | } 301 | if ((isScaleFactorVaries) && (bytesPerVoxel != 2)) { 302 | printError("ECAT scale factor varies between volumes (check for updates) '%s'\n", fname); 303 | fclose(f); 304 | free(imgOffsets); 305 | free(imgSlopes); 306 | return NULL; 307 | } 308 | // load image data 309 | unsigned char *img = NULL; 310 | if ((isScaleFactorVaries) && (bytesPerVoxel == 2)) { // we need to convert volumes from 16-bit to 32-bit to preserve scaling factors 311 | int num_vox = ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension; 312 | size_t bytesPerVolumeIn = (size_t)(num_vox * bytesPerVoxel); // bytesPerVoxel == 2 313 | unsigned char *imgIn = (unsigned char *)malloc(bytesPerVolumeIn); 314 | int16_t *img16i = (int16_t *)imgIn; 315 | bytesPerVoxel = 4; 316 | size_t bytesPerVolume = (size_t)(num_vox * bytesPerVoxel); 317 | img = (unsigned char *)malloc((size_t)(bytesPerVolume * num_vol)); 318 | float *img32 = (float *)img; 319 | for (int v = 0; v < num_vol; v++) { 320 | fseek(f, imgOffsets[v] * 512, SEEK_SET); 321 | nRead = fread(&imgIn[0], 1, bytesPerVolumeIn, f); 322 | if (nRead != bytesPerVolumeIn) { 323 | printMessage("%zu Error reading ECAT file (offset %zu bytes %zu)\n", nRead, imgOffsets[v] * 512, bytesPerVolumeIn); 324 | fclose(f); 325 | return NULL; 326 | } 327 | if (swapEndian) 328 | nifti_swap_2bytes(num_vox, imgIn); 329 | int volOffset = v * num_vox; 330 | float scale = imgSlopes[v] * mhdr.ecat_calibration_factor; 331 | for (int i = 0; i < num_vox; i++) 332 | img32[i + volOffset] = (img16i[i] * scale); 333 | } 334 | // we have applied the scale factors to the data, so eliminate them 335 | ihdr.scale_factor = 1.0; 336 | mhdr.ecat_calibration_factor = 1.0; 337 | 338 | } else { // if isScaleFactorVaries else simple conversion 339 | size_t bytesPerVolume = ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension * bytesPerVoxel; 340 | img = (unsigned char *)malloc(bytesPerVolume * num_vol); 341 | for (int v = 0; v < num_vol; v++) { 342 | fseek(f, imgOffsets[v] * 512, SEEK_SET); 343 | size_t sz = fread(&img[v * bytesPerVolume], 1, bytesPerVolume, f); 344 | if (sz != bytesPerVolume) { 345 | free(img); 346 | return NULL; 347 | } 348 | } 349 | if ((swapEndian) && (bytesPerVoxel == 2)) 350 | nifti_swap_2bytes(ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension * num_vol, img); 351 | if ((swapEndian) && (bytesPerVoxel == 4)) 352 | nifti_swap_4bytes(ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension * num_vol, img); 353 | } 354 | printWarning("ECAT support VERY experimental (Spatial transforms unknown)\n"); 355 | free(imgOffsets); 356 | free(imgSlopes); 357 | fclose(f); 358 | // fill DICOM header 359 | float timeBetweenVolumes = ihdr.frame_duration; 360 | if (num_vol > 1) 361 | timeBetweenVolumes = (float)(ihdrN.frame_start_time - ihdr.frame_start_time) / (float)(num_vol - 1); 362 | // copy and clean strings (ECAT can use 0x0D as a string terminator) 363 | strncpy(dcm->patientName, mhdr.patient_name, 32); 364 | strncpy(dcm->patientID, mhdr.patient_id, 16); 365 | strncpy(dcm->accessionNumber, mhdr.accession_number, 16); 366 | strncpy(dcm->seriesDescription, mhdr.study_description, 32); 367 | strncpy(dcm->protocolName, mhdr.study_type, 12); 368 | strncpy(dcm->imageComments, mhdr.isotope_name, 8); 369 | strncpy(dcm->procedureStepDescription, mhdr.radiopharmaceutical, 32); 370 | strClean(dcm->patientName); 371 | strClean(dcm->patientID); 372 | strClean(dcm->accessionNumber); 373 | strClean(dcm->seriesDescription); 374 | strClean(dcm->protocolName); 375 | strClean(dcm->imageComments); 376 | strClean(dcm->procedureStepDescription); 377 | dcm->ecat_dosage = mhdr.dosage; 378 | dcm->ecat_isotope_halflife = mhdr.isotope_halflife; 379 | if (opts.isVerbose) { 380 | printMessage("ECAT7 details for '%s'\n", fname); 381 | printMessage(" Software version %d\n", mhdr.sw_version); 382 | printMessage(" System Type %d\n", mhdr.system_type); 383 | printMessage(" Frame duration %dms\n", ihdr.frame_duration); 384 | printMessage(" Time between volumes %gms\n", timeBetweenVolumes); 385 | printMessage(" Patient name '%s'\n", dcm->patientName); 386 | printMessage(" Patient ID '%s'\n", dcm->patientID); 387 | printMessage(" Accession number '%s'\n", dcm->accessionNumber); 388 | printMessage(" Study description '%s'\n", dcm->seriesDescription); 389 | printMessage(" Study type '%s'\n", dcm->protocolName); 390 | printMessage(" Isotope name '%s'\n", dcm->imageComments); 391 | printMessage(" Isotope halflife %gs\n", mhdr.isotope_halflife); 392 | printMessage(" Radiopharmaceutical '%s'\n", dcm->procedureStepDescription); 393 | printMessage(" Dosage %gbequerels/cc\n", mhdr.dosage); 394 | if (!isScaleFactorVaries) { 395 | printMessage(" Scale factor %12.12g\n", ihdr.scale_factor); 396 | printMessage(" ECAT calibration factor %8.12g\n", mhdr.ecat_calibration_factor); 397 | } 398 | printMessage(" NIfTI scale slope %12.12g\n", ihdr.scale_factor * mhdr.ecat_calibration_factor); 399 | } 400 | dcm->manufacturer = kMANUFACTURER_SIEMENS; 401 | // dcm->manufacturersModelName = itoa(mhdr.system_type); 402 | snprintf(dcm->manufacturersModelName, kDICOMStr, "%d", mhdr.system_type); 403 | dcm->bitsAllocated = bytesPerVoxel * 8; 404 | if (isScaleFactorVaries) 405 | dcm->isFloat = true; 406 | dcm->bitsStored = 15; // ensures 16-bit images saved as INT16 not UINT16 407 | dcm->samplesPerPixel = 1; 408 | dcm->xyzMM[1] = ihdr.x_pixel_size * 10.0; // cm -> mm 409 | dcm->xyzMM[2] = ihdr.y_pixel_size * 10.0; // cm -> mm 410 | dcm->xyzMM[3] = ihdr.z_pixel_size * 10.0; // cm -> mm 411 | dcm->TR = timeBetweenVolumes; 412 | dcm->xyzDim[1] = ihdr.x_dimension; 413 | dcm->xyzDim[2] = ihdr.y_dimension; 414 | dcm->xyzDim[3] = ihdr.z_dimension; 415 | dcm->xyzDim[4] = num_vol; 416 | // create a NIfTI header 417 | headerDcm2Nii(*dcm, hdr, false); 418 | // here we mimic SPM's spatial starting estimate SForm 419 | mat44 m44; 420 | LOAD_MAT44(m44, -hdr->pixdim[1], 0.0f, 0.0f, ((float)dcm->xyzDim[1] - 2.0) / 2.0 * dcm->xyzMM[1], 421 | 0.0f, -hdr->pixdim[2], 0.0f, ((float)dcm->xyzDim[2] - 2.0) / 2.0 * dcm->xyzMM[2], 422 | 0.0f, 0.0f, -hdr->pixdim[3], ((float)dcm->xyzDim[3] - 2.0) / 2.0 * dcm->xyzMM[3]); 423 | setQSForm(hdr, m44, false); 424 | // make sure image does not include a spatial matrix 425 | bool isMatrix = false; 426 | for (int i = 0; i < 9; i++) 427 | if (ihdr.mtx[i] != 0.0) 428 | isMatrix = true; 429 | if (isMatrix) 430 | printWarning("ECAT volume appears to store spatial transformation matrix (please check for updates)\n"); 431 | hdr->scl_slope = ihdr.scale_factor * mhdr.ecat_calibration_factor; 432 | if (mhdr.gantry_tilt != 0.0) 433 | printMessage("Warning: ECAT gantry tilt not supported %g\n", mhdr.gantry_tilt); 434 | return img; 435 | } 436 | 437 | int convert_foreign(const char *fn, struct TDCMopts opts) { 438 | struct nifti_1_header hdr; 439 | struct TDICOMdata dcm = clear_dicom_data(); 440 | unsigned char *img = NULL; 441 | img = readEcat7(fn, &dcm, &hdr, opts, false); // false: silent, do not report if file is not ECAT format 442 | if (!img) 443 | return EXIT_FAILURE; 444 | char niiFilename[1024]; 445 | int ret = nii_createFilename(dcm, niiFilename, opts); 446 | if (ret != EXIT_SUCCESS) { 447 | printError("Failed to save ECAT as '%s'\n", niiFilename); 448 | return ret; 449 | } 450 | printMessage("Saving ECAT as '%s'\n", niiFilename); 451 | // struct TDTI4D dti4D; 452 | // nii_SaveBIDS(niiFilename, dcm, opts, &dti4D, &hdr, fn); 453 | nii_SaveBIDS(niiFilename, dcm, opts, &hdr, fn); 454 | ret = nii_saveNIIx(niiFilename, hdr, img, opts); 455 | free(img); 456 | return ret; 457 | } // convert_foreign() 458 | -------------------------------------------------------------------------------- /src/dcm2niix/jpg_0XC3.cpp: -------------------------------------------------------------------------------- 1 | #include "jpg_0XC3.h" 2 | #include "print.h" 3 | #include //requires VS 2015 or later 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | unsigned char readByte(unsigned char *lRawRA, long *lRawPos, long lRawSz) { 10 | unsigned char ret = 0x00; 11 | if (*lRawPos < lRawSz) 12 | ret = lRawRA[*lRawPos]; 13 | (*lRawPos)++; 14 | return ret; 15 | } // readByte() 16 | 17 | uint16_t readWord(unsigned char *lRawRA, long *lRawPos, long lRawSz) { 18 | return ((readByte(lRawRA, lRawPos, lRawSz) << 8) + readByte(lRawRA, lRawPos, lRawSz)); 19 | } // readWord() 20 | 21 | int readBit(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos) { // Read the next single bit 22 | int result = (lRawRA[*lRawPos] >> (7 - *lCurrentBitPos)) & 1; 23 | (*lCurrentBitPos)++; 24 | if (*lCurrentBitPos == 8) { 25 | (*lRawPos)++; 26 | *lCurrentBitPos = 0; 27 | } 28 | return result; 29 | } // readBit() 30 | 31 | int bitMask(int bits) { 32 | return ((2 << (bits - 1)) - 1); 33 | } // bitMask() 34 | 35 | int readBits(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos, int lNum) { // lNum: bits to read, not to exceed 16 36 | int result = lRawRA[*lRawPos]; 37 | result = (result << 8) + lRawRA[(*lRawPos) + 1]; 38 | result = (result << 8) + lRawRA[(*lRawPos) + 2]; 39 | result = (result >> (24 - *lCurrentBitPos - lNum)) & bitMask(lNum); // lCurrentBitPos is incremented from 1, so -1 40 | *lCurrentBitPos = *lCurrentBitPos + lNum; 41 | if (*lCurrentBitPos > 7) { 42 | *lRawPos = *lRawPos + (*lCurrentBitPos >> 3); // div 8 43 | *lCurrentBitPos = *lCurrentBitPos & 7; // mod 8 44 | } 45 | return result; 46 | } // readBits() 47 | 48 | struct HufTables { 49 | uint8_t SSSSszRA[18]; 50 | uint8_t LookUpRA[256]; 51 | int DHTliRA[32]; 52 | int DHTstartRA[32]; 53 | int HufSz[32]; 54 | int HufCode[32]; 55 | int HufVal[32]; 56 | int MaxHufSi; 57 | int MaxHufVal; 58 | }; // HufTables() 59 | 60 | int decodePixelDifference(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos, struct HufTables l) { 61 | int lByte = (lRawRA[*lRawPos] << *lCurrentBitPos) + (lRawRA[*lRawPos + 1] >> (8 - *lCurrentBitPos)); 62 | lByte = lByte & 255; 63 | int lHufValSSSS = l.LookUpRA[lByte]; 64 | if (lHufValSSSS < 255) { 65 | *lCurrentBitPos = l.SSSSszRA[lHufValSSSS] + *lCurrentBitPos; 66 | *lRawPos = *lRawPos + (*lCurrentBitPos >> 3); 67 | *lCurrentBitPos = *lCurrentBitPos & 7; 68 | } else { // full SSSS is not in the first 8-bits 69 | int lInput = lByte; 70 | int lInputBits = 8; 71 | (*lRawPos)++; // forward 8 bits = precisely 1 byte 72 | do { 73 | lInputBits++; 74 | lInput = (lInput << 1) + readBit(lRawRA, lRawPos, lCurrentBitPos); 75 | if (l.DHTliRA[lInputBits] != 0) { // if any entries with this length 76 | for (int lI = l.DHTstartRA[lInputBits]; lI <= (l.DHTstartRA[lInputBits] + l.DHTliRA[lInputBits] - 1); lI++) { 77 | if (lInput == l.HufCode[lI]) 78 | lHufValSSSS = l.HufVal[lI]; 79 | } // check each code 80 | } // if any entries with this length 81 | if ((lInputBits >= l.MaxHufSi) && (lHufValSSSS > 254)) { // exhausted options CR: added rev13 82 | lHufValSSSS = l.MaxHufVal; 83 | } 84 | } while (!(lHufValSSSS < 255)); // found; 85 | } // answer in first 8 bits 86 | // The HufVal is referred to as the SSSS in the Codec, so it is called 'lHufValSSSS' 87 | if (lHufValSSSS == 0) // NO CHANGE 88 | return 0; 89 | if (lHufValSSSS == 1) { 90 | if (readBit(lRawRA, lRawPos, lCurrentBitPos) == 0) 91 | return -1; 92 | else 93 | return 1; 94 | } 95 | if (lHufValSSSS == 16) { // ALL CHANGE 16 bit difference: Codec H.1.2.2 "No extra bits are appended after SSSS = 16 is encoded." Osiris fails here 96 | return 32768; 97 | } 98 | // to get here - there is a 2..15 bit difference 99 | int lDiff = readBits(lRawRA, lRawPos, lCurrentBitPos, lHufValSSSS); 100 | if (lDiff <= bitMask(lHufValSSSS - 1)) // add 101 | lDiff = lDiff - bitMask(lHufValSSSS); 102 | return lDiff; 103 | } // decodePixelDifference() 104 | 105 | unsigned char *decode_JPEG_SOF_0XC3(const char *fn, int skipBytes, bool verbose, int *dimX, int *dimY, int *bits, int *frames, int diskBytes) { 106 | // decompress JPEG image named "fn" where image data is located skipBytes into file. diskBytes is compressed size of image (set to 0 if unknown) 107 | // next line breaks MSVC 108 | #define abortGoto(...) \ 109 | do { \ 110 | printError(__VA_ARGS__); \ 111 | free(lRawRA); \ 112 | return NULL; \ 113 | } while (0) 114 | unsigned char *lImgRA8 = NULL; 115 | FILE *reader = fopen(fn, "rb"); 116 | int lSuccess = fseek(reader, 0, SEEK_END); 117 | long lRawSz = ftell(reader) - skipBytes; 118 | if ((diskBytes > 0) && (diskBytes < lRawSz)) // only if diskBytes is known and does not exceed length of file 119 | lRawSz = diskBytes; 120 | if ((lSuccess != 0) || (lRawSz <= 8)) { 121 | printError("Unable to load 0XC3 JPEG %s\n", fn); 122 | return NULL; // read failure 123 | } 124 | lSuccess = fseek(reader, skipBytes, SEEK_SET); // If successful, the function returns zero 125 | if (lSuccess != 0) { 126 | printError("Unable to open 0XC3 JPEG %s\n", fn); 127 | return NULL; // read failure 128 | } 129 | unsigned char *lRawRA = (unsigned char *)malloc(lRawSz); 130 | size_t lSz = fread(lRawRA, 1, lRawSz, reader); 131 | fclose(reader); 132 | if ((lSz < (size_t)lRawSz) || (lRawRA[0] != 0xFF) || (lRawRA[1] != 0xD8) || (lRawRA[2] != 0xFF)) { 133 | abortGoto("JPEG signature 0xFFD8FF not found at offset %d of %s\n", skipBytes, fn); // signature failure http://en.wikipedia.org/wiki/List_of_file_signatures 134 | } 135 | if (verbose) 136 | printMessage("JPEG signature 0xFFD8FF found at offset %d of %s\n", skipBytes, fn); 137 | // next: read header 138 | long lRawPos = 2; // Skip initial 0xFFD8, begin with third byte 139 | // long lRawPos = 0; //Skip initial 0xFFD8, begin with third byte 140 | unsigned char btS1, btS2, SOSse, SOSahal, btMarkerType, SOSns = 0x00; // tag 141 | unsigned char SOSpttrans = 0; 142 | unsigned char SOSss = 0; 143 | uint8_t SOFnf = 0; 144 | uint8_t SOFprecision = 0; 145 | uint16_t SOFydim = 0; 146 | uint16_t SOFxdim = 0; 147 | // long SOSarrayPos; //SOFarrayPos 148 | int lnHufTables = 0; 149 | int lFrameCount = 1; 150 | const int kmaxFrames = 4; 151 | struct HufTables l[kmaxFrames + 1]; 152 | do { // read each marker in the header 153 | do { 154 | btS1 = readByte(lRawRA, &lRawPos, lRawSz); 155 | if (btS1 != 0xFF) { 156 | abortGoto("JPEG header tag must begin with 0xFF\n"); 157 | } 158 | btMarkerType = readByte(lRawRA, &lRawPos, lRawSz); 159 | if ((btMarkerType == 0x01) || (btMarkerType == 0xFF) || ((btMarkerType >= 0xD0) && (btMarkerType <= 0xD7))) 160 | btMarkerType = 0; // only process segments with length fields 161 | 162 | } while ((lRawPos < lRawSz) && (btMarkerType == 0)); 163 | uint16_t lSegmentLength = readWord(lRawRA, &lRawPos, lRawSz); // read marker length 164 | long lSegmentEnd = lRawPos + (lSegmentLength - 2); 165 | if (lSegmentEnd > lRawSz) { 166 | abortGoto("Segment larger than image\n"); 167 | } 168 | if (verbose) 169 | printMessage("btMarkerType %#02X length %d@%ld\n", btMarkerType, lSegmentLength, lRawPos); 170 | if (((btMarkerType >= 0xC0) && (btMarkerType <= 0xC3)) || ((btMarkerType >= 0xC5) && (btMarkerType <= 0xCB)) || ((btMarkerType >= 0xCD) && (btMarkerType <= 0xCF))) { 171 | // if Start-Of-Frame (SOF) marker 172 | SOFprecision = readByte(lRawRA, &lRawPos, lRawSz); 173 | SOFydim = readWord(lRawRA, &lRawPos, lRawSz); 174 | SOFxdim = readWord(lRawRA, &lRawPos, lRawSz); 175 | SOFnf = readByte(lRawRA, &lRawPos, lRawSz); 176 | // SOFarrayPos = lRawPos; 177 | lRawPos = (lSegmentEnd); 178 | if (verbose) 179 | printMessage(" [Precision %d X*Y %d*%d Frames %d]\n", SOFprecision, SOFxdim, SOFydim, SOFnf); 180 | if (btMarkerType != 0xC3) { // lImgTypeC3 = true; 181 | abortGoto("This JPEG decoder can only decompress lossless JPEG ITU-T81 images (SoF must be 0XC3, not %#02X)\n", btMarkerType); 182 | } 183 | if ((SOFprecision < 1) || (SOFprecision > 16) || (SOFnf < 1) || (SOFnf == 2) || (SOFnf > 3) || ((SOFnf == 3) && (SOFprecision > 8))) { 184 | abortGoto("Scalar data must be 1..16 bit, RGB data must be 8-bit (%d-bit, %d frames)\n", SOFprecision, SOFnf); 185 | } 186 | } else if (btMarkerType == 0xC4) { // if SOF marker else if define-Huffman-tables marker (DHT) 187 | if (verbose) 188 | printMessage(" [Huffman Length %d]\n", lSegmentLength); 189 | do { 190 | uint8_t DHTnLi = readByte(lRawRA, &lRawPos, lRawSz); // we read but ignore DHTtcth. 191 | #pragma unused(DHTnLi) // we need to increment the input file position, but we do not care what the value is 192 | DHTnLi = 0; 193 | for (int lInc = 1; lInc <= 16; lInc++) { 194 | l[lFrameCount].DHTliRA[lInc] = readByte(lRawRA, &lRawPos, lRawSz); 195 | DHTnLi = DHTnLi + l[lFrameCount].DHTliRA[lInc]; 196 | if (l[lFrameCount].DHTliRA[lInc] != 0) 197 | l[lFrameCount].MaxHufSi = lInc; 198 | if (verbose) 199 | printMessage("DHT has %d combinations with %d bits\n", l[lFrameCount].DHTliRA[lInc], lInc); 200 | } 201 | if (DHTnLi > 17) { 202 | abortGoto("Huffman table corrupted.\n"); 203 | } 204 | int lIncY = 0; // frequency 205 | for (int lInc = 0; lInc <= 31; lInc++) { // lInc := 0 to 31 do begin 206 | l[lFrameCount].HufVal[lInc] = -1; 207 | l[lFrameCount].HufSz[lInc] = -1; 208 | l[lFrameCount].HufCode[lInc] = -1; 209 | } 210 | for (int lInc = 1; lInc <= 16; lInc++) { // set the huffman size values 211 | if (l[lFrameCount].DHTliRA[lInc] > 0) { 212 | l[lFrameCount].DHTstartRA[lInc] = lIncY + 1; 213 | for (int lIncX = 1; lIncX <= l[lFrameCount].DHTliRA[lInc]; lIncX++) { 214 | lIncY++; 215 | btS1 = readByte(lRawRA, &lRawPos, lRawSz); 216 | l[lFrameCount].HufVal[lIncY] = btS1; 217 | l[lFrameCount].MaxHufVal = btS1; 218 | if (verbose) 219 | printMessage("DHT combination %d has a value of %d\n", lIncY, btS1); 220 | if (btS1 <= 16) // unsigned ints ALWAYS >0, so no need for(btS1 >= 0) 221 | l[lFrameCount].HufSz[lIncY] = lInc; 222 | else { 223 | abortGoto("Huffman size array corrupted.\n"); 224 | } 225 | } 226 | } 227 | } // set huffman size values 228 | int K = 1; 229 | int Code = 0; 230 | int Si = l[lFrameCount].HufSz[K]; 231 | do { 232 | while (Si == l[lFrameCount].HufSz[K]) { 233 | l[lFrameCount].HufCode[K] = Code; 234 | Code = Code + 1; 235 | K++; 236 | } 237 | if (K <= DHTnLi) { 238 | while (l[lFrameCount].HufSz[K] > Si) { 239 | Code = Code << 1; // Shl!!! 240 | Si = Si + 1; 241 | } // while Si 242 | } // K <= 17 243 | 244 | } while (K <= DHTnLi); 245 | // if (verbose) 246 | // for (int j = 1; j <= DHTnLi; j++) 247 | // printMessage(" [%d Sz %d Code %d Value %d]\n", j, l[lFrameCount].HufSz[j], l[lFrameCount].HufCode[j], l[lFrameCount].HufVal[j]); 248 | lFrameCount++; 249 | } while ((lSegmentEnd - lRawPos) >= 18); 250 | lnHufTables = lFrameCount - 1; 251 | lRawPos = (lSegmentEnd); 252 | if (verbose) 253 | printMessage(" [FrameCount %d]\n", lnHufTables); 254 | } else if (btMarkerType == 0xDD) { // if DHT marker else if Define restart interval (DRI) marker 255 | abortGoto("btMarkerType == 0xDD: unsupported Restart Segments\n"); 256 | // lRestartSegmentSz = ReadWord(lRawRA, &lRawPos, lRawSz); 257 | // lRawPos = lSegmentEnd; 258 | } else if (btMarkerType == 0xDA) { // if DRI marker else if read Start of Scan (SOS) marker 259 | SOSns = readByte(lRawRA, &lRawPos, lRawSz); 260 | // if Ns = 1 then NOT interleaved, else interleaved: see B.2.3 261 | // SOSarrayPos = lRawPos; //not required... 262 | if (SOSns > 0) { 263 | for (int lInc = 1; lInc <= SOSns; lInc++) { 264 | btS1 = readByte(lRawRA, &lRawPos, lRawSz); // component identifier 1=Y,2=Cb,3=Cr,4=I,5=Q 265 | #pragma unused(btS1) // dummy value used to increment file position 266 | btS2 = readByte(lRawRA, &lRawPos, lRawSz); // horizontal and vertical sampling factors 267 | #pragma unused(btS2) // dummy value used to increment file position 268 | } 269 | } 270 | SOSss = readByte(lRawRA, &lRawPos, lRawSz); // predictor selection B.3 271 | SOSse = readByte(lRawRA, &lRawPos, lRawSz); 272 | #pragma unused(SOSse) // dummy value used to increment file position 273 | SOSahal = readByte(lRawRA, &lRawPos, lRawSz); // lower 4bits= pointtransform 274 | SOSpttrans = SOSahal & 16; 275 | if (verbose) 276 | printMessage(" [Predictor: %d Transform %d]\n", SOSss, SOSahal); 277 | lRawPos = (lSegmentEnd); 278 | } else // if SOS marker else skip marker 279 | lRawPos = (lSegmentEnd); 280 | } while ((lRawPos < lRawSz) && (btMarkerType != 0xDA)); // 0xDA=Start of scan: loop for reading header 281 | // NEXT: Huffman decoding 282 | if (lnHufTables < 1) { 283 | abortGoto("Decoding error: no Huffman tables.\n"); 284 | } 285 | // NEXT: unpad data - delete byte that follows $FF 286 | // int lIsRestartSegments = 0; 287 | long lIncI = lRawPos; // input position 288 | long lIncO = lRawPos; // output position 289 | do { 290 | lRawRA[lIncO] = lRawRA[lIncI]; 291 | if (lRawRA[lIncI] == 255) { 292 | if (lRawRA[lIncI + 1] == 0) 293 | lIncI = lIncI + 1; 294 | else if (lRawRA[lIncI + 1] == 0xD9) 295 | lIncO = -666; // end of padding 296 | // else 297 | // lIsRestartSegments = lRawRA[lIncI+1]; 298 | } 299 | lIncI++; 300 | lIncO++; 301 | } while (lIncO > 0); 302 | // if (lIsRestartSegments != 0) //detects both restart and corruption https://groups.google.com/forum/#!topic/comp.protocols.dicom/JUuz0B_aE5o 303 | // printWarning("Detected restart segments, decompress with dcmdjpeg or gdcmconv 0xFF%02X.\n", lIsRestartSegments); 304 | // NEXT: some RGB images use only a single Huffman table for all 3 colour planes. In this case, replicate the correct values 305 | // NEXT: prepare lookup table 306 | for (int lFrameCount = 1; lFrameCount <= lnHufTables; lFrameCount++) { 307 | for (int lInc = 0; lInc <= 17; lInc++) 308 | l[lFrameCount].SSSSszRA[lInc] = 123; // Impossible value for SSSS, suggests 8-bits can not describe answer 309 | for (int lInc = 0; lInc <= 255; lInc++) 310 | l[lFrameCount].LookUpRA[lInc] = 255; // Impossible value for SSSS, suggests 8-bits can not describe answer 311 | } 312 | // NEXT: fill lookuptable 313 | for (int lFrameCount = 1; lFrameCount <= lnHufTables; lFrameCount++) { 314 | int lIncY = 0; 315 | for (int lSz = 1; lSz <= 8; lSz++) { // set the huffman lookup table for keys with lengths <=8 316 | if (l[lFrameCount].DHTliRA[lSz] > 0) { 317 | for (int lIncX = 1; lIncX <= l[lFrameCount].DHTliRA[lSz]; lIncX++) { 318 | lIncY++; 319 | int lHufVal = l[lFrameCount].HufVal[lIncY]; // SSSS 320 | l[lFrameCount].SSSSszRA[lHufVal] = lSz; 321 | int k = (l[lFrameCount].HufCode[lIncY] << (8 - lSz)) & 255; // K= most sig bits for hufman table 322 | if (lSz < 8) { // fill in all possible bits that exceed the huffman table 323 | int lInc = bitMask(8 - lSz); 324 | for (int lCurrentBitPos = 0; lCurrentBitPos <= lInc; lCurrentBitPos++) { 325 | l[lFrameCount].LookUpRA[k + lCurrentBitPos] = lHufVal; 326 | } 327 | } else 328 | l[lFrameCount].LookUpRA[k] = lHufVal; // SSSS 329 | // printMessage("Frame %d SSSS %d Size %d Code %d SHL %d EmptyBits %ld\n", lFrameCount, lHufRA[lFrameCount][lIncY].HufVal, lHufRA[lFrameCount][lIncY].HufSz,lHufRA[lFrameCount][lIncY].HufCode, k, lInc); 330 | } // Set SSSS 331 | } // Length of size lInc > 0 332 | } // for lInc := 1 to 8 333 | } // For each frame, e.g. once each for Red/Green/Blue 334 | // NEXT: some RGB images use only a single Huffman table for all 3 colour planes. In this case, replicate the correct values 335 | if (lnHufTables < SOFnf) { // use single Hufman table for each frame 336 | for (int lFrameCount = lnHufTables + 1; lFrameCount <= SOFnf; lFrameCount++) { 337 | l[lFrameCount] = l[lnHufTables]; 338 | 339 | } // for each frame 340 | } // if lnHufTables < SOFnf 341 | // NEXT: uncompress data: different loops for different predictors 342 | int lItems = SOFxdim * SOFydim * SOFnf; 343 | // lRawPos++;// <- only for Pascal where array is indexed from 1 not 0 first byte of data 344 | int lCurrentBitPos = 0; // read in a new byte 345 | // depending on SOSss, we see Table H.1 346 | int lPredA = 0; 347 | int lPredB = 0; 348 | int lPredC = 0; 349 | if (SOSss == 2) // predictor selection 2: above 350 | lPredA = SOFxdim - 1; 351 | else if (SOSss == 3) // predictor selection 3: above+left 352 | lPredA = SOFxdim; 353 | else if ((SOSss == 4) || (SOSss == 5)) { // these use left, above and above+left WEIGHT LEFT 354 | lPredA = 0; // Ra left 355 | lPredB = SOFxdim - 1; // Rb directly above 356 | lPredC = SOFxdim; // Rc UpperLeft:above and to the left 357 | } else if (SOSss == 6) { // also use left, above and above+left, WEIGHT ABOVE 358 | lPredB = 0; 359 | lPredA = SOFxdim - 1; // Rb directly above 360 | lPredC = SOFxdim; // Rc UpperLeft:above and to the left 361 | } else 362 | lPredA = 0; // Ra: directly to left) 363 | if (SOFprecision > 8) { // start - 16 bit data 364 | *bits = 16; 365 | int lPx = -1; // pixel position 366 | int lPredicted = 1 << (SOFprecision - 1 - SOSpttrans); 367 | lImgRA8 = (unsigned char *)malloc(lItems * 2); 368 | uint16_t *lImgRA16 = (uint16_t *)lImgRA8; 369 | for (int i = 0; i < lItems; i++) 370 | lImgRA16[i] = 0; // zero array 371 | int frame = 1; 372 | for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { // for first row - here we ALWAYS use LEFT as predictor 373 | lPx++; // writenext voxel 374 | if (lIncX > 1) 375 | lPredicted = lImgRA16[lPx - 1]; 376 | lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); 377 | } 378 | for (int lIncY = 2; lIncY <= SOFydim; lIncY++) { // for all subsequent rows 379 | lPx++; // write next voxel 380 | lPredicted = lImgRA16[lPx - SOFxdim]; // use ABOVE 381 | lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); 382 | if (SOSss == 4) { 383 | for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { 384 | lPredicted = lImgRA16[lPx - lPredA] + lImgRA16[lPx - lPredB] - lImgRA16[lPx - lPredC]; 385 | lPx++; // writenext voxel 386 | lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); 387 | } // for lIncX 388 | } else if ((SOSss == 5) || (SOSss == 6)) { 389 | for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { 390 | lPredicted = lImgRA16[lPx - lPredA] + ((lImgRA16[lPx - lPredB] - lImgRA16[lPx - lPredC]) >> 1); 391 | lPx++; // writenext voxel 392 | lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); 393 | } // for lIncX 394 | } else if (SOSss == 7) { 395 | for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { 396 | lPx++; // writenext voxel 397 | lPredicted = (lImgRA16[lPx - 1] + lImgRA16[lPx - SOFxdim]) >> 1; 398 | lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); 399 | } // for lIncX 400 | } else { // SOSss 1,2,3 read single values 401 | for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { 402 | lPredicted = lImgRA16[lPx - lPredA]; 403 | lPx++; // writenext voxel 404 | lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); 405 | } // for lIncX 406 | } // if..else possible predictors 407 | } // for lIncY 408 | } else if (SOFnf == 3) { // if 16-bit data; else 8-bit 3 frames 409 | *bits = 8; 410 | lImgRA8 = (unsigned char *)malloc(lItems); 411 | int lPx[kmaxFrames + 1], lPredicted[kmaxFrames + 1]; // pixel position 412 | for (int f = 1; f <= SOFnf; f++) { 413 | lPx[f] = ((f - 1) * (SOFxdim * SOFydim)) - 1; 414 | lPredicted[f] = 1 << (SOFprecision - 1 - SOSpttrans); 415 | } 416 | for (int i = 0; i < lItems; i++) 417 | lImgRA8[i] = 255; // zero array 418 | for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { // for first row - here we ALWAYS use LEFT as predictor 419 | for (int f = 1; f <= SOFnf; f++) { 420 | lPx[f]++; // writenext voxel 421 | if (lIncX > 1) 422 | lPredicted[f] = lImgRA8[lPx[f] - 1]; 423 | lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); 424 | } 425 | } // first row always predicted by LEFT 426 | for (int lIncY = 2; lIncY <= SOFydim; lIncY++) { // for all subsequent rows 427 | for (int f = 1; f <= SOFnf; f++) { 428 | lPx[f]++; // write next voxel 429 | lPredicted[f] = lImgRA8[lPx[f] - SOFxdim]; // use ABOVE 430 | lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); 431 | } // first column of row always predicted by ABOVE 432 | if (SOSss == 4) { 433 | for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { 434 | for (int f = 1; f <= SOFnf; f++) { 435 | lPredicted[f] = lImgRA8[lPx[f] - lPredA] + lImgRA8[lPx[f] - lPredB] - lImgRA8[lPx[f] - lPredC]; 436 | lPx[f]++; // writenext voxel 437 | lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); 438 | } 439 | } // for lIncX 440 | } else if ((SOSss == 5) || (SOSss == 6)) { 441 | for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { 442 | for (int f = 1; f <= SOFnf; f++) { 443 | lPredicted[f] = lImgRA8[lPx[f] - lPredA] + ((lImgRA8[lPx[f] - lPredB] - lImgRA8[lPx[f] - lPredC]) >> 1); 444 | lPx[f]++; // writenext voxel 445 | lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); 446 | } 447 | } // for lIncX 448 | } else if (SOSss == 7) { 449 | for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { 450 | for (int f = 1; f <= SOFnf; f++) { 451 | lPx[f]++; // writenext voxel 452 | lPredicted[f] = (lImgRA8[lPx[f] - 1] + lImgRA8[lPx[f] - SOFxdim]) >> 1; 453 | lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); 454 | } 455 | } // for lIncX 456 | } else { // SOSss 1,2,3 read single values 457 | for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { 458 | for (int f = 1; f <= SOFnf; f++) { 459 | lPredicted[f] = lImgRA8[lPx[f] - lPredA]; 460 | lPx[f]++; // writenext voxel 461 | lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); 462 | } 463 | } // for lIncX 464 | } // if..else possible predictors 465 | } // for lIncY 466 | } else { // if 8-bit data 3frames; else 8-bit 1 frames 467 | *bits = 8; 468 | lImgRA8 = (unsigned char *)malloc(lItems); 469 | int lPx = -1; // pixel position 470 | int lPredicted = 1 << (SOFprecision - 1 - SOSpttrans); 471 | for (int i = 0; i < lItems; i++) 472 | lImgRA8[i] = 0; // zero array 473 | for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { // for first row - here we ALWAYS use LEFT as predictor 474 | lPx++; // writenext voxel 475 | if (lIncX > 1) 476 | lPredicted = lImgRA8[lPx - 1]; 477 | int dx = decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); 478 | lImgRA8[lPx] = lPredicted + dx; 479 | } 480 | for (int lIncY = 2; lIncY <= SOFydim; lIncY++) { // for all subsequent rows 481 | lPx++; // write next voxel 482 | lPredicted = lImgRA8[lPx - SOFxdim]; // use ABOVE 483 | lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); 484 | if (SOSss == 4) { 485 | for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { 486 | lPredicted = lImgRA8[lPx - lPredA] + lImgRA8[lPx - lPredB] - lImgRA8[lPx - lPredC]; 487 | lPx++; // writenext voxel 488 | lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); 489 | } // for lIncX 490 | } else if ((SOSss == 5) || (SOSss == 6)) { 491 | for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { 492 | lPredicted = lImgRA8[lPx - lPredA] + ((lImgRA8[lPx - lPredB] - lImgRA8[lPx - lPredC]) >> 1); 493 | lPx++; // writenext voxel 494 | lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); 495 | } // for lIncX 496 | } else if (SOSss == 7) { 497 | for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { 498 | lPx++; // writenext voxel 499 | lPredicted = (lImgRA8[lPx - 1] + lImgRA8[lPx - SOFxdim]) >> 1; 500 | lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); 501 | } // for lIncX 502 | } else { // SOSss 1,2,3 read single values 503 | for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { 504 | lPredicted = lImgRA8[lPx - lPredA]; 505 | lPx++; // writenext voxel 506 | lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); 507 | } // for lIncX 508 | } // if..else possible predictors 509 | } // for lIncY 510 | } // if 16bit else 8bit 511 | free(lRawRA); 512 | *dimX = SOFxdim; 513 | *dimY = SOFydim; 514 | *frames = SOFnf; 515 | if (verbose) 516 | printMessage("JPEG ends %ld@%ld\n", lRawPos, lRawPos + skipBytes); 517 | return lImgRA8; 518 | } // decode_JPEG_SOF_0XC3() 519 | -------------------------------------------------------------------------------- /src/dcm2niix/nifti1_io_core.cpp: -------------------------------------------------------------------------------- 1 | // This unit uses a subset of the functions from the nifti1_io available from 2 | // https://sourceforge.net/projects/niftilib/files/nifticlib/ 3 | // These functions were extended by Chris Rorden (2014) and maintain the same license 4 | /*****===================================================================*****/ 5 | /***** Sample functions to deal with NIFTI-1 and ANALYZE files *****/ 6 | /*****...................................................................*****/ 7 | /***** This code is released to the public domain. *****/ 8 | /*****...................................................................*****/ 9 | /***** Author: Robert W Cox, SSCC/DIRP/NIMH/NIH/DHHS/USA/EARTH *****/ 10 | /***** Date: August 2003 *****/ 11 | /*****...................................................................*****/ 12 | /***** Neither the National Institutes of Health (NIH), nor any of its *****/ 13 | /***** employees imply any warranty of usefulness of this software for *****/ 14 | /***** any purpose, and do not assume any liability for damages, *****/ 15 | /***** incidental or otherwise, caused by any use of this document. *****/ 16 | /*****===================================================================*****/ 17 | 18 | #include "nifti1_io_core.h" 19 | #include 20 | #include 21 | #include 22 | #include //requires VS 2015 or later 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #ifndef _MSC_VER 29 | #include 30 | #endif 31 | 32 | #include "print.h" 33 | 34 | #if !defined(USING_R) && !defined(USING_MGH_NIFTI_IO) 35 | void nifti_swap_8bytes(size_t n, void *ar) // 4 bytes at a time 36 | { 37 | size_t ii; 38 | unsigned char *cp0 = (unsigned char *)ar, *cp1, *cp2; 39 | unsigned char tval; 40 | for (ii = 0; ii < n; ii++) { 41 | cp1 = cp0; 42 | cp2 = cp0 + 7; 43 | tval = *cp1; 44 | *cp1 = *cp2; 45 | *cp2 = tval; 46 | cp1++; 47 | cp2--; 48 | tval = *cp1; 49 | *cp1 = *cp2; 50 | *cp2 = tval; 51 | cp1++; 52 | cp2--; 53 | tval = *cp1; 54 | *cp1 = *cp2; 55 | *cp2 = tval; 56 | cp1++; 57 | cp2--; 58 | tval = *cp1; 59 | *cp1 = *cp2; 60 | *cp2 = tval; 61 | cp0 += 8; 62 | } 63 | return; 64 | } 65 | 66 | void nifti_swap_4bytes(size_t n, void *ar) // 4 bytes at a time 67 | { 68 | size_t ii; 69 | unsigned char *cp0 = (unsigned char *)ar, *cp1, *cp2; 70 | unsigned char tval; 71 | for (ii = 0; ii < n; ii++) { 72 | cp1 = cp0; 73 | cp2 = cp0 + 3; 74 | tval = *cp1; 75 | *cp1 = *cp2; 76 | *cp2 = tval; 77 | cp1++; 78 | cp2--; 79 | tval = *cp1; 80 | *cp1 = *cp2; 81 | *cp2 = tval; 82 | cp0 += 4; 83 | } 84 | return; 85 | } 86 | 87 | void nifti_swap_2bytes(size_t n, void *ar) // 2 bytes at a time 88 | { 89 | size_t ii; 90 | unsigned char *cp1 = (unsigned char *)ar, *cp2; 91 | unsigned char tval; 92 | for (ii = 0; ii < n; ii++) { 93 | cp2 = cp1 + 1; 94 | tval = *cp1; 95 | *cp1 = *cp2; 96 | *cp2 = tval; 97 | cp1 += 2; 98 | } 99 | return; 100 | } 101 | 102 | /*-------------------------------------------------------------------------*/ 103 | /*! Byte swap NIFTI-1 file header in various places and ways. 104 | 105 | If is_nifti, swap all (even UNUSED) fields of NIfTI header. 106 | Else, swap as a nifti_analyze75 struct. 107 | */ 108 | /*---------------------------------------------------------------------- */ 109 | void swap_nifti_header(struct nifti_1_header *h) { 110 | 111 | /* otherwise, swap all NIFTI fields */ 112 | 113 | nifti_swap_4bytes(1, &h->sizeof_hdr); 114 | nifti_swap_4bytes(1, &h->extents); 115 | nifti_swap_2bytes(1, &h->session_error); 116 | 117 | nifti_swap_2bytes(8, h->dim); 118 | nifti_swap_4bytes(1, &h->intent_p1); 119 | nifti_swap_4bytes(1, &h->intent_p2); 120 | nifti_swap_4bytes(1, &h->intent_p3); 121 | 122 | nifti_swap_2bytes(1, &h->intent_code); 123 | nifti_swap_2bytes(1, &h->datatype); 124 | nifti_swap_2bytes(1, &h->bitpix); 125 | nifti_swap_2bytes(1, &h->slice_start); 126 | 127 | nifti_swap_4bytes(8, h->pixdim); 128 | 129 | nifti_swap_4bytes(1, &h->vox_offset); 130 | nifti_swap_4bytes(1, &h->scl_slope); 131 | nifti_swap_4bytes(1, &h->scl_inter); 132 | nifti_swap_2bytes(1, &h->slice_end); 133 | 134 | nifti_swap_4bytes(1, &h->cal_max); 135 | nifti_swap_4bytes(1, &h->cal_min); 136 | nifti_swap_4bytes(1, &h->slice_duration); 137 | nifti_swap_4bytes(1, &h->toffset); 138 | nifti_swap_4bytes(1, &h->glmax); 139 | nifti_swap_4bytes(1, &h->glmin); 140 | 141 | nifti_swap_2bytes(1, &h->qform_code); 142 | nifti_swap_2bytes(1, &h->sform_code); 143 | 144 | nifti_swap_4bytes(1, &h->quatern_b); 145 | nifti_swap_4bytes(1, &h->quatern_c); 146 | nifti_swap_4bytes(1, &h->quatern_d); 147 | nifti_swap_4bytes(1, &h->qoffset_x); 148 | nifti_swap_4bytes(1, &h->qoffset_y); 149 | nifti_swap_4bytes(1, &h->qoffset_z); 150 | 151 | nifti_swap_4bytes(4, h->srow_x); 152 | nifti_swap_4bytes(4, h->srow_y); 153 | nifti_swap_4bytes(4, h->srow_z); 154 | 155 | return; 156 | } 157 | #endif 158 | 159 | bool littleEndianPlatform() { 160 | uint32_t value = 1; 161 | return (*((char *)&value) == 1); 162 | } 163 | 164 | int isSameFloat(float a, float b) { 165 | return (fabs(a - b) <= FLT_EPSILON); 166 | } 167 | 168 | int isSameDouble(double a, double b) { 169 | return (fabs(a - b) <= DBL_EPSILON); 170 | } 171 | 172 | ivec3 setiVec3(int x, int y, int z) { 173 | ivec3 v = {{x, y, z}}; 174 | return v; 175 | } 176 | 177 | vec3 setVec3(float x, float y, float z) { 178 | vec3 v = {{x, y, z}}; 179 | return v; 180 | } 181 | 182 | vec4 setVec4(float x, float y, float z) { 183 | vec4 v = {{x, y, z, 1}}; 184 | return v; 185 | } 186 | 187 | vec3 crossProduct(vec3 u, vec3 v) { 188 | return setVec3(u.v[1] * v.v[2] - v.v[1] * u.v[2], 189 | -u.v[0] * v.v[2] + v.v[0] * u.v[2], 190 | u.v[0] * v.v[1] - v.v[0] * u.v[1]); 191 | } 192 | 193 | float dotProduct(vec3 u, vec3 v) { 194 | return (u.v[0] * v.v[0] + v.v[1] * u.v[1] + v.v[2] * u.v[2]); 195 | } 196 | 197 | vec3 nifti_vect33_norm(vec3 v) { // normalize vector length 198 | vec3 vO = v; 199 | float vLen = sqrt((v.v[0] * v.v[0]) + (v.v[1] * v.v[1]) + (v.v[2] * v.v[2])); 200 | if (vLen <= FLT_EPSILON) 201 | return vO; // avoid divide by zero 202 | for (int i = 0; i < 3; i++) 203 | vO.v[i] = v.v[i] / vLen; 204 | return vO; 205 | } 206 | 207 | vec4 nifti_vect44_norm(vec4 v) { // normalize vector length 208 | vec4 vO = v; 209 | float vLen = sqrt((v.v[0] * v.v[0]) + (v.v[1] * v.v[1]) + (v.v[2] * v.v[2]) + (v.v[3] * v.v[3])); 210 | if (vLen <= FLT_EPSILON) 211 | return vO; // avoid divide by zero 212 | for (int i = 0; i < 4; i++) 213 | vO.v[i] = v.v[i] / vLen; 214 | return vO; 215 | } 216 | 217 | vec3 nifti_vect33mat33_mul(vec3 v, mat33 m) { // multiply vector * 3x3matrix 218 | vec3 vO; 219 | for (int i = 0; i < 3; i++) { // multiply Pcrs * m 220 | vO.v[i] = 0; 221 | for (int j = 0; j < 3; j++) 222 | vO.v[i] += m.m[i][j] * v.v[j]; 223 | } 224 | return vO; 225 | } 226 | 227 | vec4 nifti_vect44mat44_mul(vec4 v, mat44 m) { // multiply vector * 4x4matrix 228 | vec4 vO; 229 | for (int i = 0; i < 4; i++) { // multiply Pcrs * m 230 | vO.v[i] = 0; 231 | for (int j = 0; j < 4; j++) 232 | vO.v[i] += m.m[i][j] * v.v[j]; 233 | } 234 | return vO; 235 | } 236 | 237 | mat44 nifti_dicom2mat(float orient[7], float patientPosition[4], float xyzMM[4]) { 238 | // create NIfTI header based on values from DICOM header 239 | // note orient has 6 values, indexed from 1, patient position and xyzMM have 3 values indexed from 1 240 | mat33 Q, diagVox; 241 | Q.m[0][0] = orient[1]; 242 | Q.m[0][1] = orient[2]; 243 | Q.m[0][2] = orient[3]; // load Q 244 | Q.m[1][0] = orient[4]; 245 | Q.m[1][1] = orient[5]; 246 | Q.m[1][2] = orient[6]; 247 | // printMessage("Orient %g %g %g %g %g %g\n",orient[1],orient[2],orient[3],orient[4],orient[5],orient[6] ); 248 | /* normalize row 1 */ 249 | double val = Q.m[0][0] * Q.m[0][0] + Q.m[0][1] * Q.m[0][1] + Q.m[0][2] * Q.m[0][2]; 250 | if (val > 0.0l) { 251 | val = 1.0l / sqrt(val); 252 | Q.m[0][0] *= (float)val; 253 | Q.m[0][1] *= (float)val; 254 | Q.m[0][2] *= (float)val; 255 | } else { 256 | Q.m[0][0] = 1.0l; 257 | Q.m[0][1] = 0.0l; 258 | Q.m[0][2] = 0.0l; 259 | } 260 | /* normalize row 2 */ 261 | val = Q.m[1][0] * Q.m[1][0] + Q.m[1][1] * Q.m[1][1] + Q.m[1][2] * Q.m[1][2]; 262 | if (val > 0.0l) { 263 | val = 1.0l / sqrt(val); 264 | Q.m[1][0] *= (float)val; 265 | Q.m[1][1] *= (float)val; 266 | Q.m[1][2] *= (float)val; 267 | } else { 268 | Q.m[1][0] = 0.0l; 269 | Q.m[1][1] = 1.0l; 270 | Q.m[1][2] = 0.0l; 271 | } 272 | /* row 3 is the cross product of rows 1 and 2*/ 273 | Q.m[2][0] = Q.m[0][1] * Q.m[1][2] - Q.m[0][2] * Q.m[1][1]; /* cross */ 274 | Q.m[2][1] = Q.m[0][2] * Q.m[1][0] - Q.m[0][0] * Q.m[1][2]; /* product */ 275 | Q.m[2][2] = Q.m[0][0] * Q.m[1][1] - Q.m[0][1] * Q.m[1][0]; 276 | Q = nifti_mat33_transpose(Q); 277 | if (nifti_mat33_determ(Q) < 0.0) { 278 | Q.m[0][2] = -Q.m[0][2]; 279 | Q.m[1][2] = -Q.m[1][2]; 280 | Q.m[2][2] = -Q.m[2][2]; 281 | } 282 | // next scale matrix 283 | LOAD_MAT33(diagVox, xyzMM[1], 0.0l, 0.0l, 0.0l, xyzMM[2], 0.0l, 0.0l, 0.0l, xyzMM[3]); 284 | Q = nifti_mat33_mul(Q, diagVox); 285 | mat44 Q44; // 4x4 matrix includes translations 286 | LOAD_MAT44(Q44, Q.m[0][0], Q.m[0][1], Q.m[0][2], patientPosition[1], 287 | Q.m[1][0], Q.m[1][1], Q.m[1][2], patientPosition[2], 288 | Q.m[2][0], Q.m[2][1], Q.m[2][2], patientPosition[3]); 289 | return Q44; 290 | } 291 | 292 | #if !defined(USING_R) && !defined(USING_MGH_NIFTI_IO) 293 | float nifti_mat33_determ(mat33 R) /* determinant of 3x3 matrix */ 294 | { 295 | double r11, r12, r13, r21, r22, r23, r31, r32, r33; 296 | /* INPUT MATRIX: */ 297 | r11 = R.m[0][0]; 298 | r12 = R.m[0][1]; 299 | r13 = R.m[0][2]; /* [ r11 r12 r13 ] */ 300 | r21 = R.m[1][0]; 301 | r22 = R.m[1][1]; 302 | r23 = R.m[1][2]; /* [ r21 r22 r23 ] */ 303 | r31 = R.m[2][0]; 304 | r32 = R.m[2][1]; 305 | r33 = R.m[2][2]; /* [ r31 r32 r33 ] */ 306 | return (float)(r11 * r22 * r33 - r11 * r32 * r23 - r21 * r12 * r33 + r21 * r32 * r13 + r31 * r12 * r23 - r31 * r22 * r13); 307 | } 308 | 309 | mat33 nifti_mat33_mul(mat33 A, mat33 B) /* multiply 2 3x3 matrices */ 310 | // see http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c 311 | { 312 | mat33 C; 313 | int i, j; 314 | for (i = 0; i < 3; i++) 315 | for (j = 0; j < 3; j++) 316 | C.m[i][j] = A.m[i][0] * B.m[0][j] + A.m[i][1] * B.m[1][j] + A.m[i][2] * B.m[2][j]; 317 | return C; 318 | } 319 | #endif 320 | 321 | mat44 nifti_mat44_mul(mat44 A, mat44 B) /* multiply 2 3x3 matrices */ 322 | { 323 | mat44 C; 324 | int i, j; 325 | for (i = 0; i < 4; i++) 326 | for (j = 0; j < 4; j++) 327 | C.m[i][j] = A.m[i][0] * B.m[0][j] + A.m[i][1] * B.m[1][j] + A.m[i][2] * B.m[2][j] + A.m[i][3] * B.m[3][j]; 328 | return C; 329 | } 330 | 331 | mat33 nifti_mat33_transpose(mat33 A) /* transpose 3x3 matrix */ 332 | // see http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c 333 | { 334 | mat33 B; 335 | int i, j; 336 | for (i = 0; i < 3; i++) 337 | for (j = 0; j < 3; j++) 338 | B.m[i][j] = A.m[j][i]; 339 | return B; 340 | } 341 | 342 | #if !defined(USING_R) && !defined(USING_MGH_NIFTI_IO) 343 | mat33 nifti_mat33_inverse(mat33 R) /* inverse of 3x3 matrix */ 344 | { 345 | double r11, r12, r13, r21, r22, r23, r31, r32, r33, deti; 346 | mat33 Q; 347 | // INPUT MATRIX: 348 | r11 = R.m[0][0]; 349 | r12 = R.m[0][1]; 350 | r13 = R.m[0][2]; // [ r11 r12 r13 ] 351 | r21 = R.m[1][0]; 352 | r22 = R.m[1][1]; 353 | r23 = R.m[1][2]; // [ r21 r22 r23 ] 354 | r31 = R.m[2][0]; 355 | r32 = R.m[2][1]; 356 | r33 = R.m[2][2]; // [ r31 r32 r33 ] 357 | deti = r11 * r22 * r33 - r11 * r32 * r23 - r21 * r12 * r33 + r21 * r32 * r13 + r31 * r12 * r23 - r31 * r22 * r13; 358 | if (deti != 0.0l) 359 | deti = 1.0l / deti; 360 | Q.m[0][0] = deti * (r22 * r33 - r32 * r23); 361 | Q.m[0][1] = deti * (-r12 * r33 + r32 * r13); 362 | Q.m[0][2] = deti * (r12 * r23 - r22 * r13); 363 | Q.m[1][0] = deti * (-r21 * r33 + r31 * r23); 364 | Q.m[1][1] = deti * (r11 * r33 - r31 * r13); 365 | Q.m[1][2] = deti * (-r11 * r23 + r21 * r13); 366 | Q.m[2][0] = deti * (r21 * r32 - r31 * r22); 367 | Q.m[2][1] = deti * (-r11 * r32 + r31 * r12); 368 | Q.m[2][2] = deti * (r11 * r22 - r21 * r12); 369 | return Q; 370 | } 371 | 372 | float nifti_mat33_rownorm(mat33 A) // max row norm of 3x3 matrix 373 | { 374 | float r1, r2, r3; 375 | r1 = fabs(A.m[0][0]) + fabs(A.m[0][1]) + fabs(A.m[0][2]); 376 | r2 = fabs(A.m[1][0]) + fabs(A.m[1][1]) + fabs(A.m[1][2]); 377 | r3 = fabs(A.m[2][0]) + fabs(A.m[2][1]) + fabs(A.m[2][2]); 378 | if (r1 < r2) 379 | r1 = r2; 380 | if (r1 < r3) 381 | r1 = r3; 382 | return r1; 383 | } 384 | 385 | float nifti_mat33_colnorm(mat33 A) // max column norm of 3x3 matrix 386 | { 387 | float r1, r2, r3; 388 | r1 = fabs(A.m[0][0]) + fabs(A.m[1][0]) + fabs(A.m[2][0]); 389 | r2 = fabs(A.m[0][1]) + fabs(A.m[1][1]) + fabs(A.m[2][1]); 390 | r3 = fabs(A.m[0][2]) + fabs(A.m[1][2]) + fabs(A.m[2][2]); 391 | if (r1 < r2) 392 | r1 = r2; 393 | if (r1 < r3) 394 | r1 = r3; 395 | return r1; 396 | } 397 | 398 | mat33 nifti_mat33_polar(mat33 A) { 399 | mat33 X, Y, Z; 400 | float alp, bet, gam, gmi, dif = 1.0; 401 | int k = 0; 402 | X = A; 403 | // force matrix to be nonsingular 404 | gam = nifti_mat33_determ(X); 405 | while (gam == 0.0) { // perturb matrix 406 | gam = 0.00001 * (0.001 + nifti_mat33_rownorm(X)); 407 | X.m[0][0] += gam; 408 | X.m[1][1] += gam; 409 | X.m[2][2] += gam; 410 | gam = nifti_mat33_determ(X); 411 | } 412 | while (1) { 413 | Y = nifti_mat33_inverse(X); 414 | if (dif > 0.3) { // far from convergence 415 | alp = sqrt(nifti_mat33_rownorm(X) * nifti_mat33_colnorm(X)); 416 | bet = sqrt(nifti_mat33_rownorm(Y) * nifti_mat33_colnorm(Y)); 417 | gam = sqrt(bet / alp); 418 | gmi = 1.0 / gam; 419 | } else 420 | gam = gmi = 1.0; // close to convergence 421 | Z.m[0][0] = 0.5 * (gam * X.m[0][0] + gmi * Y.m[0][0]); 422 | Z.m[0][1] = 0.5 * (gam * X.m[0][1] + gmi * Y.m[1][0]); 423 | Z.m[0][2] = 0.5 * (gam * X.m[0][2] + gmi * Y.m[2][0]); 424 | Z.m[1][0] = 0.5 * (gam * X.m[1][0] + gmi * Y.m[0][1]); 425 | Z.m[1][1] = 0.5 * (gam * X.m[1][1] + gmi * Y.m[1][1]); 426 | Z.m[1][2] = 0.5 * (gam * X.m[1][2] + gmi * Y.m[2][1]); 427 | Z.m[2][0] = 0.5 * (gam * X.m[2][0] + gmi * Y.m[0][2]); 428 | Z.m[2][1] = 0.5 * (gam * X.m[2][1] + gmi * Y.m[1][2]); 429 | Z.m[2][2] = 0.5 * (gam * X.m[2][2] + gmi * Y.m[2][2]); 430 | dif = fabs(Z.m[0][0] - X.m[0][0]) + fabs(Z.m[0][1] - X.m[0][1]) + fabs(Z.m[0][2] - X.m[0][2]) + fabs(Z.m[1][0] - X.m[1][0]) + fabs(Z.m[1][1] - X.m[1][1]) + fabs(Z.m[1][2] - X.m[1][2]) + fabs(Z.m[2][0] - X.m[2][0]) + fabs(Z.m[2][1] - X.m[2][1]) + fabs(Z.m[2][2] - X.m[2][2]); 431 | k = k + 1; 432 | if (k > 100 || dif < 3.e-6) 433 | break; // convergence or exhaustion 434 | X = Z; 435 | } 436 | return Z; 437 | } 438 | 439 | void nifti_mat44_to_quatern(mat44 R, 440 | float *qb, float *qc, float *qd, 441 | float *qx, float *qy, float *qz, 442 | float *dx, float *dy, float *dz, float *qfac) { 443 | double r11, r12, r13, r21, r22, r23, r31, r32, r33; 444 | double xd, yd, zd, a, b, c, d; 445 | mat33 P, Q; 446 | // offset outputs are read write out of input matrix 447 | ASSIF(qx, R.m[0][3]); 448 | ASSIF(qy, R.m[1][3]); 449 | ASSIF(qz, R.m[2][3]); 450 | // load 3x3 matrix into local variables */ 451 | r11 = R.m[0][0]; 452 | r12 = R.m[0][1]; 453 | r13 = R.m[0][2]; 454 | r21 = R.m[1][0]; 455 | r22 = R.m[1][1]; 456 | r23 = R.m[1][2]; 457 | r31 = R.m[2][0]; 458 | r32 = R.m[2][1]; 459 | r33 = R.m[2][2]; 460 | // compute lengths of each column; these determine grid spacings 461 | xd = sqrt(r11 * r11 + r21 * r21 + r31 * r31); 462 | yd = sqrt(r12 * r12 + r22 * r22 + r32 * r32); 463 | zd = sqrt(r13 * r13 + r23 * r23 + r33 * r33); 464 | // if a column length is zero, patch the trouble 465 | if (xd == 0.0l) { 466 | r11 = 1.0l; 467 | r21 = r31 = 0.0l; 468 | xd = 1.0l; 469 | } 470 | if (yd == 0.0l) { 471 | r22 = 1.0l; 472 | r12 = r32 = 0.0l; 473 | yd = 1.0l; 474 | } 475 | if (zd == 0.0l) { 476 | r33 = 1.0l; 477 | r13 = r23 = 0.0l; 478 | zd = 1.0l; 479 | } 480 | // assign the output lengths */ 481 | ASSIF(dx, xd); 482 | ASSIF(dy, yd); 483 | ASSIF(dz, zd); 484 | // normalize the columns */ 485 | r11 /= xd; 486 | r21 /= xd; 487 | r31 /= xd; 488 | r12 /= yd; 489 | r22 /= yd; 490 | r32 /= yd; 491 | r13 /= zd; 492 | r23 /= zd; 493 | r33 /= zd; 494 | /* At this point, the matrix has normal columns, but we have to allow 495 | for the fact that the hideous user may not have given us a matrix 496 | with orthogonal columns. 497 | So, now find the orthogonal matrix closest to the current matrix. 498 | One reason for using the polar decomposition to get this 499 | orthogonal matrix, rather than just directly orthogonalizing 500 | the columns, is so that inputting the inverse matrix to R 501 | will result in the inverse orthogonal matrix at this point. 502 | If we just orthogonalized the columns, this wouldn't necessarily hold. */ 503 | Q.m[0][0] = r11; 504 | Q.m[0][1] = r12; 505 | Q.m[0][2] = r13; // load Q 506 | Q.m[1][0] = r21; 507 | Q.m[1][1] = r22; 508 | Q.m[1][2] = r23; 509 | Q.m[2][0] = r31; 510 | Q.m[2][1] = r32; 511 | Q.m[2][2] = r33; 512 | P = nifti_mat33_polar(Q); // P is orthog matrix closest to Q 513 | r11 = P.m[0][0]; 514 | r12 = P.m[0][1]; 515 | r13 = P.m[0][2]; // unload 516 | r21 = P.m[1][0]; 517 | r22 = P.m[1][1]; 518 | r23 = P.m[1][2]; 519 | r31 = P.m[2][0]; 520 | r32 = P.m[2][1]; 521 | r33 = P.m[2][2]; 522 | // [ r11 r12 r13 ] 523 | // at this point, the matrix [ r21 r22 r23 ] is orthogonal 524 | // [ r31 r32 r33 ] 525 | // compute the determinant to determine if it is proper 526 | zd = r11 * r22 * r33 - r11 * r32 * r23 - r21 * r12 * r33 + r21 * r32 * r13 + r31 * r12 * r23 - r31 * r22 * r13; // should be -1 or 1 527 | if (zd > 0) { // proper 528 | ASSIF(qfac, 1.0); 529 | } else { // improper ==> flip 3rd column 530 | ASSIF(qfac, -1.0); 531 | r13 = -r13; 532 | r23 = -r23; 533 | r33 = -r33; 534 | } 535 | // now, compute quaternion parameters 536 | a = r11 + r22 + r33 + 1.0l; 537 | if (a > 0.5l) { // simplest case 538 | a = 0.5l * sqrt(a); 539 | b = 0.25l * (r32 - r23) / a; 540 | c = 0.25l * (r13 - r31) / a; 541 | d = 0.25l * (r21 - r12) / a; 542 | } else { // trickier case 543 | xd = 1.0 + r11 - (r22 + r33); // 4*b*b 544 | yd = 1.0 + r22 - (r11 + r33); // 4*c*c 545 | zd = 1.0 + r33 - (r11 + r22); // 4*d*d 546 | if (xd > 1.0) { 547 | b = 0.5l * sqrt(xd); 548 | c = 0.25l * (r12 + r21) / b; 549 | d = 0.25l * (r13 + r31) / b; 550 | a = 0.25l * (r32 - r23) / b; 551 | } else if (yd > 1.0) { 552 | c = 0.5l * sqrt(yd); 553 | b = 0.25l * (r12 + r21) / c; 554 | d = 0.25l * (r23 + r32) / c; 555 | a = 0.25l * (r13 - r31) / c; 556 | } else { 557 | d = 0.5l * sqrt(zd); 558 | b = 0.25l * (r13 + r31) / d; 559 | c = 0.25l * (r23 + r32) / d; 560 | a = 0.25l * (r21 - r12) / d; 561 | } 562 | // if( a < 0.0l ){ b=-b ; c=-c ; d=-d; a=-a; } 563 | if (a < 0.0l) { 564 | b = -b; 565 | c = -c; 566 | d = -d; 567 | } // a discarded... 568 | } 569 | ASSIF(qb, b); 570 | ASSIF(qc, c); 571 | ASSIF(qd, d); 572 | return; 573 | } 574 | 575 | mat44 nifti_quatern_to_mat44(float qb, float qc, float qd, 576 | float qx, float qy, float qz, 577 | float dx, float dy, float dz, float qfac) { 578 | mat44 R; 579 | double a, b = qb, c = qc, d = qd, xd, yd, zd; 580 | 581 | /* last row is always [ 0 0 0 1 ] */ 582 | 583 | R.m[3][0] = R.m[3][1] = R.m[3][2] = 0.0f; 584 | R.m[3][3] = 1.0f; 585 | 586 | /* compute a parameter from b,c,d */ 587 | 588 | a = 1.0l - (b * b + c * c + d * d); 589 | if (a < 1.e-7l) { /* special case */ 590 | a = 1.0l / sqrt(b * b + c * c + d * d); 591 | b *= a; 592 | c *= a; 593 | d *= a; /* normalize (b,c,d) vector */ 594 | a = 0.0l; /* a = 0 ==> 180 degree rotation */ 595 | } else { 596 | a = sqrt(a); /* angle = 2*arccos(a) */ 597 | } 598 | 599 | /* load rotation matrix, including scaling factors for voxel sizes */ 600 | 601 | xd = (dx > 0.0) ? dx : 1.0l; /* make sure are positive */ 602 | yd = (dy > 0.0) ? dy : 1.0l; 603 | zd = (dz > 0.0) ? dz : 1.0l; 604 | 605 | if (qfac < 0.0) 606 | zd = -zd; /* left handedness? */ 607 | 608 | R.m[0][0] = (float)((a * a + b * b - c * c - d * d) * xd); 609 | R.m[0][1] = 2.0l * (b * c - a * d) * yd; 610 | R.m[0][2] = 2.0l * (b * d + a * c) * zd; 611 | R.m[1][0] = 2.0l * (b * c + a * d) * xd; 612 | R.m[1][1] = (float)((a * a + c * c - b * b - d * d) * yd); 613 | R.m[1][2] = 2.0l * (c * d - a * b) * zd; 614 | R.m[2][0] = 2.0l * (b * d - a * c) * xd; 615 | R.m[2][1] = 2.0l * (c * d + a * b) * yd; 616 | R.m[2][2] = (float)((a * a + d * d - c * c - b * b) * zd); 617 | 618 | /* load offsets */ 619 | 620 | R.m[0][3] = qx; 621 | R.m[1][3] = qy; 622 | R.m[2][3] = qz; 623 | 624 | return R; 625 | } 626 | 627 | mat44 nifti_mat44_inverse(mat44 R) { 628 | double r11, r12, r13, r21, r22, r23, r31, r32, r33, v1, v2, v3, deti; 629 | mat44 Q; 630 | /* INPUT MATRIX IS: */ 631 | r11 = R.m[0][0]; 632 | r12 = R.m[0][1]; 633 | r13 = R.m[0][2]; // [ r11 r12 r13 v1 ] 634 | r21 = R.m[1][0]; 635 | r22 = R.m[1][1]; 636 | r23 = R.m[1][2]; // [ r21 r22 r23 v2 ] 637 | r31 = R.m[2][0]; 638 | r32 = R.m[2][1]; 639 | r33 = R.m[2][2]; // [ r31 r32 r33 v3 ] 640 | v1 = R.m[0][3]; 641 | v2 = R.m[1][3]; 642 | v3 = R.m[2][3]; // [ 0 0 0 1 ] 643 | deti = r11 * r22 * r33 - r11 * r32 * r23 - r21 * r12 * r33 + r21 * r32 * r13 + r31 * r12 * r23 - r31 * r22 * r13; 644 | if (deti != 0.0l) 645 | deti = 1.0l / deti; 646 | Q.m[0][0] = deti * (r22 * r33 - r32 * r23); 647 | Q.m[0][1] = deti * (-r12 * r33 + r32 * r13); 648 | Q.m[0][2] = deti * (r12 * r23 - r22 * r13); 649 | Q.m[0][3] = deti * (-r12 * r23 * v3 + r12 * v2 * r33 + r22 * r13 * v3 - r22 * v1 * r33 - r32 * r13 * v2 + r32 * v1 * r23); 650 | Q.m[1][0] = deti * (-r21 * r33 + r31 * r23); 651 | Q.m[1][1] = deti * (r11 * r33 - r31 * r13); 652 | Q.m[1][2] = deti * (-r11 * r23 + r21 * r13); 653 | Q.m[1][3] = deti * (r11 * r23 * v3 - r11 * v2 * r33 - r21 * r13 * v3 + r21 * v1 * r33 + r31 * r13 * v2 - r31 * v1 * r23); 654 | Q.m[2][0] = deti * (r21 * r32 - r31 * r22); 655 | Q.m[2][1] = deti * (-r11 * r32 + r31 * r12); 656 | Q.m[2][2] = deti * (r11 * r22 - r21 * r12); 657 | Q.m[2][3] = deti * (-r11 * r22 * v3 + r11 * r32 * v2 + r21 * r12 * v3 - r21 * r32 * v1 - r31 * r12 * v2 + r31 * r22 * v1); 658 | Q.m[3][0] = Q.m[3][1] = Q.m[3][2] = 0.0l; 659 | Q.m[3][3] = (deti == 0.0l) ? 0.0l : 1.0l; // failure flag if deti == 0 660 | return Q; 661 | } 662 | #endif 663 | 664 | // Eigen decomposition for symmetric 3x3 matrices, port of public domain Java Matrix library JAMA. 665 | // Connelly Barnes http://barnesc.blogspot.com/2007/02/eigenvectors-of-3x3-symmetric-matrix.html 666 | // see also https://www.geometrictools.com/Documentation/RobustEigenSymmetric3x3.pdf 667 | // begin: Connelly Barnes code 668 | #include 669 | 670 | #ifdef MAX 671 | #undef MAX 672 | #endif 673 | 674 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 675 | 676 | #define n 3 677 | 678 | static double hypot2(double x, double y) { 679 | return sqrt(x * x + y * y); 680 | } 681 | 682 | // Symmetric Householder reduction to tridiagonal form. 683 | 684 | static void tred2(double V[n][n], double d[n], double e[n]) { 685 | // This is derived from the Algol procedures tred2 by 686 | // Bowdler, Martin, Reinsch, and Wilkinson, Handbook for 687 | // Auto. Comp., Vol.ii-Linear Algebra, and the corresponding 688 | // Fortran subroutine in EISPACK. 689 | for (int j = 0; j < n; j++) { 690 | d[j] = V[n - 1][j]; 691 | } 692 | // Householder reduction to tridiagonal form. 693 | for (int i = n - 1; i > 0; i--) { 694 | // Scale to avoid under/overflow. 695 | double scale = 0.0; 696 | double h = 0.0; 697 | for (int k = 0; k < i; k++) { 698 | scale = scale + fabs(d[k]); 699 | } 700 | if (scale == 0.0) { 701 | e[i] = d[i - 1]; 702 | for (int j = 0; j < i; j++) { 703 | d[j] = V[i - 1][j]; 704 | V[i][j] = 0.0; 705 | V[j][i] = 0.0; 706 | } 707 | } else { 708 | // Generate Householder vector. 709 | for (int k = 0; k < i; k++) { 710 | d[k] /= scale; 711 | h += d[k] * d[k]; 712 | } 713 | double f = d[i - 1]; 714 | double g = sqrt(h); 715 | if (f > 0) { 716 | g = -g; 717 | } 718 | e[i] = scale * g; 719 | h = h - f * g; 720 | d[i - 1] = f - g; 721 | for (int j = 0; j < i; j++) { 722 | e[j] = 0.0; 723 | } 724 | // Apply similarity transformation to remaining columns. 725 | for (int j = 0; j < i; j++) { 726 | f = d[j]; 727 | V[j][i] = f; 728 | g = e[j] + V[j][j] * f; 729 | for (int k = j + 1; k <= i - 1; k++) { 730 | g += V[k][j] * d[k]; 731 | e[k] += V[k][j] * f; 732 | } 733 | e[j] = g; 734 | } 735 | f = 0.0; 736 | for (int j = 0; j < i; j++) { 737 | e[j] /= h; 738 | f += e[j] * d[j]; 739 | } 740 | double hh = f / (h + h); 741 | for (int j = 0; j < i; j++) { 742 | e[j] -= hh * d[j]; 743 | } 744 | for (int j = 0; j < i; j++) { 745 | f = d[j]; 746 | g = e[j]; 747 | for (int k = j; k <= i - 1; k++) { 748 | V[k][j] -= (f * e[k] + g * d[k]); 749 | } 750 | d[j] = V[i - 1][j]; 751 | V[i][j] = 0.0; 752 | } 753 | } 754 | d[i] = h; 755 | } 756 | // Accumulate transformations. 757 | for (int i = 0; i < n - 1; i++) { 758 | V[n - 1][i] = V[i][i]; 759 | V[i][i] = 1.0; 760 | double h = d[i + 1]; 761 | if (h != 0.0) { 762 | for (int k = 0; k <= i; k++) { 763 | d[k] = V[k][i + 1] / h; 764 | } 765 | for (int j = 0; j <= i; j++) { 766 | double g = 0.0; 767 | for (int k = 0; k <= i; k++) { 768 | g += V[k][i + 1] * V[k][j]; 769 | } 770 | for (int k = 0; k <= i; k++) { 771 | V[k][j] -= g * d[k]; 772 | } 773 | } 774 | } 775 | for (int k = 0; k <= i; k++) { 776 | V[k][i + 1] = 0.0; 777 | } 778 | } 779 | for (int j = 0; j < n; j++) { 780 | d[j] = V[n - 1][j]; 781 | V[n - 1][j] = 0.0; 782 | } 783 | V[n - 1][n - 1] = 1.0; 784 | e[0] = 0.0; 785 | } 786 | 787 | // Symmetric tridiagonal QL algorithm. 788 | 789 | static void tql2(double V[n][n], double d[n], double e[n]) { 790 | // This is derived from the Algol procedures tql2, by 791 | // Bowdler, Martin, Reinsch, and Wilkinson, Handbook for 792 | // Auto. Comp., Vol.ii-Linear Algebra, and the corresponding 793 | // Fortran subroutine in EISPACK. 794 | for (int i = 1; i < n; i++) { 795 | e[i - 1] = e[i]; 796 | } 797 | e[n - 1] = 0.0; 798 | double f = 0.0; 799 | double tst1 = 0.0; 800 | double eps = pow(2.0, -52.0); 801 | for (int l = 0; l < n; l++) { 802 | // Find small subdiagonal element 803 | tst1 = MAX(tst1, fabs(d[l]) + fabs(e[l])); 804 | int m = l; 805 | while (m < n) { 806 | if (fabs(e[m]) <= eps * tst1) { 807 | break; 808 | } 809 | m++; 810 | } 811 | // If m == l, d[l] is an eigenvalue, 812 | // otherwise, iterate. 813 | if (m > l) { 814 | int iter = 0; 815 | do { 816 | iter = iter + 1; // (Could check iteration count here.) 817 | // Compute implicit shift 818 | double g = d[l]; 819 | double p = (d[l + 1] - g) / (2.0 * e[l]); 820 | double r = hypot2(p, 1.0); 821 | if (p < 0) { 822 | r = -r; 823 | } 824 | d[l] = e[l] / (p + r); 825 | d[l + 1] = e[l] * (p + r); 826 | double dl1 = d[l + 1]; 827 | double h = g - d[l]; 828 | for (int i = l + 2; i < n; i++) { 829 | d[i] -= h; 830 | } 831 | f = f + h; 832 | // Implicit QL transformation. 833 | p = d[m]; 834 | double c = 1.0; 835 | double c2 = c; 836 | double c3 = c; 837 | double el1 = e[l + 1]; 838 | double s = 0.0; 839 | double s2 = 0.0; 840 | for (int i = m - 1; i >= l; i--) { 841 | c3 = c2; 842 | c2 = c; 843 | s2 = s; 844 | g = c * e[i]; 845 | h = c * p; 846 | r = hypot2(p, e[i]); 847 | e[i + 1] = s * r; 848 | s = e[i] / r; 849 | c = p / r; 850 | p = c * d[i] - s * g; 851 | d[i + 1] = h + s * (c * g + s * d[i]); 852 | // Accumulate transformation. 853 | for (int k = 0; k < n; k++) { 854 | h = V[k][i + 1]; 855 | V[k][i + 1] = s * V[k][i] + c * h; 856 | V[k][i] = c * V[k][i] - s * h; 857 | } 858 | } 859 | p = -s * s2 * c3 * el1 * e[l] / dl1; 860 | e[l] = s * p; 861 | d[l] = c * p; 862 | // Check for convergence. 863 | } while (fabs(e[l]) > eps * tst1); 864 | } 865 | d[l] = d[l] + f; 866 | e[l] = 0.0; 867 | } 868 | // Sort eigenvalues and corresponding vectors. 869 | for (int i = 0; i < n - 1; i++) { 870 | int k = i; 871 | double p = d[i]; 872 | for (int j = i + 1; j < n; j++) { 873 | if (d[j] < p) { 874 | k = j; 875 | p = d[j]; 876 | } 877 | } 878 | if (k != i) { 879 | d[k] = d[i]; 880 | d[i] = p; 881 | for (int j = 0; j < n; j++) { 882 | p = V[j][i]; 883 | V[j][i] = V[j][k]; 884 | V[j][k] = p; 885 | } 886 | } 887 | } 888 | } 889 | 890 | void eigen_decomposition(double A[n][n], double V[n][n], double d[n]) { 891 | double e[n]; 892 | for (int i = 0; i < n; i++) { 893 | for (int j = 0; j < n; j++) { 894 | V[i][j] = A[i][j]; 895 | } 896 | } 897 | tred2(V, d, e); 898 | tql2(V, d, e); 899 | } 900 | // end: Connelly Barnes code 901 | 902 | /*void printMat(char ch, double A[3][3]) { 903 | printf("%c=[%g %g %g; %g %g %g; %g %g %g];\n", ch, 904 | A[0][0],A[0][1],A[0][2], 905 | A[1][0],A[1][1],A[1][2], 906 | A[2][0],A[2][1],A[2][2]); 907 | }*/ 908 | 909 | vec3 nifti_mat33_eig3(double bxx, double bxy, double bxz, double byy, double byz, double bzz) { 910 | double A[3][3]; 911 | A[0][0] = bxx; 912 | A[0][1] = bxy; 913 | A[0][2] = bxz; 914 | A[1][0] = bxy; 915 | A[1][1] = byy; 916 | A[1][2] = byz; 917 | A[2][0] = bxz; 918 | A[2][1] = byz; 919 | A[2][2] = bzz; 920 | double V[3][3]; 921 | double d[3]; 922 | eigen_decomposition(A, V, d); 923 | // printMat('A',A); 924 | // printf("[V,D] = eig(A) %%where A*V = V*D\n"); 925 | // printMat('V',V); 926 | // printf("D = [%g 0 0; 0 %g 0; 0 0 %g]\n", d[0], d[1], d[2]); 927 | vec3 v3; 928 | v3.v[0] = V[0][2]; 929 | v3.v[1] = V[1][2]; 930 | v3.v[2] = V[2][2]; 931 | if (v3.v[0] < 0.0) { 932 | // B-matrix underspecified to describe B-vector 933 | // Describes direction but not polarity of B-vector 934 | // e.g. We get an eigenvector, but eigenvalue can be + or - 935 | // https://github.com/rordenlab/dcm2niix/issues/265 936 | // Like B2q We set the vector to have a positive x component by convention. 937 | // https://raw.githubusercontent.com/matthew-brett/nibabel/master/nibabel/nicom/dwiparams.py 938 | // This ensures eddy will see this as a half shell and not attempt to interpret arbitrary signs 939 | v3.v[0] = -v3.v[0]; 940 | v3.v[1] = -v3.v[1]; 941 | v3.v[2] = -v3.v[2]; 942 | } 943 | // printf("bvec = [%g 0 0; 0 %g 0; 0 0 %g]\n", v3.v[0], v3.v[1], v3.v[2]); 944 | return v3; 945 | } 946 | --------------------------------------------------------------------------------