From d32ef79f1b0435f66e1795e3a245cf2f3dfe7637 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 12 Oct 2021 09:07:58 -0400 Subject: [PATCH 01/60] XA30 behavior (https://github.com/rordenlab/dcm2niix/issues/236) --- console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 800d29e2..905f097b 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20211006" +#define kDCMdate "v1.0.20211010" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 0ec44bce..97034762 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -6279,7 +6279,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d nii_check16bitUnsigned(imgM, &hdr0, opts.isVerbose); //save UINT16 as INT16 if we can do this losslessly if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert < 2)) printWarning("Siemens XA DICOM inadequate for robust conversion (issue 236)\n"); - if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert > 1)) + if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert > 1) && (nConvert == (hdr0.dim[3] * hdr0.dim[4])) ) printWarning("Siemens XA exported as classic not enhanced DICOM (issue 236)\n"); printMessage("Convert %d DICOM as %s (%dx%dx%dx%d)\n", nConvert, pathoutname, hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4]); #ifndef USING_R From ebf7435aa329c1c2a5c7ebf87901ac7f9858f1d2 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Thu, 11 Nov 2021 07:58:27 -0500 Subject: [PATCH 02/60] additional ZIP2 detection mechanism (https://github.com/rordenlab/dcm2niix/issues) --- README.md | 6 ++++-- console/nii_dicom_batch.cpp | 26 +++++++++++++++++++------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f34ce206..ffeabf16 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ dcm2niix is designed to convert neuroimaging data from the DICOM format to the NIfTI format. This web page hosts the developmental source code - a compiled version for Linux, MacOS, and Windows of the most recent stable release is included with [MRIcroGL](https://www.nitrc.org/projects/mricrogl/). A full manual for this software is available in the form of a [NITRC wiki](http://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage). The DICOM format is the standard image format generated by modern medical imaging devices. However, DICOM is very [complicated](https://github.com/jonclayden/divest) and has been interpreted differently by different vendors. The NIfTI format is popular with scientists, it is very simple and explicit. However, this simplicity also imposes limitations (e.g. it demands equidistant slices). dcm2niix is also able to generate a [BIDS JSON format](https://bids-specification.readthedocs.io/en/stable/) `sidecar` which includes relevant information for brain scientists in a vendor agnostic and human readable form. -The [Neuroimaging DICOM and NIfTI Primer]https://github.com/DataCurationNetwork/data-primers/blob/master/Neuroimaging%20DICOM%20and%20NIfTI%20Data%20Curation%20Primer/neuroimaging-dicom-and-nifti-data-curation-primer.md) provides details. +The [Neuroimaging DICOM and NIfTI Primer](https://github.com/DataCurationNetwork/data-primers/blob/master/Neuroimaging%20DICOM%20and%20NIfTI%20Data%20Curation%20Primer/neuroimaging-dicom-and-nifti-data-curation-primer.md) provides details. ## License @@ -111,6 +111,7 @@ If you have any problems with the cmake build script described above or want to - [dicm2nii](http://www.mathworks.com/matlabcentral/fileexchange/42997-dicom-to-nifti-converter) is written in Matlab. The Matlab language makes this very scriptable. - [dicom2nifti](https://github.com/icometrix/dicom2nifti) uses the scriptable Python wrapper utilizes the [high performance GDCMCONV](http://gdcm.sourceforge.net/wiki/index.php/Gdcmconv) executables. - [dicomtonifti](https://github.com/dgobbi/vtk-dicom/wiki/dicomtonifti) leverages [VTK](https://www.vtk.org/). + - [dimon](https://afni.nimh.nih.gov/pub/dist/doc/program_help/Dimon.html) and [to3d](https://afni.nimh.nih.gov/pub/dist/doc/program_help/to3d.html) are included with AFNI. - [dinifti](http://as.nyu.edu/cbi/resources/Software/DINIfTI.html) is focused on conversion of Siemens data. - [DWIConvert](https://github.com/BRAINSia/BRAINSTools/tree/master/DWIConvert) converts DICOM images to NRRD and NIfTI formats. - [mcverter](http://lcni.uoregon.edu/%7Ejolinda/MRIConvert/) has great support for various vendors. @@ -156,9 +157,10 @@ The following tools exploit dcm2niix - [dicom2nifti_batch](https://github.com/scanUCLA/dicom2nifti_batch) is a Matlab script for automating dcm2niix. - [divest](https://github.com/jonclayden/divest) R interface to dcm2niix. - [ExploreASL](https://sites.google.com/view/exploreasl/exploreasl) uses dcm2niix to import images. - - [ezBIDS](https://github.com/brainlife/ezbids) is a web service for converting directory full of DICOM images into BIDS without users having to learn python nor custom configuration file. + - [ezBIDS](https://github.com/brainlife/ezbids) is a [web service](https://brainlife.io/ezbids/) for converting directory full of DICOM images into BIDS without users having to learn python nor custom configuration file. - [fmrif tools](https://github.com/nih-fmrif/fmrif_tools) uses dcm2niix for its [oxy2bids](https://fmrif-tools.readthedocs.io/en/latest/#) tool. - [fMRIprep.dcm2niix](https://github.com/BrettNordin/fMRIprep.dcm2niix) is designed to convert DICOM format to the NIfTI format. + - [FreeSurfer](https://github.com/freesurfer/freesurfer) includes dcm2niix for image conversion. - [fsleyes](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FSLeyes) is a powerful Python-based image viewer. It uses dcm2niix to handle DICOM files through its fslpy libraries. - [Functional Real-Time Interactive Endogenous Neuromodulation and Decoding (FRIEND) Engine](https://github.com/InstitutoDOr/FriendENGINE) uses dcm2niix. - [heudiconv](https://github.com/nipy/heudiconv) can use dcm2niix to create [BIDS](http://bids.neuroimaging.io/) datasets. Data acquired using the [reproin](https://github.com/ReproNim/reproin) convention can be easily converted to BIDS. diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 97034762..9cd6feed 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1625,8 +1625,6 @@ tse3d: T2*/ interp = true; fprintf(fp, "\t\"Interpolation2D\": %d,\n", interp); } - if (d.interp3D > 1) //through-plane interpolation e.g. GE ZIP2 through-plane http://mriquestions.com/zip.html - fprintf(fp, "\t\"Interpolation3D\": %d,\n", d.interp3D); if (csaAscii.baseResolution > 0) fprintf(fp, "\t\"BaseResolution\": %d,\n", csaAscii.baseResolution); if (shimSetting[0] != 0.0) { @@ -1706,6 +1704,8 @@ tse3d: T2*/ //printf("PhaseLines=%d EchoTrainLength=%d SENSE=%g\n", d.phaseEncodingLines, d.echoTrainLength, d.accelFactPE); //n.b. we can not distinguish pF from SENSE/GRAPPA for UIH } #endif + if (d.interp3D > 1) //through-plane interpolation e.g. GE ZIP2 through-plane http://mriquestions.com/zip.html + fprintf(fp, "\t\"Interpolation3D\": %d,\n", d.interp3D); // https://neurostars.org/t/repetitiontime-parameters-what-are-they-and-where-to-find-them/20020/6 json_Float(fp, "\t\"RepetitionTimePreparation\": %g,\n", repetitionTimePreparation); //Philips ASL specific tags, issue533 @@ -5843,12 +5843,24 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d else printMessage("DICOM images may be missing, expected %d spatial locations per volume, but found %d slices.\n", dcmList[indx0].locationsInAcquisition, nConvert); } - if (nAcq < 2) { - nAcq = 0; - for (int i = 0; i < nConvert; i++) - if (isSamePosition(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx])) - nAcq++; + //end validate number of spatial volumes: check that number of times a spatial position is repeated matches number of 3D volumes in 4D dataset + int nSamePos = 0; + for (int i = 0; i < nConvert; i++) + if (isSamePosition(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx])) + nSamePos++; + if ((nAcq < 2) && (nSamePos > 0)) nAcq = nSamePos; + if (nAcq > nSamePos) { //issue556 + if ((dcmList[indx0].manufacturer == kMANUFACTURER_GE) && (dcmList[indx0].zThick > dcmList[indx0].xyzMM[3])) { + int zipFactor = (int)roundf(dcmList[indx0].zThick / dcmList[indx0].xyzMM[3]); + if (zipFactor > 1) { + nAcq = nSamePos; + dcmList[dcmSort[0].indx].interp3D = zipFactor; + } + } } + if (nAcq != nSamePos) + printWarning("Expected %d volumes but found spatial position repeats %d times.\n", nAcq, nSamePos); + //end validate number of spatial volumes if ((nAcq > 1) && ((nConvert / nAcq) > 1) && ((nConvert % nAcq) == 0)) { hdr0.dim[3] = nConvert / nAcq; hdr0.dim[4] = nAcq; From c7ee1e5bf8eee25b570d7ec3405cc5f0a9de31f9 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Mon, 15 Nov 2021 12:59:58 -0500 Subject: [PATCH 03/60] Temporal discrepancy warning (https://github.com/rordenlab/dcm2niix/issues/560) --- console/nii_dicom_batch.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 9cd6feed..092d6d81 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -5946,6 +5946,8 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d if ((nVol > 1) && (volumeTimeStartFirstStartLast > 0.0)) { tr = volumeTimeStartFirstStartLast / (nVol - 1.0); if (fabs(tr - hdr0.pixdim[4]) > toleranceSec) { + if (hdr0.pixdim[4] > 0.0) + printWarning("Discrepancy between reported (%gs) and estimated (%gs) repetition time (issue 560).\n", hdr0.pixdim[4], tr); if ((dcmList[indx0].isIR) && (dcmList[indx0].manufacturer != kMANUFACTURER_PHILIPS)) dti4D->repetitionTimeInversion = hdr0.pixdim[4]; else From da316b0d3b471ae939a61ca4041de3901a62d484 Mon Sep 17 00:00:00 2001 From: Yujing Huang Date: Thu, 11 Nov 2021 12:21:16 -0500 Subject: [PATCH 04/60] 1. Implement a wrapper class to interface with dcm2niix functions. 2. The wrapper class provides interface to convert dicom in mgz orientation. 3. The wrapper class and dcm2niix functions are compiled into libdcm2niixfs.a with -DUSING_DCM2NIIXFSWRAPPER -DUSING_MGH_NIFTI_IO. 4. When using libdcm2niixfs.a, instead of outputting .nii, *.bval, *.bvec to disk, nifti header, image data, TDICOMdata, & TDTI information are saved in MRIFSSTRUCT struct. 5. If libdcm2niixfs.a is compiled with -DUSING_MGH_NIFTI_IO, the application needs to link with nifti library. --- console/CMakeLists.txt | 16 +++++ console/dcm2niix_fswrapper.cpp | 121 ++++++++++++++++++++++++++++++++ console/dcm2niix_fswrapper.h | 42 ++++++++++++ console/nifti1_io_core.cpp | 15 ++++ console/nifti1_io_core.h | 8 ++- console/nii_dicom.cpp | 10 ++- console/nii_dicom_batch.cpp | 122 ++++++++++++++++++++++++++++++++- console/nii_dicom_batch.h | 19 +++++ 8 files changed, 345 insertions(+), 8 deletions(-) create mode 100644 console/dcm2niix_fswrapper.cpp create mode 100644 console/dcm2niix_fswrapper.h diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index 05a16fbc..f484723e 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -93,6 +93,22 @@ if(USE_JPEGLS) add_executable(dcm2niix ${DCM2NIIX_SRCS} ${CHARLS_SRCS}) else() add_executable(dcm2niix ${DCM2NIIX_SRCS}) + + ### start of addition for FREESURFER + set(DCM2NIIXFSLIB dcm2niixfs) + set(DCM2NIIXFSLIB_SRCS + dcm2niix_fswrapper.cpp + nii_dicom.cpp + jpg_0XC3.cpp + ujpeg.cpp + nifti1_io_core.cpp + nii_foreign.cpp + nii_ortho.cpp + nii_dicom_batch.cpp) + + add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS}) + target_compile_definitions(${DCM2NIIXFSLIB} PUBLIC -DUSING_DCM2NIIXFSWRAPPER -DUSING_MGH_NIFTI_IO) + ### end of addition for FREESURFER endif() set(ZLIB_IMPLEMENTATION "Miniz" CACHE STRING "Choose zlib implementation.") diff --git a/console/dcm2niix_fswrapper.cpp b/console/dcm2niix_fswrapper.cpp new file mode 100644 index 00000000..28db86d6 --- /dev/null +++ b/console/dcm2niix_fswrapper.cpp @@ -0,0 +1,121 @@ +#include + +#include "nii_dicom.h" +#include "dcm2niix_fswrapper.h" + +struct TDCMopts dcm2niix_fswrapper::tdcmOpts; + +/* These are the TDCMopts defaults set in dcm2niix +isIgnoreTriggerTimes = false +isTestx0021x105E = false +isAddNamePostFixes = true +isSaveNativeEndian = true +isOneDirAtATime = false +isRenameNotConvert = false +isSave3D = false +isGz = false +isPipedGz = false +isFlipY = true +isCreateBIDS = true +isSortDTIbyBVal = false +isAnonymizeBIDS = true +isOnlyBIDS = false +isCreateText = false +isForceOnsetTimes = true +isIgnoreDerivedAnd2D = false +isPhilipsFloatNotDisplayScaling = true +isTiltCorrect = true +isRGBplanar = false +isOnlySingleFile = false +isForceStackDCE = true +isIgnoreSeriesInstanceUID = false // if true, d.seriesUidCrc = d.seriesNum; +isRotate3DAcq = true +isCrop = false +saveFormat = 0 +isMaximize16BitRange = 2 +isForceStackSameSeries = 2 +nameConflictBehavior = 2 +isVerbose = 0 +isProgress = 0 +compressFlag = 0 +dirSearchDepth = 5 +gzLevel = 6 +filename = "%s_%p" // seriesNum_protocol -f "%s_%p" +outdir = "..." +indir = "..." +pigzname = '\000' +optsname = "~/.dcm2nii.ini" +indirParent = "..." +imageComments = "" +seriesNumber = nnn +numSeries = 0 + */ + +// set TDCMopts defaults, overwrite settings to output in mgz orientation +void dcm2niix_fswrapper::setOpts(const char* dcmindir, const char* niioutdir) +{ + memset(&tdcmOpts, 0, sizeof(tdcmOpts)); + setDefaultOpts(&tdcmOpts, NULL); + + if (dcmindir != NULL) + strcpy(tdcmOpts.indir, dcmindir); + if (niioutdir != NULL) + strcpy(tdcmOpts.outdir, niioutdir); + + // set the options for freesurfer mgz orientation + tdcmOpts.isRotate3DAcq = false; + tdcmOpts.isFlipY = false; + tdcmOpts.isIgnoreSeriesInstanceUID = true; + tdcmOpts.isCreateBIDS = false; + tdcmOpts.isGz = false; + //tdcmOpts.isForceStackSameSeries = 1; // merge 2D slice '-m y' + tdcmOpts.isForceStackDCE = false; + //tdcmOpts.isForceOnsetTimes = false; +} + +// interface to isDICOMfile() in nii_dicom.cpp +bool dcm2niix_fswrapper::isDICOM(const char* file) +{ + return isDICOMfile(file); +} + +/* + * interface to nii_loadDirCore() to search all dicom files from the directory input file is in, + * and convert dicom files with the same series as given file. + */ +int dcm2niix_fswrapper::dcm2NiiOneSeries(const char* dcmfile) +{ + // get seriesNo for given dicom file + struct TDICOMdata tdicomData = readDICOM((char*)dcmfile); + + double seriesNo = (double)tdicomData.seriesUidCrc; + if (tdcmOpts.isIgnoreSeriesInstanceUID) + seriesNo = (double)tdicomData.seriesNum; + + // set TDCMopts to convert just one series + tdcmOpts.seriesNumber[0] = seriesNo; + tdcmOpts.numSeries = 1; + + return nii_loadDirCore(tdcmOpts.indir, &tdcmOpts); +} + +// interface to nii_getMrifsStruct() +MRIFSSTRUCT* dcm2niix_fswrapper::getMrifsStruct(void) +{ + return nii_getMrifsStruct(); +} + +// return nifti header saved in MRIFSSTRUCT +nifti_1_header* dcm2niix_fswrapper::getNiiHeader(void) +{ + MRIFSSTRUCT* mrifsStruct = getMrifsStruct(); + return &mrifsStruct->hdr0; +} + +// return image data saved in MRIFSSTRUCT +const unsigned char* dcm2niix_fswrapper::getMRIimg(void) +{ + MRIFSSTRUCT* mrifsStruct = getMrifsStruct(); + return mrifsStruct->imgM; +} + diff --git a/console/dcm2niix_fswrapper.h b/console/dcm2niix_fswrapper.h new file mode 100644 index 00000000..f9e45702 --- /dev/null +++ b/console/dcm2niix_fswrapper.h @@ -0,0 +1,42 @@ +#ifndef DCM2NIIX_FSWRAPPER_H +#define DCM2NIIX_FSWRAPPER_H + +#include "nii_dicom_batch.h" +#include "nii_dicom.h" + +/* + * This is a wrapper class to interface with dcm2niix functions. + * 1. The wrapper class provides interface to convert dicom in mgz orientation. + * 2. The wrapper class and dcm2niix functions are compiled into libdcm2niixfs.a + * with -DUSING_DCM2NIIXFSWRAPPER -DUSING_MGH_NIFTI_IO. + * 3. When using libdcm2niixfs.a, instead of outputting .nii, *.bval, *.bvec to disk, + * nifti header, image data, TDICOMdata, & TDTI information are saved in MRIFSSTRUCT struct. + * 4. If libdcm2niixfs.a is compiled with -DUSING_MGH_NIFTI_IO, the application needs to link with nifti library. + */ +class dcm2niix_fswrapper +{ +public: + // set TDCMopts defaults, overwrite settings to output in mgz orientation. + static void setOpts(const char* dcmindir, const char* niioutdir); + + // interface to isDICOMfile() in nii_dicom.cpp + static bool isDICOM(const char* file); + + // interface to nii_loadDirCore() to search all dicom files from the directory input file is in, + // and convert dicom files with the same series as given file. + static int dcm2NiiOneSeries(const char* dcmfile); + + // interface to nii_getMrifsStruct() + static MRIFSSTRUCT* getMrifsStruct(void); + + // return nifti header saved in MRIFSSTRUCT + static nifti_1_header* getNiiHeader(void); + + // return image data saved in MRIFSSTRUCT + static const unsigned char* getMRIimg(void); + +private: + static struct TDCMopts tdcmOpts; +}; + +#endif diff --git a/console/nifti1_io_core.cpp b/console/nifti1_io_core.cpp index 5f85a78c..4dc105ba 100644 --- a/console/nifti1_io_core.cpp +++ b/console/nifti1_io_core.cpp @@ -33,6 +33,7 @@ #ifndef USING_R +#ifndef USING_MGH_NIFTI_IO void nifti_swap_8bytes( size_t n , void *ar ) // 4 bytes at a time { size_t ii ; @@ -136,6 +137,7 @@ void swap_nifti_header( struct nifti_1_header *h ) return ; } #endif +#endif bool littleEndianPlatform () { @@ -268,6 +270,7 @@ mat44 nifti_dicom2mat(float orient[7], float patientPosition[4], float xyzMM[4]) } #ifndef USING_R +#ifndef USING_MGH_NIFTI_IO float nifti_mat33_determ( mat33 R ) /* determinant of 3x3 matrix */ { double r11,r12,r13,r21,r22,r23,r31,r32,r33 ; @@ -291,6 +294,7 @@ mat33 nifti_mat33_mul( mat33 A , mat33 B ) /* multiply 2 3x3 matrices */ return C ; } #endif +#endif mat44 nifti_mat44_mul( mat44 A , mat44 B ) /* multiply 2 3x3 matrices */ { @@ -315,6 +319,7 @@ mat33 nifti_mat33_transpose( mat33 A ) /* transpose 3x3 matrix */ } #ifndef USING_R +#ifndef USING_MGH_NIFTI_IO mat33 nifti_mat33_inverse( mat33 R ) /* inverse of 3x3 matrix */ { double r11,r12,r13,r21,r22,r23,r31,r32,r33 , deti ; @@ -338,6 +343,7 @@ mat33 nifti_mat33_inverse( mat33 R ) /* inverse of 3x3 matrix */ return Q ; } + float nifti_mat33_rownorm( mat33 A ) // max row norm of 3x3 matrix { float r1,r2,r3 ; @@ -403,6 +409,7 @@ mat33 nifti_mat33_polar( mat33 A ) return Z ; } + void nifti_mat44_to_quatern( mat44 R , float *qb, float *qc, float *qd, float *qx, float *qy, float *qz, @@ -572,6 +579,7 @@ mat44 nifti_mat44_inverse( mat44 R ) return Q ; } #endif +#endif // Eigen decomposition for symmetric 3x3 matrices, port of public domain Java Matrix library JAMA. // Connelly Barnes http://barnesc.blogspot.com/2007/02/eigenvectors-of-3x3-symmetric-matrix.html @@ -855,3 +863,10 @@ vec3 nifti_mat33_eig3(double bxx, double bxy, double bxz, double byy, double byz //printf("bvec = [%g 0 0; 0 %g 0; 0 0 %g]\n", v3.v[0], v3.v[1], v3.v[2]); return v3; } + + + + + + + diff --git a/console/nifti1_io_core.h b/console/nifti1_io_core.h index 98607888..77386d6f 100644 --- a/console/nifti1_io_core.h +++ b/console/nifti1_io_core.h @@ -22,7 +22,7 @@ extern "C" { #include #ifndef USING_R -typedef struct { /** 4x4 matrix struct **/ +typedef struct { /** 3x3 matrix struct **/ float m[3][3] ; } mat33 ; typedef struct { /** 4x4 matrix struct **/ @@ -74,8 +74,12 @@ ivec3 setiVec3(int x, int y, int z); vec3 setVec3(float x, float y, float z); vec4 setVec4(float x, float y, float z); #ifndef USING_R +#ifndef USING_MGH_NIFTI_IO // This declaration differs from the equivalent function in the current nifti1_io.h, so avoid the clash void swap_nifti_header ( struct nifti_1_header *h) ; +#else +void swap_nifti_header ( struct nifti_1_header *h , int is_nifti ) ; +#endif #endif vec4 nifti_vect44mat44_mul(vec4 v, mat44 m ); void nifti_swap_2bytes( size_t n , void *ar ); // 2 bytes at a time @@ -93,4 +97,4 @@ mat44 nifti_quatern_to_mat44( float qb, float qc, float qd, } #endif -#endif +#endif /* _NIFTI_IO_CORE_HEADER_ */ diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 8cba19d2..11908b43 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -765,7 +765,11 @@ struct TDICOMdata clear_dicom_data() { d.lastScanLoc = NAN; d.TR = 0.0; d.TE = 0.0; +#ifdef USING_DCM2NIIXFSWRAPPER + d.TI = -1.0; // default when there is no TI in dicom file +#else d.TI = 0.0; +#endif d.flipAngle = 0.0; d.bandwidthPerPixelPhaseEncode = 0.0; d.acquisitionDuration = 0.0; @@ -6820,7 +6824,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); if (bits == 1) break; //old style Burned-In - printMessage("Illegal/Obsolete DICOM: Overlay Bits Allocated must be 1, not %d\n", bits); + printMessage("Illegal/Obsolete DICOM (%s): Overlay Bits Allocated must be 1, not %d\n", fname, bits); overlayOK = false; break; } @@ -6829,7 +6833,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); if (pos == 0) break; //old style Burned-In - printMessage("Illegal/Obsolete DICOM: Overlay Bit Position shall be 0, not %d\n", pos); + printMessage("Illegal/Obsolete DICOM (%s): Overlay Bit Position shall be 0, not %d\n", fname, pos); overlayOK = false; break; } @@ -7500,7 +7504,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //printf("%g\t\t%g\t%g\t%g\t%s\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3], fname); //printMessage("buffer usage %d %d %d\n",d.imageStart, lPos+lFileOffset, MaxBufferSz); return d; -} // readDICOM() +} // readDICOMx() void setDefaultPrefs(struct TDCMprefs *prefs) { prefs->isVerbose = false; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 092d6d81..79be86ea 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -104,6 +104,25 @@ const char kFileSep[2] = "/"; #endif +#ifdef USING_DCM2NIIXFSWRAPPER +// create the struct to save nifti header, image data, TDICOMdata, & TDTI information. +// no .nii, .bval, .bvec are created. +MRIFSSTRUCT mrifsStruct; + +// retrieve the struct +MRIFSSTRUCT* nii_getMrifsStruct() +{ + return &mrifsStruct; +} + +// free the memory used for the image and dti +void nii_clrMrifsStruct() +{ + free(mrifsStruct.imgM); + free(mrifsStruct.tdti); +} +#endif + bool isADCnotDTI(TDTI bvec) { //returns true if bval!=0 but all bvecs == 0 (Philips code for derived ADC image) return ((!isSameFloat(bvec.V[0], 0.0f)) && //not a B-0 image ((isSameFloat(bvec.V[1], 0.0f)) && (isSameFloat(bvec.V[2], 0.0f)) && (isSameFloat(bvec.V[3], 0.0f)))); @@ -1954,7 +1973,11 @@ void swapEndian(struct nifti_1_header *hdr, unsigned char *im, bool isNative) { // must be told which is native to detect datatype and number of voxels // one could also auto-detect: hdr->sizeof_hdr==348 if (!isNative) - swap_nifti_header(hdr); +#ifdef USING_MGH_NIFTI_IO + swap_nifti_header(hdr, 1); +#else + swap_nifti_header(hdr); +#endif int nVox = 1; for (int i = 1; i < 8; i++) if (hdr->dim[i] > 1) @@ -1962,7 +1985,11 @@ void swapEndian(struct nifti_1_header *hdr, unsigned char *im, bool isNative) { int bitpix = hdr->bitpix; int datatype = hdr->datatype; if (isNative) - swap_nifti_header(hdr); +#ifdef USING_MGH_NIFTI_IO + swap_nifti_header(hdr, 1); +#else + swap_nifti_header(hdr); +#endif if (datatype == DT_RGBA32) return; //n.b. do not swap 8-bit, 24-bit RGB, and 32-bit RGBA @@ -2377,9 +2404,16 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st for (int v = 0; v < 4; v++) //for each vector+B-value dti4D->S[i].V[v] = vx[i].V[v]; } +#ifdef USING_DCM2NIIXFSWRAPPER + mrifsStruct.tdti = vx; + mrifsStruct.numDti = numDti; +#else free(vx); +#endif return volOrderIndex; } + +#ifndef USING_DCM2NIIXFSWRAPPER char txtname[2048] = {""}; strcpy(txtname, pathoutname); strcat(txtname, ".bval"); @@ -2398,10 +2432,18 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st } fprintf(fp, "%g\n", vx[numDti - 1].V[0]); fclose(fp); +#endif if (isIsotropic) { //issue 405: ISOTROPIC images have bval but not bvec +#ifdef USING_DCM2NIIXFSWRAPPER + mrifsStruct.tdti = vx; + mrifsStruct.numDti = numDti; +#else free(vx); +#endif return volOrderIndex; } + +#ifndef USING_DCM2NIIXFSWRAPPER strcpy(txtname, pathoutname); if (dcmList[indx0].isVectorFromBMatrix) strcat(txtname, ".mvec"); @@ -2425,7 +2467,14 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st } fclose(fp); #endif +#endif + +#ifdef USING_DCM2NIIXFSWRAPPER + mrifsStruct.tdti = vx; + mrifsStruct.numDti = numDti; +#else free(vx); +#endif return volOrderIndex; } // nii_saveDTI() @@ -4233,6 +4282,8 @@ int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, return EXIT_SUCCESS; } #endif + +#ifndef USING_DCM2NIIXFSWRAPPER FILE *fp = fopen(fname, "wb"); if (!fp) return EXIT_FAILURE; @@ -4243,6 +4294,8 @@ int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, fwrite(&pad, sizeof(pad), 1, fp); fwrite(&im[0], imgsz, 1, fp); fclose(fp); +#endif + if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) if ((opts.isGz) && (strlen(opts.pigzname) > 0)) { @@ -5736,6 +5789,18 @@ void loadOverlay(char *imgname, unsigned char *img, int offset, int x, int y, in } //loadOverlay() int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts, struct TDTI4D *dti4D, int segVol) { +#ifdef USING_DCM2NIIXFSWRAPPER + double seriesNum = (double) dcmList[dcmSort[0].indx].seriesUidCrc; + int segVolEcho = segVol; + if ((dcmList[dcmSort[0].indx].echoNum > 1) && (segVolEcho <= 0)) + segVolEcho = dcmList[dcmSort[0].indx].echoNum + 1; + if (segVolEcho > 0) + seriesNum = seriesNum + ((double)segVolEcho - 1.0) / 10.0; + + if (!isSameDouble(opts.seriesNumber[0], seriesNum)) + return EXIT_SUCCESS; +#endif + bool iVaries = intensityScaleVaries(nConvert, dcmSort, dcmList); float *sliceMMarray = NULL; //only used if slices are not equidistant uint64_t indx = dcmSort[0].indx; @@ -5788,6 +5853,11 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d #else bool saveAs3D = false; #endif + +#ifdef USING_DCM2NIIXFSWRAPPER + mrifsStruct.tdicomData = dcmList[indx]; // first in sorted list dcmSort +#endif + struct nifti_1_header hdr0; unsigned char *img = nii_loadImgXL(nameList->str[indx], &hdr0, dcmList[indx], iVaries, opts.compressFlag, opts.isVerbose, dti4D); if (strlen(opts.imageComments) > 0) { @@ -5803,6 +5873,11 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d unsigned char *imgM = (unsigned char *)malloc(imgsz * (uint64_t)nConvert); memcpy(&imgM[0], &img[0], imgsz); free(img); + +#ifdef USING_DCM2NIIXFSWRAPPER + printMessage("load Image %s\n", nameList->str[indx]); +#endif + //printMessage(" %d %d %d %d %lu\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], (unsigned long)[imgM length]); bool isHasOverlay = dcmList[indx0].isHasOverlay; if (nConvert > 1) { @@ -6170,6 +6245,11 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d } memcpy(&imgM[(uint64_t)i * imgsz], &img[0], imgsz); free(img); + +#ifdef USING_DCM2NIIXFSWRAPPER + if (opts.isVerbose) + printMessage("load Image #%d %s\n", i, nameList->str[indx]); +#endif } } //skip if we are only creating BIDS if (hdr0.dim[4] > 1) //for 4d datasets, last volume should be acquired before first @@ -6219,7 +6299,9 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d nii_SaveBIDSX(pathoutname, dcmList[dcmSort[0].indx], opts, &hdr0, nameList->str[dcmSort[0].indx], dti4D); if (opts.isOnlyBIDS) { //note we waste time loading every image, however this ensures hdr0 matches actual output +#ifndef USING_DCM2NIIXFSWRAPPER free(imgM); +#endif return EXIT_SUCCESS; } if ((segVol >= 0) && (hdr0.dim[4] > 1)) { @@ -6273,9 +6355,13 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d struct nifti_1_header hdrrx = hdr0; bool isFlipZ = false; if (sliceDir < 0) { +#ifdef USING_DCM2NIIXFSWRAPPER // freesurfer fix dcm/261000-10-6?.dcm + printMessage("***USING_DCM2NIIXFSWRAPPER***: skip nii_flipZ() when sliceDir < 0 (%s:%s:%d)\n", __FILE__, __func__, __LINE__); +#else isFlipZ = true; imgM = nii_flipZ(imgM, &hdr0); sliceDir = abs(sliceDir); //change this, we have flipped the image so GE DTI bvecs no longer need to be flipped! +#endif } nii_saveText(pathoutname, dcmList[dcmSort[0].indx], opts, &hdr0, nameList->str[indx]); int numADC = 0; @@ -6295,7 +6381,11 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d printWarning("Siemens XA DICOM inadequate for robust conversion (issue 236)\n"); if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert > 1) && (nConvert == (hdr0.dim[3] * hdr0.dim[4])) ) printWarning("Siemens XA exported as classic not enhanced DICOM (issue 236)\n"); +#ifndef USING_DCM2NIIXFSWRAPPER printMessage("Convert %d DICOM as %s (%dx%dx%dx%d)\n", nConvert, pathoutname, hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4]); +#else + printMessage( "Convert %d DICOM (%dx%dx%dx%d)\n", nConvert, hdr0.dim[1],hdr0.dim[2],hdr0.dim[3],hdr0.dim[4]); +#endif #ifndef USING_R fflush(stdout); //show immediately if run from MRIcroGL GUI #endif @@ -6481,7 +6571,16 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d if (returnCode == EXIT_SUCCESS) nii_saveAttributes(dcmList[dcmSort[0].indx], hdr0, opts, nameList->str[dcmSort[0].indx]); #endif + +#ifdef USING_DCM2NIIXFSWRAPPER + hdr0.vox_offset = 352; + + mrifsStruct.hdr0 = hdr0; + mrifsStruct.imgsz = nii_ImgBytes(hdr0); + mrifsStruct.imgM = imgM; +#else free(imgM); +#endif if (dcmList[dcmSort[0].indx].xyzDim[0] > 1) returnCode = kEXIT_INCOMPLETE_VOLUMES_FOUND; //issue515 return returnCode; //EXIT_SUCCESS; @@ -6805,6 +6904,9 @@ bool isSameSet(struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts *opts //if (((!(isSameFloat(d1.TE, d2.TE))) || (d1.echoNum != d2.echoNum)) && (!d1.isXRay)) { // *isMultiEcho = true; //} +#ifdef USING_DCM2NIIXFSWRAPPER + printf("isForceStackSameSeries = true, seriesNum %ld, %ld, seriesInstanceUidCrc %d, %d\n", d1.seriesNum, d2.seriesNum, d1.seriesUidCrc, d2.seriesUidCrc); +#endif return true; //we will stack these images, even if they differ in the following attributes } if ((d1.isHasImaginary != d2.isHasImaginary) || (d1.isHasPhase != d2.isHasPhase) || (d1.isHasReal != d2.isHasReal)) { @@ -7273,6 +7375,10 @@ int reportProgress(int progressPct, float frac) { #endif int nii_loadDirCore(char *indir, struct TDCMopts *opts) { +#ifdef USING_DCM2NIIXFSWRAPPER + memset(&mrifsStruct, 0, sizeof(mrifsStruct)); +#endif + struct TSearchList nameList; int nConvertTotal = 0; #if defined(_WIN64) || defined(_WIN32) || defined(USING_R) @@ -7518,6 +7624,15 @@ int nii_loadDirCore(char *indir, struct TDCMopts *opts) { continue; if (!dcmList[ii].isValid) continue; + +#ifdef USING_DCM2NIIXFSWRAPPER + if (opts->numSeries > 0) { + double seriesNum = (double) dcmList[ii].seriesUidCrc; + if (!isSameDouble(opts->seriesNumber[0], seriesNum)) + continue; // we convert one series at a time, skip the ones that we are not interested in + } +#endif + int nConvert = 0; bool isMultiEcho = false; bool isNonParallelSlices = false; @@ -7901,7 +8016,8 @@ void readFindPigz(struct TDCMopts *opts, const char *argv[]) { void setDefaultOpts(struct TDCMopts *opts, const char *argv[]) { //either "setDefaultOpts(opts,NULL)" or "setDefaultOpts(opts,argv)" where argv[0] is path to search strcpy(opts->pigzname, ""); #ifndef USING_R - readFindPigz(opts, argv); + if (argv != NULL) + readFindPigz(opts, argv); #endif #ifdef myEnableJasper opts->compressFlag = kCompressYes; //JASPER for JPEG2000 diff --git a/console/nii_dicom_batch.h b/console/nii_dicom_batch.h index b3423c81..9edb0b0b 100644 --- a/console/nii_dicom_batch.h +++ b/console/nii_dicom_batch.h @@ -23,6 +23,24 @@ extern "C" { }; #endif +#ifdef USING_DCM2NIIXFSWRAPPER +typedef struct +{ + struct nifti_1_header hdr0; + + size_t imgsz; + unsigned char *imgM; + + struct TDICOMdata tdicomData; + + struct TDTI *tdti; + int numDti; +} MRIFSSTRUCT; + +MRIFSSTRUCT* nii_getMrifsStruct(); +void nii_clrMrifsStruct(); +#endif + #define kNAME_CONFLICT_SKIP 0 //0 = write nothing for a file that exists with desired name #define kNAME_CONFLICT_OVERWRITE 1 //1 = overwrite existing file with same name #define kNAME_CONFLICT_ADD_SUFFIX 2 //default 2 = write with new suffix as a new file @@ -59,6 +77,7 @@ extern "C" { void readIniFile (struct TDCMopts *opts, const char * argv[]); int nii_saveNIIx(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts); int nii_loadDir(struct TDCMopts *opts); + int nii_loadDirCore(char *indir, struct TDCMopts* opts); void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char * filename); int nii_createFilename(struct TDICOMdata dcm, char * niiFilename, struct TDCMopts opts); void nii_createDummyFilename(char * niiFilename, struct TDCMopts opts); From 7a3c46d0b5963191cbae6a6c5c68a0f497bd2edd Mon Sep 17 00:00:00 2001 From: Yujing Huang Date: Thu, 11 Nov 2021 20:14:45 -0500 Subject: [PATCH 05/60] set target_include_directories, target_link_libraries for dcm2niixfs for -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_JPEGLS=ON -DUSE_OPENJPEG=ON --- console/CMakeLists.txt | 43 ++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index f484723e..6df8a407 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -76,6 +76,20 @@ set(DCM2NIIX_SRCS nii_ortho.cpp nii_dicom_batch.cpp) +set(DCM2NIIXFSLIB dcm2niixfs) +set(DCM2NIIXFSLIB_SRCS + dcm2niix_fswrapper.cpp + nii_dicom.cpp + jpg_0XC3.cpp + ujpeg.cpp + nifti1_io_core.cpp + nii_foreign.cpp + nii_ortho.cpp + nii_dicom_batch.cpp) + +add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS}) +target_compile_definitions(${DCM2NIIXFSLIB} PRIVATE -DUSING_DCM2NIIXFSWRAPPER -DUSING_MGH_NIFTI_IO) + option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF) if(USE_JPEGLS) add_definitions(-DmyEnableJPEGLS) @@ -93,22 +107,6 @@ if(USE_JPEGLS) add_executable(dcm2niix ${DCM2NIIX_SRCS} ${CHARLS_SRCS}) else() add_executable(dcm2niix ${DCM2NIIX_SRCS}) - - ### start of addition for FREESURFER - set(DCM2NIIXFSLIB dcm2niixfs) - set(DCM2NIIXFSLIB_SRCS - dcm2niix_fswrapper.cpp - nii_dicom.cpp - jpg_0XC3.cpp - ujpeg.cpp - nifti1_io_core.cpp - nii_foreign.cpp - nii_ortho.cpp - nii_dicom_batch.cpp) - - add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS}) - target_compile_definitions(${DCM2NIIXFSLIB} PUBLIC -DUSING_DCM2NIIXFSWRAPPER -DUSING_MGH_NIFTI_IO) - ### end of addition for FREESURFER endif() set(ZLIB_IMPLEMENTATION "Miniz" CACHE STRING "Choose zlib implementation.") @@ -124,6 +122,9 @@ if(NOT ${ZLIB_IMPLEMENTATION} STREQUAL "Miniz") add_definitions(-DmyDisableMiniZ) target_include_directories(dcm2niix PRIVATE ${ZLIB_INCLUDE_DIRS}) target_link_libraries(dcm2niix ${ZLIB_LIBRARIES}) + + target_include_directories(${DCM2NIIXFSLIB} PRIVATE ${ZLIB_INCLUDE_DIRS}) + target_link_libraries(${DCM2NIIXFSLIB} ${ZLIB_LIBRARIES}) endif() option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF) @@ -133,6 +134,9 @@ if(USE_TURBOJPEG) add_definitions(-DmyTurboJPEG) target_include_directories(dcm2niix PRIVATE ${TURBOJPEG_INCLUDEDIR}) target_link_libraries(dcm2niix ${TURBOJPEG_LIBRARIES}) + + target_include_directories(${DCM2NIIXFSLIB} PRIVATE ${TURBOJPEG_INCLUDEDIR}) + target_link_libraries(${DCM2NIIXFSLIB} ${TURBOJPEG_LIBRARIES}) endif() option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF) @@ -141,6 +145,9 @@ if(USE_JASPER) add_definitions(-DmyEnableJasper) target_include_directories(dcm2niix PRIVATE ${JASPER_INCLUDE_DIR}) target_link_libraries(dcm2niix ${JASPER_LIBRARIES}) + + target_include_directories(${DCM2NIIXFSLIB} PRIVATE ${JASPER_INCLUDE_DIR}) + target_link_libraries(${DCM2NIIXFSLIB} ${JASPER_LIBRARIES}) endif() option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF) @@ -159,6 +166,9 @@ if(USE_OPENJPEG) target_include_directories(dcm2niix PRIVATE ${OPENJPEG_INCLUDE_DIRS}) target_link_libraries(dcm2niix ${OPENJPEG_LIBRARIES}) + + target_include_directories(${DCM2NIIXFSLIB} PRIVATE ${OPENJPEG_INCLUDE_DIRS}) + target_link_libraries(${DCM2NIIXFSLIB} ${OPENJPEG_LIBRARIES}) else () add_definitions(-DmyDisableOpenJPEG) endif() @@ -222,4 +232,5 @@ if(APPLE) # launchctl plist ./dcm2niix endif() + install(TARGETS ${PROGRAMS} DESTINATION bin) From 93d899cee030dd2b0410eea96c1b15ca578e187b Mon Sep 17 00:00:00 2001 From: Yujing Huang Date: Tue, 16 Nov 2021 07:56:52 -0500 Subject: [PATCH 06/60] 1. add user control option BLD_DCM2NIIXFSLIB=ON 2. add install command for libdcm2niixfs.a 3. don't build libdcm2niixfs.a if USE_OPENJPEG OR USE_TURBOJPEG OR USE_JASPER is ON --- SuperBuild/SuperBuild.cmake | 8 +++++ console/CMakeLists.txt | 59 +++++++++++++++++++++---------------- console/nii_dicom_batch.h | 2 -- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/SuperBuild/SuperBuild.cmake b/SuperBuild/SuperBuild.cmake index 2a0a956f..399be238 100644 --- a/SuperBuild/SuperBuild.cmake +++ b/SuperBuild/SuperBuild.cmake @@ -36,6 +36,8 @@ if(USE_STATIC_RUNTIME) endif() endif() +option(BLD_DCM2NIIXFSLIB "Build libdcm2niixfs.a" OFF) + option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF) option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF) option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF) @@ -55,6 +57,10 @@ else() set(DEP_INSTALL_DIR ${CMAKE_BINARY_DIR}) endif() +if(BLD_DCM2NIIXFSLIB) + message("-- Build libdcm2niixfs.a: ${BLD_DCM2NIIXFSLIB}") +endif() + if(USE_OPENJPEG) message("-- Build with OpenJPEG: ${USE_OPENJPEG}") @@ -147,6 +153,8 @@ ExternalProject_Add(console # yaml-cpp -DBATCH_VERSION:BOOL=${BATCH_VERSION} -DYAML-CPP_DIR:PATH=${YAML-CPP_DIR} + # Build libdcm2niixfs.a + -DBLD_DCM2NIIXFSLIB:BOOL=${BLD_DCM2NIIXFSLIB} ) install(DIRECTORY ${CMAKE_BINARY_DIR}/bin/ DESTINATION bin diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index 6df8a407..dd4782ef 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -76,19 +76,26 @@ set(DCM2NIIX_SRCS nii_ortho.cpp nii_dicom_batch.cpp) -set(DCM2NIIXFSLIB dcm2niixfs) -set(DCM2NIIXFSLIB_SRCS - dcm2niix_fswrapper.cpp - nii_dicom.cpp - jpg_0XC3.cpp - ujpeg.cpp - nifti1_io_core.cpp - nii_foreign.cpp - nii_ortho.cpp - nii_dicom_batch.cpp) - -add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS}) -target_compile_definitions(${DCM2NIIXFSLIB} PRIVATE -DUSING_DCM2NIIXFSWRAPPER -DUSING_MGH_NIFTI_IO) +set(BUILD_DCM2NIIXFS FALSE) + +option(BLD_DCM2NIIXFSLIB "Build libdcm2niixfs.a" OFF) +if(BLD_DCM2NIIXFSLIB) + set(DCM2NIIXFSLIB dcm2niixfs) + set(DCM2NIIXFSLIB_SRCS + dcm2niix_fswrapper.cpp + nii_dicom.cpp + jpg_0XC3.cpp + ujpeg.cpp + nifti1_io_core.cpp + nii_foreign.cpp + nii_ortho.cpp + nii_dicom_batch.cpp) + set(BUILD_DCM2NIIXFS TRUE) +endif() + +if(USE_OPENJPEG OR USE_TURBOJPEG OR USE_JASPER) + set(BUILD_DCM2NIIXFS FALSE) +endif() option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF) if(USE_JPEGLS) @@ -105,8 +112,16 @@ if(USE_JPEGLS) charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp) add_executable(dcm2niix ${DCM2NIIX_SRCS} ${CHARLS_SRCS}) + + if(BUILD_DCM2NIIXFS) + add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS} ${CHARLS_SRCS}) + endif() else() add_executable(dcm2niix ${DCM2NIIX_SRCS}) + + if(BUILD_DCM2NIIXFS) + add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS}) + endif() endif() set(ZLIB_IMPLEMENTATION "Miniz" CACHE STRING "Choose zlib implementation.") @@ -122,9 +137,6 @@ if(NOT ${ZLIB_IMPLEMENTATION} STREQUAL "Miniz") add_definitions(-DmyDisableMiniZ) target_include_directories(dcm2niix PRIVATE ${ZLIB_INCLUDE_DIRS}) target_link_libraries(dcm2niix ${ZLIB_LIBRARIES}) - - target_include_directories(${DCM2NIIXFSLIB} PRIVATE ${ZLIB_INCLUDE_DIRS}) - target_link_libraries(${DCM2NIIXFSLIB} ${ZLIB_LIBRARIES}) endif() option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF) @@ -134,9 +146,6 @@ if(USE_TURBOJPEG) add_definitions(-DmyTurboJPEG) target_include_directories(dcm2niix PRIVATE ${TURBOJPEG_INCLUDEDIR}) target_link_libraries(dcm2niix ${TURBOJPEG_LIBRARIES}) - - target_include_directories(${DCM2NIIXFSLIB} PRIVATE ${TURBOJPEG_INCLUDEDIR}) - target_link_libraries(${DCM2NIIXFSLIB} ${TURBOJPEG_LIBRARIES}) endif() option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF) @@ -145,9 +154,6 @@ if(USE_JASPER) add_definitions(-DmyEnableJasper) target_include_directories(dcm2niix PRIVATE ${JASPER_INCLUDE_DIR}) target_link_libraries(dcm2niix ${JASPER_LIBRARIES}) - - target_include_directories(${DCM2NIIXFSLIB} PRIVATE ${JASPER_INCLUDE_DIR}) - target_link_libraries(${DCM2NIIXFSLIB} ${JASPER_LIBRARIES}) endif() option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF) @@ -166,9 +172,6 @@ if(USE_OPENJPEG) target_include_directories(dcm2niix PRIVATE ${OPENJPEG_INCLUDE_DIRS}) target_link_libraries(dcm2niix ${OPENJPEG_LIBRARIES}) - - target_include_directories(${DCM2NIIXFSLIB} PRIVATE ${OPENJPEG_INCLUDE_DIRS}) - target_link_libraries(${DCM2NIIXFSLIB} ${OPENJPEG_LIBRARIES}) else () add_definitions(-DmyDisableOpenJPEG) endif() @@ -221,6 +224,9 @@ if(BATCH_VERSION) list(APPEND PROGRAMS dcm2niibatch) endif() +if(BUILD_DCM2NIIXFS) + target_compile_definitions(${DCM2NIIXFSLIB} PRIVATE -DUSING_DCM2NIIXFSWRAPPER -DUSING_MGH_NIFTI_IO) +endif() if(APPLE) message("-- Adding Apple plist") @@ -234,3 +240,6 @@ endif() install(TARGETS ${PROGRAMS} DESTINATION bin) +if(BUILD_DCM2NIIXFS) + install(TARGETS ${DCM2NIIXFSLIB} DESTINATION lib) +endif() diff --git a/console/nii_dicom_batch.h b/console/nii_dicom_batch.h index 9edb0b0b..00f3ad8f 100644 --- a/console/nii_dicom_batch.h +++ b/console/nii_dicom_batch.h @@ -23,7 +23,6 @@ extern "C" { }; #endif -#ifdef USING_DCM2NIIXFSWRAPPER typedef struct { struct nifti_1_header hdr0; @@ -39,7 +38,6 @@ typedef struct MRIFSSTRUCT* nii_getMrifsStruct(); void nii_clrMrifsStruct(); -#endif #define kNAME_CONFLICT_SKIP 0 //0 = write nothing for a file that exists with desired name #define kNAME_CONFLICT_OVERWRITE 1 //1 = overwrite existing file with same name From a133247f5c8d7698e8cd973365c5a7044492c137 Mon Sep 17 00:00:00 2001 From: Ningfei Li Date: Wed, 17 Nov 2021 22:28:03 +0100 Subject: [PATCH 07/60] Refine CMake support for building libdcm2niixfs. Based on PR #559. Also formatting the .cpp/.h files. --- SuperBuild/SuperBuild.cmake | 21 ++++++++----- console/CMakeLists.txt | 45 ++++++++++++++------------- console/nifti1_io_core.cpp | 21 ++----------- console/nifti1_io_core.h | 2 +- console/nii_dicom_batch.cpp | 62 ++++++++++++++++++------------------- console/nii_dicom_batch.h | 14 ++++----- 6 files changed, 78 insertions(+), 87 deletions(-) diff --git a/SuperBuild/SuperBuild.cmake b/SuperBuild/SuperBuild.cmake index 399be238..7047a4ff 100644 --- a/SuperBuild/SuperBuild.cmake +++ b/SuperBuild/SuperBuild.cmake @@ -36,8 +36,6 @@ if(USE_STATIC_RUNTIME) endif() endif() -option(BLD_DCM2NIIXFSLIB "Build libdcm2niixfs.a" OFF) - option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF) option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF) option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF) @@ -45,6 +43,17 @@ option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF) option(BATCH_VERSION "Build dcm2niibatch for multiple conversions" OFF) +option(BUILD_DCM2NIIXFSLIB "Build libdcm2niixfs.a" OFF) + +if(BUILD_DCM2NIIXFSLIB) + if(USE_OPENJPEG OR USE_TURBOJPEG OR USE_JASPER) + message("-- Set BUILD_DCM2NIIXFSLIB to OFF since USE_TURBOJPEG/USE_JASPER/USE_OPENJPEG is ON.") + set(BUILD_DCM2NIIXFSLIB OFF CACHE BOOL "Build libdcm2niixfs.a" FORCE) + else() + message("-- Build libdcm2niixfs.a: ${BUILD_DCM2NIIXFSLIB}") + endif() +endif() + include(ExternalProject) set(DEPENDENCIES) @@ -57,10 +66,6 @@ else() set(DEP_INSTALL_DIR ${CMAKE_BINARY_DIR}) endif() -if(BLD_DCM2NIIXFSLIB) - message("-- Build libdcm2niixfs.a: ${BLD_DCM2NIIXFSLIB}") -endif() - if(USE_OPENJPEG) message("-- Build with OpenJPEG: ${USE_OPENJPEG}") @@ -153,8 +158,8 @@ ExternalProject_Add(console # yaml-cpp -DBATCH_VERSION:BOOL=${BATCH_VERSION} -DYAML-CPP_DIR:PATH=${YAML-CPP_DIR} - # Build libdcm2niixfs.a - -DBLD_DCM2NIIXFSLIB:BOOL=${BLD_DCM2NIIXFSLIB} + # Build libdcm2niixfs.a + -DBUILD_DCM2NIIXFSLIB:BOOL=${BUILD_DCM2NIIXFSLIB} ) install(DIRECTORY ${CMAKE_BINARY_DIR}/bin/ DESTINATION bin diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index dd4782ef..e98755af 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -66,6 +66,20 @@ endif() set(PROGRAMS dcm2niix) +option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF) +option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF) +option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF) +option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF) + +option(BATCH_VERSION "Build dcm2niibatch for multiple conversions" OFF) + +option(BUILD_DCM2NIIXFSLIB "Build libdcm2niixfs.a" OFF) + +if(USE_OPENJPEG OR USE_TURBOJPEG OR USE_JASPER) + message("-- Set BUILD_DCM2NIIXFSLIB to OFF since USE_TURBOJPEG/USE_JASPER/USE_OPENJPEG is ON.") + set(BUILD_DCM2NIIXFSLIB OFF CACHE BOOL "Build libdcm2niixfs.a" FORCE) +endif() + set(DCM2NIIX_SRCS main_console.cpp nii_dicom.cpp @@ -76,10 +90,7 @@ set(DCM2NIIX_SRCS nii_ortho.cpp nii_dicom_batch.cpp) -set(BUILD_DCM2NIIXFS FALSE) - -option(BLD_DCM2NIIXFSLIB "Build libdcm2niixfs.a" OFF) -if(BLD_DCM2NIIXFSLIB) +if(BUILD_DCM2NIIXFSLIB) set(DCM2NIIXFSLIB dcm2niixfs) set(DCM2NIIXFSLIB_SRCS dcm2niix_fswrapper.cpp @@ -90,14 +101,8 @@ if(BLD_DCM2NIIXFSLIB) nii_foreign.cpp nii_ortho.cpp nii_dicom_batch.cpp) - set(BUILD_DCM2NIIXFS TRUE) endif() -if(USE_OPENJPEG OR USE_TURBOJPEG OR USE_JASPER) - set(BUILD_DCM2NIIXFS FALSE) -endif() - -option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF) if(USE_JPEGLS) add_definitions(-DmyEnableJPEGLS) if(MSVC) @@ -113,15 +118,15 @@ if(USE_JPEGLS) charls/jpegstreamreader.cpp) add_executable(dcm2niix ${DCM2NIIX_SRCS} ${CHARLS_SRCS}) - if(BUILD_DCM2NIIXFS) + if(BUILD_DCM2NIIXFSLIB) add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS} ${CHARLS_SRCS}) - endif() + endif() else() add_executable(dcm2niix ${DCM2NIIX_SRCS}) - if(BUILD_DCM2NIIXFS) + if(BUILD_DCM2NIIXFSLIB) add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS}) - endif() + endif() endif() set(ZLIB_IMPLEMENTATION "Miniz" CACHE STRING "Choose zlib implementation.") @@ -139,7 +144,6 @@ if(NOT ${ZLIB_IMPLEMENTATION} STREQUAL "Miniz") target_link_libraries(dcm2niix ${ZLIB_LIBRARIES}) endif() -option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF) if(USE_TURBOJPEG) find_package(PkgConfig REQUIRED) pkg_check_modules(TURBOJPEG REQUIRED libturbojpeg) @@ -148,7 +152,6 @@ if(USE_TURBOJPEG) target_link_libraries(dcm2niix ${TURBOJPEG_LIBRARIES}) endif() -option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF) if(USE_JASPER) find_package(Jasper REQUIRED) add_definitions(-DmyEnableJasper) @@ -156,7 +159,6 @@ if(USE_JASPER) target_link_libraries(dcm2niix ${JASPER_LIBRARIES}) endif() -option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF) if(USE_OPENJPEG) set(OpenJPEG_DIR "${OpenJPEG_DIR}" CACHE PATH "Path to OpenJPEG configuration file" FORCE) @@ -176,7 +178,6 @@ else () add_definitions(-DmyDisableOpenJPEG) endif() -option(BATCH_VERSION "Build dcm2niibatch for multiple conversions" OFF) if(BATCH_VERSION) set(DCM2NIIBATCH_SRCS main_console_batch.cpp @@ -224,8 +225,8 @@ if(BATCH_VERSION) list(APPEND PROGRAMS dcm2niibatch) endif() -if(BUILD_DCM2NIIXFS) - target_compile_definitions(${DCM2NIIXFSLIB} PRIVATE -DUSING_DCM2NIIXFSWRAPPER -DUSING_MGH_NIFTI_IO) +if(BUILD_DCM2NIIXFSLIB) + target_compile_definitions(${DCM2NIIXFSLIB} PRIVATE -DUSING_DCM2NIIXFSWRAPPER -DUSING_MGH_NIFTI_IO) endif() if(APPLE) @@ -238,8 +239,8 @@ if(APPLE) # launchctl plist ./dcm2niix endif() - install(TARGETS ${PROGRAMS} DESTINATION bin) -if(BUILD_DCM2NIIXFS) + +if(BUILD_DCM2NIIXFSLIB) install(TARGETS ${DCM2NIIXFSLIB} DESTINATION lib) endif() diff --git a/console/nifti1_io_core.cpp b/console/nifti1_io_core.cpp index 4dc105ba..10567a3c 100644 --- a/console/nifti1_io_core.cpp +++ b/console/nifti1_io_core.cpp @@ -32,8 +32,7 @@ #include "print.h" -#ifndef USING_R -#ifndef USING_MGH_NIFTI_IO +#if !defined(USING_R) && !defined(USING_MGH_NIFTI_IO) void nifti_swap_8bytes( size_t n , void *ar ) // 4 bytes at a time { size_t ii ; @@ -137,7 +136,6 @@ void swap_nifti_header( struct nifti_1_header *h ) return ; } #endif -#endif bool littleEndianPlatform () { @@ -269,8 +267,7 @@ mat44 nifti_dicom2mat(float orient[7], float patientPosition[4], float xyzMM[4]) return Q44; } -#ifndef USING_R -#ifndef USING_MGH_NIFTI_IO +#if !defined(USING_R) && !defined(USING_MGH_NIFTI_IO) float nifti_mat33_determ( mat33 R ) /* determinant of 3x3 matrix */ { double r11,r12,r13,r21,r22,r23,r31,r32,r33 ; @@ -294,7 +291,6 @@ mat33 nifti_mat33_mul( mat33 A , mat33 B ) /* multiply 2 3x3 matrices */ return C ; } #endif -#endif mat44 nifti_mat44_mul( mat44 A , mat44 B ) /* multiply 2 3x3 matrices */ { @@ -318,8 +314,7 @@ mat33 nifti_mat33_transpose( mat33 A ) /* transpose 3x3 matrix */ return B; } -#ifndef USING_R -#ifndef USING_MGH_NIFTI_IO +#if !defined(USING_R) && !defined(USING_MGH_NIFTI_IO) mat33 nifti_mat33_inverse( mat33 R ) /* inverse of 3x3 matrix */ { double r11,r12,r13,r21,r22,r23,r31,r32,r33 , deti ; @@ -343,7 +338,6 @@ mat33 nifti_mat33_inverse( mat33 R ) /* inverse of 3x3 matrix */ return Q ; } - float nifti_mat33_rownorm( mat33 A ) // max row norm of 3x3 matrix { float r1,r2,r3 ; @@ -409,7 +403,6 @@ mat33 nifti_mat33_polar( mat33 A ) return Z ; } - void nifti_mat44_to_quatern( mat44 R , float *qb, float *qc, float *qd, float *qx, float *qy, float *qz, @@ -579,7 +572,6 @@ mat44 nifti_mat44_inverse( mat44 R ) return Q ; } #endif -#endif // Eigen decomposition for symmetric 3x3 matrices, port of public domain Java Matrix library JAMA. // Connelly Barnes http://barnesc.blogspot.com/2007/02/eigenvectors-of-3x3-symmetric-matrix.html @@ -863,10 +855,3 @@ vec3 nifti_mat33_eig3(double bxx, double bxy, double bxz, double byy, double byz //printf("bvec = [%g 0 0; 0 %g 0; 0 0 %g]\n", v3.v[0], v3.v[1], v3.v[2]); return v3; } - - - - - - - diff --git a/console/nifti1_io_core.h b/console/nifti1_io_core.h index 77386d6f..487b782f 100644 --- a/console/nifti1_io_core.h +++ b/console/nifti1_io_core.h @@ -76,7 +76,7 @@ vec4 setVec4(float x, float y, float z); #ifndef USING_R #ifndef USING_MGH_NIFTI_IO // This declaration differs from the equivalent function in the current nifti1_io.h, so avoid the clash -void swap_nifti_header ( struct nifti_1_header *h) ; +void swap_nifti_header ( struct nifti_1_header *h ) ; #else void swap_nifti_header ( struct nifti_1_header *h , int is_nifti ) ; #endif diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 79be86ea..506df335 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -112,14 +112,14 @@ MRIFSSTRUCT mrifsStruct; // retrieve the struct MRIFSSTRUCT* nii_getMrifsStruct() { - return &mrifsStruct; + return &mrifsStruct; } // free the memory used for the image and dti void nii_clrMrifsStruct() { - free(mrifsStruct.imgM); - free(mrifsStruct.tdti); + free(mrifsStruct.imgM); + free(mrifsStruct.tdti); } #endif @@ -1974,9 +1974,9 @@ void swapEndian(struct nifti_1_header *hdr, unsigned char *im, bool isNative) { // one could also auto-detect: hdr->sizeof_hdr==348 if (!isNative) #ifdef USING_MGH_NIFTI_IO - swap_nifti_header(hdr, 1); + swap_nifti_header(hdr, 1); #else - swap_nifti_header(hdr); + swap_nifti_header(hdr); #endif int nVox = 1; for (int i = 1; i < 8; i++) @@ -1986,9 +1986,9 @@ void swapEndian(struct nifti_1_header *hdr, unsigned char *im, bool isNative) { int datatype = hdr->datatype; if (isNative) #ifdef USING_MGH_NIFTI_IO - swap_nifti_header(hdr, 1); + swap_nifti_header(hdr, 1); #else - swap_nifti_header(hdr); + swap_nifti_header(hdr); #endif if (datatype == DT_RGBA32) return; @@ -5790,15 +5790,15 @@ void loadOverlay(char *imgname, unsigned char *img, int offset, int x, int y, in int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts, struct TDTI4D *dti4D, int segVol) { #ifdef USING_DCM2NIIXFSWRAPPER - double seriesNum = (double) dcmList[dcmSort[0].indx].seriesUidCrc; - int segVolEcho = segVol; - if ((dcmList[dcmSort[0].indx].echoNum > 1) && (segVolEcho <= 0)) - segVolEcho = dcmList[dcmSort[0].indx].echoNum + 1; - if (segVolEcho > 0) - seriesNum = seriesNum + ((double)segVolEcho - 1.0) / 10.0; - - if (!isSameDouble(opts.seriesNumber[0], seriesNum)) - return EXIT_SUCCESS; + double seriesNum = (double) dcmList[dcmSort[0].indx].seriesUidCrc; + int segVolEcho = segVol; + if ((dcmList[dcmSort[0].indx].echoNum > 1) && (segVolEcho <= 0)) + segVolEcho = dcmList[dcmSort[0].indx].echoNum + 1; + if (segVolEcho > 0) + seriesNum = seriesNum + ((double)segVolEcho - 1.0) / 10.0; + + if (!isSameDouble(opts.seriesNumber[0], seriesNum)) + return EXIT_SUCCESS; #endif bool iVaries = intensityScaleVaries(nConvert, dcmSort, dcmList); @@ -5855,7 +5855,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d #endif #ifdef USING_DCM2NIIXFSWRAPPER - mrifsStruct.tdicomData = dcmList[indx]; // first in sorted list dcmSort + mrifsStruct.tdicomData = dcmList[indx]; // first in sorted list dcmSort #endif struct nifti_1_header hdr0; @@ -5875,7 +5875,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d free(img); #ifdef USING_DCM2NIIXFSWRAPPER - printMessage("load Image %s\n", nameList->str[indx]); + printMessage("load Image %s\n", nameList->str[indx]); #endif //printMessage(" %d %d %d %d %lu\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], (unsigned long)[imgM length]); @@ -6247,8 +6247,8 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d free(img); #ifdef USING_DCM2NIIXFSWRAPPER - if (opts.isVerbose) - printMessage("load Image #%d %s\n", i, nameList->str[indx]); + if (opts.isVerbose) + printMessage("load Image #%d %s\n", i, nameList->str[indx]); #endif } } //skip if we are only creating BIDS @@ -6356,7 +6356,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d bool isFlipZ = false; if (sliceDir < 0) { #ifdef USING_DCM2NIIXFSWRAPPER // freesurfer fix dcm/261000-10-6?.dcm - printMessage("***USING_DCM2NIIXFSWRAPPER***: skip nii_flipZ() when sliceDir < 0 (%s:%s:%d)\n", __FILE__, __func__, __LINE__); + printMessage("***USING_DCM2NIIXFSWRAPPER***: skip nii_flipZ() when sliceDir < 0 (%s:%s:%d)\n", __FILE__, __func__, __LINE__); #else isFlipZ = true; imgM = nii_flipZ(imgM, &hdr0); @@ -6573,11 +6573,11 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d #endif #ifdef USING_DCM2NIIXFSWRAPPER - hdr0.vox_offset = 352; + hdr0.vox_offset = 352; - mrifsStruct.hdr0 = hdr0; - mrifsStruct.imgsz = nii_ImgBytes(hdr0); - mrifsStruct.imgM = imgM; + mrifsStruct.hdr0 = hdr0; + mrifsStruct.imgsz = nii_ImgBytes(hdr0); + mrifsStruct.imgM = imgM; #else free(imgM); #endif @@ -6905,7 +6905,7 @@ bool isSameSet(struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts *opts // *isMultiEcho = true; //} #ifdef USING_DCM2NIIXFSWRAPPER - printf("isForceStackSameSeries = true, seriesNum %ld, %ld, seriesInstanceUidCrc %d, %d\n", d1.seriesNum, d2.seriesNum, d1.seriesUidCrc, d2.seriesUidCrc); + printf("isForceStackSameSeries = true, seriesNum %ld, %ld, seriesInstanceUidCrc %d, %d\n", d1.seriesNum, d2.seriesNum, d1.seriesUidCrc, d2.seriesUidCrc); #endif return true; //we will stack these images, even if they differ in the following attributes } @@ -7376,7 +7376,7 @@ int reportProgress(int progressPct, float frac) { int nii_loadDirCore(char *indir, struct TDCMopts *opts) { #ifdef USING_DCM2NIIXFSWRAPPER - memset(&mrifsStruct, 0, sizeof(mrifsStruct)); + memset(&mrifsStruct, 0, sizeof(mrifsStruct)); #endif struct TSearchList nameList; @@ -7627,9 +7627,9 @@ int nii_loadDirCore(char *indir, struct TDCMopts *opts) { #ifdef USING_DCM2NIIXFSWRAPPER if (opts->numSeries > 0) { - double seriesNum = (double) dcmList[ii].seriesUidCrc; - if (!isSameDouble(opts->seriesNumber[0], seriesNum)) - continue; // we convert one series at a time, skip the ones that we are not interested in + double seriesNum = (double) dcmList[ii].seriesUidCrc; + if (!isSameDouble(opts->seriesNumber[0], seriesNum)) + continue; // we convert one series at a time, skip the ones that we are not interested in } #endif @@ -8017,7 +8017,7 @@ void setDefaultOpts(struct TDCMopts *opts, const char *argv[]) { //either "setDe strcpy(opts->pigzname, ""); #ifndef USING_R if (argv != NULL) - readFindPigz(opts, argv); + readFindPigz(opts, argv); #endif #ifdef myEnableJasper opts->compressFlag = kCompressYes; //JASPER for JPEG2000 diff --git a/console/nii_dicom_batch.h b/console/nii_dicom_batch.h index 00f3ad8f..c5f453a5 100644 --- a/console/nii_dicom_batch.h +++ b/console/nii_dicom_batch.h @@ -25,15 +25,15 @@ extern "C" { typedef struct { - struct nifti_1_header hdr0; + struct nifti_1_header hdr0; - size_t imgsz; - unsigned char *imgM; + size_t imgsz; + unsigned char *imgM; - struct TDICOMdata tdicomData; + struct TDICOMdata tdicomData; - struct TDTI *tdti; - int numDti; + struct TDTI *tdti; + int numDti; } MRIFSSTRUCT; MRIFSSTRUCT* nii_getMrifsStruct(); @@ -75,7 +75,7 @@ void nii_clrMrifsStruct(); void readIniFile (struct TDCMopts *opts, const char * argv[]); int nii_saveNIIx(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts); int nii_loadDir(struct TDCMopts *opts); - int nii_loadDirCore(char *indir, struct TDCMopts* opts); + int nii_loadDirCore(char *indir, struct TDCMopts* opts); void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char * filename); int nii_createFilename(struct TDICOMdata dcm, char * niiFilename, struct TDCMopts opts); void nii_createDummyFilename(char * niiFilename, struct TDCMopts opts); From 08d6e789eb821868d4f861def7b96b9136c1aaec Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Tue, 21 Dec 2021 13:04:28 +0100 Subject: [PATCH 08/60] Resolving additional printf() calls --- console/nii_dicom.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 11908b43..0ece2033 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -7204,8 +7204,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); stackPositionItem = i; if ((d.manufacturer == kMANUFACTURER_CANON) && (nVariableItems == 1) && (d.xyzDim[4] > 1)) { //WARNING: Canon CANON V6.0SP2001* (0018,9005) = "AX fMRI" strangely sets TemporalPositionIndex(0020,9128) as 1 for all volumes: (0020,9157) and (0020,9128) are INCORRECT! - printf("Invalid enhanced DICOM created by Canon: Only single dimension in DimensionIndexValues (0020,9157) varies, for 4D file (e.g. BOTH space and time should vary)\n"); - printf("%d %d\n", stackPositionItem, maxVariableItem); + printMessage("Invalid enhanced DICOM created by Canon: Only single dimension in DimensionIndexValues (0020,9157) varies, for 4D file (e.g. BOTH space and time should vary)\n"); + printMessage("%d %d\n", stackPositionItem, maxVariableItem); int stackTimeItem = 0; if (stackPositionItem == 0) { maxVariableItem++; From 83608aec7fa02a70d8ca3b6e9af095bf422d30ad Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Wed, 22 Dec 2021 16:42:13 +0100 Subject: [PATCH 09/60] Removing fence for pure comment lines --- console/nii_dicom_batch.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 506df335..7cb501b0 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -366,11 +366,9 @@ void siemensPhilipsCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI if (vx[i].V[v] == -0.0f) vx[i].V[v] = 0.0f; //remove sign from values that are virtually zero } -#ifndef USING_R //simple diagnostics for data prior to realignment: useful as first direction is the same for al Philips sequences //for (int i = 0; i < 3; i++) // printf("%g = %g %g %g\n", vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); -#endif return; } //https://github.com/rordenlab/dcm2niix/issues/225 if ((toupper(d->patientOrient[0]) == 'H') && (toupper(d->patientOrient[1]) == 'F') && (toupper(d->patientOrient[2]) == 'S')) From 49c76eb8516aeb9e57dd55a490ddf72381592002 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Wed, 22 Dec 2021 16:42:50 +0100 Subject: [PATCH 10/60] std::min and std::max don't always do the job because argument types differ --- console/nii_dicom_batch.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 7cb501b0..f537dc7d 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -79,11 +79,11 @@ const char kFileSep[2] = "/"; #ifdef USING_R #ifndef max -#define max(a, b) std::max(a, b) +#define max(a, b) (a > b ? a : b) #endif #ifndef min -#define min(a, b) std::min(a, b) +#define min(a, b) (a < b ? a : b) #endif #else From 53281c45050523dbbf1513a486cd22f3851906e7 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 11 Jan 2022 10:39:30 -0500 Subject: [PATCH 11/60] Experimental support for 1-bit DICOM (https://github.com/rordenlab/dcm2niix/issues/572) --- console/nii_dicom.cpp | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 0ece2033..b3ed3331 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -1492,6 +1492,8 @@ int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeS h->datatype = DT_RGB24; } else if ((d.bitsAllocated == 8) && (d.samplesPerPixel == 1)) h->datatype = DT_UINT8; + else if ((d.bitsAllocated == 1) && (d.samplesPerPixel == 1)) + h->datatype = DT_UINT8; else if ((d.bitsAllocated == 12) && (d.samplesPerPixel == 1)) h->datatype = DT_INT16; else if ((d.bitsAllocated == 16) && (d.samplesPerPixel == 1) && (d.isSigned)) @@ -1530,7 +1532,9 @@ int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeS h->magic[2] = '1'; h->magic[3] = '\0'; h->vox_offset = (float)d.imageStart; - if (d.bitsAllocated == 12) + if (d.bitsAllocated == 1) + h->bitpix = 8 * d.samplesPerPixel; + else if (d.bitsAllocated == 12) h->bitpix = 16 * d.samplesPerPixel; else h->bitpix = d.bitsAllocated * d.samplesPerPixel; @@ -2748,11 +2752,23 @@ unsigned char *nii_flipY(unsigned char *bImg, struct nifti_1_header *h) { return nii_flipImgY(bImg, h); } // nii_flipY() +void conv1bit16bit(unsigned char *img, struct nifti_1_header hdr) { //issue572 + printWarning("Support for images that allocate 1 bits is experimental\n"); + int nVox = (int)nii_ImgBytes(hdr) / (hdr.bitpix / 8); + for (int i = (nVox - 1); i >= 0; i--) { + int ibyte = i >> 3; //byte to sample + int ibit = (i % 8); //bit 0..7 + //if (highBit > 0) + // ibit = 7 - ibit; + int val = img[ibyte] >> ibit; + img[i] = (val & 1); + } +} //conv1bit16bit() + void conv12bit16bit(unsigned char *img, struct nifti_1_header hdr) { //convert 12-bit allocated data to 16-bit // works for MR-MONO2-12-angio-an1 from http://www.barre.nom.fr/medical/samples/ // looks wrong: this sample toggles between big and little endian stores - printWarning("Support for images that allocate 12 bits is experimental\n"); int nVox = (int)nii_ImgBytes(hdr) / (hdr.bitpix / 8); for (int i = (nVox - 1); i >= 0; i--) { int i16 = i * 2; @@ -2773,6 +2789,8 @@ unsigned char *nii_loadImgCore(char *imgname, struct nifti_1_header hdr, int bit size_t imgsz = nii_ImgBytes(hdr); size_t imgszRead = imgsz; size_t imageStart = imageStart32; + if (bitsAllocated == 1) + imgszRead = (imgsz + 7) >> 3; if (bitsAllocated == 12) imgszRead = round(imgsz * 0.75); FILE *file = fopen(imgname, "rb"); @@ -2800,6 +2818,8 @@ unsigned char *nii_loadImgCore(char *imgname, struct nifti_1_header hdr, int bit printError("Only loaded %zu of %zu bytes for %s\n", sz, imgszRead, imgname); return NULL; } + if (bitsAllocated == 1) + conv1bit16bit(bImg, hdr); if (bitsAllocated == 12) conv12bit16bit(bImg, hdr); return bImg; @@ -4291,6 +4311,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kXYSpacing 0x0028 + (0x0030 << 16) //DS 'PixelSpacing' #define kBitsAllocated 0x0028 + (0x0100 << 16) #define kBitsStored 0x0028 + (0x0101 << 16) //US 'BitsStored' +#define kHighBit 0x0028 + (0x0102 << 16) //US 'HighBit' #define kIsSigned 0x0028 + (0x0103 << 16) //PixelRepresentation #define kPixelPaddingValue 0x0028 + (0x0120 << 16) // https://github.com/rordenlab/dcm2niix/issues/262 #define kFloatPixelPaddingValue 0x0028 + (0x0122 << 16) // https://github.com/rordenlab/dcm2niix/issues/262 @@ -4436,6 +4457,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //int temporalPositionIdentifier = 0; int locationsInAcquisitionPhilips = 0; int imagesInAcquisition = 0; + int highBit = 0; //int sumSliceNumberMrPhilips = 0; int sliceNumberMrPhilips = 0; int volumeNumber = -1; @@ -4951,8 +4973,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); dcmStr(lLength, &buffer[lPos], mediaUID); //Philips "XX_" files //see https://github.com/rordenlab/dcm2niix/issues/328 - if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.66") != NULL) - d.isRawDataStorage = true; + if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.66.4") != NULL) //Segmentation Storage + d.isDerived = true; //Segmentation IOD, issue 572 + else if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.66") != NULL) + d.isRawDataStorage = true; //e.g. Raw Data IOD, https://dicom.nema.org/dicom/2013/output/chtml/part04/sect_i.4.html if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.1") != NULL) d.isRawDataStorage = true; //Private MR Spectrum Storage if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.2") != NULL) @@ -5900,6 +5924,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); case kBitsStored: d.bitsStored = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; + case kHighBit: + highBit = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; case kIsSigned: //http://dicomiseasy.blogspot.com/2012/08/chapter-12-pixel-data.html d.isSigned = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; @@ -6931,8 +6958,6 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); free(buffer); if (d.bitsStored < 0) d.isValid = false; - if (d.bitsStored == 1) - printWarning("1-bit binary DICOMs not supported\n"); //maybe not valid - no examples to test //printf("%d bval=%g bvec=%g %g %g<<<\n", d.CSA.numDti, d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); //printMessage("><>< DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); if (encapsulatedDataFragmentStart > 0) { @@ -7503,6 +7528,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //printf("%d\t%g\t%g\t%g\t%g\n", d.imageNum, d.rtia_timerGE, d.patientPosition[1],d.patientPosition[2],d.patientPosition[3]); //printf("%g\t\t%g\t%g\t%g\t%s\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3], fname); //printMessage("buffer usage %d %d %d\n",d.imageStart, lPos+lFileOffset, MaxBufferSz); + if ((d.bitsStored == 1) && (highBit != 0)) { + printWarning("1-bit binary with high bit = %d not supported (issue 572)\n", highBit); + d.isValid = false; + } return d; } // readDICOMx() From 57a3e9d7dcfbe485fce2bcad2d25f39594261d29 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 21 Jan 2022 08:55:45 -0500 Subject: [PATCH 12/60] Detect echo number for Siemens XA that omit 0018,0086 (https://github.com/rordenlab/dcm2niix/issues/568) --- README.md | 1 + Siemens/README.md | 20 ++++++++++++++++++++ console/nii_dicom.cpp | 17 ++++++++++++++++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ffeabf16..9da60861 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,7 @@ The following tools exploit dcm2niix - [Brain imAgiNg Analysis iN Arcana (Banana)](https://pypi.org/project/banana/) is a collection of brain imaging analysis workflows, it uses dcm2niix for format conversions. - [BraTS-Preprocessor](https://neuronflow.github.io/BraTS-Preprocessor/) uses dcm2niix to import files for [Brain Tumor Segmentation](https://www.frontiersin.org/articles/10.3389/fnins.2020.00125/full). - [clinica](https://github.com/aramis-lab/clinica) is a software platform for clinical neuroimaging studies that uses dcm2niix to convert DICOM images. + - [bidsconvertr](https://github.com/wulms/bidsconvertr) uses R to converts DICOM data to NIfTI and finally to BIDS. - [bidsify](https://github.com/spinoza-rec/bidsify) is a Python project that uses dcm2niix to convert DICOM and Philips PAR/REC images to the BIDS standard. - [bidskit](https://github.com/jmtyszka/bidskit) uses dcm2niix to create [BIDS](http://bids.neuroimaging.io/) datasets. - [BioImage Suite Web Project](https://github.com/bioimagesuiteweb/bisweb) is a JavaScript project that uses dcm2niix for its DICOM conversion module. diff --git a/Siemens/README.md b/Siemens/README.md index 8609f80a..d898e2e8 100644 --- a/Siemens/README.md +++ b/Siemens/README.md @@ -15,6 +15,7 @@ While X-series consoles allow users to export data as enhanced, mosaic or classi When creating enhanced DICOMs diffusion information is provided in public tags. Based on a limited sample, it seems that classic DICOMs do not store diffusion data for XA10, and use private tags for [XA11](https://www.nitrc.org/forum/forum.php?thread_id=10013&forum_id=4703). Public Tags + ``` (0018,9089) FD -0.20\-0.51\-0.83 #DiffusionGradientOrientation (0018,9087) FD 1000 #DiffusionBValue @@ -22,6 +23,7 @@ Public Tags ``` Private Tags + ``` (0019,100c) IS 1000 #SiemensDiffusionBValue (0019,100e) FD -0.20\-0.51\-0.83 #SiemensDiffusionGradientOrientation @@ -30,6 +32,24 @@ Private Tags In theory, the public DICOM tag 'Frame Acquisition Date Time' (0018,9074) and the private tag 'Time After Start' (0021,1104) should each allow one to infer slice timing. The tag 0018,9074 uses the DT (date time) format, for example "20190621095520.330000" providing the YYYYYMMDDHHMMSS. Unfortunately, the Siemens de-identification routines will scramble these values, as time of data could be considered an identifiable attribute. The tag 0021,1104 is saved in DS (decimal string) format, for example "4.635" reporting the number of seconds since acquisition started. Be aware that some [Siemens Vida multi-band sequences](https://github.com/rordenlab/dcm2niix/issues/303) appear to fill these tags with the single-band times rather than the actual acquisition times. Therefore, neither of these two methods is perfectly reliable in determining slice timing. +The private `ICE_Dims` (0021,1106) tag can prove useful for parsing data. The list below is specific to XA scans: [SPM12](https://github.com/spm/spm12/blob/3085dac00ac804adb190a7e82c6ef11866c8af02/spm_dicom_convert.m#L268) suggests that this tag used to contain fewer elements. dcm2niix will use 0021,1106 to deduce echo number for [XA20 sequences that do not generate the public Echo Number (0018,0086)](https://github.com/rordenlab/dcm2niix/issues/568) tag. For example, consider an image of the 4th echo and 160th slice: + +``` +(0021,1106) LO [X_4_1_1_1_1_160_1_1_1_1_1_277] # ICE_Dims +``` + +1. eco = echo number +2. phs = phase encode +3. set = +4. rep = repetition +5. seg = segment +6. par = partition +7. slc = slice +8. idA = optional index +9. idB = optional index +10. idC = optional index +11. avg = average number + ## CSA Header Many crucial Siemens parameters are stored in the [proprietary CSA header](http://nipy.org/nibabel/dicom/siemens_csa.html), in particular the CSA Image Header Info (0029, 1010) and CSA Series Header Info (0029, 1020). These have binary sections that allows quick reading for many useful parameters. They also include an ASCII text portion that includes a lot of information but is slow to parse and poorly curated. Be aware that Siemens Vida scanners do not generate a CSA header. diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index b3ed3331..4c0f4dcf 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -916,7 +916,7 @@ void dcmStrDigitsDotOnlyKey(char key, char *lStr) { } else if (!isKey) lStr[i] = ' '; } -} //dcmStrDigitsOnlyKey() +} //dcmStrDigitsDotOnlyKey() void dcmStrDigitsOnlyKey(char key, char *lStr) { //e.g. string "p2s3" returns 2 if key=="p" and 3 if key=="s" @@ -4290,6 +4290,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kSequenceVariant21 0x0021 + (0x105B << 16) //CS #define kPATModeText 0x0021 + (0x1009 << 16) //LO, see kImaPATModeText #define kTimeAfterStart 0x0021 + (0x1104 << 16) //DS +#define kICE_dims 0x0021 + (0x1106 << 16) //LO [X_4_1_1_1_1_160_1_1_1_1_1_277] #define kPhaseEncodingDirectionPositiveSiemens 0x0021 + (0x111C << 16) //IS //#define kRealDwellTime 0x0021+(0x1142<< 16 )//IS #define kBandwidthPerPixelPhaseEncode21 0x0021 + (0x1153 << 16) //FD @@ -5800,6 +5801,20 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //printf("x\t%d\t%g\tkTimeAfterStart\n", acquisitionTimesGE_UIH, d.CSA.sliceTiming[acquisitionTimesGE_UIH]); acquisitionTimesGE_UIH++; break; + case kICE_dims: { //issue568: LO (0021,1106) [X_4_1_1_1_1_160_1_1_1_1_1_277] + if ((d.manufacturer != kMANUFACTURER_SIEMENS) || (d.echoNum > 1)) + break; + char iceStr[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], iceStr); + dcmStrDigitsOnly(iceStr); + char *end; + int echo = (int)strtol(iceStr, &end, 10); + //printMessage("%d:%d:'%s'\n", d.echoNum, echo, iceStr); + if (iceStr != end) + d.echoNum = echo; + //printMessage("%d:'%s'\n", echo, iceStr); + break; + } case kPhaseEncodingDirectionPositiveSiemens: { if (d.manufacturer != kMANUFACTURER_SIEMENS) break; From 41416b1c67ea937c6d7ee93d01127252034da6bd Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Sun, 23 Jan 2022 06:28:38 -0500 Subject: [PATCH 13/60] Treat CPS manufacturer as Siemens (https://github.com/rordenlab/dcm2niix/issues/576) --- console/nii_dicom.cpp | 11 +++++++---- console/nii_dicom.h | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 4c0f4dcf..8df0712c 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -1183,15 +1183,16 @@ int dcmStrManufacturer(const int lByteLength, unsigned char lBuffer[]) { //read ret = kMANUFACTURER_PHILIPS; if ((toupper(cString[0]) == 'T') && (toupper(cString[1]) == 'O')) ret = kMANUFACTURER_TOSHIBA; - //CANON_MEC if ((toupper(cString[0]) == 'C') && (toupper(cString[1]) == 'A')) ret = kMANUFACTURER_CANON; + if ((toupper(cString[0]) == 'C') && (toupper(cString[1]) == 'P')) + ret = kMANUFACTURER_SIEMENS; //CPS is a Siemens venture, issue 568 if ((toupper(cString[0]) == 'U') && (toupper(cString[1]) == 'I')) ret = kMANUFACTURER_UIH; if ((toupper(cString[0]) == 'B') && (toupper(cString[1]) == 'R')) ret = kMANUFACTURER_BRUKER; - if (ret == kMANUFACTURER_UNKNOWN) - printWarning("Unknown manufacturer %s\n", cString); + //if (ret == kMANUFACTURER_UNKNOWN) //reduce verbosity: single warning for series : Unable to determine manufacturer (0008,0070) + // printWarning("Unknown manufacturer %s\n", cString); //#ifdef _MSC_VER free(cString); //#endif @@ -4435,6 +4436,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); bool isDICOMANON = false; //issue383 bool isMATLAB = false; //issue383 bool isASL = false; + bool has00200013 = false; //double contentTime = 0.0; int echoTrainLengthPhil = 0; int philMRImageDiffBValueNumber = 0; @@ -5685,6 +5687,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); case kImageNum: //Enhanced Philips also uses this in once per file SQ 0008,1111 //Enhanced Philips also uses this once per slice in SQ 2005,140f + has00200013 = true; if (d.imageNum < 1) d.imageNum = dcmStrInt(lLength, &buffer[lPos]); //Philips renames each image again in 2001,9000, which can lead to duplicates break; @@ -7370,7 +7373,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); } if ((hasDwiDirectionality) && (d.CSA.numDti < 1)) d.CSA.numDti = 1; - if ((d.isValid) && (d.imageNum == 0)) { //Philips non-image DICOMs (PS_*, XX_*) are not valid images and do not include instance numbers + if ((d.isValid) && (! has00200013)) { //Philips non-image DICOMs (PS_*, XX_*) are not valid images and do not include instance numbers //TYPE 1 for MR image http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0020,0013) // Only type 2 for some other DICOMs! Therefore, generate warning not error printWarning("Instance number (0020,0013) not found: %s\n", fname); diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 905f097b..2ffc1c59 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20211010" +#define kDCMdate "v1.0.20220123" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic @@ -223,7 +223,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; uint32_t coilCrc, seriesUidCrc, instanceUidCrc; int overlayStart[kMaxOverlay]; int phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; - float xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; + float xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) float frameDuration, ecat_isotope_halflife, ecat_dosage; From 06a1f97ad3101bed02e38afd8fd542e2c340fc59 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Sat, 29 Jan 2022 11:21:33 -0500 Subject: [PATCH 14/60] Experimental support for Mediso DWI --- console/nii_dicom.cpp | 4 ++-- console/nii_dicom_batch.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 8df0712c..d94f1e7e 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -6352,7 +6352,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); // ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ)) && // (isAtFirstPatientPosition || isnan(d.patientPosition[1]))) //if((d.manufacturer == kMANUFACTURER_SIEMENS) || ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ)) - if ((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_HITACHI) || (d.manufacturer == kMANUFACTURER_SIEMENS) || (d.manufacturer == kMANUFACTURER_PHILIPS)) { + if ((d.manufacturer == kMANUFACTURER_MEDISO) || (d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_HITACHI) || (d.manufacturer == kMANUFACTURER_SIEMENS) || (d.manufacturer == kMANUFACTURER_PHILIPS)) { //for kMANUFACTURER_HITACHI see https://nciphub.org/groups/qindicom/wiki/StandardcompliantenhancedmultiframeDWI float v[4]; //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v); @@ -6366,7 +6366,6 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //d.CSA.dtiV[1] = v[0]; //d.CSA.dtiV[2] = v[1]; //d.CSA.dtiV[3] = v[2]; - //printMessage("><>< 0018,9089: DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); hasDwiDirectionality = true; d.isBVecWorldCoordinates = true; //e.g. Canon saved image space coordinates in Comments, world space in 0018, 9089 set_orientation0018_9089(&volDiffusion, lLength, &buffer[lPos], d.isLittleEndian); @@ -7550,6 +7549,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); printWarning("1-bit binary with high bit = %d not supported (issue 572)\n", highBit); d.isValid = false; } + //printf("%g\t%g\t%s\n", d.intenIntercept, d.intenScale, fname); return d; } // readDICOMx() diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index f537dc7d..26afff7a 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -355,7 +355,7 @@ void siemensPhilipsCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI //convert DTI vectors from scanner coordinates to image frame of reference //Uses 6 orient values from ImageOrientationPatient (0020,0037) // requires PatientPosition 0018,5100 is HFS (head first supine) - if ((!d->isBVecWorldCoordinates) && (d->manufacturer != kMANUFACTURER_BRUKER) && (d->manufacturer != kMANUFACTURER_TOSHIBA) && (d->manufacturer != kMANUFACTURER_HITACHI) && (d->manufacturer != kMANUFACTURER_UIH) && (d->manufacturer != kMANUFACTURER_SIEMENS) && (d->manufacturer != kMANUFACTURER_PHILIPS)) + if ((!d->isBVecWorldCoordinates) && (d->manufacturer != kMANUFACTURER_MEDISO) && (d->manufacturer != kMANUFACTURER_BRUKER) && (d->manufacturer != kMANUFACTURER_TOSHIBA) && (d->manufacturer != kMANUFACTURER_HITACHI) && (d->manufacturer != kMANUFACTURER_UIH) && (d->manufacturer != kMANUFACTURER_SIEMENS) && (d->manufacturer != kMANUFACTURER_PHILIPS)) return; if (d->CSA.numDti < 1) return; @@ -374,7 +374,7 @@ void siemensPhilipsCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI if ((toupper(d->patientOrient[0]) == 'H') && (toupper(d->patientOrient[1]) == 'F') && (toupper(d->patientOrient[2]) == 'S')) ; //participant was head first supine else { - printMessage("Check Siemens/Philips bvecs: expected Patient Position (0018,5100) to be 'HFS' not '%s'\n", d->patientOrient); + printMessage("Check bvecs: expected Patient Position (0018,5100) to be 'HFS' not '%s'\n", d->patientOrient); //return; //see https://github.com/rordenlab/dcm2niix/issues/238 } vec3 read_vector = setVec3(d->orient[1], d->orient[2], d->orient[3]); From 2057d8938df8b0c5f96694d74d6d6feaaddb490a Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 1 Feb 2022 10:57:05 -0500 Subject: [PATCH 15/60] Reduce compiler warnings, TR check now is aware of multiple averages (Mediso) --- console/nii_dicom.cpp | 109 +++++++++++++++++++++--------------- console/nii_dicom_batch.cpp | 68 +++++++++++----------- 2 files changed, 99 insertions(+), 78 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index d94f1e7e..1c3bb258 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -1614,7 +1614,7 @@ void cleanStr(char *lOut) { char *cString = (char *)malloc(sizeof(char) * (lLength + 1)); cString[lLength] = 0; memcpy(cString, (char *)&lOut[0], lLength); - for (int i = 0; i < lLength; i++) + for (int i = 0; i < (int)lLength; i++) //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1 if (cString[i] < 1) { unsigned char c = (unsigned char)cString[i]; @@ -1663,12 +1663,12 @@ void cleanStr(char *lOut) { if (c == 255) cString[i] = 'y'; } - for (int i = 0; i < lLength; i++) + for (int i = 0; i < (int)lLength; i++) if ((cString[i] < 1) || (cString[i] == ' ') || (cString[i] == ',') || (cString[i] == '/') || (cString[i] == '\\') || (cString[i] == '%') || (cString[i] == '*') || (cString[i] == 9) || (cString[i] == 10) || (cString[i] == 11) || (cString[i] == 13)) cString[i] = '_'; //issue398 //if ((cString[i]<1) || (cString[i]==' ') || (cString[i]==',') || (cString[i]=='^') || (cString[i]=='/') || (cString[i]=='\\') || (cString[i]=='%') || (cString[i]=='*') || (cString[i] == 9) || (cString[i] == 10) || (cString[i] == 11) || (cString[i] == 13)) cString[i] = '_'; int len = 1; - for (int i = 1; i < lLength; i++) { //remove repeated "_" + for (int i = 1; i < (int)lLength; i++) { //remove repeated "_" if ((cString[i - 1] != '_') || (cString[i] != '_')) { cString[len] = cString[i]; len++; @@ -2799,12 +2799,19 @@ unsigned char *nii_loadImgCore(char *imgname, struct nifti_1_header hdr, int bit printError("Unable to open '%s'\n", imgname); return NULL; } - fseek(file, 0, SEEK_END); - long fileLen = ftell(file); + //fseek(file, 0, SEEK_END); + //long fileLen = ftell(file); + #ifdef _MSC_VER + _fseeki64(file, 0, SEEK_END); + size_t fileLen = _ftelli64(file); + #else + fseeko(file, 0, SEEK_END); //Windows _fseeki64 + size_t fileLen = ftello(file); //Windows _ftelli64 + #endif if (fileLen < (imgszRead + imageStart)) { //note hdr.vox_offset is a float: issue507 //https://www.nitrc.org/forum/message.php?msg_id=27155 - printMessage("FileSize < (ImageSize+HeaderSize): %ld < (%zu+%zu) \n", fileLen, imgszRead, imageStart); + printMessage("FileSize < (ImageSize+HeaderSize): %zu < (%zu+%zu) \n", fileLen, imgszRead, imageStart); printWarning("File not large enough to store image data: %s\n", imgname); return NULL; } @@ -3380,14 +3387,14 @@ unsigned char *nii_loadImgPMSCT_RLE1(char *imgname, struct nifti_1_header hdr, s free(cImg); return NULL; } - if (imgsz == dcm.imageBytes) { // Handle special case that data is not compressed: + if ((int)imgsz == dcm.imageBytes) { // Handle special case that data is not compressed: return (unsigned char *)cImg; } unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output // RLE pass: compressed -> temp (bImg -> tImg) char *tImg = (char *)malloc(imgsz); //temp output - int o = 0; - for (size_t i = 0; i < dcm.imageBytes; ++i) { + size_t o = 0; + for (size_t i = 0; (int)i < dcm.imageBytes; ++i) { if (cImg[i] == (char)0xa5) { int repeat = (unsigned char)cImg[i + 1] + 1; char value = cImg[i + 2]; @@ -3403,13 +3410,13 @@ unsigned char *nii_loadImgPMSCT_RLE1(char *imgname, struct nifti_1_header hdr, s } } //for i free(cImg); - int tempsize = o; + size_t tempsize = o; //Looks like this RLE is pretty ineffective... // printMessage("RLE %d -> %d\n", dcm.imageBytes, o); //Delta encoding pass: temp -> output (tImg -> bImg) unsigned short delta = 0; o = 0; - int n16 = (int)imgsz >> 1; + size_t n16 = (int)imgsz >> 1; unsigned short *bImg16 = (unsigned short *)bImg; for (size_t i = 0; i < tempsize; ++i) { if (tImg[i] == (unsigned char)0x5a) { @@ -4091,8 +4098,13 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D printMessage("Unable to open file %s\n", fname); return d; } - fseek(file, 0, SEEK_END); - long fileLen = ftell(file); //Get file length + #ifdef _MSC_VER + _fseeki64(file, 0, SEEK_END); + size_t fileLen = _ftelli64(file); + #else + fseeko(file, 0, SEEK_END); //Windows _fseeki64 + size_t fileLen = ftello(file); //Windows _ftelli64 + #endif if (fileLen < 256) { printMessage("File too small to be a DICOM image %s\n", fname); return d; @@ -4115,7 +4127,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D if (MaxBufferSz > (size_t)fileLen) MaxBufferSz = fileLen; //printf("%d -> %d\n", MaxBufferSz, fileLen); - long lFileOffset = 0; + size_t lFileOffset = 0; fseek(file, 0, SEEK_SET); //Allocate memory unsigned char *buffer = (unsigned char *)malloc(MaxBufferSz + 1); @@ -4431,11 +4443,11 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); int overlayRows = 0; int overlayCols = 0; bool isNeologica = false; - bool isTriggerSynced = false; - bool isProspectiveSynced = false; + //bool isTriggerSynced = false; + //bool isProspectiveSynced = false; bool isDICOMANON = false; //issue383 bool isMATLAB = false; //issue383 - bool isASL = false; + //bool isASL = false; bool has00200013 = false; //double contentTime = 0.0; int echoTrainLengthPhil = 0; @@ -4473,7 +4485,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //int mRSeriesAcquisitionNumber = 0; uint32_t lLength; uint32_t groupElement; - long lPos = 0; + size_t lPos = 0; bool isPhilipsDerived = false; //bool isPhilipsDiffusion = false; if (isPart10prefix) { //for part 10 files, skip preamble and prefix @@ -4628,7 +4640,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); sqDepth00189114 = -1; //triggered //printf("slice %d---> 0020,9157 = %d %d %d\n", inStackPositionNumber, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2]); // d.aslFlags = kASL_FLAG_PHILIPS_LABEL; kASL_FLAG_PHILIPS_LABEL - if ((nDimIndxVal > 1) && (volumeNumber > 0) && (inStackPositionNumber > 0) && (d.aslFlags == kASL_FLAG_PHILIPS_LABEL) || (d.aslFlags == kASL_FLAG_PHILIPS_CONTROL)) { + if ((nDimIndxVal > 1) && (volumeNumber > 0) && (inStackPositionNumber > 0) && ((d.aslFlags == kASL_FLAG_PHILIPS_LABEL) || (d.aslFlags == kASL_FLAG_PHILIPS_CONTROL))) { isKludgeIssue533 = true; for (int i = 0; i < nDimIndxVal; i++) d.dimensionIndexValues[i] = 0; @@ -4669,7 +4681,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); break; } uint32_t dimensionIndexOrder[MAX_NUMBER_OF_DIMENSIONS]; - for (size_t i = 0; i < nDimIndxVal; i++) + for (int i = 0; i < nDimIndxVal; i++) dimensionIndexOrder[i] = i; // Bruker Enhanced MR IOD: reorder dimensions to ensure InStackPositionNumber corresponds to the first one // This will ensure correct ordering of slices in 4D datasets @@ -4930,7 +4942,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); if (strstr(privateCreator, "Image Private Header") != NULL) privateCreatorRemap = 0x0065 + (0x1000 << 16); //sanity check: group should match - if (grp != (privateCreatorRemap & 65535)) + if (grp != (int)(privateCreatorRemap & 65535)) privateCreatorRemap = 0; if (privateCreatorRemap == 0) goto skipRemap; //this is not a known private group @@ -5248,8 +5260,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); dcmStr(lLength, &buffer[lPos], acqContrast); if (((int)strlen(acqContrast) > 8) && (strstr(acqContrast, "DIFFUSION") != NULL)) d.isDiffusion = true; - if (((int)strlen(acqContrast) > 8) && (strstr(acqContrast, "PERFUSION") != NULL)) - isASL = true; //see series 301 of dcm_qa_philips_asl + //if (((int)strlen(acqContrast) > 8) && (strstr(acqContrast, "PERFUSION") != NULL)) + // isASL = true; //see series 301 of dcm_qa_philips_asl break; case kAcquisitionTime: { char acquisitionTimeTxt[kDICOMStr]; @@ -5420,10 +5432,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_COMBINATION; break; } - case kCardiacSynchronizationTechnique: + /*case kCardiacSynchronizationTechnique: if (toupper(buffer[lPos]) == 'P') isProspectiveSynced = true; - break; + break;*/ case kParallelReductionFactorInPlane: if (d.manufacturer == kMANUFACTURER_SIEMENS) break; @@ -6306,12 +6318,12 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); break; d.phaseNumber = dcmStrInt(lLength, &buffer[lPos]); //see dcm_qa_philips_asl break; - case kCardiacSync: //CS [TRIGGERED],[NO] + /*case kCardiacSync: //CS [TRIGGERED],[NO] if (lLength < 2) break; if (toupper(buffer[lPos]) != 'N') isTriggerSynced = true; - break; + break;*/ case kDiffusion_bValue: // 0018,9087 if (d.manufacturer == kMANUFACTURER_UNKNOWN) { d.manufacturer = kMANUFACTURER_PHILIPS; @@ -6465,12 +6477,12 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); if ((toupper(buffer[lPos]) != 'I') && (toupper(buffer[lPos + 1]) != 'N') && (toupper(buffer[lPos + 2]) != 'V')) d.isIR = true; break; - case kRespirationSync: //CS [TRIGGERED],[NO] + /*case kRespirationSync: //CS [TRIGGERED],[NO] if (lLength < 2) break; if (toupper(buffer[lPos]) != 'N') isTriggerSynced = true; - break; + break;*/ case kNumberOfSlicesMrPhilips: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; @@ -6601,9 +6613,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); printMessage(" GE header overflows buffer\n"); break; } - uint16_t hdr_offset = dcmInt(2, &buffer[lPos + 24], true); + size_t hdr_offset = dcmInt(2, &buffer[lPos + 24], true); if (isVerboseX > 1) - printMessage(" header offset: %d\n", hdr_offset); + printMessage(" header offset: %zu\n", hdr_offset); if (lLength < (hdr_offset + 916)) { //minimum size is hdr_offset=0, read 0x0394 printMessage(" GE header too small to be valid (B)\n"); break; @@ -6899,19 +6911,27 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); sprintf(str, "%*c%04x,%04x %u@%ld ", sqDepth + 1, ' ', groupElement & 65535, groupElement >> 16, lLength, lFileOffset + lPos); bool isStr = false; if (d.isExplicitVR) { - sprintf(str, "%s%c%c ", str, vr[0], vr[1]); + //sprintf(str, "%s%c%c ", str, vr[0], vr[1]); + //if (snprintf(str2, kDICOMStr-1, "%s%c%c", str, vr[0], vr[1]) < 0) exit(EXIT_FAILURE); + strncat(str, &vr[0], 1); + str[kDICOMStr-1] = '\0'; //silence warning -Wstringop-truncation + strncat(str, &vr[1], 1); + str[kDICOMStr-1] = '\0'; //silence warning -Wstringop-truncation + strcat(str, " "); + //sprintf(str, "%s%c%c ", str2, vr[0], vr[1]); + char str2[kDICOMStr] = ""; if ((vr[0] == 'F') && (vr[1] == 'D')) - sprintf(str, "%s%g ", str, dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian)); + sprintf(str2, "%g ", dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'F') && (vr[1] == 'L')) - sprintf(str, "%s%g ", str, dcmFloat(lLength, &buffer[lPos], d.isLittleEndian)); + sprintf(str2, "%g ", dcmFloat(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'S') && (vr[1] == 'S')) - sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + sprintf(str2, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'S') && (vr[1] == 'L')) - sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + sprintf(str2, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'U') && (vr[1] == 'S')) - sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + sprintf(str2, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'U') && (vr[1] == 'L')) - sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + sprintf(str, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'A') && (vr[1] == 'E')) isStr = true; if ((vr[0] == 'A') && (vr[1] == 'S')) @@ -6930,10 +6950,6 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); isStr = true; if ((vr[0] == 'L') && (vr[1] == 'T')) isStr = true; - //if ((vr[0]=='O') && (vr[1]=='B')) isStr = xxx; - //if ((vr[0]=='O') && (vr[1]=='D')) isStr = xxx; - //if ((vr[0]=='O') && (vr[1]=='F')) isStr = xxx; - //if ((vr[0]=='O') && (vr[1]=='W')) isStr = xxx; if ((vr[0] == 'P') && (vr[1] == 'N')) isStr = true; if ((vr[0] == 'S') && (vr[1] == 'H')) @@ -6946,11 +6962,12 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); isStr = true; if ((vr[0] == 'U') && (vr[1] == 'T')) isStr = true; + strncat(str, str2, kDICOMStr-4); + str[kDICOMStr-1] = '\0'; } else isStr = (lLength > 12); //implicit encoding: not always true as binary vectors may exceed 12 bytes, but often true if (lLength > 128) { - sprintf(str, "%s<%d bytes> ", str, lLength); - printMessage("%s\n", str); + printMessage("%s<%d bytes>\n", str, lLength); } else if (isStr) { //if length is greater than 8 bytes (+4 hdr) the MIGHT be a string char tagStr[kDICOMStr]; //tagStr[0] = 'X'; //avoid compiler warning: orientStr filled by dcmStr @@ -7220,9 +7237,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); mx[j] = dcmDim[0].dimIdx[j]; mn[j] = mx[j]; for (int i = 0; i < numDimensionIndexValues; i++) { - if (mx[j] < dcmDim[i].dimIdx[j]) + if (mx[j] < (int)dcmDim[i].dimIdx[j]) mx[j] = dcmDim[i].dimIdx[j]; - if (mn[j] > dcmDim[i].dimIdx[j]) + if (mn[j] > (int)dcmDim[i].dimIdx[j]) mn[j] = dcmDim[i].dimIdx[j]; } if (mx[j] != mn[j]) { diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 26afff7a..a6efe168 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -926,7 +926,7 @@ int geProtocolBlock(const char *filename, int geOffset, int geLength, int isVerb uint8_t flags = pCmp[3]; bool isFNAME = ((flags & 0x08) == 0x08); bool isFCOMMENT = ((flags & 0x10) == 0x10); - uint32_t hdrSz = 10; + int hdrSz = 10; if (isFNAME) { //skip null-terminated string FNAME for (; hdrSz < cmpSz; hdrSz++) if (pCmp[hdrSz] == 0) @@ -1016,7 +1016,7 @@ void json_Str(FILE *fp, const char *sLabel, char *sVal) { // issue131,425 unsigned char sValEsc[2048] = {""}; unsigned char *iVal = (unsigned char *)sVal; int o = 0; - for (int i = 0; i < strlen(sVal); i++) { + for (int i = 0; i < (int)strlen(sVal); i++) { //escape double quote (") and Backslash if ((sVal[i] == '"') || (sVal[i] == '\\')) { //escape double quotes and back slash sValEsc[o] = '\\'; @@ -2655,23 +2655,23 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s int nConvert = d3 * d4; if (d3 < 3) return true; //always consistent + /* float dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); - bool isConsistent = !isSameFloatGE(dx, 0.0); //slice distance of zero is not consistent with XYZT order (perhaps XYTZ) + //bool isConsistent = !isSameFloatGE(dx, 0.0); //slice distance of zero is not consistent with XYZT order (perhaps XYTZ) bool isAscending1 = (dx > 0); for (int v = 0; v < d4; v++) { int volStart = v * d3; - if (!isSameFloatGE(intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[volStart].indx]), 0.0)) - isConsistent = false; //XYZT requires first slice of each volume is at same position + //if (!isSameFloatGE(intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[volStart].indx]), 0.0)) + // isConsistent = false; //XYZT requires first slice of each volume is at same position for (int i = 1; i < d3; i++) { dx = intersliceDistanceSigned(dcmList[dcmSort[volStart + i - 1].indx], dcmList[dcmSort[volStart + i].indx]); bool isAscending = (dx > 0); //printf("volume %d slice %d distanceFromSlice1 %g DICOMvolume %d\n", v, i+1, dx, dcmList[dcmSort[volStart + i].indx].rawDataRunNumber); - if (isAscending != isAscending1) - isConsistent = false; //direction reverses + //if (isAscending != isAscending1) + // isConsistent = false; //direction reverses } } - //if (isConsistent) - // return true; + */ TFloatSort *floatSort = (TFloatSort *)malloc(nConvert * sizeof(TFloatSort)); int minVol = dcmList[dcmSort[0].indx].rawDataRunNumber; int maxVol = minVol; @@ -2716,7 +2716,7 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s if (isUsePhaseForVol) vol = phase; if (isPhaseIsBValNumber) vol += phase * maxVol; int isAslLabel = dcmList[dcmSort[i].indx].aslFlags == kASL_FLAG_PHILIPS_LABEL; - dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); + float dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); if (isASL) { #ifdef myMatchEnhanced00209157 //issue533: make classic DICOMs match enhanced DICOM volume order //disk order: slice < repeat < phase < label/control @@ -3224,7 +3224,7 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts #endif cleanISO8859(outname); //re-insert explicit path separators: -f %t/%s_%p will have folder for time, but will not segment a protocol named "fMRI\bold" - for (int pos = 0; pos < strlen(outname); pos++) { + for (int pos = 0; pos < (int)strlen(outname); pos++) { if (outname[pos] == kTempPathSeparator) outname[pos] = kPathSeparator; //e.g. for Windows, convert "/" to "\" if (outname[pos] < 32) //https://en.wikipedia.org/wiki/ASCII#Control_characters @@ -3707,7 +3707,6 @@ void writeMghGz(char *baseName, Tmgh hdr, TmghFooter footer, unsigned char *src_ free(pCmp); return; } - unsigned char *pHdr; //add header strm.avail_in = (unsigned int)sizeof(hdr); // size of input strm.next_in = (uint8_t *) &hdr.version; @@ -3767,7 +3766,7 @@ void writeMghGz(char *baseName, Tmgh hdr, TmghFooter footer, unsigned char *src_ int nii_saveMGH(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, struct TDTI4D *dti4D, int numDTI) { // FreeeSurfer does not use a permissive license, so we must reverse engineer code // https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat - int n, nDim = hdr.dim[0]; + int nDim = hdr.dim[0]; //printMessage("NRRD writer is experimental\n"); if (nDim < 1) return EXIT_FAILURE; @@ -5259,7 +5258,7 @@ void sliceTimeGE(struct TDICOMdata *d, int mb, int dim3, float TR, bool isInterl if ((mb > 1) && (!is27r3) && ((nExcitations % 2) == 0) ) { //number of slices divided by MB factor should is Even nExcitations ++; //https://osf.io/q4d53/wiki/home/; Figure 3 of https://pubmed.ncbi.nlm.nih.gov/26308571/ } - int nDiscardedSlices = (nExcitations * mb) - dim3; + //int nDiscardedSlices = (nExcitations * mb) - dim3; float secPerSlice = (TR - groupDelaysec) / (nExcitations); if (!isInterleaved) { for (int i = 0; i < nExcitations; i++) @@ -5333,7 +5332,6 @@ void readSoftwareVersionsGE(char softwareVersionsGE[], int verbose, char geVersi char *versionString = (char *)malloc(sizeof(char) * len); versionString[len - 1] = 0; memcpy(versionString, sepStart, len); - int ver1, ver2, ver3; char c1, c2, c3, c4; // RX27.0_R02_ or MR29.1_EA_2 sscanf(versionString, "%c%c%d.%d_%c%c%d", &c1, &c2, geMajorVersionInt, geMinorVersionInt, &c3, &c4, geReleaseVersionInt); @@ -5767,13 +5765,15 @@ void loadOverlay(char *imgname, unsigned char *img, int offset, int x, int y, in } fseek(file, 0, SEEK_END); long fileLen = ftell(file); - if (fileLen < (imgszRead + offset)) { + if (fileLen < (int)(imgszRead + offset)) { printWarning("File not large enough to store overlay: %s\n", imgname); return; } fseek(file, (long)offset, SEEK_SET); unsigned char *bImg = (unsigned char *)malloc(imgszRead); size_t sz = fread(bImg, 1, imgszRead, file); + if (sz < imgszRead) + printWarning("loadOverlay fread error."); //static unsigned char mask[] = {128, 64, 32, 16, 8, 4, 2, 1}; static unsigned char mask[] = {1, 2, 4, 8, 16, 32, 64, 128}; for (int i = 0; i < nvox; i++) { @@ -5803,9 +5803,9 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d float *sliceMMarray = NULL; //only used if slices are not equidistant uint64_t indx = dcmSort[0].indx; uint64_t indx0 = dcmSort[0].indx; - uint64_t indx1 = indx0; - if (nConvert > 1) - indx1 = dcmSort[1].indx; + //uint64_t indx1 = indx0; + //if (nConvert > 1) + // indx1 = dcmSort[1].indx; uint64_t indxEnd = dcmSort[nConvert - 1].indx; dti4D->repetitionTimeInversion = 0.0; //only set for Siemens and GE 3D T1 "TR" dti4D->repetitionTimeExcitation = 0.0; //only set for Philips 3D T1 "TR" @@ -6019,14 +6019,18 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d if ((nVol > 1) && (volumeTimeStartFirstStartLast > 0.0)) { tr = volumeTimeStartFirstStartLast / (nVol - 1.0); if (fabs(tr - hdr0.pixdim[4]) > toleranceSec) { - if (hdr0.pixdim[4] > 0.0) - printWarning("Discrepancy between reported (%gs) and estimated (%gs) repetition time (issue 560).\n", hdr0.pixdim[4], tr); - if ((dcmList[indx0].isIR) && (dcmList[indx0].manufacturer != kMANUFACTURER_PHILIPS)) - dti4D->repetitionTimeInversion = hdr0.pixdim[4]; - else - dti4D->repetitionTimeExcitation = hdr0.pixdim[4]; - hdr0.pixdim[4] = tr; - dcmList[indx0].TR = tr * 1000.0; //as msec + if (dcmList[indx0].numberOfAverages > 1.0) //e.g. Mediso will save averaged data + tr = tr / dcmList[indx0].numberOfAverages; + if (fabs(tr - hdr0.pixdim[4]) > toleranceSec) { + if (hdr0.pixdim[4] > 0.0) + printWarning("Discrepancy between reported (%gs) and estimated (%gs) repetition time (issue 560).\n", hdr0.pixdim[4], tr); + if ((dcmList[indx0].isIR) && (dcmList[indx0].manufacturer != kMANUFACTURER_PHILIPS)) + dti4D->repetitionTimeInversion = hdr0.pixdim[4]; + else + dti4D->repetitionTimeExcitation = hdr0.pixdim[4]; + hdr0.pixdim[4] = tr; + dcmList[indx0].TR = tr * 1000.0; //as msec + } } } if (dcmList[indx0].aslFlags != kASL_FLAG_NONE) { //issue533 @@ -6082,8 +6086,8 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d if (!ensureSequentialSlicePositions(hdr0.dim[3], hdr0.dim[4], dcmSort, dcmList, opts.isVerbose)) dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); indx0 = dcmSort[0].indx; - if (nConvert > 1) - indx1 = dcmSort[1].indx; + //if (nConvert > 1) + // indx1 = dcmSort[1].indx; #endif bool dxVaries = false; for (int i = 1; i < nConvert; i++) @@ -6130,8 +6134,8 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d isInconsistenSliceDir = false; //code below duplicates prior code, could be written as modular function(s) indx0 = dcmSort[0].indx; - if (nConvert > 1) - indx1 = dcmSort[1].indx; + //if (nConvert > 1) + // indx1 = dcmSort[1].indx; dxVaries = false; dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); for (int i = 1; i < nConvert; i++) @@ -6143,7 +6147,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d //for (int i = 1; i < nConvert; i++) // printf("%g ", intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[i].indx]) ); //printf("\n"); - bool isInconsistenSliceDir = false; + isInconsistenSliceDir = false; int slicePositionRepeats = 1; //how many times is first position repeated if (nConvert > 2) { float dxPrev = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); From a0b75101ec7130ab171d702994185d5384ac9b7d Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Sat, 5 Feb 2022 09:38:20 -0500 Subject: [PATCH 16/60] HRRT sequence details (https://github.com/rordenlab/dcm2niix/issues/577) --- console/nii_dicom.cpp | 17 +++++++++++++++-- console/nii_dicom.h | 6 +++--- console/nii_dicom_batch.cpp | 24 ++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 1c3bb258..75c1d472 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -810,6 +810,8 @@ struct TDICOMdata clear_dicom_data() { d.doseCalibrationFactor = 0.0; d.ecat_isotope_halflife = 0.0; d.frameDuration = -1.0; + d.frameReferenceTime = -1.0; + d.contentTime = 1.0; d.ecat_dosage = 0.0; d.radionuclideTotalDose = 0.0; d.seriesNum = 1; @@ -1700,6 +1702,8 @@ struct TDICOMdata nii_readParRec(char *parname, int isVerbose, struct TDTI4D *dt dti4D->volumeOnsetTime[0] = -1; dti4D->decayFactor[0] = -1; dti4D->frameDuration[0] = -1; + dti4D->frameReferenceTime[0] = -1; + dti4D->contentTime[0] = -1; //dti4D->fragmentOffset[0] = -1; dti4D->intenScale[0] = 0.0; strcpy(d.protocolName, ""); //erase dummy with empty @@ -4165,7 +4169,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kStudyTime 0x0008 + (0x0030 << 16) #define kSeriesTime 0x0008 + (0x0031 << 16) #define kAcquisitionTime 0x0008 + (0x0032 << 16) //TM -//#define kContentTime 0x0008+(0x0033 << 16 ) //TM +#define kContentTime 0x0008+(0x0033 << 16 ) //TM #define kModality 0x0008 + (0x0060 << 16) //CS #define kManufacturer 0x0008 + (0x0070 << 16) #define kInstitutionName 0x0008 + (0x0080 << 16) @@ -4360,7 +4364,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kAttenuationCorrectionMethod 0x0054 + (0x1101 << 16) //LO #define kDecayCorrection 0x0054 + (0x1102 << 16) //CS #define kReconstructionMethod 0x0054 + (0x1103 << 16) //LO -#define kDecayFactor 0x0054 + (0x1321 << 16) //LO +#define kFrameReferenceTime 0x0054 + (0x1300 << 16) //DS +#define kDecayFactor 0x0054 + (0x1321 << 16) //DS //ftp://dicom.nema.org/MEDICAL/dicom/2014c/output/chtml/part03/sect_C.8.9.4.html //If ImageType is REPROJECTION we slice direction is reversed - need example to test // #define kSeriesType 0x0054+(0x1000 << 16 ) @@ -5274,6 +5279,11 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); acquisitionTimesGE_UIH++; break; } + case kContentTime: { + char contentTimeTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], contentTimeTxt); + d.contentTime = atof(contentTimeTxt); + } case kSeriesTime: dcmStr(lLength, &buffer[lPos], seriesTimeTxt); break; @@ -6183,6 +6193,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); case kReconstructionMethod: //LO dcmStr(lLength, &buffer[lPos], d.reconstructionMethod); break; + case kFrameReferenceTime: + d.frameReferenceTime = dcmStrFloat(lLength, &buffer[lPos]); + break; case kDecayFactor: d.decayFactor = dcmStrFloat(lLength, &buffer[lPos]); break; diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 2ffc1c59..65cbbc06 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -177,7 +177,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; int sliceOrder[kMaxSlice2D]; // [7,3,2] means the first slice on disk should be moved to 7th position int gradDynVol[kMaxDTI4D]; //used to parse dimensions of Philips data, e.g. file with multiple dynamics, echoes, phase+magnitude //int fragmentOffset[kMaxDTI4D], fragmentLength[kMaxDTI4D]; //for images with multiple compressed fragments - float frameDuration[kMaxDTI4D], decayFactor[kMaxDTI4D], volumeOnsetTime[kMaxDTI4D], triggerDelayTime[kMaxDTI4D], TE[kMaxDTI4D], RWVScale[kMaxDTI4D], RWVIntercept[kMaxDTI4D], intenScale[kMaxDTI4D], intenIntercept[kMaxDTI4D], intenScalePhilips[kMaxDTI4D]; + float contentTime[kMaxDTI4D], frameReferenceTime[kMaxDTI4D], frameDuration[kMaxDTI4D], decayFactor[kMaxDTI4D], volumeOnsetTime[kMaxDTI4D], triggerDelayTime[kMaxDTI4D], TE[kMaxDTI4D], RWVScale[kMaxDTI4D], RWVIntercept[kMaxDTI4D], intenScale[kMaxDTI4D], intenIntercept[kMaxDTI4D], intenScalePhilips[kMaxDTI4D]; bool isReal[kMaxDTI4D]; bool isImaginary[kMaxDTI4D]; bool isPhase[kMaxDTI4D]; @@ -226,8 +226,8 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; float xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) - float frameDuration, ecat_isotope_halflife, ecat_dosage; - float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. + float contentTime, frameReferenceTime, frameDuration, ecat_isotope_halflife, ecat_dosage; + float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. double acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr]; char imageOrientationText[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], scanOptions[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[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]; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index a6efe168..fb638242 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1401,6 +1401,28 @@ tse3d: T2*/ } fprintf(fp, "\t],\n"); } + if (dti4D->frameReferenceTime[0] >= 0.0) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 + fprintf(fp, "\t\"FrameReferenceTime\": [\n"); + for (int i = 0; i < h->dim[4]; i++) { + if (i != 0) + fprintf(fp, ",\n"); + if (dti4D->frameReferenceTime[i] < 0) + break; + fprintf(fp, "\t\t%g", dti4D->frameReferenceTime[i] / 1000.0); // from 0018,1242 ms -> sec + } + fprintf(fp, "\t],\n"); + } + if (dti4D->contentTime[0] >= 0.0) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 + fprintf(fp, "\t\"ContentTimeHHMMSS\": [\n"); + for (int i = 0; i < h->dim[4]; i++) { + if (i != 0) + fprintf(fp, ",\n"); + if (dti4D->contentTime[i] < 0) + break; + fprintf(fp, "\t\t%09.2f", dti4D->contentTime[i]); // from 0018,1242 ms -> sec + } + fprintf(fp, "\t],\n"); + } //CT parameters json_Float(fp, "\t\"ExposureTime\": %g,\n", d.exposureTimeMs / 1000.0); json_Float(fp, "\t\"XRayTubeCurrent\": %g,\n", d.xRayTubeCurrent); @@ -5987,6 +6009,8 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d for (int i = 0; i < nConvert; i++) if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { dti4D->frameDuration[nTR] = dcmList[dcmSort[i].indx].frameDuration; + dti4D->frameReferenceTime[nTR] = dcmList[dcmSort[i].indx].frameReferenceTime; + dti4D->contentTime[nTR] = dcmList[dcmSort[i].indx].contentTime; nTR += 1; if (nTR >= kMaxDTI4D) break; From 822a4e12c1f4a22eede55cb58266008879d35ba2 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Sun, 6 Feb 2022 09:26:31 -0500 Subject: [PATCH 17/60] Reorder PT volumes by ascending FrameReferenceTime (https://github.com/rordenlab/dcm2niix/issues/577) --- console/nii_dicom_batch.cpp | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index fb638242..1a9329f4 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -2677,23 +2677,6 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s int nConvert = d3 * d4; if (d3 < 3) return true; //always consistent - /* - float dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); - //bool isConsistent = !isSameFloatGE(dx, 0.0); //slice distance of zero is not consistent with XYZT order (perhaps XYTZ) - bool isAscending1 = (dx > 0); - for (int v = 0; v < d4; v++) { - int volStart = v * d3; - //if (!isSameFloatGE(intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[volStart].indx]), 0.0)) - // isConsistent = false; //XYZT requires first slice of each volume is at same position - for (int i = 1; i < d3; i++) { - dx = intersliceDistanceSigned(dcmList[dcmSort[volStart + i - 1].indx], dcmList[dcmSort[volStart + i].indx]); - bool isAscending = (dx > 0); - //printf("volume %d slice %d distanceFromSlice1 %g DICOMvolume %d\n", v, i+1, dx, dcmList[dcmSort[volStart + i].indx].rawDataRunNumber); - //if (isAscending != isAscending1) - // isConsistent = false; //direction reverses - } - } - */ TFloatSort *floatSort = (TFloatSort *)malloc(nConvert * sizeof(TFloatSort)); int minVol = dcmList[dcmSort[0].indx].rawDataRunNumber; int maxVol = minVol; @@ -2712,8 +2695,11 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s maxInstance = max(maxInstance, instance); maxPhase = max(maxPhase, dcmList[dcmSort[i].indx].phaseNumber); } + bool isUseFrameReferenceTimeForVolume = false; + if (dcmList[dcmSort[0].indx].frameReferenceTime >= 0.0) + isUseFrameReferenceTimeForVolume = true; bool isUseInstanceNumberForVolume = false; - if ((d4 > 1) && (maxPhase == 1) && (minVol == maxVolNotADC) && (minInstance < maxInstance)) { + if ((!isUseFrameReferenceTimeForVolume) && (d4 > 1) && (maxPhase == 1) && (minVol == maxVolNotADC) && (minInstance < maxInstance)) { printWarning("Volume number does not vary (0019,10A2; 0020,0100; 2005,1063; 2005,1413), assuming meaningful instance number (0020,0013).\n"); isUseInstanceNumberForVolume = true; } @@ -2735,6 +2721,7 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s int rawvol = vol; int instance = dcmList[dcmSort[i].indx].imageNum; int phase = max(1, dcmList[dcmSort[i].indx].phaseNumber); + int refTime = (int)dcmList[dcmSort[i].indx].frameReferenceTime; //issue577: refTime in ms, so int conversion sufficient if (isUsePhaseForVol) vol = phase; if (isPhaseIsBValNumber) vol += phase * maxVol; int isAslLabel = dcmList[dcmSort[i].indx].aslFlags == kASL_FLAG_PHILIPS_LABEL; @@ -2759,6 +2746,8 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s printMessage("%d\t%g\t%d\t%d\t%d\t%d\t%g\n", instance, dx, vol, rawvol, isAslLabel, phase, dcmList[dcmSort[i].indx].triggerDelayTime); if (vol > kMaxDTI4D) //issue529 Philips derived Trace/ADC embedded into DWI vol = maxVol + 1; + if (isUseFrameReferenceTimeForVolume) + vol = refTime; minVolOut = min(minVolOut, vol); maxVolOut = max(maxVolOut, vol); floatSort[i].volume = vol; @@ -2766,9 +2755,10 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s floatSort[i].index = i; } //n.b. should we change dim[3] and dim[4] if number of volumes = dim[3]? - if ((!isPhaseIsBValNumber) && ((maxVolOut-minVolOut+1) != d4)) + if (isUseFrameReferenceTimeForVolume) + printWarning("Reordering volumes based on FrameReferenceTime (0054,1300; issue 577)\n"); + else if ((!isPhaseIsBValNumber) && ((maxVolOut-minVolOut+1) != d4)) printError("Check sorted order: 4D dataset has %d volumes, but volume index ranges from %d..%d\n", d4, minVolOut, maxVolOut); - //printf("dx = %g instance %d %d\n", intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]), dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[1].indx].imageNum); TDCMsort *dcmSortIn = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); for (int i = 0; i < nConvert; i++) dcmSortIn[i] = dcmSort[i]; @@ -2777,11 +2767,9 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s dcmSort[i] = dcmSortIn[floatSort[i].index]; free(floatSort); free(dcmSortIn); - //printf("dx = %g instance %d %d\n", intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]), dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[1].indx].imageNum); return false; } // ensureSequentialSlicePositions() - void swapDim3Dim4(int d3, int d4, struct TDCMsort dcmSort[]) { //swap space and time: input A0,A1...An,B0,B1...Bn output A0,B0,A1,B1,... int nConvert = d3 * d4; @@ -5998,7 +5986,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d //next: detect variable inter-volume time https://github.com/rordenlab/dcm2niix/issues/184 //if ((nConvert > 1) && ((dcmList[indx0].modality == kMODALITY_PT)|| (opts.isForceOnsetTimes))) { if ((nConvert > 1) && ((dcmList[indx0].modality == kMODALITY_PT) || ((opts.isForceOnsetTimes) && (dcmList[indx0].manufacturer != kMANUFACTURER_GE)))) { - if (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_PHILIPS) { + if (((dcmList[indx0].modality == kMODALITY_PT) && (dcmList[indx0].frameReferenceTime >= 0.0)) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_PHILIPS)) { //PT: issue 577 ensureSequentialSlicePositions(hdr0.dim[3], hdr0.dim[4], dcmSort, dcmList, opts.isVerbose); //issue529 indx0 = dcmSort[0].indx; } From fd10424a1a6895f31c3631b0b94081f13e9fb378 Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Sun, 6 Feb 2022 22:45:44 -0500 Subject: [PATCH 18/60] Export NeuroJSON JSON/binary-JSON based JNIfTI files (jnii/bnii) --- console/CMakeLists.txt | 8 +- console/main_console.cpp | 8 +- console/makefile | 4 +- console/nii_dicom_batch.cpp | 553 +++++++++++++++++++++++++++++++++++- console/nii_dicom_batch.h | 2 + 5 files changed, 568 insertions(+), 7 deletions(-) diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index e98755af..2ef3abe1 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -88,7 +88,9 @@ set(DCM2NIIX_SRCS nifti1_io_core.cpp nii_foreign.cpp nii_ortho.cpp - nii_dicom_batch.cpp) + nii_dicom_batch.cpp + cJSON.c + base64.c) if(BUILD_DCM2NIIXFSLIB) set(DCM2NIIXFSLIB dcm2niixfs) @@ -187,7 +189,9 @@ if(BATCH_VERSION) nifti1_io_core.cpp nii_foreign.cpp nii_ortho.cpp - nii_dicom_batch.cpp) + nii_dicom_batch.cpp + cJSON.c + base64.c) if(USE_JPEGLS) add_executable(dcm2niibatch ${DCM2NIIBATCH_SRCS} ${CHARLS_SRCS}) diff --git a/console/main_console.cpp b/console/main_console.cpp index a5aaf799..aa141c40 100644 --- a/console/main_console.cpp +++ b/console/main_console.cpp @@ -80,7 +80,7 @@ void showHelp(const char *argv[], struct TDCMopts opts) { printf(" -ba : anonymize BIDS (y/n, default %c)\n", bool2Char(opts.isAnonymizeBIDS)); printf(" -c : comment stored in NIfTI aux_file (provide up to 24 characters e.g. '-c first_visit')\n"); printf(" -d : directory search depth. Convert DICOMs in sub-folders of in_folder? (0..9, default %d)\n", opts.dirSearchDepth); - printf(" -e : export as NRRD (y) or MGH (o) instead of NIfTI (y/n/o, default n)\n"); + printf(" -e : export as NRRD (y) or MGH (o) or JSON/JNIfTI (j) or BJNIfTI (b) instead of NIfTI (y/n/o/j/b, default n)\n"); #ifdef mySegmentByAcq #define kQstr " %%q=sequence number," #else @@ -174,7 +174,7 @@ void showHelp(const char *argv[], struct TDCMopts opts) { } //showHelp() int invalidParam(int i, const char *argv[]) { - if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == 'o') || (argv[i][0] == 'O') || (argv[i][0] == 'h') || (argv[i][0] == 'H') || (argv[i][0] == 'i') || (argv[i][0] == 'I') || (argv[i][0] == '0') || (argv[i][0] == '1') || (argv[i][0] == '2') || (argv[i][0] == '3')) + if (strchr("yYnNoOhHiIjJBb01234",argv[i][0])) return 0; //if (argv[i][0] != '-') return 0; @@ -359,6 +359,10 @@ int main(int argc, const char *argv[]) { opts.saveFormat = kSaveFormatNRRD; if ((argv[i][0] == 'o') || (argv[i][0] == 'O') || (argv[i][0] == '2')) opts.saveFormat = kSaveFormatMGH; + if ((argv[i][0] == 'j') || (argv[i][0] == 'J') || (argv[i][0] == '3')) + opts.saveFormat = kSaveFormatJNII; + if ((argv[i][0] == 'b') || (argv[i][0] == 'B') || (argv[i][0] == '4')) + opts.saveFormat = kSaveFormatBNII; } else if ((argv[i][1] == 'g') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) diff --git a/console/makefile b/console/makefile index 49084991..3f37cb04 100644 --- a/console/makefile +++ b/console/makefile @@ -6,7 +6,7 @@ CFLAGS=-s -O3 #Leak tests: # https://clang.llvm.org/docs/AddressSanitizer.html -# clang++ -O1 -g -fsanitize=address -fno-omit-frame-pointer -I. main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG +# clang++ -O1 -g -fsanitize=address -fno-omit-frame-pointer -I. main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp base64.c cJSON.c -o dcm2niix -DmyDisableOpenJPEG #run "make" for default build @@ -31,4 +31,4 @@ ifneq ($(OS),Windows_NT) endif endif all: - g++ $(CFLAGS) -I. $(JFLAGS) main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG + g++ $(CFLAGS) -I. $(JFLAGS) main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp base64.c cJSON.c -o dcm2niix -DmyDisableOpenJPEG diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 1a9329f4..de8853f3 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -38,6 +38,8 @@ #endif #include "nii_dicom.h" #include "nii_ortho.h" +#include "base64.h" +#include "cJSON.h" #include //toupper #include #include @@ -3340,11 +3342,20 @@ void nii_createDummyFilename(char *niiFilename, struct TDCMopts opts) { nii_createFilename(d, niiFilenameBase, opts); strcpy(niiFilename, "Example output filename: '"); strcat(niiFilename, niiFilenameBase); - if (opts.saveFormat != kSaveFormatNIfTI) { + if (opts.saveFormat == kSaveFormatMGH) { + if (opts.isGz) + strcat(niiFilename, ".mgz'"); + else + strcat(niiFilename, ".mgh'"); + } else if (opts.saveFormat == kSaveFormatNRRD) { if (opts.isGz) strcat(niiFilename, ".nhdr'"); else strcat(niiFilename, ".nrrd'"); + } else if (opts.saveFormat == kSaveFormatJNII) { + strcat(niiFilename, ".jnii'"); + } else if (opts.saveFormat == kSaveFormatBNII) { + strcat(niiFilename, ".bnii'"); } else { if (opts.isGz) strcat(niiFilename, ".nii.gz'"); @@ -4138,9 +4149,549 @@ int nii_saveNRRD(char *niiFilename, struct nifti_1_header hdr, unsigned char *im return pigz_File(fname, opts, imgsz); } // nii_saveNRRD() +enum TZipMethod {zmZlib, zmGzip, zmBase64}; + +#ifdef Z_DEFLATED + +int zmat_run(const size_t inputsize, unsigned char *inputstr, size_t *outputsize, unsigned char **outputbuf, const int zipid, int *ret, const int iscompress){ + z_stream zs; + size_t buflen[2]={0}; + *outputbuf=NULL; + + zs.zalloc = Z_NULL; + zs.zfree = Z_NULL; + zs.opaque = Z_NULL; + + if(inputsize==0) + return -1; + + if(iscompress){ + /** perform compression or encoding */ + if(zipid==zmBase64){ + /** base64 encoding */ + *outputbuf=base64_encode((const unsigned char*)inputstr, inputsize, outputsize); + }else if(zipid==zmZlib || zipid==zmGzip){ + /** zlib (.zip) or gzip (.gz) compression */ + if(zipid==zmZlib){ + if(deflateInit(&zs, (iscompress>0) ? Z_DEFAULT_COMPRESSION : (-iscompress)) != Z_OK) + return -2; + }else{ + if(deflateInit2(&zs, (iscompress>0) ? Z_DEFAULT_COMPRESSION : (-iscompress), Z_DEFLATED, 15|16, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) + return -2; + } + if(deflateInit(&zs, (iscompress>0) ? Z_DEFAULT_COMPRESSION : (-iscompress)) != Z_OK) + return -2; + buflen[0] =deflateBound(&zs,inputsize); + *outputbuf=(unsigned char *)malloc(buflen[0]); + zs.avail_in = inputsize; /* size of input, string + terminator*/ + zs.next_in = (Bytef *)inputstr; /* input char array*/ + zs.avail_out = buflen[0]; /* size of output*/ + + zs.next_out = (Bytef *)(*outputbuf); /*(Bytef *)(); // output char array*/ + + *ret=deflate(&zs, Z_FINISH); + *outputsize=zs.total_out; + if(*ret!=Z_STREAM_END && *ret!=Z_OK) + return -3; + deflateEnd(&zs); + }else{ + return -7; + } + }else{ + /** perform decompression or decoding */ + if(zipid==zmBase64){ + /** base64 decoding */ + *outputbuf=base64_decode((const unsigned char*)inputstr, inputsize, outputsize); + }else if(zipid==zmZlib || zipid==zmGzip){ + /** zlib (.zip) or gzip (.gz) decompression */ + int count=1; + if(zipid==zmZlib){ + if(inflateInit(&zs) != Z_OK) + return -2; + }else{ + if(inflateInit2(&zs, 15|32) != Z_OK) + return -2; + } + + buflen[0] =inputsize*20; + *outputbuf=(unsigned char *)malloc(buflen[0]); + + zs.avail_in = inputsize; /* size of input, string + terminator*/ + zs.next_in =inputstr; /* input char array*/ + zs.avail_out = buflen[0]; /* size of output*/ + + zs.next_out = (Bytef *)(*outputbuf); /*(Bytef *)(); // output char array*/ + + while((*ret=inflate(&zs, Z_SYNC_FLUSH))!=Z_STREAM_END && count<=10){ + *outputbuf=(unsigned char *)realloc(*outputbuf, (buflen[0]<1) + dim[ndim++]=jdataelemlen; + + for(int i=0;i0 && output[i][0]!='?'){ // take care all name-tags and constant string values + if(!(slen==1 && output[i][0]=='N')) + fwrite(output[i],1,slen,fp); + if(slen==1 && (output[i][0]=='N' || output[i][0]=='S') && i+20){ + int slotid=0; + if(sscanf(output[i],"\?%d",&slotid)==1 && slotid>0){ + unsigned char *compressed=NULL; + size_t compressedbytes; + int ret=0, status=0; + switch(slotid){ // mapping data to the pre-defined slots in the form of "?number" in the template + case 1: {write_ubjsonint(&hdr.sizeof_hdr,sizeof(hdr.sizeof_hdr),1,fp);break;} + case 2: {fputc(ndim,fp);break;} + case 3: {write_ubjsonint(hdr.dim+1, sizeof(hdr.dim[0]), ndim,fp);break;} + case 4: {write_ubjsonint(&hdr.intent_p1,1,sizeof(hdr.intent_p1),fp);break;} + case 5: {write_ubjsonint(&hdr.intent_p2,1,sizeof(hdr.intent_p2),fp);break;} + case 6: {write_ubjsonint(&hdr.intent_p3,1,sizeof(hdr.intent_p3),fp);break;} + case 7: {unsigned char val=strlen(intent);fputc('U',fp);fputc(val,fp); fwrite(intent,1,val,fp);break;} + case 8: {unsigned char val=strlen(dtype); fputc('U',fp);fputc(val,fp); fwrite(dtype,1,val,fp);break;} + case 9: {fputc(hdr.bitpix,fp);break;} + case 10: {write_ubjsonint(&hdr.slice_start, sizeof(hdr.slice_start),1,fp);break;} + case 11: {fputc(ndim,fp);break;} + case 12: {write_ubjsonint(&hdr.pixdim[1],ndim,sizeof(hdr.pixdim[1]),fp);break;} + case 13: {fputc((int)hdr.pixdim[0] ? 'l' : 'r',fp);break;} + case 14: {write_ubjsonint(&hdr.scl_slope,1,sizeof(hdr.scl_slope),fp);break;} + case 15: {write_ubjsonint(&hdr.scl_inter,1,sizeof(hdr.scl_inter),fp);break;} + case 16: {write_ubjsonint(&hdr.slice_end,sizeof(hdr.slice_end),1,fp);break;} + case 17: {unsigned char val=strlen(slicetype);fputc('U',fp);fputc(val,fp); fwrite(slicetype,1,val,fp);break;} + case 18: {unsigned char val=strlen(lunit);fputc('U',fp);fputc(val,fp); fwrite(lunit,1,val,fp);break;} + case 19: {unsigned char val=strlen(tunit);fputc('U',fp);fputc(val,fp); fwrite(tunit,1,val,fp);break;} + case 20: {write_ubjsonint(&hdr.cal_max,1,sizeof(hdr.cal_max),fp);break;} + case 21: {write_ubjsonint(&hdr.cal_min,1,sizeof(hdr.cal_min),fp);break;} + case 22: {write_ubjsonint(&hdr.slice_duration,1,sizeof(hdr.slice_duration),fp);break;} + case 23: {write_ubjsonint(&hdr.toffset,1,sizeof(hdr.toffset),fp);break;} + case 24: {unsigned char val=strlen(hdr.descrip);fputc('U',fp);fputc(val,fp); fwrite(hdr.descrip,1,val,fp);break;} + case 25: {unsigned char val=strlen(hdr.aux_file);fputc('U',fp);fputc(val,fp); fwrite(hdr.aux_file,1,val,fp);break;} + case 26: {write_ubjsonint(&hdr.qform_code,sizeof(hdr.qform_code),1,fp);break;} + case 27: {write_ubjsonint(&hdr.sform_code,sizeof(hdr.sform_code),1,fp);break;} + case 28: {write_ubjsonint(&hdr.quatern_b,1,sizeof(hdr.quatern_b),fp);break;} + case 29: {write_ubjsonint(&hdr.quatern_c,1,sizeof(hdr.quatern_c),fp);break;} + case 30: {write_ubjsonint(&hdr.quatern_d,1,sizeof(hdr.quatern_d),fp);break;} + case 31: {write_ubjsonint(&hdr.qoffset_x,1,sizeof(hdr.qoffset_x),fp);break;} + case 32: {write_ubjsonint(&hdr.qoffset_y,1,sizeof(hdr.qoffset_y),fp);break;} + case 33: {write_ubjsonint(&hdr.qoffset_z,1,sizeof(hdr.qoffset_z),fp);break;} + case 34: { + write_ubjsonint(&hdr.srow_x[0],4,sizeof(hdr.srow_x[0]),fp); + write_ubjsonint(&hdr.srow_y[0],4,sizeof(hdr.srow_y[0]),fp); + write_ubjsonint(&hdr.srow_z[0],4,sizeof(hdr.srow_z[0]),fp); + break; + } + case 35: {unsigned char val=strlen(hdr.intent_name);fputc('U',fp);fputc(val,fp); fwrite(hdr.intent_name,1,val,fp);break;} + case 36: {unsigned char val=strlen(hdr.magic);fputc('U',fp);fputc(val,fp); fwrite(hdr.magic,1,val,fp);break;} + case 37: {int val=hdr.vox_offset;write_ubjsonint(&val, sizeof(val), 1,fp);break;} + case 38: {unsigned char val=strlen(jdtype);fputc('U',fp);fputc(val,fp); fwrite(jdtype,1,val,fp);break;} + case 39: {fputc(ndim,fp);break;} + case 40: {write_ubjsonint(dim, sizeof(dim[0]), ndim, fp); break;} +#ifdef Z_DEFLATED + case 41: {fputc('U',fp);fputc(4,fp);fwrite((opts.isGz ? "gzip" : "zlib"),1,4,fp);break;} + case 42: {unsigned int val=(totalbytes/(hdr.bitpix>>3));write_ubjsonint(&val,sizeof(val), 1,fp);break;} + case 43: + ret=zmat_run(totalbytes, im, &compressedbytes, (unsigned char **)&compressed, (opts.isGz ? zmGzip : zmZlib), &status,-opts.gzLevel); + if(!ret){ + unsigned int clen=compressedbytes; + if((size_t)clen==compressedbytes){ + fputc('l',fp); + write_ubjsonint(&clen, sizeof(clen), 1,fp); + }else{ + fputc('L',fp); + write_ubjsonint(&compressedbytes, sizeof(compressedbytes), 1,fp); + } + fwrite(compressed,1,compressedbytes,fp); + }else{ + return EXIT_FAILURE; + } + if(compressed) + free(compressed); + break; +#else // if zlib is not available, save raw binary data in _ArrayData_ record instead + case 41: + unsigned int clen=totalbytes; + if((size_t)clen==totalbytes) + write_ubjsonint(&clen, sizeof(clen), 1,fp); + else + write_ubjsonint(&totalbytes, sizeof(totalbytes), 1,fp); + fwrite(im,1,totalbytes,fp); + break; +#endif + } + } + } + } + fclose(fp); + return EXIT_SUCCESS; +} + +int nii_savejnii(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, struct TDTI4D *dti4D, int numDTI) { +// JNIfTI is a JSON wrapper to the NIfTI-1/2 format, supports both plain-text (.jnii) and binary (.bnii) formats +// Specification: https://github.com/NeuroJSON/jnifti/blob/master/JNIfTI_specification.md +// jnii is a plain JSON file and can be parsed in nearly all JSON parsers; to decode the +// embedded binary data as strings, the NIFTIData section can be be parsed in MATLAB using +// jnifty (https://github.com/NeuroJSON/jnifty) and Python using jdata (https://github.com/NeuroJSON/pyjdata) + + FILE *fp; + int dim[8]={0}, ndim=0; /* extra dimension in dim to hold the split data from complex voxel types, such as complex64 */ + + cJSON *root=NULL, *info=NULL, *jhdr=NULL, *dat=NULL, *sub=NULL; + char *jsonstr=NULL; + size_t compressedbytes, totalbytes; + unsigned char *compressed=NULL, *buf=NULL; + int ret=0, status=0; + + /*jnifti convers code-based header fields to human-readable/standardized strings*/ + int datatypeidx; + const char *datatypestr[]={"uint8","int16","int32","single","complex64","double", + "rgb24" ,"int8","uint16","uint32","int64","uint64", + "double128","complex128","complex256","rgba32",""}; + const char *jdatatypestr[]={"uint8","int16","int32","single","double","double", + "uint8" ,"int8","uint16","uint32","int64","uint64", + "uint8","double","uint8","uint8",""}; + unsigned char jdataelemlen[]={1,1,1,1,2,1,3,1,1,1,1,1,16,2,32,4,0}; + int datatypeid[]={NIFTI_TYPE_UINT8,NIFTI_TYPE_INT16,NIFTI_TYPE_INT32, + NIFTI_TYPE_FLOAT32,NIFTI_TYPE_COMPLEX64,NIFTI_TYPE_FLOAT64, + NIFTI_TYPE_RGB24,NIFTI_TYPE_INT8,NIFTI_TYPE_UINT16, + NIFTI_TYPE_UINT32,NIFTI_TYPE_INT64,NIFTI_TYPE_UINT64, + NIFTI_TYPE_FLOAT128,NIFTI_TYPE_COMPLEX128, + NIFTI_TYPE_COMPLEX256,NIFTI_TYPE_RGBA32}; + + int lunitidx, tunitidx; + const char *unitstr[]={"m","mm","um","s","ms","us","hz","ppm","rad",""}; + int unitid[]={NIFTI_UNITS_METER,NIFTI_UNITS_MM,NIFTI_UNITS_MICRON, + NIFTI_UNITS_SEC,NIFTI_UNITS_MSEC,NIFTI_UNITS_USEC, + NIFTI_UNITS_HZ,NIFTI_UNITS_PPM,NIFTI_UNITS_RADS}; + + int slicetypeidx; + const char *slicetypestr[]={"","seq+","seq-","alt+","alt-","alt2","alt2+","alt2-",""}; + int slicetypeid[]={NIFTI_SLICE_UNKNOWN,NIFTI_SLICE_SEQ_INC, + NIFTI_SLICE_SEQ_DEC,NIFTI_SLICE_ALT_INC,NIFTI_SLICE_ALT_DEC, + NIFTI_SLICE_ALT_INC2,NIFTI_SLICE_ALT_DEC2}; + + int intentidx; + const char *intentstr[]={"","corr","ttest","ftest","zscore","chi2","beta","binomial", + "gamma","poisson","normal","ncftest","ncchi2","logistic","laplace", + "uniform","ncttest","weibull","chi","invgauss","extval","pvalue", + "logpvalue","log10pvalue","estimate","label","neuronames","matrix", + "symmatrix","dispvec","vector","point","triangle","quaternion", + "unitless","tseries","elem","rgb","rgba","shape",""}; + int intentid[]={NIFTI_INTENT_NONE,NIFTI_INTENT_CORREL,NIFTI_INTENT_TTEST, + NIFTI_INTENT_FTEST,NIFTI_INTENT_ZSCORE,NIFTI_INTENT_CHISQ, + NIFTI_INTENT_BETA,NIFTI_INTENT_BINOM,NIFTI_INTENT_GAMMA, + NIFTI_INTENT_POISSON,NIFTI_INTENT_NORMAL,NIFTI_INTENT_FTEST_NONC, + NIFTI_INTENT_CHISQ_NONC,NIFTI_INTENT_LOGISTIC,NIFTI_INTENT_LAPLACE, + NIFTI_INTENT_UNIFORM,NIFTI_INTENT_TTEST_NONC,NIFTI_INTENT_WEIBULL, + NIFTI_INTENT_CHI,NIFTI_INTENT_INVGAUSS,NIFTI_INTENT_EXTVAL, + NIFTI_INTENT_PVAL,NIFTI_INTENT_LOGPVAL,NIFTI_INTENT_LOG10PVAL, + NIFTI_INTENT_ESTIMATE,NIFTI_INTENT_LABEL,NIFTI_INTENT_NEURONAME, + NIFTI_INTENT_GENMATRIX,NIFTI_INTENT_SYMMATRIX,NIFTI_INTENT_DISPVECT, + NIFTI_INTENT_VECTOR,NIFTI_INTENT_POINTSET,NIFTI_INTENT_TRIANGLE, + NIFTI_INTENT_QUATERNION,NIFTI_INTENT_DIMLESS,NIFTI_INTENT_TIME_SERIES, + NIFTI_INTENT_NODE_INDEX,NIFTI_INTENT_RGB_VECTOR,NIFTI_INTENT_RGBA_VECTOR, + NIFTI_INTENT_SHAPE}; + + char fname[2048] = {""}; + strcpy(fname, niiFilename); + if (opts.saveFormat == kSaveFormatBNII) + strcat(fname, ".bnii"); + else + strcat(fname, ".jnii"); + + /* preprocess nifti header to convert to human-readable jnifti fields */ + totalbytes=nii_ImgBytes(hdr); + ndim=hdr.dim[0]; + for(int i=1;i<8;i++) + dim[i-1]=hdr.dim[i]; + + datatypeidx=jnifti_lookup(datatypeid, sizeof(datatypeid)/sizeof(int), hdr.datatype); + slicetypeidx=jnifti_lookup(slicetypeid, sizeof(slicetypeid)/sizeof(int), hdr.slice_code); + intentidx=jnifti_lookup(intentid, sizeof(intentid)/sizeof(int), hdr.intent_code); + lunitidx=sizeof(unitid)/sizeof(unitid[0]); + tunitidx=sizeof(unitid)/sizeof(unitid[0]); + for(int i=0;i=NIFTI_UNITS_HZ && hdr.xyzt_units==unitid[i]) + cJSON_AddStringToObject(sub, "Special", unitstr[i]); + } + cJSON_AddNumberToObject(jhdr, "MaxIntensity", hdr.cal_max); + cJSON_AddNumberToObject(jhdr, "MinIntensity", hdr.cal_min); + cJSON_AddNumberToObject(jhdr, "SliceTime", hdr.slice_duration); + cJSON_AddNumberToObject(jhdr, "TimeOffset", hdr.toffset); + cJSON_AddStringToObject(jhdr, "Description", hdr.descrip); + cJSON_AddStringToObject(jhdr, "AuxFile", hdr.aux_file); + cJSON_AddNumberToObject(jhdr, "QForm", hdr.qform_code); + cJSON_AddNumberToObject(jhdr, "SForm", hdr.sform_code); + cJSON_AddItemToObject(jhdr, "Quatern", sub=cJSON_CreateObject()); + cJSON_AddNumberToObject(sub, "b", hdr.quatern_b); + cJSON_AddNumberToObject(sub, "c", hdr.quatern_c); + cJSON_AddNumberToObject(sub, "d", hdr.quatern_d); + cJSON_AddItemToObject(jhdr, "QuaternOffset", sub=cJSON_CreateObject()); + cJSON_AddNumberToObject(sub, "x", hdr.qoffset_x); + cJSON_AddNumberToObject(sub, "y", hdr.qoffset_y); + cJSON_AddNumberToObject(sub, "z", hdr.qoffset_z); + cJSON_AddItemToObject(jhdr, "Affine", sub=cJSON_CreateArray()); + cJSON_AddItemToArray(sub, cJSON_CreateFloatArray(hdr.srow_x,4)); + cJSON_AddItemToArray(sub, cJSON_CreateFloatArray(hdr.srow_y,4)); + cJSON_AddItemToArray(sub, cJSON_CreateFloatArray(hdr.srow_z,4)); + cJSON_AddStringToObject(jhdr, "Name", hdr.intent_name); + cJSON_AddStringToObject(jhdr, "NIIFormat", hdr.magic); + + /* save depreciated header flags if non-trival values are provided */ + if(hdr.vox_offset) + cJSON_AddNumberToObject(jhdr, "NIIByteOffset", hdr.vox_offset); + if(strlen(hdr.data_type)) + cJSON_AddStringToObject(jhdr, "A75DataTypeName", hdr.data_type); + if(strlen(hdr.db_name)) + cJSON_AddStringToObject(jhdr, "A75DBName", hdr.db_name); + if(hdr.extents) + cJSON_AddNumberToObject(jhdr, "A75Extends", hdr.extents); + if(hdr.session_error) + cJSON_AddNumberToObject(jhdr, "A75SessionError", hdr.session_error); + if(hdr.regular) + cJSON_AddNumberToObject(jhdr, "A75DataTypeName", hdr.regular); + if(hdr.glmax) + cJSON_AddNumberToObject(jhdr, "A75GlobalMax", hdr.glmax); + if(hdr.glmin) + cJSON_AddNumberToObject(jhdr, "A75GlobalMin", hdr.glmin); + + /* the "NIFTIData" section stores volumetric data */ + cJSON_AddItemToObject(root, "NIFTIData",dat = cJSON_CreateObject()); + cJSON_AddStringToObject(dat,"_ArrayType_",jdatatypestr[datatypeidx]); + if(jdataelemlen[datatypeidx]>1) + dim[ndim++]=jdataelemlen[datatypeidx]; + cJSON_AddItemToObject(dat, "_ArraySize_",cJSON_CreateIntArray(dim,ndim)); +#ifdef Z_DEFLATED + cJSON_AddStringToObject(dat, "_ArrayZipType_",(opts.isGz ? "gzip" : "zlib")); + cJSON_AddNumberToObject(dat, "_ArrayZipSize_",totalbytes/(hdr.bitpix>>3)); + + ret=zmat_run(totalbytes, im, &compressedbytes, (unsigned char **)&compressed, (opts.isGz ? zmGzip : zmZlib), &status,-opts.gzLevel); + + if(!ret){ + ret=zmat_run(compressedbytes, compressed, &totalbytes, (unsigned char **)&buf, zmBase64, &status,1); + cJSON_AddStringToObject(dat, "_ArrayZipData_",(char *)buf); + } + if(compressed) + free(compressed); +#else + buf=base64_encode(im, totalbytes, &compressedbytes) + cJSON_AddStringToObject(dat,"_ArrayData_", buf); +#endif + if(buf) + free(buf); + + /* now save JSON to file */ + jsonstr=cJSON_Print(root); + if(jsonstr==NULL){ + printMessage("Error: error when converting to JNIfTI\n"); + return EXIT_FAILURE; + } + + fp=fopen(fname,"wt"); + if(fp==NULL){ + printMessage("Error: error when writing to JNIfTI file\n"); + return EXIT_FAILURE; + } + fprintf(fp,"%s\n",jsonstr); + fclose(fp); + + if(jsonstr) + free(jsonstr); + if(root) + cJSON_Delete(root); + return EXIT_SUCCESS; +} // nii_savejnii() + int nii_saveForeign(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, struct TDTI4D *dti4D, int numDTI) { if (opts.saveFormat == kSaveFormatMGH) return nii_saveMGH(niiFilename, hdr, im, opts, d, dti4D, numDTI); + else if (opts.saveFormat == kSaveFormatJNII || opts.saveFormat == kSaveFormatBNII) + return nii_savejnii(niiFilename, hdr, im, opts, d, dti4D, numDTI); return nii_saveNRRD(niiFilename, hdr, im, opts, d, dti4D, numDTI); }// nii_saveForeign() diff --git a/console/nii_dicom_batch.h b/console/nii_dicom_batch.h index c5f453a5..80902c0d 100644 --- a/console/nii_dicom_batch.h +++ b/console/nii_dicom_batch.h @@ -50,6 +50,8 @@ void nii_clrMrifsStruct(); #define kSaveFormatNIfTI 0 #define kSaveFormatNRRD 1 #define kSaveFormatMGH 2 +#define kSaveFormatJNII 3 +#define kSaveFormatBNII 4 #define MAX_NUM_SERIES 16 From 3d8eeaf22b673cdd1c1c49b4a50cc7d9a3c98657 Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Sun, 6 Feb 2022 23:16:52 -0500 Subject: [PATCH 19/60] fix an indentation --- console/nii_dicom_batch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index de8853f3..52d8a250 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -4337,7 +4337,7 @@ int nii_savebnii(char *bniifile, struct nifti_1_header hdr, unsigned char *im, s "N","","_ArrayZipSize_","l","?42", "N","","_ArrayZipData_","[$U#","","?43", #else - "N","","_ArrayData_","[$U#","","?41", + "N","","_ArrayData_","[$U#","","?41", #endif "}", "}" From 36ffc8cc2ad2c2a0c109347114c39f7dc9b6d8ab Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Sun, 6 Feb 2022 23:21:29 -0500 Subject: [PATCH 20/60] add new units related to JNIfTI support --- console/base64.c | 162 +++ console/base64.h | 25 + console/cJSON.c | 2979 ++++++++++++++++++++++++++++++++++++++++++++++ console/cJSON.h | 293 +++++ 4 files changed, 3459 insertions(+) create mode 100644 console/base64.c create mode 100644 console/base64.h create mode 100644 console/cJSON.c create mode 100644 console/cJSON.h diff --git a/console/base64.c b/console/base64.c new file mode 100644 index 00000000..e7033279 --- /dev/null +++ b/console/base64.c @@ -0,0 +1,162 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005-2011, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +//#include "includes.h" + +//#include "os.h" +#include "base64.h" +#include +#include +#include +#include + +static const unsigned char base64_table[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * base64_encode - Base64 encode + * @src: Data to be encoded + * @len: Length of the data to be encoded + * @out_len: Pointer to output length variable, or %NULL if not used + * Returns: Allocated buffer of out_len bytes of encoded data, + * or %NULL on failure + * + * Caller is responsible for freeing the returned buffer. Returned buffer is + * nul terminated to make it easier to use as a C string. The nul terminator is + * not included in out_len. + */ +unsigned char * base64_encode(const unsigned char *src, size_t len, + size_t *out_len) +{ + unsigned char *out, *pos; + const unsigned char *end, *in; + size_t olen; + + olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */ + #ifdef USE_EOLN + int line_len 0; + olen += olen / 72; /* line feeds */ + #endif + olen++; /* nul termination */ + if (olen < len) + return NULL; /* integer overflow */ + out = (unsigned char *) malloc(olen); //os_ + if (out == NULL) + return NULL; + end = src + len; + in = src; + pos = out; + while (end - in >= 3) { + *pos++ = base64_table[in[0] >> 2]; + *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; + *pos++ = base64_table[in[2] & 0x3f]; + in += 3; + #ifdef USE_EOLN + line_len += 4; + if (line_len >= 72) { + *pos++ = '\n'; + line_len = 0; + } + #endif + } + + if (end - in) { + *pos++ = base64_table[in[0] >> 2]; + if (end - in == 1) { + *pos++ = base64_table[(in[0] & 0x03) << 4]; + *pos++ = '='; + } else { + *pos++ = base64_table[((in[0] & 0x03) << 4) | + (in[1] >> 4)]; + *pos++ = base64_table[(in[1] & 0x0f) << 2]; + } + *pos++ = '='; + #ifdef USE_EOLN + line_len += 4; + #endif + } + #ifdef USE_EOLN + if (line_len) + *pos++ = '\n'; + #endif + *pos = '\0'; + if (out_len) + *out_len = pos - out; + return out; +} + +/** + * base64_decode - Base64 decode + * @src: Data to be decoded + * @len: Length of the data to be decoded + * @out_len: Pointer to output length variable + * Returns: Allocated buffer of out_len bytes of decoded data, + * or %NULL on failure + * + * Caller is responsible for freeing the returned buffer. + */ +unsigned char * base64_decode(const unsigned char *src, size_t len, + size_t *out_len) +{ + unsigned char dtable[256], *out, *pos, block[4], tmp; + size_t i, count, olen; + int pad = 0; + + memset(dtable, 0x80, 256); //os_ + for (i = 0; i < sizeof(base64_table) - 1; i++) + dtable[base64_table[i]] = (unsigned char) i; + dtable['='] = 0; + + count = 0; + for (i = 0; i < len; i++) { + if (dtable[src[i]] != 0x80) + count++; + } + + if (count == 0 || count % 4) + return NULL; + + olen = count / 4 * 3; + pos = out = (unsigned char *) malloc(olen); //os_ + if (out == NULL) + return NULL; + + count = 0; + for (i = 0; i < len; i++) { + tmp = dtable[src[i]]; + if (tmp == 0x80) + continue; + + if (src[i] == '=') + pad++; + block[count] = tmp; + count++; + if (count == 4) { + *pos++ = (block[0] << 2) | (block[1] >> 4); + *pos++ = (block[1] << 4) | (block[2] >> 2); + *pos++ = (block[2] << 6) | block[3]; + count = 0; + if (pad) { + if (pad == 1) + pos--; + else if (pad == 2) + pos -= 2; + else { + /* Invalid padding */ + free(out); //os_ + return NULL; + } + break; + } + } + } + + *out_len = pos - out; + return out; +} \ No newline at end of file diff --git a/console/base64.h b/console/base64.h new file mode 100644 index 00000000..c05eb158 --- /dev/null +++ b/console/base64.h @@ -0,0 +1,25 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef BASE64_H +#define BASE64_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +unsigned char * base64_encode(const unsigned char *src, size_t len,size_t *out_len); +unsigned char * base64_decode(const unsigned char *src, size_t len,size_t *out_len); + +#ifdef __cplusplus +} +#endif + +#endif /* BASE64_H */ diff --git a/console/cJSON.c b/console/cJSON.c new file mode 100644 index 00000000..bc95cde6 --- /dev/null +++ b/console/cJSON.c @@ -0,0 +1,2979 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) { + if (!cJSON_IsString(item)) { + return NULL; + } + + return item->valuestring; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 12) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + if (newbuffer) + { + memcpy(newbuffer, p->buffer, p->offset + 1); + } + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + return (fabs(a - b) <= CJSON_DOUBLE_PRECISION); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if ((d * 0) != 0) + { + length = sprintf((char*)number_buffer, "null"); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = strlen((const char*)value) + sizeof(""); + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +#define cjson_min(a, b) ((a < b) ? a : b) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) + { + hooks->deallocate(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL)) + { + return false; + } + + child = array->child; + + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + } + else + { + /* append to the end */ + while (child->next) + { + child = child->next; + } + suffix_object(child, item); + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return; + } + + add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return; + } + + add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL)) + { + return NULL; + } + + if (item->prev != NULL) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0) + { + return; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + add_item_to_array(array, newitem); + return; + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (parent->child == item) + { + parent->child = replacement; + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return; + } + + cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + replacement->type &= ~cJSON_StringIsConst; + + cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); + + return true; +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0;a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a)) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); +} diff --git a/console/cJSON.h b/console/cJSON.h new file mode 100644 index 00000000..2c535628 --- /dev/null +++ b/console/cJSON.h @@ -0,0 +1,293 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 12 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* Precision of double variables comparison */ +#ifndef CJSON_DOUBLE_PRECISION +#define CJSON_DOUBLE_PRECISION .0000000000000001 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check if the item is a string and return its valuestring */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable adress area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif From ae2a10364f67ba7ba0222752ac89c74728062e15 Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Mon, 7 Feb 2022 18:59:01 -0500 Subject: [PATCH 21/60] use -z to decide if jnii and bnii need compression, use zlib as ziptype --- console/nii_dicom_batch.cpp | 83 ++++++++++++++++++++++++------------- dcm_qa | 2 +- dcm_qa_nih | 2 +- dcm_qa_uih | 2 +- 4 files changed, 57 insertions(+), 32 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 52d8a250..539e13ec 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -4267,7 +4267,8 @@ void write_ubjsonint(void *dat, int bytelen, int count, FILE *fp){ } int nii_savebnii(char *bniifile, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, unsigned char ndim, - size_t totalbytes, const char *dtype, const char *slicetype, const char *intent, const char *lunit, const char *tunit, const char *jdtype, int jdataelemlen) { + size_t totalbytes, const char *dtype, const char *slicetype, const char *intent, const char *lunit, const char *tunit, + const char *jdtype, int jdataelemlen, char jdatamarker) { int markerlen=0, dim[8]={0}; const char *output[]={ "{", @@ -4419,12 +4420,34 @@ int nii_savebnii(char *bniifile, struct nifti_1_header hdr, unsigned char *im, s case 37: {int val=hdr.vox_offset;write_ubjsonint(&val, sizeof(val), 1,fp);break;} case 38: {unsigned char val=strlen(jdtype);fputc('U',fp);fputc(val,fp); fwrite(jdtype,1,val,fp);break;} case 39: {fputc(ndim,fp);break;} - case 40: {write_ubjsonint(dim, sizeof(dim[0]), ndim, fp); break;} + case 40: { + write_ubjsonint(dim, sizeof(dim[0]), ndim, fp); + if(!opts.isGz){ + const char *datastub="U\x0b_ArrayData_[$"; + fwrite(datastub,1,strlen(datastub),fp); + fputc(jdatamarker,fp); + fputc('#',fp); + + size_t totalelem=(totalbytes/(hdr.bitpix>>3)); + unsigned int clen=totalelem; + if((size_t)clen==totalelem){ + fputc('l',fp); + write_ubjsonint(&clen, sizeof(clen), 1,fp); + }else{ + fputc('L',fp); + write_ubjsonint(&totalelem, sizeof(totalelem), 1,fp); + } + fwrite(im,1,totalbytes,fp); + fputc('}',fp); // end of NIFTIData + fputc('}',fp); // end of the root object + } + break; + } #ifdef Z_DEFLATED - case 41: {fputc('U',fp);fputc(4,fp);fwrite((opts.isGz ? "gzip" : "zlib"),1,4,fp);break;} + case 41: {fputc('U',fp);fputc(4,fp);fwrite("zlib",1,4,fp);break;} case 42: {unsigned int val=(totalbytes/(hdr.bitpix>>3));write_ubjsonint(&val,sizeof(val), 1,fp);break;} case 43: - ret=zmat_run(totalbytes, im, &compressedbytes, (unsigned char **)&compressed, (opts.isGz ? zmGzip : zmZlib), &status,-opts.gzLevel); + ret=zmat_run(totalbytes, im, &compressedbytes, (unsigned char **)&compressed, zmZlib, &status, -opts.gzLevel); if(!ret){ unsigned int clen=compressedbytes; if((size_t)clen==compressedbytes){ @@ -4441,17 +4464,10 @@ int nii_savebnii(char *bniifile, struct nifti_1_header hdr, unsigned char *im, s if(compressed) free(compressed); break; -#else // if zlib is not available, save raw binary data in _ArrayData_ record instead - case 41: - unsigned int clen=totalbytes; - if((size_t)clen==totalbytes) - write_ubjsonint(&clen, sizeof(clen), 1,fp); - else - write_ubjsonint(&totalbytes, sizeof(totalbytes), 1,fp); - fwrite(im,1,totalbytes,fp); - break; #endif } + if(!opts.isGz && slotid==40) + break; } } } @@ -4473,7 +4489,6 @@ int nii_savejnii(char *niiFilename, struct nifti_1_header hdr, unsigned char *im char *jsonstr=NULL; size_t compressedbytes, totalbytes; unsigned char *compressed=NULL, *buf=NULL; - int ret=0, status=0; /*jnifti convers code-based header fields to human-readable/standardized strings*/ int datatypeidx; @@ -4482,8 +4497,10 @@ int nii_savejnii(char *niiFilename, struct nifti_1_header hdr, unsigned char *im "double128","complex128","complex256","rgba32",""}; const char *jdatatypestr[]={"uint8","int16","int32","single","double","double", "uint8" ,"int8","uint16","uint32","int64","uint64", - "uint8","double","uint8","uint8",""}; - unsigned char jdataelemlen[]={1,1,1,1,2,1,3,1,1,1,1,1,16,2,32,4,0}; + "uint8","double","uint8","uint8","uint8"}; + const char jdatamarker[]={'U','I','l','d','D','D','U','i','u','m','L','M', + 'U','D','U','U','U'}; + unsigned char jdataelemlen[]={1,1,1,1,2,1,3,1,1,1,1,1,16,2,32,4,1}; int datatypeid[]={NIFTI_TYPE_UINT8,NIFTI_TYPE_INT16,NIFTI_TYPE_INT32, NIFTI_TYPE_FLOAT32,NIFTI_TYPE_COMPLEX64,NIFTI_TYPE_FLOAT64, NIFTI_TYPE_RGB24,NIFTI_TYPE_INT8,NIFTI_TYPE_UINT16, @@ -4498,7 +4515,7 @@ int nii_savejnii(char *niiFilename, struct nifti_1_header hdr, unsigned char *im NIFTI_UNITS_HZ,NIFTI_UNITS_PPM,NIFTI_UNITS_RADS}; int slicetypeidx; - const char *slicetypestr[]={"","seq+","seq-","alt+","alt-","alt2","alt2+","alt2-",""}; + const char *slicetypestr[]={"","seq+","seq-","alt+","alt-","alt2+","alt2-",""}; int slicetypeid[]={NIFTI_SLICE_UNKNOWN,NIFTI_SLICE_SEQ_INC, NIFTI_SLICE_SEQ_DEC,NIFTI_SLICE_ALT_INC,NIFTI_SLICE_ALT_DEC, NIFTI_SLICE_ALT_INC2,NIFTI_SLICE_ALT_DEC2}; @@ -4524,7 +4541,6 @@ int nii_savejnii(char *niiFilename, struct nifti_1_header hdr, unsigned char *im NIFTI_INTENT_QUATERNION,NIFTI_INTENT_DIMLESS,NIFTI_INTENT_TIME_SERIES, NIFTI_INTENT_NODE_INDEX,NIFTI_INTENT_RGB_VECTOR,NIFTI_INTENT_RGBA_VECTOR, NIFTI_INTENT_SHAPE}; - char fname[2048] = {""}; strcpy(fname, niiFilename); if (opts.saveFormat == kSaveFormatBNII) @@ -4554,7 +4570,7 @@ int nii_savejnii(char *niiFilename, struct nifti_1_header hdr, unsigned char *im return nii_savebnii(fname, hdr, im, opts, (unsigned char)ndim, totalbytes, datatypestr[datatypeidx], slicetypestr[slicetypeidx], intentstr[intentidx], unitstr[lunitidx], unitstr[tunitidx], - jdatatypestr[datatypeidx], jdataelemlen[datatypeidx]); + jdatatypestr[datatypeidx], jdataelemlen[datatypeidx], jdatamarker[datatypeidx]); root=cJSON_CreateObject(); @@ -4563,7 +4579,7 @@ int nii_savejnii(char *niiFilename, struct nifti_1_header hdr, unsigned char *im cJSON_AddStringToObject(info, "JNIFTIVersion", "0.5"); cJSON_AddStringToObject(info, "Comment", "Created by dcm2niix and NeuroJSON (http://neurojson.org)"); cJSON_AddStringToObject(info, "AnnotationFormat", "https://github.com/NeuroJSON/jnifti/blob/master/JNIfTI_specification.md"); - cJSON_AddStringToObject(info, "SerialFormat", "https://github.com/NeuroJSON/bjdata/blob/master/Binary_JData_Specification.md"); + cJSON_AddStringToObject(info, "SerialFormat", "http://json.org"); cJSON_AddItemToObject(info, "Parser", sub = cJSON_CreateObject()); cJSON_AddStringToObject(sub, "Python", "https://pypi.org/project/jdata\thttps://pypi.org/project/bjdata"); cJSON_AddStringToObject(sub, "MATLAB", "https://github.com/NeuroJSON/jnifty"); @@ -4646,21 +4662,30 @@ int nii_savejnii(char *niiFilename, struct nifti_1_header hdr, unsigned char *im if(jdataelemlen[datatypeidx]>1) dim[ndim++]=jdataelemlen[datatypeidx]; cJSON_AddItemToObject(dat, "_ArraySize_",cJSON_CreateIntArray(dim,ndim)); + #ifdef Z_DEFLATED - cJSON_AddStringToObject(dat, "_ArrayZipType_",(opts.isGz ? "gzip" : "zlib")); - cJSON_AddNumberToObject(dat, "_ArrayZipSize_",totalbytes/(hdr.bitpix>>3)); + if(opts.isGz){ + int ret=0, status=0; + cJSON_AddStringToObject(dat, "_ArrayZipType_", "zlib"); + cJSON_AddNumberToObject(dat, "_ArrayZipSize_",totalbytes/(hdr.bitpix>>3)); - ret=zmat_run(totalbytes, im, &compressedbytes, (unsigned char **)&compressed, (opts.isGz ? zmGzip : zmZlib), &status,-opts.gzLevel); + ret=zmat_run(totalbytes, im, &compressedbytes, (unsigned char **)&compressed, zmZlib, &status, -opts.gzLevel); - if(!ret){ - ret=zmat_run(compressedbytes, compressed, &totalbytes, (unsigned char **)&buf, zmBase64, &status,1); - cJSON_AddStringToObject(dat, "_ArrayZipData_",(char *)buf); + if(!ret){ + ret=zmat_run(compressedbytes, compressed, &totalbytes, (unsigned char **)&buf, zmBase64, &status,1); + cJSON_AddStringToObject(dat, "_ArrayZipData_",(const char *)buf); + }else{ + printf("ret=%d status=%d\n",ret,status); + } + if(compressed) + free(compressed); + }else{ + buf=base64_encode(im, totalbytes, &compressedbytes); + cJSON_AddStringToObject(dat,"_ArrayData_", (const char*)buf); } - if(compressed) - free(compressed); #else buf=base64_encode(im, totalbytes, &compressedbytes) - cJSON_AddStringToObject(dat,"_ArrayData_", buf); + cJSON_AddStringToObject(dat,"_ArrayData_", (const char*)buf); #endif if(buf) free(buf); diff --git a/dcm_qa b/dcm_qa index 2f59a065..bb457d98 160000 --- a/dcm_qa +++ b/dcm_qa @@ -1 +1 @@ -Subproject commit 2f59a065686f1bdab52f8c4c529e1e2e7e4c133f +Subproject commit bb457d98bdb320af3b9f29fad1c706788490dab8 diff --git a/dcm_qa_nih b/dcm_qa_nih index 0a0aa943..aa82e560 160000 --- a/dcm_qa_nih +++ b/dcm_qa_nih @@ -1 +1 @@ -Subproject commit 0a0aa943b5e5263645a33359e52dedf147c6a5a7 +Subproject commit aa82e560d5471b53f0d0332c4de33d88bf179157 diff --git a/dcm_qa_uih b/dcm_qa_uih index 78f21294..4c0e2b37 160000 --- a/dcm_qa_uih +++ b/dcm_qa_uih @@ -1 +1 @@ -Subproject commit 78f212941ca7adc13bd3810667084b48a0047484 +Subproject commit 4c0e2b37430fa566a501bb65f35a9ad4089e71a9 From aa50fbed2bd79ad14608e815c00aeebbc7b180a5 Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Tue, 8 Feb 2022 09:27:38 -0500 Subject: [PATCH 22/60] add _ArrayOrder_ field because nifti is column-major --- console/nii_dicom_batch.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 539e13ec..3b638106 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -4333,6 +4333,7 @@ int nii_savebnii(char *bniifile, struct nifti_1_header hdr, unsigned char *im, s "N","","NIFTIData","{", "N","","_ArrayType_","S","","?38", "N","","_ArraySize_","[$l#U","?39","?40", + "N","","_ArrayOrder_","S","","c", // NIfTI array is column-major #ifdef Z_DEFLATED "N","","_ArrayZipType_","S","","?41", "N","","_ArrayZipSize_","l","?42", @@ -4662,6 +4663,7 @@ int nii_savejnii(char *niiFilename, struct nifti_1_header hdr, unsigned char *im if(jdataelemlen[datatypeidx]>1) dim[ndim++]=jdataelemlen[datatypeidx]; cJSON_AddItemToObject(dat, "_ArraySize_",cJSON_CreateIntArray(dim,ndim)); + cJSON_AddStringToObject(dat,"_ArrayOrder_","c"); // NIfTI array is column-major #ifdef Z_DEFLATED if(opts.isGz){ From cc514c7b6f1f259bcfff9cabf2149a579648eaea Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Wed, 9 Feb 2022 16:39:58 -0500 Subject: [PATCH 23/60] remove memory leak due to duplicated deflateInit --- console/nii_dicom_batch.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 3b638106..f9054c98 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -4169,7 +4169,7 @@ int zmat_run(const size_t inputsize, unsigned char *inputstr, size_t *outputsize /** perform compression or encoding */ if(zipid==zmBase64){ /** base64 encoding */ - *outputbuf=base64_encode((const unsigned char*)inputstr, inputsize, outputsize); + *outputbuf=base64_encode((const unsigned char*)inputstr, inputsize, outputsize); }else if(zipid==zmZlib || zipid==zmGzip){ /** zlib (.zip) or gzip (.gz) compression */ if(zipid==zmZlib){ @@ -4179,8 +4179,6 @@ int zmat_run(const size_t inputsize, unsigned char *inputstr, size_t *outputsize if(deflateInit2(&zs, (iscompress>0) ? Z_DEFAULT_COMPRESSION : (-iscompress), Z_DEFLATED, 15|16, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) return -2; } - if(deflateInit(&zs, (iscompress>0) ? Z_DEFAULT_COMPRESSION : (-iscompress)) != Z_OK) - return -2; buflen[0] =deflateBound(&zs,inputsize); *outputbuf=(unsigned char *)malloc(buflen[0]); zs.avail_in = inputsize; /* size of input, string + terminator*/ From d8fb0b594ac92a73ebe685689992566423832fbf Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Thu, 10 Feb 2022 08:41:25 -0500 Subject: [PATCH 24/60] JNIfTI support modular (cmake -DUSE_JNIFTI=OFF ..) --- SuperBuild/SuperBuild.cmake | 2 ++ console/CMakeLists.txt | 11 ++++++++--- console/{base64.c => base64.cpp} | 0 console/{cJSON.c => cJSON.cpp} | 0 console/main_console.cpp | 10 ++++++++++ console/makefile | 10 ++++++++-- console/nii_dicom_batch.cpp | 15 +++++++++++---- 7 files changed, 39 insertions(+), 9 deletions(-) rename console/{base64.c => base64.cpp} (100%) rename console/{cJSON.c => cJSON.cpp} (100%) diff --git a/SuperBuild/SuperBuild.cmake b/SuperBuild/SuperBuild.cmake index 7047a4ff..44d28689 100644 --- a/SuperBuild/SuperBuild.cmake +++ b/SuperBuild/SuperBuild.cmake @@ -40,6 +40,7 @@ option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF) option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF) option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF) option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF) +option(USE_JNIFTI "Build with JNIFTI support" ON) option(BATCH_VERSION "Build dcm2niibatch for multiple conversions" OFF) @@ -150,6 +151,7 @@ ExternalProject_Add(console -DUSE_TURBOJPEG:BOOL=${USE_TURBOJPEG} -DUSE_JASPER:BOOL=${USE_JASPER} -DUSE_JPEGLS:BOOL=${USE_JPEGLS} + -DUSE_JNIFTI:BOOL=${USE_JNIFTI} -DZLIB_IMPLEMENTATION:STRING=${ZLIB_IMPLEMENTATION} -DZLIB_ROOT:PATH=${ZLIB_ROOT} # OpenJPEG diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index 2ef3abe1..4edb19f3 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -88,9 +88,14 @@ set(DCM2NIIX_SRCS nifti1_io_core.cpp nii_foreign.cpp nii_ortho.cpp - nii_dicom_batch.cpp - cJSON.c - base64.c) + nii_dicom_batch.cpp) + + +option(USE_JNIfTI "Build with JNIfTI support" ON) +if(USE_JNIFTI) + add_definitions(-DmyEnableJNIfTI) + set(DCM2NIIX_SRCS ${DCM2NIIX_SRCS} cJSON.cpp base64.cpp) +endif() if(BUILD_DCM2NIIXFSLIB) set(DCM2NIIXFSLIB dcm2niixfs) diff --git a/console/base64.c b/console/base64.cpp similarity index 100% rename from console/base64.c rename to console/base64.cpp diff --git a/console/cJSON.c b/console/cJSON.cpp similarity index 100% rename from console/cJSON.c rename to console/cJSON.cpp diff --git a/console/main_console.cpp b/console/main_console.cpp index aa141c40..48539dbe 100644 --- a/console/main_console.cpp +++ b/console/main_console.cpp @@ -80,7 +80,11 @@ void showHelp(const char *argv[], struct TDCMopts opts) { printf(" -ba : anonymize BIDS (y/n, default %c)\n", bool2Char(opts.isAnonymizeBIDS)); printf(" -c : comment stored in NIfTI aux_file (provide up to 24 characters e.g. '-c first_visit')\n"); printf(" -d : directory search depth. Convert DICOMs in sub-folders of in_folder? (0..9, default %d)\n", opts.dirSearchDepth); +#ifdef myEnableJNIfTI printf(" -e : export as NRRD (y) or MGH (o) or JSON/JNIfTI (j) or BJNIfTI (b) instead of NIfTI (y/n/o/j/b, default n)\n"); +#else + printf(" -e : export as NRRD (y) or MGH (o) instead of NIfTI (y/n/o/j/b, default n)\n"); +#endif #ifdef mySegmentByAcq #define kQstr " %%q=sequence number," #else @@ -363,6 +367,12 @@ int main(int argc, const char *argv[]) { opts.saveFormat = kSaveFormatJNII; if ((argv[i][0] == 'b') || (argv[i][0] == 'B') || (argv[i][0] == '4')) opts.saveFormat = kSaveFormatBNII; + #ifndef myEnableJNIfTI + if ((opts.saveFormat == kSaveFormatJNII) || (opts.saveFormat == kSaveFormatBNII)) { + printf("Recompile for JNIfTI support.\n"); + return EXIT_SUCCESS; + } + #endif } else if ((argv[i][1] == 'g') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) diff --git a/console/makefile b/console/makefile index 3f37cb04..3b14bfa9 100644 --- a/console/makefile +++ b/console/makefile @@ -13,7 +13,13 @@ CFLAGS=-s -O3 #run "JPEGLS=1 make" for JPEGLS build JFLAGS= ifeq "$(JPEGLS)" "1" - JFLAGS=-std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp + JFLAGS=-std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp +endif + +#run "JNIfTI=0 make" to disable JNIFTI build +JSFLAGS= +ifneq "$(JNIfTI)" "0" + JSFLAGS=-DmyEnableJNIfTI base64.cpp cJSON.cpp endif ifneq ($(OS),Windows_NT) @@ -31,4 +37,4 @@ ifneq ($(OS),Windows_NT) endif endif all: - g++ $(CFLAGS) -I. $(JFLAGS) main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp base64.c cJSON.c -o dcm2niix -DmyDisableOpenJPEG + g++ $(CFLAGS) -I. $(JSFLAGS) $(JFLAGS) main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index f9054c98..b89f0ba4 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -38,8 +38,10 @@ #endif #include "nii_dicom.h" #include "nii_ortho.h" -#include "base64.h" -#include "cJSON.h" +#ifdef myEnableJNIfTI + #include "base64.h" + #include "cJSON.h" +#endif #include //toupper #include #include @@ -3352,10 +3354,12 @@ void nii_createDummyFilename(char *niiFilename, struct TDCMopts opts) { strcat(niiFilename, ".nhdr'"); else strcat(niiFilename, ".nrrd'"); + #ifdef myEnableJNIfTI } else if (opts.saveFormat == kSaveFormatJNII) { strcat(niiFilename, ".jnii'"); } else if (opts.saveFormat == kSaveFormatBNII) { strcat(niiFilename, ".bnii'"); + #endif } else { if (opts.isGz) strcat(niiFilename, ".nii.gz'"); @@ -4151,6 +4155,7 @@ int nii_saveNRRD(char *niiFilename, struct nifti_1_header hdr, unsigned char *im enum TZipMethod {zmZlib, zmGzip, zmBase64}; +#ifdef myEnableJNIfTI #ifdef Z_DEFLATED int zmat_run(const size_t inputsize, unsigned char *inputstr, size_t *outputsize, unsigned char **outputbuf, const int zipid, int *ret, const int iscompress){ @@ -4227,7 +4232,6 @@ int zmat_run(const size_t inputsize, unsigned char *inputstr, size_t *outputsize count++; } *outputsize=zs.total_out; - if(*ret!=Z_STREAM_END && *ret!=Z_OK) return -3; inflateEnd(&zs); @@ -4238,7 +4242,7 @@ int zmat_run(const size_t inputsize, unsigned char *inputstr, size_t *outputsize return 0; } -#endif +#endif //Z_DEFLATED int jnifti_lookup(int *keyid, int keylen, int val){ for(int i=0;i Date: Thu, 10 Feb 2022 09:51:52 -0500 Subject: [PATCH 25/60] update cmake setting to use renamed cJSON.cpp --- console/CMakeLists.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index 4edb19f3..0dcfe303 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -194,9 +194,11 @@ if(BATCH_VERSION) nifti1_io_core.cpp nii_foreign.cpp nii_ortho.cpp - nii_dicom_batch.cpp - cJSON.c - base64.c) + nii_dicom_batch.cpp) + + if(USE_JNIFTI) + set(DCM2NIIBATCH_SRCS ${DCM2NIIBATCH_SRCS} cJSON.cpp base64.cpp) + endif() if(USE_JPEGLS) add_executable(dcm2niibatch ${DCM2NIIBATCH_SRCS} ${CHARLS_SRCS}) From b3c4bfe8f12ae16a71b49829860cfbd282286f21 Mon Sep 17 00:00:00 2001 From: Qianqian Fang Date: Thu, 10 Feb 2022 18:13:19 -0500 Subject: [PATCH 26/60] force base64 encoding when compression is not specified --- console/nii_dicom_batch.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index b89f0ba4..7b6a6958 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -4462,6 +4462,7 @@ int nii_savebnii(char *bniifile, struct nifti_1_header hdr, unsigned char *im, s } fwrite(compressed,1,compressedbytes,fp); }else{ + printError("Failed to compress data stream, error code=%d, status code=%d\n", ret, status); return EXIT_FAILURE; } if(compressed) @@ -4679,17 +4680,22 @@ int nii_savejnii(char *niiFilename, struct nifti_1_header hdr, unsigned char *im ret=zmat_run(compressedbytes, compressed, &totalbytes, (unsigned char **)&buf, zmBase64, &status,1); cJSON_AddStringToObject(dat, "_ArrayZipData_",(const char *)buf); }else{ - printf("ret=%d status=%d\n",ret,status); + printError("Failed to compress data stream, error code=%d, status code=%d\n", ret, status); + return EXIT_FAILURE; } if(compressed) free(compressed); }else{ + cJSON_AddStringToObject(dat, "_ArrayZipType_", "base64"); + cJSON_AddNumberToObject(dat, "_ArrayZipSize_",totalbytes/(hdr.bitpix>>3)); buf=base64_encode(im, totalbytes, &compressedbytes); - cJSON_AddStringToObject(dat,"_ArrayData_", (const char*)buf); + cJSON_AddStringToObject(dat,"_ArrayZipData_", (const char*)buf); } #else - buf=base64_encode(im, totalbytes, &compressedbytes) - cJSON_AddStringToObject(dat,"_ArrayData_", (const char*)buf); + cJSON_AddStringToObject(dat, "_ArrayZipType_", "base64"); + cJSON_AddNumberToObject(dat, "_ArrayZipSize_",totalbytes/(hdr.bitpix>>3)); + buf=base64_encode(im, totalbytes, &compressedbytes); + cJSON_AddStringToObject(dat,"_ArrayZipData_", (const char*)buf); #endif if(buf) free(buf); From 496ccc3121b368965e91f8ea4d07acbf42fc1fe2 Mon Sep 17 00:00:00 2001 From: Nicolas Pannetier Date: Mon, 21 Feb 2022 17:43:20 -0700 Subject: [PATCH 27/60] fix new line for warning message --- console/nii_dicom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 0e08d79a..9aed1202 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -2902,7 +2902,7 @@ unsigned char *nii_iVaries(unsigned char *img, struct nifti_1_header *hdr, struc free(img); //release previous image if ((dti4D != NULL) && (dti4D->intenScale[0] != 0.0)) { //enhanced dataset, intensity varies across slices of a single file if (dti4D->RWVScale[0] != 0.0) - printWarning("Intensity scale/slope using 0028,1053 and 0028,1052"); //to do: real-world values and precise values + printWarning("Intensity scale/slope using 0028,1053 and 0028,1052\n"); //to do: real-world values and precise values int dim1to2 = hdr->dim[1] * hdr->dim[2]; int slice = -1; //(0028,1052) SS = scale slope (2005,100E) RealWorldIntercept = (0040,9224) Real World Slope = (0040,9225) From be9581d05147a4bdd96a9d0e3aa2671eb80c8dab Mon Sep 17 00:00:00 2001 From: Chris Rorden Date: Tue, 22 Feb 2022 15:47:35 -0700 Subject: [PATCH 28/60] Revert "Fix the example batch config to match defaults" --- BATCH.md | 4 ++-- batch_config.yml | 4 ++-- console/main_console_batch.cpp | 4 ++-- docs/source/dcm2niibatch.rst | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/BATCH.md b/BATCH.md index b9e489ca..e3c6809b 100644 --- a/BATCH.md +++ b/BATCH.md @@ -10,9 +10,9 @@ The configuration file should be in yaml format as shown in example `batch_confi ```yaml Options: isGz: false - isFlipY: true + isFlipY: false isVerbose: false - isCreateBIDS: true + isCreateBIDS: false isOnlySingleFile: false Files: - diff --git a/batch_config.yml b/batch_config.yml index 4bbf8384..d22a987c 100644 --- a/batch_config.yml +++ b/batch_config.yml @@ -1,8 +1,8 @@ Options: isGz: false - isFlipY: true + isFlipY: false isVerbose: false - isCreateBIDS: true + isCreateBIDS: false isOnlySingleFile: false Files: diff --git a/console/main_console_batch.cpp b/console/main_console_batch.cpp index 29262d81..e968962e 100644 --- a/console/main_console_batch.cpp +++ b/console/main_console_batch.cpp @@ -49,9 +49,9 @@ void showHelp(const char * argv[]) { printf("### START YAML FILE ###\n"); printf("Options:\n"); printf(" isGz: false\n"); - printf(" isFlipY: true\n"); + printf(" isFlipY: false\n"); printf(" isVerbose: false\n"); - printf(" isCreateBIDS: true\n"); + printf(" isCreateBIDS: false\n"); printf(" isOnlySingleFile: false\n"); printf("Files:\n"); printf(" -\n"); diff --git a/docs/source/dcm2niibatch.rst b/docs/source/dcm2niibatch.rst index ffbc3a0f..574d5a46 100644 --- a/docs/source/dcm2niibatch.rst +++ b/docs/source/dcm2niibatch.rst @@ -41,9 +41,9 @@ The configuration file should be in yaml format as shown in example *batch_confi Options: isGz: false - isFlipY: true + isFlipY: false isVerbose: false - isCreateBIDS: true + isCreateBIDS: false isOnlySingleFile: false Files: - From b77bd2965708ccfa6166f0c40cd47611ad30ed15 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 23 Feb 2022 05:01:50 -0700 Subject: [PATCH 29/60] BEP009 tuning: Only report FrameReferenceTime if it varies, remove content time --- README.md | 1 + Siemens/README.md | 2 ++ console/nii_dicom.cpp | 9 ++++---- console/nii_dicom.h | 6 ++--- console/nii_dicom_batch.cpp | 44 ++++++++++++++----------------------- 5 files changed, 27 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index d60443de..029006d4 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,7 @@ The following tools exploit dcm2niix - [Neuroinformatics Database (NiDB)](https://github.com/gbook/nidb) is designed to store, retrieve, analyze, and share neuroimaging data. It uses dcm2niix for image QA and handling some formats. - [NiftyPET](https://niftypet.readthedocs.io/en/latest/install.html) provides PET image reconstruction and analysis, and uses dcm2niix to handle DICOM images. - [nipype](https://github.com/nipy/nipype) can use dcm2niix to convert images. + - [PET2BIDS](https://github.com/openneuropet/PET2BIDS) uses dcm2niix for DICOM images. - [py2bids](https://github.com/Jolinda/py2bids) dcm2niix dicom to bids conversion wrapper. - [pyBIDSconv provides a graphical format for converting DICOM images to the BIDS format](https://github.com/DrMichaelLindner/pyBIDSconv). It includes clever default heuristics for identifying Siemens scans. - [pydcm2niix is a Python module for working with dcm2niix](https://github.com/jstutters/pydcm2niix). diff --git a/Siemens/README.md b/Siemens/README.md index d898e2e8..17c5fdb7 100644 --- a/Siemens/README.md +++ b/Siemens/README.md @@ -78,6 +78,8 @@ For Siemens V-series systems from the B-generation onward (around 2005), the mos Tools like [ExploreASL](https://sites.google.com/view/exploreasl) and [FSL BASIL](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/BASIL) can help process arterial spin labeling data. These tools require sequence details. These details differ between different sequences. If you create a BIDS JSON file with dcm2niix, the following tags will be created, using the same names used in the Siemens sequence PDFs. Note different sequences provide different values. The [dcm_qa_asl](https://github.com/neurolabusc/dcm_qa_asl) repository provides example DICOM ASL datasets. See the [BIDS page for details](../BIDS/README.md). +The Siemens CSA header also stores some ASL details as a base64 stream. These can be read using [gdcmdump](http://gdcm.sourceforge.net/wiki/index.php/Gdcmdump), e.g. `gdcmdump -i i001.dcm --csa-asl --print` + ## Sample Datasets - [Slice timing dataset](httphttps://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Slice_timing_corrections://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage). diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 04ba942b..f7790a20 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -811,7 +811,6 @@ struct TDICOMdata clear_dicom_data() { d.ecat_isotope_halflife = 0.0; d.frameDuration = -1.0; d.frameReferenceTime = -1.0; - d.contentTime = 1.0; d.ecat_dosage = 0.0; d.radionuclideTotalDose = 0.0; d.seriesNum = 1; @@ -1703,7 +1702,7 @@ struct TDICOMdata nii_readParRec(char *parname, int isVerbose, struct TDTI4D *dt dti4D->decayFactor[0] = -1; dti4D->frameDuration[0] = -1; dti4D->frameReferenceTime[0] = -1; - dti4D->contentTime[0] = -1; + //dti4D->contentTime[0] = -1; //dti4D->fragmentOffset[0] = -1; dti4D->intenScale[0] = 0.0; strcpy(d.protocolName, ""); //erase dummy with empty @@ -4169,7 +4168,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kStudyTime 0x0008 + (0x0030 << 16) #define kSeriesTime 0x0008 + (0x0031 << 16) #define kAcquisitionTime 0x0008 + (0x0032 << 16) //TM -#define kContentTime 0x0008+(0x0033 << 16 ) //TM +//#define kContentTime 0x0008+(0x0033 << 16 ) //TM #define kModality 0x0008 + (0x0060 << 16) //CS #define kManufacturer 0x0008 + (0x0070 << 16) #define kInstitutionName 0x0008 + (0x0080 << 16) @@ -5279,11 +5278,11 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); acquisitionTimesGE_UIH++; break; } - case kContentTime: { + /*case kContentTime: { char contentTimeTxt[kDICOMStr]; dcmStr(lLength, &buffer[lPos], contentTimeTxt); d.contentTime = atof(contentTimeTxt); - } + }*/ case kSeriesTime: dcmStr(lLength, &buffer[lPos], seriesTimeTxt); break; diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 65cbbc06..d2208bdf 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20220123" +#define kDCMdate "v1.0.20220223" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic @@ -177,7 +177,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; int sliceOrder[kMaxSlice2D]; // [7,3,2] means the first slice on disk should be moved to 7th position int gradDynVol[kMaxDTI4D]; //used to parse dimensions of Philips data, e.g. file with multiple dynamics, echoes, phase+magnitude //int fragmentOffset[kMaxDTI4D], fragmentLength[kMaxDTI4D]; //for images with multiple compressed fragments - float contentTime[kMaxDTI4D], frameReferenceTime[kMaxDTI4D], frameDuration[kMaxDTI4D], decayFactor[kMaxDTI4D], volumeOnsetTime[kMaxDTI4D], triggerDelayTime[kMaxDTI4D], TE[kMaxDTI4D], RWVScale[kMaxDTI4D], RWVIntercept[kMaxDTI4D], intenScale[kMaxDTI4D], intenIntercept[kMaxDTI4D], intenScalePhilips[kMaxDTI4D]; + float frameReferenceTime[kMaxDTI4D], frameDuration[kMaxDTI4D], decayFactor[kMaxDTI4D], volumeOnsetTime[kMaxDTI4D], triggerDelayTime[kMaxDTI4D], TE[kMaxDTI4D], RWVScale[kMaxDTI4D], RWVIntercept[kMaxDTI4D], intenScale[kMaxDTI4D], intenIntercept[kMaxDTI4D], intenScalePhilips[kMaxDTI4D]; bool isReal[kMaxDTI4D]; bool isImaginary[kMaxDTI4D]; bool isPhase[kMaxDTI4D]; @@ -226,7 +226,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; float xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) - float contentTime, frameReferenceTime, frameDuration, ecat_isotope_halflife, ecat_dosage; + float frameReferenceTime, frameDuration, ecat_isotope_halflife, ecat_dosage; float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. double acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr]; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index fd4a6667..30fd4a80 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1405,27 +1405,22 @@ tse3d: T2*/ } fprintf(fp, "\t],\n"); } - if (dti4D->frameReferenceTime[0] >= 0.0) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 - fprintf(fp, "\t\"FrameReferenceTime\": [\n"); - for (int i = 0; i < h->dim[4]; i++) { - if (i != 0) - fprintf(fp, ",\n"); - if (dti4D->frameReferenceTime[i] < 0) - break; - fprintf(fp, "\t\t%g", dti4D->frameReferenceTime[i] / 1000.0); // from 0018,1242 ms -> sec - } - fprintf(fp, "\t],\n"); - } - if (dti4D->contentTime[0] >= 0.0) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 - fprintf(fp, "\t\"ContentTimeHHMMSS\": [\n"); - for (int i = 0; i < h->dim[4]; i++) { - if (i != 0) - fprintf(fp, ",\n"); - if (dti4D->contentTime[i] < 0) - break; - fprintf(fp, "\t\t%09.2f", dti4D->contentTime[i]); // from 0018,1242 ms -> sec + if ((dti4D->frameReferenceTime[0] >= 0.0) && (h->dim[4] > 1)) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 + bool varies = false; + for (int i = 0; i < h->dim[4]; i++) + if (dti4D->frameReferenceTime[i] != dti4D->frameReferenceTime[0]) + varies = true; + if (varies) { + fprintf(fp, "\t\"FrameReferenceTime\": [\n"); + for (int i = 0; i < h->dim[4]; i++) { + if (i != 0) + fprintf(fp, ",\n"); + if (dti4D->frameReferenceTime[i] < 0) + break; + fprintf(fp, "\t\t%g", dti4D->frameReferenceTime[i] / 1000.0); // from 0018,1242 ms -> sec + } + fprintf(fp, "\t],\n"); } - fprintf(fp, "\t],\n"); } //CT parameters json_Float(fp, "\t\"ExposureTime\": %g,\n", d.exposureTimeMs / 1000.0); @@ -6590,7 +6585,6 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { dti4D->frameDuration[nTR] = dcmList[dcmSort[i].indx].frameDuration; dti4D->frameReferenceTime[nTR] = dcmList[dcmSort[i].indx].frameReferenceTime; - dti4D->contentTime[nTR] = dcmList[dcmSort[i].indx].contentTime; nTR += 1; if (nTR >= kMaxDTI4D) break; @@ -7525,12 +7519,8 @@ bool isSameSet(struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts *opts if ((!(isSameFloat(d1.TE, d2.TE))) || (d1.echoNum != d2.echoNum)) { if ((!warnings->echoVaries) && (d1.isXRay)) //for CT/XRay we check DICOM tag 0018,1152 (XRayExposure) printMessage("Slices not stacked: X-Ray Exposure varies (exposure %g, %g; number %d, %d). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE, d1.echoNum, d2.echoNum); - if ((!warnings->echoVaries) && (!d1.isXRay)) {//for MRI - if (d1.echoNum == d2.echoNum) - printMessage("Slices not stacked: echo varies (TE %g, %g). No echo number (0018,0086; issue 568). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE); - else - printMessage("Slices not stacked: echo varies (TE %g, %g; echo %d, %d). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE, d1.echoNum, d2.echoNum); - } + if ((!warnings->echoVaries) && (!d1.isXRay)) //for MRI + printMessage("Slices not stacked: echo varies (TE %g, %g; echo %d, %d). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE, d1.echoNum, d2.echoNum); warnings->echoVaries = true; *isMultiEcho = true; return false; From ec006d8422ca91d22330aa81ea1e80bddb61cb7a Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Thu, 10 Mar 2022 18:06:30 -0500 Subject: [PATCH 30/60] GE PulseSequenceName (https://github.com/rordenlab/dcm2niix/issues/588) fix typo (https://github.com/rordenlab/dcm2niix/pull/589) --- console/main_console.cpp | 2 +- console/nii_dicom.cpp | 18 +++++++++--------- console/nii_dicom.h | 4 ++-- console/nii_dicom_batch.cpp | 9 +++++---- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/console/main_console.cpp b/console/main_console.cpp index 48539dbe..143995c6 100644 --- a/console/main_console.cpp +++ b/console/main_console.cpp @@ -86,7 +86,7 @@ void showHelp(const char *argv[], struct TDCMopts opts) { printf(" -e : export as NRRD (y) or MGH (o) instead of NIfTI (y/n/o/j/b, default n)\n"); #endif #ifdef mySegmentByAcq -#define kQstr " %%q=sequence number," +#define kQstr " %q=sequence number," #else #define kQstr "" #endif diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index f7790a20..c9168bbe 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -740,6 +740,7 @@ struct TDICOMdata clear_dicom_data() { strcpy(d.bodyPartExamined, ""); strcpy(d.coilName, ""); strcpy(d.coilElements, ""); + strcpy(d.pulseSequenceNameGE, ""); strcpy(d.radiopharmaceutical, ""); strcpy(d.convolutionKernel, ""); strcpy(d.parallelAcquisitionTechnique, ""); @@ -4303,7 +4304,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kTemporalPositionIndex 0x0020 + uint32_t(0x9128 << 16) // UL #define kDimensionIndexPointer 0x0020 + uint32_t(0x9165 << 16) //Private Group 21 as Used by Siemens: -#define kSequenceVariant21 0x0021 + (0x105B << 16) //CS +#define kSequenceVariant21 0x0021 + (0x105B << 16) //CS Siemens ONLY: For GE this is TaggingFlipAngle #define kPATModeText 0x0021 + (0x1009 << 16) //LO, see kImaPATModeText #define kTimeAfterStart 0x0021 + (0x1104 << 16) //DS #define kICE_dims 0x0021 + (0x1106 << 16) //LO [X_4_1_1_1_1_160_1_1_1_1_1_277] @@ -5596,18 +5597,17 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); case kPulseSequenceNameGE: { //LO 'epi'/'epiRT' if (d.manufacturer != kMANUFACTURER_GE) break; - char epiStr[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], epiStr); - if (strstr(epiStr, "epi_pepolar") != NULL) { + dcmStr(lLength, &buffer[lPos], d.pulseSequenceNameGE); + if (strstr( d.pulseSequenceNameGE, "epi_pepolar") != NULL) { d.epiVersionGE = kGE_EPI_PEPOLAR_FWD; //n.b. combine with 0019,10B3 - } else if (strstr(epiStr, "epi2") != NULL) { + } else if (strstr( d.pulseSequenceNameGE, "epi2") != NULL) { d.epiVersionGE = kGE_EPI_EPI2; //-1 = not epi, 0 = epi, 1 = epiRT, 2 = epi2 - } else if (strstr(epiStr, "epiRT") != NULL) { + } else if (strstr( d.pulseSequenceNameGE, "epiRT") != NULL) { d.epiVersionGE = kGE_EPI_EPIRT; //-1 = not epi, 0 = epi, 1 = epiRT - } else if (strstr(epiStr, "epi") != NULL) { + } else if (strstr( d.pulseSequenceNameGE, "epi") != NULL) { d.epiVersionGE = kGE_EPI_EPI; //-1 = not epi, 0 = epi, 1 = epiRT } - if (strcmp(epiStr, "3db0map") == 0) { + if (strcmp( d.pulseSequenceNameGE, "3db0map") == 0) { isGEfieldMap = true; //issue501 } break; @@ -6240,7 +6240,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); } case kSequenceVariant21: if (d.manufacturer != kMANUFACTURER_SIEMENS) - break; //see GE dataset in dcm_qa_nih + break; //n.b. for GE 0021,105B' TaggingFlipAngle //fall through... case kSequenceVariant: { dcmStr(lLength, &buffer[lPos], d.sequenceVariant); diff --git a/console/nii_dicom.h b/console/nii_dicom.h index d2208bdf..4f1e7723 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20220223" +#define kDCMdate "v1.0.20220310" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic @@ -230,7 +230,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. double acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr]; - char imageOrientationText[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], scanOptions[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[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]; + char imageOrientationText[kDICOMStr], pulseSequenceNameGE[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], scanOptions[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[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]; char institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge]; uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; struct TCSAdata CSA; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 30fd4a80..0d20ea64 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1229,10 +1229,11 @@ tse3d: T2*/ fprintf(fp, "\t\"Manufacturer\": \"UIH\",\n"); break; }; - if (d.epiVersionGE == 0) - fprintf(fp, "\t\"PulseSequenceName\": \"epi\",\n"); - if (d.epiVersionGE == 1) - fprintf(fp, "\t\"PulseSequenceName\": \"epiRT\",\n"); + json_Str(fp, "\t\"PulseSequenceName\": \"%s\",\n", d.pulseSequenceNameGE); + //if (d.epiVersionGE == 0) + // fprintf(fp, "\t\"PulseSequenceName\": \"epi\",\n"); + //if (d.epiVersionGE == 1) + // fprintf(fp, "\t\"PulseSequenceName\": \"epiRT\",\n"); if (d.internalepiVersionGE == 1) fprintf(fp, "\t\"InternalPulseSequenceName\": \"EPI\",\n"); if (d.internalepiVersionGE == 2) From 8be70441eb350b2ead5799ddd5a75ff071c3b516 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 22 Mar 2022 14:19:03 -0400 Subject: [PATCH 31/60] Handle Philips enhanced DICOM DWIs where phase, megnitude and isotropic images are saved as the same series. --- console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 4f1e7723..bc5b8c16 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20220310" +#define kDCMdate "v1.0.20220322" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 0d20ea64..0a2fe11f 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -7197,6 +7197,8 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmLi printError("Unexpected error for image with varying echo time or intensity scaling\n"); return EXIT_FAILURE; } + if ((nConvert == 1) && (dcmList[indx].manufacturer == kMANUFACTURER_PHILIPS)) + printWarning("Philips enhanced DICOMs (hint: export as classic DICOM)\n"); int ret = EXIT_SUCCESS; //check for repeated echoes - count unique number of echoes //code below checks for multi-echoes - not required if maxNumberOfEchoes reported in PARREC @@ -7221,10 +7223,14 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmLi for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) dti4D->gradDynVol[i] = 0; dti4D->gradDynVol[0] = 1; + int dim3 = dcmList[indx].xyzDim[3];//vol2slice(int vol, int dim3) for (int i = 1; i < dcmList[indx].xyzDim[4]; i++) { - for (int j = 0; j < i; j++) - if (( (dcmList[indx].aslFlags != kASL_FLAG_NONE) || isSameFloatGE(dti4D->triggerDelayTime[i], dti4D->triggerDelayTime[j])) && (dti4D->intenIntercept[i] == dti4D->intenIntercept[j]) && (dti4D->intenScale[i] == dti4D->intenScale[j]) && (dti4D->isReal[i] == dti4D->isReal[j]) && (dti4D->isImaginary[i] == dti4D->isImaginary[j]) && (dti4D->isPhase[i] == dti4D->isPhase[j]) && (dti4D->TE[i] == dti4D->TE[j])) + int iv = (i * dim3); //intenIntercept and intenScale can vary within a volume + for (int j = 0; j < i; j++) { + int jv = (j * dim3); + if (( (dcmList[indx].aslFlags != kASL_FLAG_NONE) || isSameFloatGE(dti4D->triggerDelayTime[i], dti4D->triggerDelayTime[j])) && (dti4D->intenIntercept[iv] == dti4D->intenIntercept[jv]) && (dti4D->intenScale[iv] == dti4D->intenScale[jv]) && (dti4D->isReal[i] == dti4D->isReal[j]) && (dti4D->isImaginary[i] == dti4D->isImaginary[j]) && (dti4D->isPhase[i] == dti4D->isPhase[j]) && (dti4D->TE[i] == dti4D->TE[j])) dti4D->gradDynVol[i] = dti4D->gradDynVol[j]; + } if (dti4D->gradDynVol[i] == 0) { series++; dti4D->gradDynVol[i] = series; From 8aa0565fd26fbc46660757d6e58697d9bf104262 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Thu, 24 Mar 2022 07:21:21 -0400 Subject: [PATCH 32/60] All black JPEG2000 compressed images can be less than 128 bytes --- console/nii_dicom.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index c9168bbe..412739a8 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -4864,7 +4864,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); d.imageBytes = dcmInt(4, &buffer[lPos], d.isLittleEndian); lPos = lPos + 4; lLength = d.imageBytes; - if (d.imageBytes > 128) { + if (d.imageBytes <= 0) goto skipRemap; + if (d.imageBytes > 24) { /*if (encapsulatedDataFragments < kMaxDTI4D) { dti4D->fragmentOffset[encapsulatedDataFragments] = (int)lPos + (int)lFileOffset; dti4D->fragmentLength[encapsulatedDataFragments] = lLength; @@ -4979,8 +4980,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); printMessage("remapping %04x,%04x -> %04x,%04x\n", groupElement & 65535, groupElement >> 16, remappedGroupElement & 65535, remappedGroupElement >> 16); groupElement = remappedGroupElement; } - skipRemap: #endif // salvageAgfa + skipRemap: if ((lLength % 2) != 0) { //https://www.nitrc.org/forum/forum.php?thread_id=11827&forum_id=4703 printMessage("Illegal DICOM tag %04x,%04x (odd element length %d): %s\n", groupElement & 65535, groupElement >> 16, lLength, fname); //proper to return here, but we can carry on as a hail mary From ad09a1c6ff03821fa07851d4312c50f7bb782f02 Mon Sep 17 00:00:00 2001 From: Ningfei Li Date: Thu, 24 Mar 2022 22:54:52 +0100 Subject: [PATCH 33/60] By default use HTTPS instead of SSH to clone external dependencies. --- SuperBuild/SuperBuild.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SuperBuild/SuperBuild.cmake b/SuperBuild/SuperBuild.cmake index 44d28689..e1057a0f 100644 --- a/SuperBuild/SuperBuild.cmake +++ b/SuperBuild/SuperBuild.cmake @@ -5,7 +5,7 @@ if(NOT GIT_FOUND) endif() # Use git protocol or not -option(USE_GIT_PROTOCOL "If behind a firewall turn this off to use http instead." ON) +option(USE_GIT_PROTOCOL "If behind a firewall turn this off to use http instead." OFF) if(USE_GIT_PROTOCOL) set(git_protocol "git") else() From a118831deca17af14e43f171a9a23bfc14b62c51 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 12 Apr 2022 14:18:34 -0400 Subject: [PATCH 34/60] Update readme --- Siemens/README.md | 2 +- console/nii_dicom.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Siemens/README.md b/Siemens/README.md index 17c5fdb7..4738c3a9 100644 --- a/Siemens/README.md +++ b/Siemens/README.md @@ -10,7 +10,7 @@ The DICOM images exported by the X-series is radically different than the V-seri X-series users are strongly encouraged to export data using the "Enhanced" format and to not use any of the "Anonymize" features on the console. The consequences of these options is discussed in detail in [issue 236](https://github.com/rordenlab/dcm2niix/issues/236). Siemens notes `We highly recommend that the Enhanced DICOM format be used. This is because this format retains far more information in the header`. Failure to export data in this format has led to catastrophic data loss for numerous users (for publicly reported details see issues [203](https://github.com/rordenlab/dcm2niix/issues/203), [236](https://github.com/rordenlab/dcm2niix/issues/236), [240](https://github.com/rordenlab/dcm2niix/issues/240), [274](https://github.com/rordenlab/dcm2niix/issues/274), [303](https://github.com/rordenlab/dcm2niix/issues/303), [370](https://github.com/rordenlab/dcm2niix/issues/370), [394](https://github.com/rordenlab/dcm2niix/issues/394)). This reflects limitations of the DICOM data, not dcm2niix. -While X-series consoles allow users to export data as enhanced, mosaic or classic 2D formats, choosing an option other than enhanced dramatically degrades the meta data. Note that the Siemens considers mosaic images `secondary capture` data intended for quality assurance only. The mosaic scans lack several "Type 1" DICOM properties, necessarily limiting conversion. The non-mosaic 2D enhanced DICOMs are compact and efficient, but appear to have limited details relative to the enhanced output. This is unfortunate, as for the V-series the mosaic format has major benefits, so users may be in the habit of preferring mosaic export. Finally, each of the formats (enhanced, mosaic, classic) can be exported as anonymized. The Siemens console anonymization of current XA10A (Fall 2018) strips many useful tags. Siemens suggests `the use an offline/in-house anonymization software instead`. Another limitation of the current X-series format is that it retains no versioning details beyond the minor version for software and hardware stepping (e.g. versions are merely XA10 or XA11 with no details for service packs). If you use a X-series, you are strongly encouraged to manually log every hardware or software upgrade to allow future analyses to identify and regress out any effects of these modifications. Since the X-series format does not have a CSA header, dcm2niix will attempt to use the new private DICOM tags to populate the BIDS file. These tags are described in [issue 240](https://github.com/rordenlab/dcm2niix/issues/240). +While X-series consoles allow users to export data as enhanced, mosaic or classic 2D formats, choosing an option other than enhanced dramatically degrades the meta data. Note that the Siemens considers mosaic images `secondary capture` data intended for quality assurance only. The mosaic scans lack several "Type 1" DICOM properties, necessarily limiting conversion. This is unfortunate, as for the V-series the mosaic format has major benefits, so users may be in the habit of preferring mosaic export. The non-mosaic 2D enhanced DICOMs are compact and efficient, but appear to have limited details relative to the previous generation V-series with its rich CSA header. Finally, each of the formats (enhanced, mosaic, classic) can be exported as anonymized. The Siemens console anonymization of current XA10A (Fall 2018) strips many useful tags. Siemens suggests `the use an offline/in-house anonymization software instead`. Another limitation of the current X-series format is that it retains no versioning details beyond the minor version for software and hardware stepping (e.g. versions are merely XA10 or XA11 with no details for service packs). If you use a X-series, you are strongly encouraged to manually log every hardware or software upgrade to allow future analyses to identify and regress out any effects of these modifications. Since the X-series format does not have a CSA header, dcm2niix will attempt to use the new private DICOM tags to populate the BIDS file. These tags are described in [issue 240](https://github.com/rordenlab/dcm2niix/issues/240). When creating enhanced DICOMs diffusion information is provided in public tags. Based on a limited sample, it seems that classic DICOMs do not store diffusion data for XA10, and use private tags for [XA11](https://www.nitrc.org/forum/forum.php?thread_id=10013&forum_id=4703). diff --git a/console/nii_dicom.h b/console/nii_dicom.h index bc5b8c16..60da1cb8 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20220322" +#define kDCMdate "v1.0.20220412" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic From 7d12f98c8d4f95edae7e3390b14e0c64c87244d1 Mon Sep 17 00:00:00 2001 From: Ningfei Li Date: Tue, 12 Apr 2022 21:57:13 +0200 Subject: [PATCH 35/60] Bump CMake minimum required version. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2afb5414..89e1a282 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.11) +cmake_minimum_required(VERSION 2.8.12) if(COMMAND CMAKE_POLICY) CMAKE_POLICY(SET CMP0003 NEW) From 61397b5aa0016cd4aaa5425a0cc94d47a6b66d7e Mon Sep 17 00:00:00 2001 From: Jaemin Shin Date: Tue, 26 Apr 2022 18:49:13 -0400 Subject: [PATCH 36/60] GE bias value for b-value --- console/nii_dicom.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 412739a8..83b92ac8 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -3808,7 +3808,8 @@ void set_directionality0018_9075(struct TVolumeDiffusion *ptvd, unsigned char *i int set_bValGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf) { //see Series 16 https://github.com/nikadon/cc-dcm2bids-wrapper/tree/master/dicom-qa-examples/ge-mr750-dwi-b-vals#table b750 = 1000000750\8\0\0 b1500 = 1000001500\8\0\0 int bVal = dcmStrInt(lLength, inbuf); - bVal = (bVal % 10000); + // GE bias value for b-value https://github.com/rordenlab/dcm2niix/issues/602 + bVal = (bVal % 1000000000); ptvd->_dtiV[0] = bVal; //printf("(0043,1039) '%s' Slop_int_6 -->%d \n", inbuf, bVal); //dd.CSA.numDti = 1; // Always true for GE. From c6b80d34528282b4a859a1bbe0fe9d1d9eb79095 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 29 Apr 2022 10:42:04 -0400 Subject: [PATCH 37/60] Ignore Original Attributes SQ (https://github.com/rordenlab/dcm2niix/issues/599) --- console/nii_dicom.cpp | 8 ++++++++ console/nii_dicom.h | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 83b92ac8..f100c261 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -4418,6 +4418,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kMRImageGradientOrientationNumber 0x2005+(0x1413 << 16) //IS #define kMRImageLabelType 0x2005 + (0x1429 << 16) //CS ASL LBL_CTL https://github.com/physimals/dcm_convert_phillips/ #define kMRImageDiffVolumeNumber 0x2005+(0x1596 << 16) //IS +#define kOriginalAttributesSq 0x0400 + (0x0561 << 16) #define kSharedFunctionalGroupsSequence 0x5200 + uint32_t(0x9229 << 16) // SQ #define kPerFrameFunctionalGroupsSequence 0x5200 + uint32_t(0x9230 << 16) // SQ #define kWaveformSq 0x5400 + (0x0100 << 16) @@ -4444,6 +4445,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); float temporalResolutionMS = 0.0; float MRImageDynamicScanBeginTime = 0.0; bool is2005140FSQ = false; + bool is4000561SQ = false; //Original Attributes Sequence Attribute bool overlayOK = true; int userData12GE = 0; int overlayRows = 0; @@ -4639,6 +4641,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); } if (unNest) { is2005140FSQ = false; + is4000561SQ = false; if (sqDepth < 0) sqDepth = 0; //should not happen, but protect for faulty anonymization //if we leave the folder MREchoSequence 0018,9114 @@ -4876,6 +4879,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); encapsulatedDataFragmentStart = (int)lPos + (int)lFileOffset; } } + if (is4000561SQ) + groupElement = kUnused; //ignore Original Attributes if ((isIconImageSequence) && ((groupElement & 0x0028) == 0x0028)) groupElement = kUnused; //ignore icon dimensions #ifdef salvageAgfa //issue435 @@ -6565,6 +6570,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); break; philMRImageDiffVolumeNumber = dcmStrInt(lLength, &buffer[lPos]); break; + case kOriginalAttributesSq: + is4000561SQ = true; + break; case kWaveformSq: d.imageStart = 1; //abort!!! printMessage("Skipping DICOM (audio not image) '%s'\n", fname); diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 60da1cb8..2e59a51f 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20220412" +#define kDCMdate "v1.0.20220429" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic From 89c284493b0c24d66fcb52a158d09c5e473364f5 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 29 Apr 2022 10:53:32 -0400 Subject: [PATCH 38/60] Ignore ReferencedImageEvidenceSQ --- console/nii_dicom.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index f100c261..6776fe7d 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -4181,6 +4181,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kInstitutionalDepartmentName 0x0008 + (0x1040 << 16) #define kManufacturersModelName 0x0008 + (0x1090 << 16) #define kDerivationDescription 0x0008 + (0x2111 << 16) +#define kReferencedImageEvidenceSQ (uint32_t)0x0008 + (0x9092 << 16) #define kComplexImageComponent (uint32_t)0x0008 + (0x9208 << 16) //'0008' '9208' 'CS' 'ComplexImageComponent' #define kAcquisitionContrast (uint32_t)0x0008 + (0x9209 << 16) //'0008' '9209' 'CS' 'AcquisitionContrast' #define kPatientName 0x0010 + (0x0010 << 16) @@ -4445,7 +4446,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); float temporalResolutionMS = 0.0; float MRImageDynamicScanBeginTime = 0.0; bool is2005140FSQ = false; - bool is4000561SQ = false; //Original Attributes Sequence Attribute + bool is4000561SQ = false; //Original Attributes SQ + bool is00089092SQ = false; //Referenced Image Evidence SQ bool overlayOK = true; int userData12GE = 0; int overlayRows = 0; @@ -4642,6 +4644,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); if (unNest) { is2005140FSQ = false; is4000561SQ = false; + is00089092SQ = false; if (sqDepth < 0) sqDepth = 0; //should not happen, but protect for faulty anonymization //if we leave the folder MREchoSequence 0018,9114 @@ -4879,7 +4882,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); encapsulatedDataFragmentStart = (int)lPos + (int)lFileOffset; } } - if (is4000561SQ) + if ((is4000561SQ) || (is00089092SQ)) groupElement = kUnused; //ignore Original Attributes if ((isIconImageSequence) && ((groupElement & 0x0028) == 0x0028)) groupElement = kUnused; //ignore icon dimensions @@ -5238,6 +5241,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); case kReferringPhysicianName: dcmStr(lLength, &buffer[lPos], d.referringPhysicianName); break; + case kReferencedImageEvidenceSQ: + printf(">>>\n"); + is00089092SQ = true; + break; case kComplexImageComponent: if (is2005140FSQ) break; //see Maastricht DICOM data for magnitude data with this field set as REAL! https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging From 9169c7f34c2455223041212e13276059b6e2a34d Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 29 Apr 2022 10:53:54 -0400 Subject: [PATCH 39/60] Remove test --- console/nii_dicom.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 6776fe7d..a9da0d97 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -5242,7 +5242,6 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); dcmStr(lLength, &buffer[lPos], d.referringPhysicianName); break; case kReferencedImageEvidenceSQ: - printf(">>>\n"); is00089092SQ = true; break; case kComplexImageComponent: From 5d096d12074d5dc66752f48c6d1eb56fc4dcf012 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Sat, 7 May 2022 20:49:46 -0400 Subject: [PATCH 40/60] XA30 can include CSA header --- BIDS/README.md | 1 + console/nii_dicom.cpp | 16 ++++++++++++++++ console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 2 -- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index bf596b66..342ea7a5 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -321,6 +321,7 @@ Fields specific to Siemens XA-series MRI systems (Sola, Vida). |------------------------------|------|---------------------|------------| | ReceiveCoilActiveElements | | DICOM tag 0021,114F | B | | BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0021,1153 | D | +| Scanning Sequence | | DICOM tag 0021,105a | D | ### Manufacturer UIH diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index a9da0d97..d591b0d9 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -4306,8 +4306,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kTemporalPositionIndex 0x0020 + uint32_t(0x9128 << 16) // UL #define kDimensionIndexPointer 0x0020 + uint32_t(0x9165 << 16) //Private Group 21 as Used by Siemens: +#define kScanningSequenceSiemens 0x0021 + (0x105A << 16) //CS #define kSequenceVariant21 0x0021 + (0x105B << 16) //CS Siemens ONLY: For GE this is TaggingFlipAngle #define kPATModeText 0x0021 + (0x1009 << 16) //LO, see kImaPATModeText +#define kCSASeriesHeaderInfoXA 0x0021 + (0x1019 << 16) #define kTimeAfterStart 0x0021 + (0x1104 << 16) //DS #define kICE_dims 0x0021 + (0x1106 << 16) //LO [X_4_1_1_1_1_160_1_1_1_1_1_277] #define kPhaseEncodingDirectionPositiveSiemens 0x0021 + (0x111C << 16) //IS @@ -4518,6 +4520,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //float intenScalePhilips = 0.0; char seriesTimeTxt[kDICOMStr] = ""; char acquisitionDateTimeTxt[kDICOMStr] = ""; + char scanningSequenceSiemens[kDICOMStr] = ""; char imageType1st[kDICOMStr] = ""; bool isEncapsulatedData = false; int multiBandFactor = 0; @@ -5825,6 +5828,14 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //printMessage("p%gs%d\n", d.accelFactPE, multiBandFactor); break; } + case kCSASeriesHeaderInfoXA: + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + if ((lPos + lLength) > fileLen) + break; + d.CSA.SeriesHeader_offset = (int)lPos; + d.CSA.SeriesHeader_length = lLength; + break; case kTimeAfterStart: //0021,1104 see https://github.com/rordenlab/dcm2niix/issues/303 // 0021,1104 6@159630 DS 4.635 @@ -6251,6 +6262,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); d.isEPI = true; break; //warp } + case kScanningSequenceSiemens: + dcmStr(lLength, &buffer[lPos], scanningSequenceSiemens); + break; case kSequenceVariant21: if (d.manufacturer != kMANUFACTURER_SIEMENS) break; //n.b. for GE 0021,105B' TaggingFlipAngle @@ -7162,6 +7176,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); printWarning("0008,0008=MOSAIC but number of slices not specified: %s\n", fname); if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.dtiV[1] < -1.0) && (d.CSA.dtiV[2] < -1.0) && (d.CSA.dtiV[3] < -1.0)) d.CSA.dtiV[0] = 0; //SiemensTrio-Syngo2004A reports B=0 images as having impossible b-vectors. + if ((strlen(d.scanningSequence) < 1) && (strlen(scanningSequenceSiemens) > 1)) + strcpy(d.scanningSequence, scanningSequenceSiemens); if ((strlen(d.protocolName) < 1) && (strlen(d.seriesDescription) > 1)) strcpy(d.protocolName, d.seriesDescription); if ((strlen(d.protocolName) > 1) && (isMoCo)) diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 2e59a51f..6e74ebbf 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20220429" +#define kDCMdate "v1.0.20220505" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 0a2fe11f..f9218f24 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -6978,8 +6978,6 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d nii_scale16bitUnsigned(imgM, &hdr0, opts.isVerbose); //allow UINT16 to use full dynamic range } else if ((opts.isMaximize16BitRange == kMaximize16BitRange_False) && (hdr0.datatype == DT_UINT16) && (!dcmList[dcmSort[0].indx].isSigned)) nii_check16bitUnsigned(imgM, &hdr0, opts.isVerbose); //save UINT16 as INT16 if we can do this losslessly - if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert < 2)) - printWarning("Siemens XA DICOM inadequate for robust conversion (issue 236)\n"); if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert > 1) && (nConvert == (hdr0.dim[3] * hdr0.dim[4])) ) printWarning("Siemens XA exported as classic not enhanced DICOM (issue 236)\n"); #ifndef USING_DCM2NIIXFSWRAPPER From 0af51592d1aafca61953f451a49579b059f880c0 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 10 May 2022 07:07:50 -0400 Subject: [PATCH 41/60] Report DwellTime for Siemens XA (https://github.com/rordenlab/dcm2niix/issues/240) --- console/nii_dicom.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index d591b0d9..532cd475 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -4313,7 +4313,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kTimeAfterStart 0x0021 + (0x1104 << 16) //DS #define kICE_dims 0x0021 + (0x1106 << 16) //LO [X_4_1_1_1_1_160_1_1_1_1_1_277] #define kPhaseEncodingDirectionPositiveSiemens 0x0021 + (0x111C << 16) //IS -//#define kRealDwellTime 0x0021+(0x1142<< 16 )//IS +#define kRealDwellTime 0x0021+(0x1142<< 16 )//IS #define kBandwidthPerPixelPhaseEncode21 0x0021 + (0x1153 << 16) //FD #define kCoilElements 0x0021 + (0x114F << 16) //LO #define kAcquisitionMatrixText21 0x0021 + (0x1158 << 16) //SH @@ -5873,10 +5873,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; break; } - //case kRealDwellTime : //https://github.com/rordenlab/dcm2niix/issues/240 - // if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - // d.dwellTime = dcmStrInt(lLength, &buffer[lPos]); - // break; + case kRealDwellTime : //https://github.com/rordenlab/dcm2niix/issues/240 + if (d.manufacturer != kMANUFACTURER_SIEMENS) break; + d.dwellTime = dcmStrInt(lLength, &buffer[lPos]); + break; case kBandwidthPerPixelPhaseEncode21: if (d.manufacturer != kMANUFACTURER_SIEMENS) break; From b29ea9335417ece939021b0c40efdcf08ebfcf19 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 10 May 2022 15:55:27 -0400 Subject: [PATCH 42/60] Better Siemens XA support (https://github.com/rordenlab/dcm2niix/issues/606) --- console/nii_dicom.cpp | 26 ++++++++++++++++++-------- console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 10 ++++++---- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 532cd475..00e46276 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -740,7 +740,7 @@ struct TDICOMdata clear_dicom_data() { strcpy(d.bodyPartExamined, ""); strcpy(d.coilName, ""); strcpy(d.coilElements, ""); - strcpy(d.pulseSequenceNameGE, ""); + strcpy(d.pulseSequenceName, ""); strcpy(d.radiopharmaceutical, ""); strcpy(d.convolutionKernel, ""); strcpy(d.parallelAcquisitionTechnique, ""); @@ -4236,6 +4236,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kInPlanePhaseEncodingDirection 0x0018 + (0x1312 << 16) //CS #define kSAR 0x0018 + (0x1316 << 16) //'DS' 'SAR' #define kPatientOrient 0x0018 + (0x5100 << 16) //0018,5100. patient orientation - 'HFS' +#define kPulseSequenceName 0x0018 + uint32_t(0x9005 << 16) //'SH' 'YES'/'NO' #define kInversionRecovery 0x0018 + uint32_t(0x9009 << 16) //'CS' 'YES'/'NO' #define kSpoiling 0x0018 + uint32_t(0x9016 << 16) //'CS' #define kEchoPlanarPulseSequence 0x0018 + uint32_t(0x9018 << 16) //'CS' 'YES'/'NO' @@ -4308,6 +4309,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //Private Group 21 as Used by Siemens: #define kScanningSequenceSiemens 0x0021 + (0x105A << 16) //CS #define kSequenceVariant21 0x0021 + (0x105B << 16) //CS Siemens ONLY: For GE this is TaggingFlipAngle +#define kScanOptionsSiemens 0x0021 + (0x105C << 16) //CS Siemens ONLY #define kPATModeText 0x0021 + (0x1009 << 16) //LO, see kImaPATModeText #define kCSASeriesHeaderInfoXA 0x0021 + (0x1019 << 16) #define kTimeAfterStart 0x0021 + (0x1104 << 16) //DS @@ -4518,6 +4520,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); } char vr[2]; //float intenScalePhilips = 0.0; + char scanOptionsSiemens[kDICOMStr] = ""; char seriesTimeTxt[kDICOMStr] = ""; char acquisitionDateTimeTxt[kDICOMStr] = ""; char scanningSequenceSiemens[kDICOMStr] = ""; @@ -5398,6 +5401,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); case kPatientOrient: dcmStr(lLength, &buffer[lPos], d.patientOrient); break; + case kPulseSequenceName: + dcmStr(lLength, &buffer[lPos], d.pulseSequenceName); + break; case kInversionRecovery: // CS [YES],[NO] if (lLength < 2) break; @@ -5613,17 +5619,17 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); case kPulseSequenceNameGE: { //LO 'epi'/'epiRT' if (d.manufacturer != kMANUFACTURER_GE) break; - dcmStr(lLength, &buffer[lPos], d.pulseSequenceNameGE); - if (strstr( d.pulseSequenceNameGE, "epi_pepolar") != NULL) { + dcmStr(lLength, &buffer[lPos], d.pulseSequenceName); + if (strstr( d.pulseSequenceName, "epi_pepolar") != NULL) { d.epiVersionGE = kGE_EPI_PEPOLAR_FWD; //n.b. combine with 0019,10B3 - } else if (strstr( d.pulseSequenceNameGE, "epi2") != NULL) { + } else if (strstr( d.pulseSequenceName, "epi2") != NULL) { d.epiVersionGE = kGE_EPI_EPI2; //-1 = not epi, 0 = epi, 1 = epiRT, 2 = epi2 - } else if (strstr( d.pulseSequenceNameGE, "epiRT") != NULL) { + } else if (strstr( d.pulseSequenceName, "epiRT") != NULL) { d.epiVersionGE = kGE_EPI_EPIRT; //-1 = not epi, 0 = epi, 1 = epiRT - } else if (strstr( d.pulseSequenceNameGE, "epi") != NULL) { + } else if (strstr( d.pulseSequenceName, "epi") != NULL) { d.epiVersionGE = kGE_EPI_EPI; //-1 = not epi, 0 = epi, 1 = epiRT } - if (strcmp( d.pulseSequenceNameGE, "3db0map") == 0) { + if (strcmp( d.pulseSequenceName, "3db0map") == 0) { isGEfieldMap = true; //issue501 } break; @@ -5810,7 +5816,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); dcmStr(lLength, &buffer[lPos], d.imageComments, true); break; //group 21: siemens - //g21 + case kScanOptionsSiemens: + dcmStr(lLength, &buffer[lPos], scanOptionsSiemens); + break; case kPATModeText: { //e.g. Siemens iPAT x2 listed as "p2" char accelStr[kDICOMStr]; dcmStr(lLength, &buffer[lPos], accelStr); @@ -7178,6 +7186,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); d.CSA.dtiV[0] = 0; //SiemensTrio-Syngo2004A reports B=0 images as having impossible b-vectors. if ((strlen(d.scanningSequence) < 1) && (strlen(scanningSequenceSiemens) > 1)) strcpy(d.scanningSequence, scanningSequenceSiemens); + if ((strlen(d.scanOptions) < 1) && (strlen(scanOptionsSiemens) > 1)) + strcpy(d.scanOptions, scanOptionsSiemens); if ((strlen(d.protocolName) < 1) && (strlen(d.seriesDescription) > 1)) strcpy(d.protocolName, d.seriesDescription); if ((strlen(d.protocolName) > 1) && (isMoCo)) diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 6e74ebbf..fae5213b 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -230,7 +230,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. double acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr]; - char imageOrientationText[kDICOMStr], pulseSequenceNameGE[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], scanOptions[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[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]; + char imageOrientationText[kDICOMStr], pulseSequenceName[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], scanOptions[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[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]; char institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge]; uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; struct TCSAdata CSA; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index f9218f24..185964bb 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1229,7 +1229,7 @@ tse3d: T2*/ fprintf(fp, "\t\"Manufacturer\": \"UIH\",\n"); break; }; - json_Str(fp, "\t\"PulseSequenceName\": \"%s\",\n", d.pulseSequenceNameGE); + json_Str(fp, "\t\"PulseSequenceName\": \"%s\",\n", d.pulseSequenceName); //if (d.epiVersionGE == 0) // fprintf(fp, "\t\"PulseSequenceName\": \"epi\",\n"); //if (d.epiVersionGE == 1) @@ -1900,9 +1900,11 @@ tse3d: T2*/ // FSL definition is start of first line until start of last line. // Other than the use of (n-1), the value is basically just 1.0/bandwidthPerPixelPhaseEncode. // https://github.com/rordenlab/dcm2niix/issues/130 - if ((d.manufacturer == kMANUFACTURER_UIH) && (effectiveEchoSpacing <= 0.0)) //issue225, issue531 - json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", d.acquisitionDuration / 1000.0); - else if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0) ) + if (d.manufacturer != kMANUFACTURER_UIH) //issue606 + json_Float(fp, "\t\"AcquisitionDuration\": %g,\n", d.acquisitionDuration); + if ((d.manufacturer == kMANUFACTURER_UIH) && (effectiveEchoSpacing <= 0.0)) //issue225, issue531 + json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", d.acquisitionDuration / 1000.0); + else if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0) ) fprintf(fp, "\t\"TotalReadoutTime\": %g,\n", effectiveEchoSpacing * (reconMatrixPE - 1.0)); json_Float(fp, "\t\"PixelBandwidth\": %g,\n", d.pixelBandwidth); if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.dwellTime > 0)) From 6e513689716380652e0584fb3ad2806c380a464d Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 10 May 2022 20:02:19 -0400 Subject: [PATCH 43/60] Flipping Y also flips sign of determinant --- console/nii_dicom_batch.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 185964bb..f77dce2c 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -2396,10 +2396,16 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st return NULL; } if (!opts.isFlipY) { //!FLIP_Y&& (dcmList[indx0].CSA.mosaicSlices < 2) mosaics are always flipped in the Y direction + //the order of rows is flipped: flip the y-polarity for (int i = 0; i < (numDti); i++) { if (fabs(vx[i].V[2]) > FLT_EPSILON) vx[i].V[2] = -vx[i].V[2]; } //for each direction + //less intuitively: we have now flipped the determinant, so we must swap the x-polarity + for (int i = 0; i < (numDti); i++) { + if (fabs(vx[i].V[1]) > FLT_EPSILON) + vx[i].V[1] = -vx[i].V[1]; + } //for each direction } //if not a mosaic if (opts.isVerbose) { for (int i = 0; i < (numDti); i++) { From dea3fb9ca7d2451a333ab9708ad0842d532eedd6 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Thu, 12 May 2022 07:30:36 -0400 Subject: [PATCH 44/60] Ignore non-spatial physio data (https://github.com/rordenlab/dcm2niix/issues/606) --- console/nii_dicom.cpp | 1 + console/nii_dicom_batch.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 00e46276..c74555d0 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -6607,6 +6607,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); break; case kSpectroscopyData: //kSpectroscopyDataPointColumns printMessage("Skipping Spectroscopy DICOM '%s'\n", fname); + d.xyzDim[1] = 0; //issue606 d.imageStart = (int)lPos + (int)lFileOffset; break; case kCSAImageHeaderInfo: diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index f77dce2c..12dd7c4b 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1229,7 +1229,6 @@ tse3d: T2*/ fprintf(fp, "\t\"Manufacturer\": \"UIH\",\n"); break; }; - json_Str(fp, "\t\"PulseSequenceName\": \"%s\",\n", d.pulseSequenceName); //if (d.epiVersionGE == 0) // fprintf(fp, "\t\"PulseSequenceName\": \"epi\",\n"); //if (d.epiVersionGE == 1) @@ -1284,6 +1283,7 @@ tse3d: T2*/ json_Str(fp, "\t\"SequenceVariant\": \"%s\",\n", d.sequenceVariant); json_Str(fp, "\t\"ScanOptions\": \"%s\",\n", d.scanOptions); json_Str(fp, "\t\"SequenceName\": \"%s\",\n", d.sequenceName); + json_Str(fp, "\t\"PulseSequenceName\": \"%s\",\n", d.pulseSequenceName); if (strlen(d.imageType) > 0) { fprintf(fp, "\t\"ImageType\": [\""); bool isSep = false; @@ -2125,6 +2125,8 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st if (opts.isOnlyBIDS) return NULL; uint64_t indx0 = dcmSort[0].indx; //first volume + if (isnan(dcmList[indx0].patientPosition[0])) + return NULL; //issue606 do not save bvec for non-spatial data (e.g. physio) int numDti = dcmList[indx0].CSA.numDti; #ifdef USING_R ImageList *images = (ImageList *)opts.imageList; @@ -6999,8 +7001,8 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d //~ if (!dcmList[dcmSort[0].indx].isSlicesSpatiallySequentialPhilips) //~ nii_reorderSlices(imgM, &hdr0, dti4D); //hdr0.pixdim[3] = dxNoTilt; - if (hdr0.dim[3] < 2) - printWarning("Check that 2D images are not mirrored.\n"); + if ((hdr0.dim[3] < 2) && (!isnan(dcmList[dcmSort[0].indx].patientPosition[0]))) + printWarning("Check that 2D images are not mirrored.\n"); // isnan does not warn for non-spatial images (physio) #ifndef USING_R else fflush(stdout); //GUI buffers printf, display all results From 98ccdbe7aeaefd40988e5f45c671ea4d9f4c601e Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 20 May 2022 08:06:09 -0400 Subject: [PATCH 45/60] scan options is long string, fix bvec rejection (https://github.com/rordenlab/dcm2niix/issues/606) --- console/nii_dicom.cpp | 4 ++-- console/nii_dicom_batch.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index c74555d0..91bc8eda 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -5242,7 +5242,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); dcmStr(lLength, &buffer[lPos], d.institutionName); break; case kInstitutionAddress: //VR is "ST": 1024 chars maximum - dcmStr(lLength, &buffer[lPos], d.institutionAddress); + dcmStr(lLength, &buffer[lPos], d.institutionAddress, true); break; case kReferringPhysicianName: dcmStr(lLength, &buffer[lPos], d.referringPhysicianName); @@ -5817,7 +5817,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); break; //group 21: siemens case kScanOptionsSiemens: - dcmStr(lLength, &buffer[lPos], scanOptionsSiemens); + dcmStr(lLength, &buffer[lPos], scanOptionsSiemens, true); break; case kPATModeText: { //e.g. Siemens iPAT x2 listed as "p2" char accelStr[kDICOMStr]; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 12dd7c4b..aaccc197 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -2125,7 +2125,7 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st if (opts.isOnlyBIDS) return NULL; uint64_t indx0 = dcmSort[0].indx; //first volume - if (isnan(dcmList[indx0].patientPosition[0])) + if (isnan(dcmList[indx0].patientPosition[1])) return NULL; //issue606 do not save bvec for non-spatial data (e.g. physio) int numDti = dcmList[indx0].CSA.numDti; #ifdef USING_R From f8f421ef419c1e67af3ef237a10e63046e551d99 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 20 May 2022 11:28:20 -0400 Subject: [PATCH 46/60] Change scanOptions --- console/nii_dicom.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/console/nii_dicom.h b/console/nii_dicom.h index fae5213b..e04f2283 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -230,8 +230,8 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. double acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr]; - char imageOrientationText[kDICOMStr], pulseSequenceName[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], scanOptions[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[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]; - char institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge]; + char imageOrientationText[kDICOMStr], pulseSequenceName[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[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]; + char scanOptions[kDICOMStrLarge], institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge]; uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; struct TCSAdata CSA; bool 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; From b71678adc5d9ce920d1e042aa6725f726ec098ba Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 24 May 2022 10:30:32 -0400 Subject: [PATCH 47/60] Verbose scan options (issue 606) --- console/nii_dicom.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 91bc8eda..f7f6fbd5 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -4520,7 +4520,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); } char vr[2]; //float intenScalePhilips = 0.0; - char scanOptionsSiemens[kDICOMStr] = ""; + char scanOptionsSiemens[kDICOMStrLarge] = ""; char seriesTimeTxt[kDICOMStr] = ""; char acquisitionDateTimeTxt[kDICOMStr] = ""; char scanningSequenceSiemens[kDICOMStr] = ""; @@ -6282,7 +6282,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); break; } case kScanOptions: - dcmStr(lLength, &buffer[lPos], d.scanOptions); + dcmStr(lLength, &buffer[lPos], d.scanOptions, true); if ((lLength > 1) && (strstr(d.scanOptions, "PFF") != NULL)) d.isPartialFourier = true; //e.g. GE does not populate (0018,9081) break; From c3b4fef83eba197f500789e25917895f50afa618 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 1 Jun 2022 07:05:06 -0400 Subject: [PATCH 48/60] Philips slice timing notes --- Philips/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Philips/README.md b/Philips/README.md index b48aa7d8..428333f7 100644 --- a/Philips/README.md +++ b/Philips/README.md @@ -148,7 +148,7 @@ The BIDS tag [PartialFourier](https://bids-specification.readthedocs.io/en/stabl Philips DICOMs do not allow one to determine the temporal order of volumes for diffusion (DWI, DTI) and arterial spin labelling (ASL) sequences. This can hinder tools that attempt to model motion motion or T1 effects. For ASL data, dcm2niix will attempt to store volumes in the order phase < control/label < repeat. [For ASL examples and a table describing the ordering hierarchy, see this dataset](https://github.com/neurolabusc/dcm_qa_philips_asl). For diffusion images, dcm2niix may order Philips volumes differently depending on if the data is stored as classic or enhanced DICOM. For enhanced DICOM, dcm2niix follows the hierarchy specified by DimensionIndexValues (0020,9157). On the other hand, for classic DICOMs, volumes with identical b-value index (2005,1412) will be stored sequentially, with ties sorted based on gradient direction number (2005,1413). [For diffusion examples, see series 22 and 23 from this dataset](https://github.com/neurolabusc/dcm_qa_philips_enh). These two different sorting methods may not necessarily sort data identically. -[Slice timing correction](https://www.mccauslandcenter.sc.edu/crnl/tools/stc) can account for some variability in fMRI datasets. Unfortunately, Philips DICOM data [does not encode slice timing information](https://neurostars.org/t/heudiconv-no-extraction-of-slice-timing-data-based-on-philips-dicoms/2201/4). Therefore, dcm2niix is unable to populate the "SliceTiming" BIDS field. However, one can typically infer slice timing by recording the [mode and number of packages](https://en.wikibooks.org/w/index.php?title=SPM/Slice_Timing&stable=0#Philips_scanners) reported for the sequence on the scanner console or the [sequence PDF](http://adni.loni.usc.edu/wp-content/uploads/2017/09/ADNI-3-Basic-Philips-R5.pdf). For precise timing, it is also worth knowing if equidistant "temporal slice spacing" is set and whether "prospect. motion corr." is on or off (if on, a short delay occurs between volumes). +[Slice timing correction](https://www.mccauslandcenter.sc.edu/crnl/tools/stc) can account for some variability in fMRI datasets. Unfortunately, Philips DICOM data [does not explicitly encode slice timing information](https://neurostars.org/t/heudiconv-no-extraction-of-slice-timing-data-based-on-philips-dicoms/2201/4). Therefore, dcm2niix is unable to populate the "SliceTiming" BIDS field. However, the [philips_order.py script](https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/7) may be able to determine slice timing using an undocumented and unverified technique. Additionally, one can typically infer slice timing by recording the [mode and number of packages](https://en.wikibooks.org/w/index.php?title=SPM/Slice_Timing&stable=0#Philips_scanners) reported for the sequence on the scanner console or the [sequence PDF](http://adni.loni.usc.edu/wp-content/uploads/2017/09/ADNI-3-Basic-Philips-R5.pdf). For precise timing, it is also worth knowing if equidistant "temporal slice spacing" is set and whether "prospect. motion corr." is on or off (if on, a short delay occurs between volumes). Likewise, the BIDS tag "PhaseEncodingDirection" allows tools like [eddy](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/eddy) and [TOPUP](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup) to undistort images. While the Philips DICOM header distinguishes the phase encoding axis (e.g. anterior-posterior vs left-right) it does not encode the polarity (A->P vs P->A). From 8788733a93de1dd30cf31fde3f19bc39c6a2f16b Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Mon, 6 Jun 2022 15:39:28 -0400 Subject: [PATCH 49/60] GE sequence details (https://github.com/rordenlab/dcm2niix/issues/608) --- BIDS/README.md | 16 ++++++++++------ console/nii_dicom.cpp | 28 ++++++++++++++++++++++++++++ console/nii_dicom.h | 6 +++--- console/nii_dicom_batch.cpp | 5 +++++ 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index 342ea7a5..6c6b028c 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -214,15 +214,19 @@ Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Deter | PulseSequenceName | | `epi` or `epiRT` | D | | InternalPulseSequenceName | | `EPI` or `EPI2` | D | | PhaseEncodingPolarityGE | | `Unflipped` or `Flipped` | D | -| ASLContrastTechnique | | DICOM tag 0043,10A3 | D | -| ASLLabelingTechnique | | DICOM tag 0043,10A4 | D | -| LabelingDuration | s | DICOM tag 0043,10A5 | B | -| MultibandAccelerationFactor | | DICOM tag 0043,1083 | B | +| PostLabelingDelay | s | DICOM tag 0018,0082 (TI) | B | | NumberOfPointsPerArm | | DICOM tag 0027,1060 | D | | NumberOfArms | | DICOM tag 0027,1061 | D | | NumberOfExcitations | | DICOM tag 0027,1062 | D | +| ShimGradientX | | DICOM tag 0043,1002 | D | +| ShimGradientY | | DICOM tag 0043,1003 | D | +| ShimGradientZ | | DICOM tag 0043,1004 | D | | ParallelReductionFactorInPlane | | DICOM tag 0043,1083 | B | -| PostLabelingDelay | s | DICOM tag 0018,0082 (TI) | B | +| PrescanReuseString | | DICOM tag 0043,1095 | D | +| MultibandAccelerationFactor | | DICOM tag 0043,1083 | B | +| ASLContrastTechnique | | DICOM tag 0043,10A3 | D | +| ASLLabelingTechnique | | DICOM tag 0043,10A4 | D | +| LabelingDuration | s | DICOM tag 0043,10A5 | B | ### Manufacturer Philips @@ -321,7 +325,7 @@ Fields specific to Siemens XA-series MRI systems (Sola, Vida). |------------------------------|------|---------------------|------------| | ReceiveCoilActiveElements | | DICOM tag 0021,114F | B | | BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0021,1153 | D | -| Scanning Sequence | | DICOM tag 0021,105a | D | +| ScanningSequence | | DICOM tag 0021,105a | D | ### Manufacturer UIH diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index f7f6fbd5..8a6f3fea 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -786,6 +786,10 @@ struct TDICOMdata clear_dicom_data() { d.echoTrainLength = 0; d.waterFatShift = 0.0; d.groupDelay = 0.0; + d.shimGradientX = -33333;//impossible value for UINT16 + d.shimGradientY = -33333;//impossible value for UINT16 + d.shimGradientZ = -33333;//impossible value for UINT16 + strcpy(d.prescanReuseString, ""); d.decayFactor = 0.0; d.percentSampling = 0.0; d.phaseFieldofView = 0.0; @@ -4351,6 +4355,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kProcedureStepDescription 0x0040 + (0x0254 << 16) #define kRealWorldIntercept 0x0040 + uint32_t(0x9224 << 16) //IS dicm2nii's SlopInt_6_9 #define kRealWorldSlope 0x0040 + uint32_t(0x9225 << 16) //IS dicm2nii's SlopInt_6_9 +#define kShimGradientX 0x0043 + (0x1002 << 16) //SS +#define kShimGradientY 0x0043 + (0x1003 << 16) //SS +#define kShimGradientZ 0x0043 + (0x1004 << 16) //SS +#define kPrescanReuseString 0x0043 + (0x1095 << 16) //LO #define kUserDefineDataGE 0x0043 + (0x102A << 16) //OB #define kEffectiveEchoSpacingGE 0x0043 + (0x102C << 16) //SS #define kImageTypeGE 0x0043 + (0x102F << 16) //SS 0/1/2/3 for magnitude/phase/real/imaginary @@ -6639,6 +6647,26 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); else if (isSameFloat(1.0, d.intenScale)) //give precedence to standard value d.intenScale = d.RWVScale; break; + case kShimGradientX: + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.shimGradientX = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kShimGradientY: + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.shimGradientY = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kShimGradientZ: + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.shimGradientZ = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kPrescanReuseString: //LO + if (d.manufacturer != kMANUFACTURER_GE) + break; + dcmStr(lLength, &buffer[lPos], d.prescanReuseString); + break; case kUserDefineDataGE: { //0043,102A if ((d.manufacturer != kMANUFACTURER_GE) || (lLength < 128)) break; diff --git a/console/nii_dicom.h b/console/nii_dicom.h index e04f2283..4ff5bd0e 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20220505" +#define kDCMdate "v1.0.20220606" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic @@ -222,7 +222,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; int xyzDim[5]; uint32_t coilCrc, seriesUidCrc, instanceUidCrc; int overlayStart[kMaxOverlay]; - int phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; + int shimGradientX, shimGradientY, shimGradientZ, phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; float xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) @@ -230,7 +230,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. double acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr]; - char imageOrientationText[kDICOMStr], pulseSequenceName[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[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]; + char prescanReuseString[kDICOMStr], imageOrientationText[kDICOMStr], pulseSequenceName[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[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]; char scanOptions[kDICOMStrLarge], institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge]; uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; struct TCSAdata CSA; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index aaccc197..fcc03d56 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1471,6 +1471,11 @@ tse3d: T2*/ if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED) fprintf(fp, "\t\"PhaseEncodingPolarityGE\": \"Flipped\",\n"); } + //GE specific fields + if (d.shimGradientX > -33333) fprintf(fp, "\t\"ShimGradientX\": %d,\n", d.shimGradientX); + if (d.shimGradientY > -33333) fprintf(fp, "\t\"ShimGradientY\": %d,\n", d.shimGradientY); + if (d.shimGradientZ > -33333) fprintf(fp, "\t\"ShimGradientZ\": %d,\n", d.shimGradientZ); + json_Str(fp, "\t\"PrescanReuseString\": \"%s\",\n", d.prescanReuseString); float delayTimeInTR = -0.01; float repetitionTimePreparation = 0.0; #ifdef myReadAsciiCsa From 0191d515c4d80c4985edad40ec34c83b00bddead Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 7 Jun 2022 07:07:30 -0400 Subject: [PATCH 50/60] Store GE ShimSetting as array (https://github.com/rordenlab/dcm2niix/issues/608) --- BIDS/README.md | 6 +++--- console/nii_dicom_batch.cpp | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index 6c6b028c..b3815d87 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -218,9 +218,9 @@ Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Deter | NumberOfPointsPerArm | | DICOM tag 0027,1060 | D | | NumberOfArms | | DICOM tag 0027,1061 | D | | NumberOfExcitations | | DICOM tag 0027,1062 | D | -| ShimGradientX | | DICOM tag 0043,1002 | D | -| ShimGradientY | | DICOM tag 0043,1003 | D | -| ShimGradientZ | | DICOM tag 0043,1004 | D | +| ShimSetting[0] | | DICOM tag 0043,1002 | D | +| ShimSetting[1] | | DICOM tag 0043,1003 | D | +| ShimSetting[2] | | DICOM tag 0043,1004 | D | | ParallelReductionFactorInPlane | | DICOM tag 0043,1083 | B | | PrescanReuseString | | DICOM tag 0043,1095 | D | | MultibandAccelerationFactor | | DICOM tag 0043,1083 | B | diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index fcc03d56..a1bc7208 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1472,9 +1472,8 @@ tse3d: T2*/ fprintf(fp, "\t\"PhaseEncodingPolarityGE\": \"Flipped\",\n"); } //GE specific fields - if (d.shimGradientX > -33333) fprintf(fp, "\t\"ShimGradientX\": %d,\n", d.shimGradientX); - if (d.shimGradientY > -33333) fprintf(fp, "\t\"ShimGradientY\": %d,\n", d.shimGradientY); - if (d.shimGradientZ > -33333) fprintf(fp, "\t\"ShimGradientZ\": %d,\n", d.shimGradientZ); + if (d.shimGradientX > -33333) + fprintf(fp, "\t\"ShimSetting\": [\n\t\t%d,\n\t\t%d,\n\t\t%d\t],\n", d.shimGradientX, d.shimGradientY, d.shimGradientZ); json_Str(fp, "\t\"PrescanReuseString\": \"%s\",\n", d.prescanReuseString); float delayTimeInTR = -0.01; float repetitionTimePreparation = 0.0; From 8f66f1e4007afc26220736c0c6aee7f576b6c42b Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 7 Jun 2022 07:28:23 -0400 Subject: [PATCH 51/60] Add AcquisitionVoxelSize tag for Siemens ASL (https://github.com/rordenlab/dcm2niix/issues/608) --- console/nii_dicom_batch.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index a1bc7208..adfb642f 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1651,6 +1651,10 @@ tse3d: T2*/ fprintf(fp, "\t\"ArterialSpinLabelingType\": \"PCASL\",\n"); if (isPASL) fprintf(fp, "\t\"ArterialSpinLabelingType\": \"PASL\",\n"); + //AcquisitionVoxelSize uses slice thickness (without gap) + // https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#common-metadata-fields-applicable-to-both-pcasl-and-pasl + if ((isPASL) || (isPCASL)) + fprintf(fp, "\t\"AcquisitionVoxelSize\": [\n\t\t%g,\n\t\t%g,\n\t\t%g\t],\n", d.xyzMM[1], d.xyzMM[2], d.zThick); //general properties if ((csaAscii.partialFourier > 0) && ((d.modality == kMODALITY_MR))) { //check MR, e.g. do not report for Siemens PET //https://github.com/ismrmrd/siemens_to_ismrmrd/blob/master/parameter_maps/IsmrmrdParameterMap_Siemens_EPI_FLASHREF.xsl From f9cc43168eef64e69c69c11eb5b896f7617df67b Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 8 Jun 2022 07:22:18 -0400 Subject: [PATCH 52/60] AcquisitionVoxelSize before any interpolation or resampling within reconstruction or image processing --- console/nii_dicom_batch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index adfb642f..58486105 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1653,7 +1653,7 @@ tse3d: T2*/ fprintf(fp, "\t\"ArterialSpinLabelingType\": \"PASL\",\n"); //AcquisitionVoxelSize uses slice thickness (without gap) // https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#common-metadata-fields-applicable-to-both-pcasl-and-pasl - if ((isPASL) || (isPCASL)) + if (((isPASL) || (isPCASL)) && (csaAscii.interp <= 0)) fprintf(fp, "\t\"AcquisitionVoxelSize\": [\n\t\t%g,\n\t\t%g,\n\t\t%g\t],\n", d.xyzMM[1], d.xyzMM[2], d.zThick); //general properties if ((csaAscii.partialFourier > 0) && ((d.modality == kMODALITY_MR))) { //check MR, e.g. do not report for Siemens PET From a716dc67ef4695f30c1760378f8a97d94a5ea3ad Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 15 Jun 2022 11:31:32 -0400 Subject: [PATCH 53/60] shims are signed (https://github.com/rordenlab/dcm2niix/issues/608) --- Canon/README.md | 12 +++++++-- Philips/README.md | 2 +- console/nii_dicom.cpp | 49 ++++++++++++++++++++++++++++++++++--- console/nii_dicom_batch.cpp | 2 ++ 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/Canon/README.md b/Canon/README.md index 6a458856..945ef84c 100644 --- a/Canon/README.md +++ b/Canon/README.md @@ -6,18 +6,26 @@ dcm2niix can convert Canon (né Toshiba) DICOM format images to NIfTI. This page Users of Canon MRI equipment are strongly advised to export data from their scanners as enhanced DICOM (with all images from the series stored as a single file) rather than classic DICOM (each 2D slice stored as a separate file). Limitations of the Canon classic DICOMs are described [here](https://github.com/rordenlab/dcm2niix/issues/495) and [here](https://github.com/neurolabusc/dcm_qa_canon). +## Avoid Enhanced DICOM + +Users of Canon MRI equipment are strongly advised to export data from their scanners as classic DICOM (with each slice from the series stored as a separate files) rather than enhanced DICOM. The enhanced 4D sequences such as fMRI incorrectly sets the public tag TemporalPositionIndex (0020,9128) as 1 for all volumes: both (0020,9157) and (0020,9128). This is a violation of the DICOM standard and interferes with attempts to sort data into the correct temporal order. Any software or user who assumes these tags are truthful will fail. + +## The Enhanced versus Classic DICOM dilemma + +The prior two sections provide conflicting advice, due to limitations in both the classic and enhanced datasets generated by Canon instruments. Users of Canon equipment should lobby the manufacturer to honor their DICOM conformance statement. DICOM data from Canon instruments may not be handled correctly by any software tool including dcm2niix. This reflects a limitation in the source DICOM data, not dcm2niix. + ## Diffusion Weighted Imaging Notes In contrast to several other vendors, Toshiba used public tags to report diffusion properties. Specifically, [DiffusionBValue (0018,9087)](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,9087)) and [DiffusionGradientOrientation (0018,9089)](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,9089)). Be aware that these tags are only populated for images where a diffusion gradient is applied. Consider a typical diffusion series where some volumes are acquired with B=0 while others have B=1000. In this case, only the volumes with B>0 will report a DiffusionBValue. These coordinates are with respect to the scanner bore, not image space. -Since the acquisition by Canon, these public tags are no longer populated for images saved in classic 2D DICOM format. The diffusion gradient directions are now stored in the ASCII Image Comments tag. Like GE (but unlike [Siemens, GE and Toshiba](https://www.na-mic.org/wiki/NAMIC_Wiki:DTI:DICOM_for_DWI_and_DTI)), these directions are with respect to the image space, not the scanner bore. Further, gradient direction is not adjusted for phase encoding polarity, and it is impossible to determine phase encoding polarity. For detailed discussion and a validation dataset that exhibits these attributes please see [dcm_qa_canon](https://github.com/neurolabusc/dcm_qa_canon). A Canon classic DICOM DWI image may report: +Be aware that Canon software V6.0 stored diffusion directions in the ASCII Image Comments tag when exporting to classic DICOM. For detailed discussion and a validation dataset that exhibits these attributes please see [dcm_qa_canon](https://github.com/neurolabusc/dcm_qa_canon). A Canon classic DICOM DWI image may report: ``` (0018,9087) FD 1500 # 8, 1 DiffusionBValue (0020,4000) LT [b=1500(0.445,0.000,0.895)] # 26, 1 ImageComments ``` -In contrast, when exporting images as enhanced (4D) DICOM, information is stored in public tags and does appear to compensate for phase encode polarity. These coordinates are with respect to the scanner bore, not image space. A Canon classic DICOM DWI image may report: +In contrast, Canon software [V6.1](https://github.com/neurolabusc/dcm_qa_canon_61) uses pulbic tags for both classic and enhanced DICOMs. A Canon V6.1 DICOM DWI image may report: ``` (0018,9087) FD 1500 # 8, 1 DiffusionBValue diff --git a/Philips/README.md b/Philips/README.md index 428333f7..3ac7e353 100644 --- a/Philips/README.md +++ b/Philips/README.md @@ -148,7 +148,7 @@ The BIDS tag [PartialFourier](https://bids-specification.readthedocs.io/en/stabl Philips DICOMs do not allow one to determine the temporal order of volumes for diffusion (DWI, DTI) and arterial spin labelling (ASL) sequences. This can hinder tools that attempt to model motion motion or T1 effects. For ASL data, dcm2niix will attempt to store volumes in the order phase < control/label < repeat. [For ASL examples and a table describing the ordering hierarchy, see this dataset](https://github.com/neurolabusc/dcm_qa_philips_asl). For diffusion images, dcm2niix may order Philips volumes differently depending on if the data is stored as classic or enhanced DICOM. For enhanced DICOM, dcm2niix follows the hierarchy specified by DimensionIndexValues (0020,9157). On the other hand, for classic DICOMs, volumes with identical b-value index (2005,1412) will be stored sequentially, with ties sorted based on gradient direction number (2005,1413). [For diffusion examples, see series 22 and 23 from this dataset](https://github.com/neurolabusc/dcm_qa_philips_enh). These two different sorting methods may not necessarily sort data identically. -[Slice timing correction](https://www.mccauslandcenter.sc.edu/crnl/tools/stc) can account for some variability in fMRI datasets. Unfortunately, Philips DICOM data [does not explicitly encode slice timing information](https://neurostars.org/t/heudiconv-no-extraction-of-slice-timing-data-based-on-philips-dicoms/2201/4). Therefore, dcm2niix is unable to populate the "SliceTiming" BIDS field. However, the [philips_order.py script](https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/7) may be able to determine slice timing using an undocumented and unverified technique. Additionally, one can typically infer slice timing by recording the [mode and number of packages](https://en.wikibooks.org/w/index.php?title=SPM/Slice_Timing&stable=0#Philips_scanners) reported for the sequence on the scanner console or the [sequence PDF](http://adni.loni.usc.edu/wp-content/uploads/2017/09/ADNI-3-Basic-Philips-R5.pdf). For precise timing, it is also worth knowing if equidistant "temporal slice spacing" is set and whether "prospect. motion corr." is on or off (if on, a short delay occurs between volumes). +[Slice timing correction](https://www.mccauslandcenter.sc.edu/crnl/tools/stc) can account for some variability in fMRI datasets. Unfortunately, Philips DICOM data [does not explicitly encode slice timing information](https://neurostars.org/t/heudiconv-no-extraction-of-slice-timing-data-based-on-philips-dicoms/2201/4). Therefore, dcm2niix is unable to populate the "SliceTiming" BIDS field. Note, the [philips_order.py script](https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/7) may be able to determine slice timing using an undocumented and unverified technique that works in some instances but certainly fails with others. Additionally, one can typically infer slice timing by recording the [mode and number of packages](https://en.wikibooks.org/w/index.php?title=SPM/Slice_Timing&stable=0#Philips_scanners) reported for the sequence on the scanner console or the [sequence PDF](http://adni.loni.usc.edu/wp-content/uploads/2017/09/ADNI-3-Basic-Philips-R5.pdf). For precise timing, it is also worth knowing if equidistant "temporal slice spacing" is set and whether "prospect. motion corr." is on or off (if on, a short delay occurs between volumes). Likewise, the BIDS tag "PhaseEncodingDirection" allows tools like [eddy](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/eddy) and [TOPUP](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup) to undistort images. While the Philips DICOM header distinguishes the phase encoding axis (e.g. anterior-posterior vs left-right) it does not encode the polarity (A->P vs P->A). diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 8a6f3fea..27972743 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -1138,6 +1138,14 @@ double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], c } //dcmFloatDouble() #endif +// SS/IS datatype +int16_t dcmIntSS(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 16 or 32 bit integer + if (littleEndian) + return (uint16_t)lBuffer[0] | ((uint16_t)lBuffer[1] << 8); //shortint vs word? + return (uint16_t)lBuffer[1] | ((uint16_t)lBuffer[1] << 0); //shortint vs word? +} //dcmInt() + +//UL/US unsigned integer int dcmInt(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 16 or 32 bit integer if (littleEndian) { if (lByteLength <= 3) @@ -4166,7 +4174,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kDirectoryRecordSequence 0x0004 + (0x1220 << 16) //#define kSpecificCharacterSet 0x0008+(0x0005 << 16 ) //someday we should handle foreign characters... #define kImageTypeTag 0x0008 + (0x0008 << 16) -//#define kSOPInstanceUID 0x0008+(0x0018 << 16 ) //Philips inserts time as last item, e.g. ?.?.?.YYYYMMDDHHmmSS.SSSS +#define kSOPInstanceUID 0x0008+(0x0018 << 16 ) //Philips inserts time as last item, e.g. ?.?.?.YYYYMMDDHHmmSS.SSSS // not reliable https://neurostars.org/t/heudiconv-no-extraction-of-slice-timing-data-based-on-philips-dicoms/2201/21 #define kStudyDate 0x0008 + (0x0020 << 16) #define kAcquisitionDate 0x0008 + (0x0022 << 16) @@ -4557,6 +4565,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); float vRLPhilips = 0.0; float vAPPhilips = 0.0; float vFHPhilips = 0.0; + double acquisitionTimePhilips = -1.0; bool isPhase = false; bool isReal = false; bool isImaginary = false; @@ -4667,6 +4676,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //printf("slice %d---> 0020,9157 = %d %d %d\n", inStackPositionNumber, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2]); // d.aslFlags = kASL_FLAG_PHILIPS_LABEL; kASL_FLAG_PHILIPS_LABEL if ((nDimIndxVal > 1) && (volumeNumber > 0) && (inStackPositionNumber > 0) && ((d.aslFlags == kASL_FLAG_PHILIPS_LABEL) || (d.aslFlags == kASL_FLAG_PHILIPS_CONTROL))) { + isKludgeIssue533 = true; for (int i = 0; i < nDimIndxVal; i++) d.dimensionIndexValues[i] = 0; @@ -4679,6 +4689,11 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); nDimIndxVal = 4; //slice < phase < control/label < volume //printf("slice %d phase %d control/label %d repeat %d\n", inStackPositionNumber, d.phaseNumber, d.aslFlags == kASL_FLAG_PHILIPS_LABEL, volumeNumber); } + if ((volumeNumber == 1) && (acquisitionTimePhilips >= 0.0) && (inStackPositionNumber > 0)) { + d.CSA.sliceTiming[inStackPositionNumber - 1] = acquisitionTimePhilips; + printf("%d\t%f\n", inStackPositionNumber, acquisitionTimePhilips); + acquisitionTimePhilips = - 1.0; + } int ndim = nDimIndxVal; if (inStackPositionNumber > 0) { //for images without SliceNumberMrPhilips (2001,100A) @@ -5222,6 +5237,32 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); dcmStr(lLength, &buffer[lPos], acquisitionDateTimeTxt); //printMessage("%s\n",acquisitionDateTimeTxt); break; +/* Failed attempt to infer slice timing for Philips MRI +https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6 + + case kSOPInstanceUID: { + if (d.manufacturer != kMANUFACTURER_PHILIPS) break; + char uid[kDICOMStrLarge]; + dcmStr(lLength, &buffer[lPos], uid, true); + char *timeStr = strrchr(uid, '.'); + //nb Manufactuer (0008,0070) comes AFTER (0008,0018) SOPInstanceUID. + //format of (0008,0018) UI + //[1.23.4.2019051416101221842 + // .YYYYMMDDHHmmssxxxxx + timeStr++; //skip "." + if ((strlen(timeStr) != 19) || (strlen(d.studyDate) < 8)) break; + bool sameDay = true; + for (int z = 0; z < 8; z++) + if (timeStr[z] != d.studyDate[z]) sameDay = false; + if (!sameDay) + printf("SOPInstanceUID does not match StudyDate: assuming study cross midnight\n"); + char *hourStr = timeStr + 8; //Skip 8 charactersYear,Month,Day YYYYMMDD + acquisitionTimePhilips = (double) atof(hourStr) * (double) 0.00001; + //printf(" %s %s %f\n", timeStr, hourStr, acquisitionTimePhilips); + + break; + } +*/ case kStudyDate: dcmStr(lLength, &buffer[lPos], d.studyDate); if (((int)strlen(d.studyDate) > 7) && (strstr(d.studyDate, "19000101") != NULL)) @@ -6650,17 +6691,17 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); case kShimGradientX: if (d.manufacturer != kMANUFACTURER_GE) break; - d.shimGradientX = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + d.shimGradientX = dcmIntSS(lLength, &buffer[lPos], d.isLittleEndian); break; case kShimGradientY: if (d.manufacturer != kMANUFACTURER_GE) break; - d.shimGradientY = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + d.shimGradientY = dcmIntSS(lLength, &buffer[lPos], d.isLittleEndian); break; case kShimGradientZ: if (d.manufacturer != kMANUFACTURER_GE) break; - d.shimGradientZ = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + d.shimGradientZ = dcmIntSS(lLength, &buffer[lPos], d.isLittleEndian); break; case kPrescanReuseString: //LO if (d.manufacturer != kMANUFACTURER_GE) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 58486105..d91ab5cb 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -5705,6 +5705,8 @@ void checkSliceTiming(struct TDICOMdata *d, struct TDICOMdata *d1, int verbose, //modified 20190704: this function now ensures all slice times are in msec if ((d->TR < 0.0) || (d->CSA.sliceTiming[0] < 0.0)) return; //no slice timing + if (d->manufacturer == kMANUFACTURER_PHILIPS) + return; //Philips does not provide slice timing details in DICOM if (d->manufacturer == kMANUFACTURER_GE) return; //compute directly from Protocol Block if (d->modality == kMODALITY_PT) From 63c76f2603c56137d76f70b88c23af996cf8f2d9 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 12 Jul 2022 13:00:58 -0400 Subject: [PATCH 54/60] PostLabelDelay for XA30, FrameDuration is only for 4D datasets (https://github.com/rordenlab/dcm2niix/issues/616) --- BIDS/README.md | 1 + README.md | 29 +++++++++++++++++------------ console/nii_dicom.cpp | 6 ++++++ console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 7 +++++-- 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index b3815d87..eba4b81a 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -326,6 +326,7 @@ Fields specific to Siemens XA-series MRI systems (Sola, Vida). | ReceiveCoilActiveElements | | DICOM tag 0021,114F | B | | BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0021,1153 | D | | ScanningSequence | | DICOM tag 0021,105a | D | +| PostLabelDelay | s | DICOM tag 0018,9258 | D | ### Manufacturer UIH diff --git a/README.md b/README.md index 029006d4..39f46ffe 100644 --- a/README.md +++ b/README.md @@ -127,39 +127,41 @@ If you have any problems with the cmake build script described above or want to ## Links + - [Table of DICOM to BIDS converters](https://bids.neuroimaging.io/benefits#mri-and-pet-converterss) + The following tools exploit dcm2niix - [abcd-dicom2bids](https://github.com/DCAN-Labs/abcd-dicom2bids) selectively downloads high quality ABCD datasets. - [autobids](https://github.com/khanlab/autobids) automates dcm2bids which uses dcm2niix. - [BiDirect_BIDS_Converter](https://github.com/wulms/BiDirect_BIDS_Converter) for conversion from DICOM to the BIDS standard. - - [BIDScoin](https://github.com/Donders-Institute/bidscoin) is a DICOM to BIDS converter with a GUI and thorough [documentation](https://bidscoin.readthedocs.io). - [BIDS Toolbox](https://github.com/cardiff-brain-research-imaging-centre/bids-toolbox) is a web service for the creation and manipulation of BIDS datasets, using dcm2niix for importing DICOM data. - - [birc-bids](https://github.com/bircibrain/birc-bids) provides a Docker/Singularity container with various BIDS conversion utilities. - - [BOLD5000_autoencoder](https://github.com/nmningmei/BOLD5000_autoencoder) uses dcm2niix to pipe imaging data into an unsupervised machine learning algorithm. - - [brainnetome DiffusionKit](http://diffusion.brainnetome.org/en/latest/) uses dcm2niix to convert images. - - [Brain imAgiNg Analysis iN Arcana (Banana)](https://pypi.org/project/banana/) is a collection of brain imaging analysis workflows, it uses dcm2niix for format conversions. - - [BraTS-Preprocessor](https://neuronflow.github.io/BraTS-Preprocessor/) uses dcm2niix to import files for [Brain Tumor Segmentation](https://www.frontiersin.org/articles/10.3389/fnins.2020.00125/full). - - [clinica](https://github.com/aramis-lab/clinica) is a software platform for clinical neuroimaging studies that uses dcm2niix to convert DICOM images. + - [BIDScoin](https://github.com/Donders-Institute/bidscoin) is a DICOM to BIDS converter with a GUI and thorough [documentation](https://bidscoin.readthedocs.io). - [bidsconvertr](https://github.com/wulms/bidsconvertr) uses R to converts DICOM data to NIfTI and finally to BIDS. - [bidsify](https://github.com/spinoza-rec/bidsify) is a Python project that uses dcm2niix to convert DICOM and Philips PAR/REC images to the BIDS standard. - [bidskit](https://github.com/jmtyszka/bidskit) uses dcm2niix to create [BIDS](http://bids.neuroimaging.io/) datasets. - [BioImage Suite Web Project](https://github.com/bioimagesuiteweb/bisweb) is a JavaScript project that uses dcm2niix for its DICOM conversion module. + - [birc-bids](https://github.com/bircibrain/birc-bids) provides a Docker/Singularity container with various BIDS conversion utilities. + - [BOLD5000_autoencoder](https://github.com/nmningmei/BOLD5000_autoencoder) uses dcm2niix to pipe imaging data into an unsupervised machine learning algorithm. - [boutiques-dcm2niix](https://github.com/lalet/boutiques-dcm2niix) is a dockerfile for installing and validating dcm2niix. + - [Brain imAgiNg Analysis iN Arcana (Banana)](https://pypi.org/project/banana/) is a collection of brain imaging analysis workflows, it uses dcm2niix for format conversions. + - [brainnetome DiffusionKit](http://diffusion.brainnetome.org/en/latest/) uses dcm2niix to convert images. + - [BraTS-Preprocessor](https://neuronflow.github.io/BraTS-Preprocessor/) uses dcm2niix to import files for [Brain Tumor Segmentation](https://www.frontiersin.org/articles/10.3389/fnins.2020.00125/full). - [clinica](https://github.com/aramis-lab/clinica) is a software platform for clinical neuroimaging studies that uses dcm2niix to convert DICOM images. - [clpipe](https://github.com/cohenlabUNC/clpipe) uses dcm2bids for DICOM import. - [conversion](https://github.com/pnlbwh/conversion) is a Python library that can convert dcm2niix created NIfTI files to the popular NRRD format (including DWI gradient tables). Note, recent versions of dcm2niix can directly convert DICOM images to NRRD. - [DAC2BIDS](https://github.com/dangom/dac2bids) uses dcm2niibatch to create [BIDS](http://bids.neuroimaging.io/) datasets. + - [Data2Bids](https://github.com/SIMEXP/Data2Bids) converts non-DICOM images with associated JSON files to BIDS. While this tool does not require dcm2niix, it can leverage dcm2niix output similar to niix2bids. - [Dcm2Bids](https://github.com/cbedetti/Dcm2Bids) uses dcm2niix to create [BIDS](http://bids.neuroimaging.io/) datasets. Here is a [tutorial](https://andysbrainbook.readthedocs.io/en/latest/OpenScience/OS/BIDS_Overview.html) describing usage. - [dcm2niir](https://github.com/muschellij2/dcm2niir) R wrapper for dcm2niix/dcm2nii. - - [dcm2niixpy](https://github.com/Svdvoort/dcm2niixpy) Python package of dcm2niix. - [dcm2niix_afni](https://afni.nimh.nih.gov/pub/dist/doc/program_help/dcm2niix_afni.html) is a version of dcm2niix included with the [AFNI](https://afni.nimh.nih.gov/) distribution. - [dcm2niiXL](https://github.com/neurolabusc/dcm2niiXL) is a shell script and tuned compilation of dcm2niix designed for accelerated conversion of extra large datasets. + - [dcm2niixpy](https://github.com/Svdvoort/dcm2niixpy) Python package of dcm2niix. - [dcmwrangle](https://github.com/jbteves/dcmwrangle) a Python interactive and static tool for organizing dicoms. - [DeepDicomSort](https://github.com/Svdvoort/DeepDicomSort) can recognize different scan types. - - [DICOM2BIDS](https://github.com/klsea/DICOM2BIDS) is a Python 2 script for creating BIDS files. + - [DICOM-to-NIfTI-GUI](https://github.com/Zunairviqar/DICOM-to-NIfTI-GUI) is a Python script that provides a graphical wrapper for dcm2niix. - [dicom2bids](https://github.com/Jolinda/lcnimodules) includes python modules for converting dicom files to nifti in a bids-compatible file structure that use dcm2niix. + - [DICOM2BIDS](https://github.com/klsea/DICOM2BIDS) is a Python 2 script for creating BIDS files. - [dicom2nifti_batch](https://github.com/scanUCLA/dicom2nifti_batch) is a Matlab script for automating dcm2niix. - - [DICOM-to-NIfTI-GUI](https://github.com/Zunairviqar/DICOM-to-NIfTI-GUI) is a Python script that provides a graphical wrapper for dcm2niix. - [divest](https://github.com/jonclayden/divest) R interface to dcm2niix. - [ExploreASL](https://sites.google.com/view/exploreasl/exploreasl) uses dcm2niix to import images. - [ezBIDS](https://github.com/brainlife/ezbids) is a [web service](https://brainlife.io/ezbids/) for converting directory full of DICOM images into BIDS without users having to learn python nor custom configuration file. @@ -169,17 +171,19 @@ The following tools exploit dcm2niix - [fsleyes](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FSLeyes) is a powerful Python-based image viewer. It uses dcm2niix to handle DICOM files through its fslpy libraries. - [Functional Real-Time Interactive Endogenous Neuromodulation and Decoding (FRIEND) Engine](https://github.com/InstitutoDOr/FriendENGINE) uses dcm2niix. - [heudiconv](https://github.com/nipy/heudiconv) can use dcm2niix to create [BIDS](http://bids.neuroimaging.io/) datasets. Data acquired using the [reproin](https://github.com/ReproNim/reproin) convention can be easily converted to BIDS. + - [Horos (Osirix) Bids Output Extension](https://github.com/mslw/horos-bids-output) is a OsiriX / Horos plugin that uses dcm2niix for creating BIDS output. - [kipettools](https://github.com/mathesong/kipettools) uses dcm2niix to load PET data. - [LEAD-DBS](http://www.lead-dbs.org/) uses dcm2niix for [DICOM import](https://github.com/leaddbs/leaddbs/blob/master/ea_dicom_import.m). - [lin4neuro](http://www.lin4neuro.net/lin4neuro/18.04bionic/vm/) releases such as the English l4n-18.04.4-amd64-20200801-en.ova include MRIcroGL and dcm2niix pre-installed. This allows user with VirtualBox or VMWarePlayer to use these tools (and many other neuroimaging tools) in a graphical virtual machine. - [MRIcroGL](https://github.com/neurolabusc/MRIcroGL) is available for MacOS, Linux and Windows and provides a graphical interface for dcm2niix. You can get compiled copies from the [MRIcroGL NITRC web site](https://www.nitrc.org/projects/mricrogl/). - - [neurodocker](https://github.com/kaczmarj/neurodocker) includes dcm2niix as a lean, minimal install Dockerfile. - [neuro_docker](https://github.com/Neurita/neuro_docker) includes dcm2niix as part of a single, static Dockerfile. - [NeuroDebian](http://neuro.debian.net/pkgs/dcm2niix.html) provides up-to-date version of dcm2niix for Debian-based systems. - [neurodocker](https://github.com/kaczmarj/neurodocker) generates [custom](https://github.com/rordenlab/dcm2niix/issues/138) Dockerfiles given specific versions of neuroimaging software. + - [neurodocker](https://github.com/kaczmarj/neurodocker) includes dcm2niix as a lean, minimal install Dockerfile. - [NeuroElf](http://neuroelf.net) can use dcm2niix to convert DICOM images. - [Neuroinformatics Database (NiDB)](https://github.com/gbook/nidb) is designed to store, retrieve, analyze, and share neuroimaging data. It uses dcm2niix for image QA and handling some formats. - [NiftyPET](https://niftypet.readthedocs.io/en/latest/install.html) provides PET image reconstruction and analysis, and uses dcm2niix to handle DICOM images. + - [niix2bids](https://github.com/benoitberanger/niix2bids ) attempts to automatically convert Siemens MRI images converted by dcm2niix to BIDS. - [nipype](https://github.com/nipy/nipype) can use dcm2niix to convert images. - [PET2BIDS](https://github.com/openneuropet/PET2BIDS) uses dcm2niix for DICOM images. - [py2bids](https://github.com/Jolinda/py2bids) dcm2niix dicom to bids conversion wrapper. @@ -191,7 +195,8 @@ The following tools exploit dcm2niix - [Retina_OCT_dcm2nii](https://github.com/Choupan/Retina_OCT_dcm2nii) converts optical coherence tomography (OCT) data to NIfTI. - [sci-tran dcm2niix](https://github.com/scitran-apps/dcm2niix) Flywheel Gear (docker). - [shimming-toolbox](https://github.com/shimming-toolbox/shimming-toolbox) enabled static and real-time shimming, using dcm2niix to import DICOM data. - - The [SlicerDcm2nii extension](https://github.com/Slicer/ExtensionsIndex/blob/master/SlicerDcm2nii.s4ext) is one method to import DICOM data into Slicer. + - [SlicerDcm2nii extension](https://github.com/Slicer/ExtensionsIndex/blob/master/SlicerDcm2nii.s4ext) is one method to import DICOM data into Slicer. - [tar2bids](https://github.com/khanlab/tar2bids) converts DICOM tarball(s) to BIDS using heudiconv which invokes dcm2niix. - [TORTOISE](https://tortoise.nibib.nih.gov) is used for processing diffusion MRI data, and uses dcm2niix to import DICOM images. - [TractoR (Tracto­graphy with R) uses dcm2niix for image conversion](http://www.tractor-mri.org.uk/TractoR-and-DICOM). + - [XNAT2BIDS](https://github.com/kamillipi/2bids) is a simple xnat pipeline to convert DICOM scans to BIDS-compatible output. diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 27972743..c57a2198 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -786,6 +786,7 @@ struct TDICOMdata clear_dicom_data() { d.echoTrainLength = 0; d.waterFatShift = 0.0; d.groupDelay = 0.0; + d.postLabelDelay = 0; d.shimGradientX = -33333;//impossible value for UINT16 d.shimGradientY = -33333;//impossible value for UINT16 d.shimGradientZ = -33333;//impossible value for UINT16 @@ -4270,6 +4271,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kImagingFrequency2 0x0018 + uint32_t(0x9098 << 16) //FD #define kParallelReductionFactorOutOfPlane 0x0018 + uint32_t(0x9155 << 16) //FD //#define kFrameAcquisitionDuration 0x0018+uint32_t(0x9220 << 16 ) //FD +#define kASLPulseTrainDuration 0x0018 + uint32_t(0x9258 << 16) //UL #define kDiffusionBValueXX 0x0018 + uint32_t(0x9602 << 16) //FD #define kDiffusionBValueXY 0x0018 + uint32_t(0x9603 << 16) //FD #define kDiffusionBValueXZ 0x0018 + uint32_t(0x9604 << 16) //FD @@ -6491,6 +6493,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //case kFrameAcquisitionDuration : // frameAcquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); //issue369 // break; + case kASLPulseTrainDuration: { + d.postLabelDelay = dcmInt(4, &buffer[lPos], d.isLittleEndian); + break; + } case kDiffusionBValueXX: { if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 4ff5bd0e..2deaf240 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -222,7 +222,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; int xyzDim[5]; uint32_t coilCrc, seriesUidCrc, instanceUidCrc; int overlayStart[kMaxOverlay]; - int shimGradientX, shimGradientY, shimGradientZ, phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; + int postLabelDelay, shimGradientX, shimGradientY, shimGradientZ, phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; float xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index d91ab5cb..d7e21dd3 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1392,7 +1392,7 @@ tse3d: T2*/ } fprintf(fp, "\t],\n"); } - if (dti4D->frameDuration[0] >= 0.0) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 + if ((h->dim[4] > 1) && (dti4D->frameDuration[0] >= 0.0)) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 fprintf(fp, "\t\"FrameDuration\": [\n"); for (int i = 0; i < h->dim[4]; i++) { if (i != 0) @@ -1778,6 +1778,9 @@ tse3d: T2*/ json_Float(fp, "\t\"InitialPostLabelDelay\": %g,\n", dti4D->triggerDelayTime[0] / 1000.0); } */ + //generic public ASL tags + if (d.postLabelDelay > 0) + json_Float(fp, "\t\"PostLabelDelay\": %g,\n", float(d.postLabelDelay) / 1000.0); //GE ASL specific tags if (d.aslFlags & kASL_FLAG_GE_CONTINUOUS) fprintf(fp, "\t\"ASLContrastTechnique\": \"CONTINUOUS\",\n"); @@ -1789,7 +1792,7 @@ tse3d: T2*/ fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D continuous ASL technique\",\n"); if (d.durationLabelPulseGE > 0) { json_Float(fp, "\t\"LabelingDuration\": %g,\n", d.durationLabelPulseGE / 1000.0); - json_Float(fp, "\t\"PostLabelingDelay\": %g,\n", d.TI / 1000.0); //For GE ASL: InversionTime -> Post-label delay + json_Float(fp, "\t\"PostLabelDelay\": %g,\n", d.TI / 1000.0); //For GE ASL: InversionTime -> Post-label delay json_Float(fp, "\t\"NumberOfPointsPerArm\": %g,\n", d.numberOfPointsPerArm); json_Float(fp, "\t\"NumberOfArms\": %g,\n", d.numberOfArms); } From 3583fcc80a0c01b6b3f555184c34e3bcc9e9ec6f Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 13 Jul 2022 08:31:52 -0400 Subject: [PATCH 55/60] Reset PET values for classic DICOMs (https://github.com/rordenlab/dcm2niix/issues/616) --- console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 2deaf240..4fb9b5f6 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20220606" +#define kDCMdate "v1.0.20220707" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index d7e21dd3..13b9ebfd 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1381,7 +1381,7 @@ tse3d: T2*/ } fprintf(fp, "\t],\n"); } - if (dti4D->volumeOnsetTime[0] >= 0.0) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 + if ((h->dim[4] > 1) && (dti4D->volumeOnsetTime[0] >= 0.0)) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 fprintf(fp, "\t\"FrameTimesStart\": [\n"); for (int i = 0; i < h->dim[4]; i++) { if (i != 0) @@ -6430,6 +6430,12 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d uint64_t indxEnd = dcmSort[nConvert - 1].indx; dti4D->repetitionTimeInversion = 0.0; //only set for Siemens and GE 3D T1 "TR" dti4D->repetitionTimeExcitation = 0.0; //only set for Philips 3D T1 "TR" + if (nConvert > 0) { //issue 616: not enhanced DICOMs: infer these arrays from multiple volumes + dti4D->volumeOnsetTime[0] = -1; + dti4D->decayFactor[0] = -1; + dti4D->frameDuration[0] = -1; + dti4D->frameReferenceTime[0] = -1; + } #ifdef newTilt //see issue 254 if (((nConvert > 1) || (dcmList[indx0].xyzDim[3] > 1)) && ((dcmList[indx0].modality == kMODALITY_CT) || (dcmList[indx0].isXRay) || (dcmList[indx0].gantryTilt > 0.0))) { //issue372: enhanced DICOMs can also have gantry tilt dcmList[indx0].gantryTilt = computeGantryTiltPrecise(dcmList[indx0], dcmList[indxEnd], opts.isVerbose); From 4f8907390e15f0eef705eac0d1bc1422ef037d51 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 19 Jul 2022 18:00:43 -0400 Subject: [PATCH 56/60] Siemens XA30 ASL parameters and ImageTypeText 0021,1175 --- console/nii_dicom.cpp | 27 +++++++++++++++++++++++ console/nii_dicom.h | 5 +++-- console/nii_dicom_batch.cpp | 43 ++++++++++++++++++++++++++++--------- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index c57a2198..d663de2a 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -712,6 +712,7 @@ struct TDICOMdata clear_dicom_data() { strcpy(d.patientID, ""); strcpy(d.accessionNumber, ""); strcpy(d.imageType, ""); + strcpy(d.imageTypeText, ""); strcpy(d.imageComments, ""); strcpy(d.imageBaseName, ""); strcpy(d.phaseEncodingDirectionDisplayedUIH, ""); @@ -4271,6 +4272,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kImagingFrequency2 0x0018 + uint32_t(0x9098 << 16) //FD #define kParallelReductionFactorOutOfPlane 0x0018 + uint32_t(0x9155 << 16) //FD //#define kFrameAcquisitionDuration 0x0018+uint32_t(0x9220 << 16 ) //FD +#define kArterialSpinLabelingContrast 0x0018 + uint32_t(0x9250 << 16) //CS #define kASLPulseTrainDuration 0x0018 + uint32_t(0x9258 << 16) //UL #define kDiffusionBValueXX 0x0018 + uint32_t(0x9602 << 16) //FD #define kDiffusionBValueXY 0x0018 + uint32_t(0x9603 << 16) //FD @@ -4333,6 +4335,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kBandwidthPerPixelPhaseEncode21 0x0021 + (0x1153 << 16) //FD #define kCoilElements 0x0021 + (0x114F << 16) //LO #define kAcquisitionMatrixText21 0x0021 + (0x1158 << 16) //SH +#define kImageTypeText 0x0021 + (0x1175 << 16) //CS //Private Group 21 as used by GE: #define kLocationsInAcquisitionGE 0x0021 + (0x104F << 16) //SS 'LocationsInAcquisitionGE' #define kRTIA_timer 0x0021 + (0x105E << 16) //DS @@ -6206,6 +6209,18 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); d.xyzMM[3] = dcmStrFloat(lLength, &buffer[lPos]); d.zThick = d.xyzMM[3]; break; + case kImageTypeText: { + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + dcmStr(lLength, &buffer[lPos], d.imageTypeText); + int slen = (int)strlen(d.imageTypeText); + if (slen > 1) { + for (int i = 0; i < slen; i++) + if (d.imageTypeText[i] == '\\') + d.imageTypeText[i] = '_'; + } + break; + } case kAcquisitionMatrixText21: //fall through to kAcquisitionMatrixText case kAcquisitionMatrixText: { @@ -6493,6 +6508,18 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //case kFrameAcquisitionDuration : // frameAcquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); //issue369 // break; + case kArterialSpinLabelingContrast: { //CS + char st[kDICOMStr]; + //aslFlags + dcmStr(lLength, &buffer[lPos], st); + if (strstr(st, "PSEUDOCONTINUOUS") != NULL) + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_PSEUDOCONTINUOUS); + else if (strstr(st, "CONTINUOUS") != NULL) + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_CONTINUOUS); + else if (strstr(st, "PULSED") != NULL) + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_PULSED); + break; + } case kASLPulseTrainDuration: { d.postLabelDelay = dcmInt(4, &buffer[lPos], d.isLittleEndian); break; diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 4fb9b5f6..dbdfb139 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20220707" +#define kDCMdate "v1.0.20220717" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic @@ -133,6 +133,7 @@ static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI dire #define kASL_FLAG_GE_CONTINUOUS 8 #define kASL_FLAG_PHILIPS_CONTROL 16 #define kASL_FLAG_PHILIPS_LABEL 32 +#define kASL_FLAG_GE_PULSED 64 //for spoiling 0018,9016 @@ -230,7 +231,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. double acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr]; - char prescanReuseString[kDICOMStr], imageOrientationText[kDICOMStr], pulseSequenceName[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[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]; + char prescanReuseString[kDICOMStr], imageOrientationText[kDICOMStr], pulseSequenceName[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], stationName[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]; char scanOptions[kDICOMStrLarge], institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge]; uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; struct TCSAdata CSA; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 13b9ebfd..f8dadbbd 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -652,12 +652,14 @@ typedef struct { float alFree[kMaxWipFree]; float adFree[kMaxWipFree]; float alTI[kMaxWipFree]; - float dAveragesDouble, dThickness, ulShape, sPositionDTra, sNormalDTra; + float sPostLabelingDelay, ulLabelingDuration, dAveragesDouble, dThickness, ulShape, sPositionDTra, sNormalDTra; } TCsaAscii; void siemensCsaAscii(const char *filename, TCsaAscii *csaAscii, int csaOffset, int csaLength, float *shimSetting, char *coilID, char *consistencyInfo, char *coilElements, char *pulseSequenceDetails, char *fmriExternalInfo, char *protocolName, char *wipMemBlock) { //reads ASCII portion of CSASeriesHeaderInfo and returns lEchoTrainDuration or lEchoSpacing value // returns 0 if no value found + csaAscii->sPostLabelingDelay = 0.0; + csaAscii->ulLabelingDuration = 0.0; csaAscii->TE0 = 0.0; csaAscii->TE1 = 0.0; csaAscii->delayTimeInTR = -0.001; @@ -786,6 +788,10 @@ void siemensCsaAscii(const char *filename, TCsaAscii *csaAscii, int csaOffset, i csaAscii->TE0 = readKeyFloatNan(keyStrTE0, keyPos, csaLengthTrim); char keyStrTE1[] = "alTE[1]"; csaAscii->TE1 = readKeyFloatNan(keyStrTE1, keyPos, csaLengthTrim); + char keyStrPLD[] = "sAsl.sPostLabelingDelay[0]"; + csaAscii->sPostLabelingDelay = readKeyFloatNan(keyStrPLD, keyPos, csaLengthTrim); + char keyStrLD[] = "sAsl.ulLabelingDuration"; + csaAscii->ulLabelingDuration = readKeyFloatNan(keyStrLD, keyPos, csaLengthTrim); //read ALL alTI[*] values for (int k = 0; k < kMaxWipFree; k++) csaAscii->alTI[k] = NAN; @@ -1306,6 +1312,20 @@ tse3d: T2*/ fprintf(fp, "\", \"FIELDMAPHZ"); fprintf(fp, "\"],\n"); } + if (strlen(d.imageTypeText) > 0) { + fprintf(fp, "\t\"ImageTypeText\": [\""); + bool isSep = false; + for (size_t i = 0; i < strlen(d.imageTypeText); i++) { + if (d.imageTypeText[i] != '_') { + if (isSep) + fprintf(fp, "\", \""); + isSep = false; + fprintf(fp, "%c", d.imageTypeText[i]); + } else + isSep = true; + } + fprintf(fp, "\"],\n"); + } if (d.isDerived) //DICOM is derived image or non-spatial file (sounds, etc) fprintf(fp, "\t\"RawImage\": false,\n"); if (d.seriesNum > 0) @@ -1518,6 +1538,11 @@ tse3d: T2*/ bool isPCASL = false; bool isPASL = false; //ASL specific tags - 2D pCASL Danny J.J. Wang http://www.loft-lab.org + //ASL specific tags - 2D PASL Siemens Product XA30, n.b pasl/casl/pcasl set using 0018,9250 + if (strstr(pulseSequenceDetails, "ep2d_asl")) { + json_FloatNotNan(fp, "\t\"LabelingDuration\": %g,\n", csaAscii.ulLabelingDuration * (1.0 / 1000000.0)); //usec->sec + json_FloatNotNan(fp, "\t\"PostLabelingDelay\": %g,\n", csaAscii.sPostLabelingDelay * (1.0 / 1000000.0)); //usec -> sec + } if ((strstr(pulseSequenceDetails, "ep2d_pcasl")) || (strstr(pulseSequenceDetails, "ep2d_pcasl_UI_PHC"))) { isPCASL = true; repetitionTimePreparation = d.TR; @@ -1781,15 +1806,13 @@ tse3d: T2*/ //generic public ASL tags if (d.postLabelDelay > 0) json_Float(fp, "\t\"PostLabelDelay\": %g,\n", float(d.postLabelDelay) / 1000.0); - //GE ASL specific tags - if (d.aslFlags & kASL_FLAG_GE_CONTINUOUS) - fprintf(fp, "\t\"ASLContrastTechnique\": \"CONTINUOUS\",\n"); - if (d.aslFlags & kASL_FLAG_GE_PSEUDOCONTINUOUS) - fprintf(fp, "\t\"ASLContrastTechnique\": \"PSEUDOCONTINUOUS\",\n"); - if (d.aslFlags & kASL_FLAG_GE_3DPCASL) - fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D pulsed continuous ASL technique\",\n"); - if (d.aslFlags & kASL_FLAG_GE_3DCASL) - fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D continuous ASL technique\",\n"); + //ASL BIDS tags + if ((d.aslFlags & kASL_FLAG_GE_CONTINUOUS) || (d.aslFlags & kASL_FLAG_GE_3DCASL)) + fprintf(fp, "\t\"ArterialSpinLabelingType\": \"CASL\",\n"); + if ((d.aslFlags & kASL_FLAG_GE_PSEUDOCONTINUOUS) || (d.aslFlags & kASL_FLAG_GE_3DPCASL)) + fprintf(fp, "\t\"ArterialSpinLabelingType\": \"PCASL\",\n"); + if (d.aslFlags & kASL_FLAG_GE_PULSED) + fprintf(fp, "\t\"ArterialSpinLabelingType\": \"PASL\",\n"); if (d.durationLabelPulseGE > 0) { json_Float(fp, "\t\"LabelingDuration\": %g,\n", d.durationLabelPulseGE / 1000.0); json_Float(fp, "\t\"PostLabelDelay\": %g,\n", d.TI / 1000.0); //For GE ASL: InversionTime -> Post-label delay From 26a74a553d8b34085aaed6755aa2f8158918408a Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 20 Jul 2022 06:55:17 -0400 Subject: [PATCH 57/60] Update notes --- Siemens/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Siemens/README.md b/Siemens/README.md index 4738c3a9..0ad9a333 100644 --- a/Siemens/README.md +++ b/Siemens/README.md @@ -80,6 +80,10 @@ Tools like [ExploreASL](https://sites.google.com/view/exploreasl) and [FSL BASIL The Siemens CSA header also stores some ASL details as a base64 stream. These can be read using [gdcmdump](http://gdcm.sourceforge.net/wiki/index.php/Gdcmdump), e.g. `gdcmdump -i i001.dcm --csa-asl --print` +## Nonlinear Gradient Correction + +dcm2niix does not populate the recommended [NonlinearGradientCorrection](https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#sequence-specifics) BIDS tag. dcm2niix does save the DICOM [Image Type (0008,0008)](https://dicom.innolitics.com/ciods/rt-dose/general-image/00080008) tag as `ImageType`, and recent versions will also export a private tag (0021,1175) as `ImageTypeText`. The inclusion of `DIS2D` or `DIS3D` in these one of these fields (the former prior to XA30, the latter with XA30 and later) is consistent with `NonlinearGradientCorrection` being `True`. See [issue 597](https://github.com/rordenlab/dcm2niix/issues/597) for further details. + ## Sample Datasets - [Slice timing dataset](httphttps://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Slice_timing_corrections://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage). From 160aaa2213e7e7814c499cdb913ea73e0363e46a Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 20 Jul 2022 07:32:20 -0400 Subject: [PATCH 58/60] Issue 618 (https://github.com/rordenlab/dcm2niix/issues/618) --- BIDS/README.md | 15 +++++++++------ Siemens/README.md | 2 +- console/nii_dicom_batch.cpp | 7 +++++++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index eba4b81a..d018fa5d 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -35,6 +35,7 @@ The Defined By column uses: The Unit column uses: +- b : boolean - deg : degrees - f : fraction - kg : Kilogram @@ -316,17 +317,19 @@ Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio | DwellTime | | DICOM tag 0019,1018 | B | | BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0019,1028 | D | | ImageOrientationText | | DICOM tag 0051,100E | D | +| NonlinearGradientCorrection | b | DICOM tag 0008,0008 | B | ### Manufacturer Siemens Magnetic Resonance Imaging (XA) Fields specific to Siemens XA-series MRI systems (Sola, Vida). -| Field | Unit | Comments | Defined By | -|------------------------------|------|---------------------|------------| -| ReceiveCoilActiveElements | | DICOM tag 0021,114F | B | -| BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0021,1153 | D | -| ScanningSequence | | DICOM tag 0021,105a | D | -| PostLabelDelay | s | DICOM tag 0018,9258 | D | +| Field | Unit | Comments | Defined By | +|------------------------------|------|------------------------|------------| +| ReceiveCoilActiveElements | | DICOM tag 0021,114F | B | +| BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0021,1153 | D | +| ScanningSequence | | DICOM tag 0021,105a | D | +| PostLabelDelay | s | DICOM tag 0018,9258 | D | +| NonlinearGradientCorrection | b | 0008,0008 or 0021,1175 | B | ### Manufacturer UIH diff --git a/Siemens/README.md b/Siemens/README.md index 0ad9a333..d387031e 100644 --- a/Siemens/README.md +++ b/Siemens/README.md @@ -82,7 +82,7 @@ The Siemens CSA header also stores some ASL details as a base64 stream. These ca ## Nonlinear Gradient Correction -dcm2niix does not populate the recommended [NonlinearGradientCorrection](https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#sequence-specifics) BIDS tag. dcm2niix does save the DICOM [Image Type (0008,0008)](https://dicom.innolitics.com/ciods/rt-dose/general-image/00080008) tag as `ImageType`, and recent versions will also export a private tag (0021,1175) as `ImageTypeText`. The inclusion of `DIS2D` or `DIS3D` in these one of these fields (the former prior to XA30, the latter with XA30 and later) is consistent with `NonlinearGradientCorrection` being `True`. See [issue 597](https://github.com/rordenlab/dcm2niix/issues/597) for further details. +dcm2niix does not populate the recommended [NonlinearGradientCorrection](https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#sequence-specifics) BIDS tag. dcm2niix does save the DICOM [Image Type (0008,0008)](https://dicom.innolitics.com/ciods/rt-dose/general-image/00080008) tag as `ImageType`, and recent versions will also export a private tag (0021,1175) as `ImageTypeText`. The inclusion of `DIS2D` or `DIS3D` in these one of these fields (the former prior to XA30, the latter with XA30 and later) is consistent with `NonlinearGradientCorrection` being `true` while `ND` suggests `false`. See [issue 597](https://github.com/rordenlab/dcm2niix/issues/597) for further details. ## Sample Datasets diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index f8dadbbd..83af5878 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1326,6 +1326,13 @@ tse3d: T2*/ } fprintf(fp, "\"],\n"); } + if ((strstr(d.imageTypeText, "_DIS2D") != NULL) || (strstr(d.imageType, "_DIS2D") != NULL) || + (strstr(d.imageTypeText, "_DIS3D") != NULL) || (strstr(d.imageType, "_DIS3D") != NULL)) + fprintf(fp, "\t\"NonlinearGradientCorrection\": true,\n"); + if ((strstr(d.imageTypeText, "_ND") != NULL) || (strstr(d.imageType, "_ND") != NULL) || + (strstr(d.imageTypeText, "_ND") != NULL) || (strstr(d.imageType, "_ND") != NULL)) + fprintf(fp, "\t\"NonlinearGradientCorrection\": false,\n"); + if (d.isDerived) //DICOM is derived image or non-spatial file (sounds, etc) fprintf(fp, "\t\"RawImage\": false,\n"); if (d.seriesNum > 0) From 625a24788fe1fac745aafc6f07906c34159037db Mon Sep 17 00:00:00 2001 From: Jaemin Shin Date: Wed, 20 Jul 2022 15:55:45 -0400 Subject: [PATCH 59/60] GE Direct field mapping (TE1/TE2) (#617) --- console/nii_dicom.cpp | 8 ++++++++ console/nii_dicom.h | 4 ++-- console/nii_dicom_batch.cpp | 11 +++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index d663de2a..9c03232d 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -861,6 +861,7 @@ struct TDICOMdata clear_dicom_data() { d.isLittleEndian = true; //DICOM initially always little endian d.converted2NII = 0; d.numberOfDiffusionDirectionGE = -1; + d.velocityEncodeScaleGE = 1.0; d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNKNOWN; d.rtia_timerGE = -1.0; d.rawDataRunNumber = -1; @@ -4301,6 +4302,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kDiffusionDirectionGEY 0x0019 + (0x10BC << 16) //DS frequency diffusion direction #define kDiffusionDirectionGEZ 0x0019 + (0x10BD << 16) //DS slice diffusion direction #define kNumberOfDiffusionDirectionGE 0x0019 + (0x10E0 << 16) ///DS NumberOfDiffusionDirection:UserData24 +#define kVelocityEncodeScaleGE 0x0019 + (0x10E2 << 16) ///DS Velocity Encode Scale #define kStudyID 0x0020 + (0x0010 << 16) #define kSeriesNum 0x0020 + (0x0011 << 16) #define kAcquNum 0x0020 + (0x0012 << 16) @@ -5633,6 +5635,12 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); d.numberOfDiffusionDirectionGE = round(f); break; } + case kVelocityEncodeScaleGE: { + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.velocityEncodeScaleGE = dcmStrFloat(lLength, &buffer[lPos]); + break; + } case kLastScanLoc: d.lastScanLoc = dcmStrFloat(lLength, &buffer[lPos]); break; diff --git a/console/nii_dicom.h b/console/nii_dicom.h index dbdfb139..2458f75a 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20220717" +#define kDCMdate "v1.0.20220720" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic @@ -224,7 +224,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; uint32_t coilCrc, seriesUidCrc, instanceUidCrc; int overlayStart[kMaxOverlay]; int postLabelDelay, shimGradientX, shimGradientY, shimGradientZ, phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; - float xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; + float xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4], velocityEncodeScaleGE; float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) float frameReferenceTime, frameDuration, ecat_isotope_halflife, ecat_dosage; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 83af5878..6e233315 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1466,8 +1466,15 @@ tse3d: T2*/ json_Float(fp, "\t\"NumberOfAverages\": %g,\n", d.numberOfAverages); if ((d.echoNum > 1) || ((d.isMultiEcho) && (d.echoNum > 0))) fprintf(fp, "\t\"EchoNumber\": %d,\n", d.echoNum); - if ((d.TE > 0.0) && (!d.isXRay)) - fprintf(fp, "\t\"EchoTime\": %g,\n", d.TE / 1000.0); + if ((d.TE > 0.0) && (!d.isXRay)) { + if ((d.manufacturer == kMANUFACTURER_GE) && (d.isRealIsPhaseMapHz) && (d.velocityEncodeScaleGE < 0)) { //issue617, only set for GE fieldmaphz + json_Float(fp, "\t\"EchoTime1\": %g,\n", d.TE / 1000.0 ); + json_Float(fp, "\t\"EchoTime2\": %g,\n", d.TE / 1000.0 - 1/(2 * M_PI * d.velocityEncodeScaleGE)); + // delta TE = -1/(2 * pi * velocityEncodeScaleGE) + } + else + fprintf(fp, "\t\"EchoTime\": %g,\n", d.TE / 1000.0); + } //if ((d.TE2 > 0.0) && (!d.isXRay)) fprintf(fp, "\t\"EchoTime2\": %g,\n", d.TE2 / 1000.0 ); if (dti4D->frameDuration[0] < 0.0) //e.g. PET scans can have variable TR json_Float(fp, "\t\"RepetitionTime\": %g,\n", d.TR / 1000.0); From cfa4b98b4d8247ff84858c2182611976acd301a7 Mon Sep 17 00:00:00 2001 From: Jaemin Shin Date: Wed, 20 Jul 2022 16:36:21 -0400 Subject: [PATCH 60/60] GE Direct field mapping (TE1/TE2) (#617) --- console/nii_dicom_batch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 6e233315..5cfb3ad9 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1469,7 +1469,7 @@ tse3d: T2*/ if ((d.TE > 0.0) && (!d.isXRay)) { if ((d.manufacturer == kMANUFACTURER_GE) && (d.isRealIsPhaseMapHz) && (d.velocityEncodeScaleGE < 0)) { //issue617, only set for GE fieldmaphz json_Float(fp, "\t\"EchoTime1\": %g,\n", d.TE / 1000.0 ); - json_Float(fp, "\t\"EchoTime2\": %g,\n", d.TE / 1000.0 - 1/(2 * M_PI * d.velocityEncodeScaleGE)); + json_Float(fp, "\t\"EchoTime2\": %g,\n", d.TE / 1000.0 - 1.0/(2.0 * M_PI * d.velocityEncodeScaleGE)); // delta TE = -1/(2 * pi * velocityEncodeScaleGE) } else