diff --git a/BATCH.md b/BATCH.md new file mode 100644 index 00000000..e3c6809b --- /dev/null +++ b/BATCH.md @@ -0,0 +1,28 @@ +**Optional batch processing version:** + +Perform a batch conversion of multiple dicoms using the configurations specified in a yaml file. +```bash +dcm2niibatch batch_config.yml +``` + +The configuration file should be in yaml format as shown in example `batch_config.yaml` + +```yaml +Options: + isGz: false + isFlipY: false + isVerbose: false + isCreateBIDS: false + isOnlySingleFile: false +Files: + - + in_dir: /path/to/first/folder + out_dir: /path/to/output/folder + filename: dcemri + - + in_dir: /path/to/second/folder + out_dir: /path/to/output/folder + filename: fa3 +``` + +You can add as many files as you want to convert as long as this structure stays consistent. Note that a dash must separate each file. diff --git a/COMPILE.md b/COMPILE.md index c5c8e06e..a1b8946c 100644 --- a/COMPILE.md +++ b/COMPILE.md @@ -6,7 +6,6 @@ The README.md file describes the typical compilation of the software, using the The text below generally describes how to build dcm2niix using the [GCC](https://gcc.gnu.org) compiler using the `g++` command. However, the code is portable and you can use different compilers. For [clang/llvm](https://clang.llvm.org) compile using `clang++`. If you have the [Intel C compiler](https://software.intel.com/en-us/c-compilers), you can substitute the `icc` command. For [Microsoft's C compiler](http://landinghub.visualstudio.com/visual-cpp-build-tools) you would use the `cl` command. In theory, the code should support other compilers, but this has not been tested. Be aware that if you do not have gcc installed the `g++` command may use a default to a compiler (e.g. clang). To check what compiler was used, run the dcm2niix software: it always reports the version and the compiler used for the build. - ## Building the command line version without cmake You can also build the software without C-make. The easiest way to do this is to run the function "make" from the "console" folder. Note that this only creates the default version of dcm2niix, not the optional batch version described above. The make command simply calls the g++ compiler, and if you want you can tune this for your build. In essence, the make function simply calls @@ -17,6 +16,10 @@ g++ -dead_strip -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp ni The following sub-sections list how you can modify this basic recipe for your needs. +## Trouble Shooting + +Some [Centos/Redhat](https://github.com/rordenlab/dcm2niix/issues/137) may report "/usr/bin/ld: cannot find -lstdc++". This can be resolved by installing static versions of libstd: `yum install libstdc++-static`. + ##### ZLIB BUILD If we have zlib, we can use it (-lz) and disable [miniz](https://code.google.com/p/miniz/) (-myDisableMiniZ) diff --git a/README.md b/README.md index 3411e16a..97eb97b9 100644 --- a/README.md +++ b/README.md @@ -11,153 +11,17 @@ This software is open source. The bulk of the code is covered by the BSD license ## Dependencies -This software should run on macOS, Linux and Windows without requiring any other software. However, if you use dcm2niix to create gz-compressed images it will be faster if you have [pigz](https://github.com/madler/pigz) installed. You can get a version of both dcm2niix and pigz compiled for your operating system by downloading [MRIcroGL](https://www.nitrc.org/projects/mricrogl/). +This software should run on macOS, Linux and Windows typically without requiring any other software. However, if you use dcm2niix to create gz-compressed images it will be faster if you have [pigz](https://github.com/madler/pigz) installed. You can get a version of both dcm2niix and pigz compiled for your operating system by downloading [MRIcroGL](https://www.nitrc.org/projects/mricrogl/). ## Versions -23-Sept-2017 - - Swap [phase-encoding direction polarity](https://github.com/rordenlab/dcm2niix/issues/125) for Siemens images where PE is in the Column direction. - - Sort diffusion volumes by [B-value amplitude](https://www.nitrc.org/forum/forum.php?thread_id=8396&forum_id=4703) (use "-d n"/"-d y" to turn the feature off/on). - - BIDS tag [TotalReadoutTime](https://github.com/rordenlab/dcm2niix/issues/130) handles partial fourier, Phase Resolution, etc (Michael Harms). - - Additional [json fields](https://github.com/rordenlab/dcm2niix/issues/127). - -18-Aug-2017 - - Better BVec extraction for [PAR/REC 4.1](https://www.nitrc.org/forum/forum.php?thread_id=8387&forum_id=4703). - - Support for [Segami Cerescan volumes](https://www.nitrc.org/forum/forum.php?thread_id=8076&forum_id=4703). - -24-July-2017 - - Compiles with recent releases of [OpenJPEG](https://github.com/neurolabusc/dcm_qa/issues/5#issuecomment-317443179) for JPEG2000 support. - -23-June-2017 - - [Ensure slice timing always encoded for Siemens EPI](https://github.com/neurolabusc/dcm_qa/issues/4#issuecomment-310707906) - - [Integrates validation](https://github.com/neurolabusc/dcm_qa) - - JSON fix (InstitutionName -> InstitutionAddress) - -21-June-2017 - - Read DICOM header in 1Mb segments rather than loading whole file : reduces ram usage and [faster for systems with slow io](https://github.com/rordenlab/dcm2niix/issues/104). - - Report [TotalReadoutTime](https://github.com/rordenlab/dcm2niix/issues/98). - - Fix JPEG2000 support in [Superbuild](https://github.com/rordenlab/dcm2niix/issues/105). - -28-May-2017 - - Remove all derived images from [Philips DTI series](http://www.nitrc.org/forum/message.php?msg_id=21025). - - Provide some [Siemens EPI sequence details](https://github.com/rordenlab/dcm2niix/issues). - -28-April-2017 - - Experimental [ECAT support](https://github.com/rordenlab/dcm2niix/issues/95). - - Updated cmake to make JPEG2000 support easier with improved Travis and AppVeyor support [Ningfei Li](https://github.com/ningfei). - - Supports Data/Time for images that report Data/Time (0008,002A) but not separate Date and Time (0008,0022 and 0008,0032). - - [BIDS reports morning times correctly](http://www.nitrc.org/forum/message.php?msg_id=20852). - - Options -1..-9 to control [gz compression level](https://github.com/rordenlab/dcm2niix/issues/90). - - Includes some [PET details in the BIDS JSON sidecar](https://github.com/rordenlab/dcm2niix/issues/87). - - Better detection of image order for Philips 4D DICOM (reported by Jason McMorrow and Stephen Wilson). - - [Include StudyInstanceUID and SeriesInstanceUID in filename](https://github.com/rordenlab/dcm2niix/issues/94). - -7-Feb-2017 - - Can be compiled to use either Philips [Float or Display](http://www.nitrc.org/forum/message.php?msg_id=20213) intensity intercept and slope values. - - Handle 3D Philips DICOM and [PAR/REC](https://www.nitrc.org/forum/forum.php?thread_id=7707&forum_id=4703) files where images are not stored in a spatially contiguous order. - - Handle DICOM violations where icon is uncompressed but image data is compressed. - - Best guess matrix for 2D slices (similar to dcm2nii, SPM and MRIconvert). - - Linux (case sensitive filenames) now handles par/rec as well as PAR/REC. - - Images with unknown phase encoding do not generate [BIDS entry](https://github.com/rordenlab/dcm2niix/issues/79). - - Unified printMessage/printWarning/printError aids embedding in other projects, such as [divest](https://github.com/jonclayden/divest). - -1-Nov-2016 - - AppVeyor Support (Ningfei Li & Chris Filo Gorgolewski) - - Swap 3rd/4th dimensions for GE sequential multi-phase acquisitions (Niels Janssen). - -10-Oct-2016 - - Restores/improves building for the Windows operating system using MinGW. - -30-Sept-2016 - - Save ImageType (0x0008,0x0008) to BIDS. - - Separate CT scans with different exposures. - - Fixed issues where some compilers would generate erratic filenames for zero-padded series (e.g. "-f %3s"). - -21-Sept-2016 - - Reduce verbosity (reduce number of repeated warnings, less scary warnings for derived rather than raw images). - - Re-enable custom output directory "-o" option broken by 30-Apr-2016 version. - - Deal with mis-behaved GE CT images where slice direction across images is not consistent. - - Add new BIDS fields (field strength, manufacturer, etc). - - Philips PAR/REC conversion now reports inconsistent requested vs measured TR (due to prospect. motion corr.?). - - GE: Locations In Acquisition (0054, 0081) is inaccurate if slices are interpolated, use Images In Acquisition (0020,1002) if available. - - New filename options %d Series description (0008,103E), %z Sequence Name (0018,0024). - - New filename options %a antenna (coil) number, %e echo number. - - Initialize unused portions of NIfTI header to zero so multiple runs always produce identical results. - - Supports 3D lossless JPEG saved as [multiple fragments](http://www.nitrc.org/forum/forum.php?thread_id=5872&forum_id=4703). - -5-May-2016 - - Crop 3D T1 acquisitions (e.g. ./dcm2niix -x y ~/DICOM). - -30-Apr-2016 - - Convert multiple files/folders with single command line invocation (e.g. ./dcm2niix -b y ~/tst ~/tst2). - -22-Apr-2016 - - Detect Siemens Phase maps (phase image names end with "_ph"). - - Use current working directory if file name not specified. - -12-Apr-2016 - - Provide override (command line option "-m y") to stack images of the same series even if they differ in study date/time, echo/coil number, or slice orientation. This mechanism allows users to concatenate images that break strict DICOM compliance. - -22-Mar-2016 - - Experimental support for [DICOM datasets without DICOM file meta information](http://dicom.nema.org/dicom/2013/output/chtml/part10/chapter_7.html). - -12-Dec-2015 - - Support PAR/REC FP values when possible (see PMC3998685). - -11-Nov-2015 - - Minor refinements. - -12-June-2015 - - Uses less memory (helpful for large datasets). - -2-Feb-2015 - - Support for Visual Studio. - - Remove dependency on zlib (now uses miniz). - -1-Jan-2015 - - Images separated based on TE (fieldmaps). - - Support for JPEG2000 using OpenJPEG or Jasper libraries. - - Support for JPEG using NanoJPEG library. - - Support for lossless JPEG using custom library. - -24-Nov-2014 - - Support for CT scans with gantry tilt and varying distance between slices. - -11-Oct-2014 - - Initial public release. +[See the VERSIONS.md file for details on releases](./VERSIONS.md). ## Running Command line usage is described in the [NITRC wiki](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#General_Usage). The minimal command line call would be `dcm2niix /path/to/dicom/folder`. However, you may want to invoke additional options, for example the call `dcm2niix -z y -f %p_%t_%s -o /path/ouput /path/to/dicom/folder` will save data as gzip compressed, with the filename based on the protocol name (%p) acquisition time (%t) and DICOM series number (%s), with all files saved to the folder "output". For more help see help: `dcm2niix -h`. -**Optional batch processing version:** - -Perform a batch conversion of multiple dicoms using the configurations specified in a yaml file. -```bash -dcm2niibatch batch_config.yml -``` - -The configuration file should be in yaml format as shown in example `batch_config.yaml` - -```yaml -Options: - isGz: false - isFlipY: false - isVerbose: false - isCreateBIDS: false - isOnlySingleFile: false -Files: - - - in_dir: /path/to/first/folder - out_dir: /path/to/output/folder - filename: dcemri - - - in_dir: /path/to/second/folder - out_dir: /path/to/output/folder - filename: fa3 -``` - -You can add as many files as you want to convert as long as this structure stays consistent. Note that a dash must separate each file. +[See the BATCH.md file for instructions on using the batch processing version](./BATCH.md). ## Build @@ -189,14 +53,11 @@ make **optional batch processing version:** -The batch processing binary `dcm2niibatch` is optional. To build `dcm2niibatch` as well change the cmake command to `cmake -DBATCH_VERSION=ON ..` - -This requires a compiler that supports c++11. - +The batch processing binary `dcm2niibatch` is optional. To build `dcm2niibatch` as well change the cmake command to `cmake -DBATCH_VERSION=ON ..`. This requires a compiler that supports c++11. ### Building the command line version without cmake -[See the COMPILE.md file for details on manual compilation](./COMPILE.md). +If you have any problems with the cmake build script described above or want to customize the software see the [COMPILE.md file for details on manual compilation](./COMPILE.md). ## Links @@ -209,6 +70,7 @@ This requires a compiler that supports c++11. - [dcm2niir](https://github.com/muschellij2/dcm2niir) R wrapper for dcm2niix/dcm2nii. - [divest](https://github.com/jonclayden/divest) R interface to dcm2niix. - [sci-tran dcm2niix](https://github.com/scitran-apps/dcm2niix) docker. - - [neuro_docker](https://github.com/Neurita/neuro_docker) includes dcm2niix. + - [neuro_docker](https://github.com/Neurita/neuro_docker) includes dcm2niix as part of a provides a single, static Dockerfile. + - [neurodocker](https://github.com/kaczmarj/neurodocker) generates [custom](https://github.com/rordenlab/dcm2niix/issues/138) Dockerfiles given specific versions of neuroimaging software. - [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. - [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/). \ No newline at end of file diff --git a/VERSIONS.md b/VERSIONS.md new file mode 100644 index 00000000..43ef2d4e --- /dev/null +++ b/VERSIONS.md @@ -0,0 +1,112 @@ +## Versions + +17-Oct-2017 + - Swap [phase-encoding direction polarity](https://github.com/rordenlab/dcm2niix/issues/125) for Siemens images where PE is in the Column direction. + - Sort diffusion volumes by [B-value amplitude](https://www.nitrc.org/forum/forum.php?thread_id=8396&forum_id=4703) (use "-d n"/"-d y" to turn the feature off/on). + - BIDS tag [TotalReadoutTime](https://github.com/rordenlab/dcm2niix/issues/130) handles partial fourier, Phase Resolution, etc (Michael Harms). + - Additional [json fields](https://github.com/rordenlab/dcm2niix/issues/127). + +18-Aug-2017 + - Better BVec extraction for [PAR/REC 4.1](https://www.nitrc.org/forum/forum.php?thread_id=8387&forum_id=4703). + - Support for [Segami Cerescan volumes](https://www.nitrc.org/forum/forum.php?thread_id=8076&forum_id=4703). + +24-July-2017 + - Compiles with recent releases of [OpenJPEG](https://github.com/neurolabusc/dcm_qa/issues/5#issuecomment-317443179) for JPEG2000 support. + +23-June-2017 + - [Ensure slice timing always reported for Siemens EPI](https://github.com/neurolabusc/dcm_qa/issues/4#issuecomment-310707906) + - [Integrates validation](https://github.com/neurolabusc/dcm_qa) + - JSON fix (InstitutionName -> InstitutionAddress) + +21-June-2017 + - Read DICOM header in 1Mb segments rather than loading whole file : reduces ram usage and [faster for systems with slow io](https://github.com/rordenlab/dcm2niix/issues/104). + - Report [TotalReadoutTime](https://github.com/rordenlab/dcm2niix/issues/98). + - Fix JPEG2000 support in [Superbuild](https://github.com/rordenlab/dcm2niix/issues/105). + +28-May-2017 + - Remove all derived images from [Philips DTI series](http://www.nitrc.org/forum/message.php?msg_id=21025). + - Provide some [Siemens EPI sequence details](https://github.com/rordenlab/dcm2niix/issues). + +28-April-2017 + - Experimental [ECAT support](https://github.com/rordenlab/dcm2niix/issues/95). + - Updated cmake to make JPEG2000 support easier with improved Travis and AppVeyor support [Ningfei Li](https://github.com/ningfei). + - Supports Data/Time for images that report Data/Time (0008,002A) but not separate Date and Time (0008,0022 and 0008,0032). + - [BIDS reports SliceTiming correctly](http://www.nitrc.org/forum/message.php?msg_id=20852). + - Options -1..-9 to control [gz compression level](https://github.com/rordenlab/dcm2niix/issues/90). + - Includes some [PET details in the BIDS JSON sidecar](https://github.com/rordenlab/dcm2niix/issues/87). + - Better detection of image order for Philips 4D DICOM (reported by Jason McMorrow and Stephen Wilson). + - [Include StudyInstanceUID and SeriesInstanceUID in filename](https://github.com/rordenlab/dcm2niix/issues/94). + +7-Feb-2017 + - Can be compiled to use either Philips [Float or Display](http://www.nitrc.org/forum/message.php?msg_id=20213) intensity intercept and slope values. + - Handle 3D Philips DICOM and [PAR/REC](https://www.nitrc.org/forum/forum.php?thread_id=7707&forum_id=4703) files where images are not stored in a spatially contiguous order. + - Handle DICOM violations where icon is uncompressed but image data is compressed. + - Best guess matrix for 2D slices (similar to dcm2nii, SPM and MRIconvert). + - Linux (case sensitive filenames) now handles par/rec as well as PAR/REC. + - Images with unknown phase encoding do not generate [BIDS entry](https://github.com/rordenlab/dcm2niix/issues/79). + - Unified printMessage/printWarning/printError aids embedding in other projects, such as [divest](https://github.com/jonclayden/divest). + +1-Nov-2016 + - AppVeyor Support (Ningfei Li & Chris Filo Gorgolewski) + - Swap 3rd/4th dimensions for GE sequential multi-phase acquisitions (Niels Janssen). + +10-Oct-2016 + - Restores/improves building for the Windows operating system using MinGW. + +30-Sept-2016 + - Save ImageType (0x0008,0x0008) to BIDS. + - Separate CT scans with different exposures. + - Fixed issues where some compilers would generate erratic filenames for zero-padded series (e.g. "-f %3s"). + +21-Sept-2016 + - Reduce verbosity (reduce number of repeated warnings, less scary warnings for derived rather than raw images). + - Re-enable custom output directory "-o" option broken by 30-Apr-2016 version. + - Deal with mis-behaved GE CT images where slice direction across images is not consistent. + - Add new BIDS fields (field strength, manufacturer, etc). + - Philips PAR/REC conversion now reports inconsistent requested vs measured TR (due to prospect. motion corr.?). + - GE: Locations In Acquisition (0054, 0081) is inaccurate if slices are interpolated, use Images In Acquisition (0020,1002) if available. + - New filename options %d Series description (0008,103E), %z Sequence Name (0018,0024). + - New filename options %a antenna (coil) number, %e echo number. + - Initialize unused portions of NIfTI header to zero so multiple runs always produce identical results. + - Supports 3D lossless JPEG saved as [multiple fragments](http://www.nitrc.org/forum/forum.php?thread_id=5872&forum_id=4703). + +5-May-2016 + - Crop 3D T1 acquisitions (e.g. ./dcm2niix -x y ~/DICOM). + +30-Apr-2016 + - Convert multiple files/folders with single command line invocation (e.g. ./dcm2niix -b y ~/tst ~/tst2). + +22-Apr-2016 + - Detect Siemens Phase maps (phase image names end with "_ph"). + - Use current working directory if file name not specified. + +12-Apr-2016 + - Provide override (command line option "-m y") to stack images of the same series even if they differ in study date/time, echo/coil number, or slice orientation. This mechanism allows users to concatenate images that break strict DICOM compliance. + +22-Mar-2016 + - Experimental support for [DICOM datasets without DICOM file meta information](http://dicom.nema.org/dicom/2013/output/chtml/part10/chapter_7.html). + +12-Dec-2015 + - Support PAR/REC FP values when possible (see PMC3998685). + +11-Nov-2015 + - Minor refinements. + +12-June-2015 + - Uses less memory (helpful for large datasets). + +2-Feb-2015 + - Support for Visual Studio. + - Remove dependency on zlib (now uses miniz). + +1-Jan-2015 + - Images separated based on TE (fieldmaps). + - Support for JPEG2000 using OpenJPEG or Jasper libraries. + - Support for JPEG using NanoJPEG library. + - Support for lossless JPEG using custom library. + +24-Nov-2014 + - Support for CT scans with gantry tilt and varying distance between slices. + +11-Oct-2014 + - Initial public release. \ No newline at end of file diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index e19d70bf..b9a2979e 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -624,6 +624,11 @@ int headerDcm2Nii2(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_hea h->dim_info = (3 << 4) + (1 << 2) + 2; if (d.phaseEncodingRC =='C') h->dim_info = (3 << 4) + (2 << 2) + 1; + if (d.CSA.multiBandFactor > 1) { + char dtxt[1024] = {""}; + sprintf(dtxt, ";mb=%d", d.CSA.multiBandFactor); + strcat(txt,dtxt); + } snprintf(h->descrip,80, "%s",txt); if (strlen(d.imageComments) > 0) snprintf(h->aux_file,24,"%s",d.imageComments); @@ -670,6 +675,7 @@ struct TDICOMdata clear_dicom_data() { strcpy(d.scanningSequence, ""); strcpy(d.sequenceVariant, ""); strcpy(d.manufacturersModelName, ""); + strcpy(d.institutionalDepartmentName, ""); strcpy(d.procedureStepDescription, ""); strcpy(d.institutionName, ""); strcpy(d.referringPhysicianName, ""); @@ -678,6 +684,7 @@ struct TDICOMdata clear_dicom_data() { strcpy(d.softwareVersions, ""); strcpy(d.stationName, ""); strcpy(d.scanOptions, ""); + //strcpy(d.mrAcquisitionType, ""); strcpy(d.seriesInstanceUID, ""); strcpy(d.studyID, ""); strcpy(d.studyInstanceUID, ""); @@ -699,6 +706,10 @@ struct TDICOMdata clear_dicom_data() { d.flipAngle = 0.0; d.bandwidthPerPixelPhaseEncode = 0.0; d.fieldStrength = 0.0; + d.SAR = 0.0; + d.pixelBandwidth = 0.0; + d.zSpacing = 0.0; + d.zThick = 0.0; d.numberOfDynamicScans = 0; d.echoNum = 1; d.echoTrainLength = 0; @@ -724,6 +735,7 @@ struct TDICOMdata clear_dicom_data() { d.imageNum = 1; d.imageStart = 0; d.is3DAcq = false; //e.g. MP-RAGE, SPACE, TFE + d.is2DAcq = false; // d.isSlicesSpatiallySequentialPhilips = true; //Philips can save slices in random order, e.g. 4,5,6,1,2,3 d.isDerived = false; //0008,0008 = DERIVED,CSAPARALLEL,POSDISP d.isSegamiOasis = false; //these images do not store spatial coordinates @@ -741,6 +753,10 @@ struct TDICOMdata clear_dicom_data() { d.isLittleEndian = true; //DICOM initially always little endian d.converted2NII = 0; d.phaseEncodingRC = '?'; + d.patientSex = '?'; + d.patientWeight = 0.0; + strcpy(d.patientBirthDate, ""); + strcpy(d.patientAge, ""); d.CSA.bandwidthPerPixelPhaseEncode = 0.0; d.CSA.mosaicSlices = 0; d.CSA.sliceNormV[1] = 1.0; @@ -1142,6 +1158,7 @@ int readCSAImageHeader(unsigned char *buff, int lLength, struct TCSAdata *CSA, i } } //if at least 1 item }// for lT 1..lnTag + if (CSA->protocolSliceNumber1 > 1) CSA->sliceOrder = NIFTI_SLICE_UNKNOWN; return EXIT_SUCCESS; } // readCSAImageHeader() @@ -1956,7 +1973,7 @@ unsigned char * nii_loadImgCore(char* imgname, struct nifti_1_header hdr, int bi if (bitsAllocated == 12) conv12bit16bit(bImg, hdr); return bImg; -} //nii_loadImg() +} //nii_loadImgCore() unsigned char * nii_planar2rgb(unsigned char* bImg, struct nifti_1_header *hdr, int isPlanar) { //DICOM data saved in triples RGBRGBRGB, NIfTI RGB saved in planes RRR..RGGG..GBBBB..B @@ -2573,15 +2590,13 @@ unsigned char * nii_loadImgXL(char* imgname, struct nifti_1_header *hdr, struct if ((dcm.compressionScheme == kCompressYes) && (compressFlag != kCompressNone) ) img = nii_loadImgCoreJasper(imgname, *hdr, dcm, compressFlag); else - #else - UNUSED(compressFlag); //avoid compiler -Wunused-parameter warning: compressFlag required when myEnableJasper or not myDisableOpenJPEG - #endif + #endif #endif - if (dcm.compressionScheme == kCompressYes) { + if (dcm.compressionScheme == kCompressYes) { printMessage("Software not set up to decompress DICOM\n"); return NULL; } else - img = nii_loadImgCore(imgname, *hdr, dcm.bitsAllocated); + img = nii_loadImgCore(imgname, *hdr, dcm.bitsAllocated); if (img == NULL) return img; if ((dcm.compressionScheme == kCompressNone) && (dcm.isLittleEndian != littleEndianPlatform()) && (hdr->bitpix > 8)) img = nii_byteswap(img, hdr); @@ -2717,11 +2732,16 @@ struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, stru #define kReferringPhysicianName 0x0008+(0x0090 << 16 ) #define kStationName 0x0008+(0x1010 << 16 ) #define kSeriesDescription 0x0008+(0x103E << 16 ) // '0008' '103E' 'LO' 'SeriesDescription' +#define kInstitutionalDepartmentName 0x0008+(0x1040 << 16 ) #define kManufacturersModelName 0x0008+(0x1090 << 16 ) #define kDerivationDescription 0x0008+(0x2111 << 16 ) #define kComplexImageComponent (uint32_t) 0x0008+(0x9208 << 16 )//'0008' '9208' 'CS' 'ComplexImageComponent' #define kPatientName 0x0010+(0x0010 << 16 ) #define kPatientID 0x0010+(0x0020 << 16 ) +#define kPatientBirthDate 0x0010+(0x0030 << 16 ) +#define kPatientSex 0x0010+(0x0040 << 16 ) +#define kPatientAge 0x0010+(0x1010 << 16 ) +#define kPatientWeight 0x0010+(0x1030 << 16 ) #define kAnatomicalOrientationType 0x0010+(0x2210 << 16 ) #define kBodyPartExamined 0x0018+(0x0015 << 16) #define kScanningSequence 0x0018+(0x0020 << 16) @@ -2739,6 +2759,7 @@ struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, stru #define kPhaseEncodingSteps 0x0018+(0x0089 << 16 ) //'IS' #define kEchoTrainLength 0x0018+(0x0091 << 16 ) //IS #define kPhaseFieldofView 0x0018+(0x0094 << 16 ) //'DS' +#define kPixelBandwidth 0x0018+(0x0095 << 16 ) //'DS' 'PixelBandwidth' #define kDeviceSerialNumber 0x0018+(0x1000 << 16 ) //LO #define kSoftwareVersions 0x0018+(0x1020 << 16 ) //LO #define kProtocolName 0x0018+(0x1030<< 16 ) @@ -2750,6 +2771,7 @@ struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, stru #define kAcquisitionMatrix 0x0018+(0x1310 << 16 ) //US #define kFlipAngle 0x0018+(0x1314 << 16 ) #define kInPlanePhaseEncodingDirection 0x0018+(0x1312<< 16 ) //CS +#define kSAR 0x0018+(0x1316 << 16 ) //'DS' 'SAR' #define kPatientOrient 0x0018+(0x5100<< 16 ) //0018,5100. patient orientation - 'HFS' //#define kDiffusionBFactorSiemens 0x0019+(0x100C<< 16 ) // 0019;000C;SIEMENS MR HEADER ;B_value #define kDwellTime 0x0019+(0x1018<< 16 ) //IS in NSec, see https://github.com/rordenlab/dcm2niix/issues/127 @@ -2826,7 +2848,7 @@ uint32_t kNest = 0xFFFE +(0xE000 << 16 ); //#define kNest 0xFFFE +(0xE000 << 16 uint32_t kUnnest = 0xFFFE +(0xE00D << 16 ); //#define kUnnest 0xFFFE +(0xE00D << 16 ) //ItemDelimitationItem [length defined] http://www.dabsoft.ch/dicom/5/7.5/ uint32_t kUnnest2 = 0xFFFE +(0xE0DD << 16 ); //#define kUnnest2 0xFFFE +(0xE0DD << 16 )//SequenceDelimitationItem [length undefined] int nest = 0; - double zSpacing = -1.0l; //includes slice thickness plus gap + //double zSpacing = -1.0l; //includes slice thickness plus gap int locationsInAcquisitionGE = 0; int locationsInAcquisitionPhilips = 0; int imagesInAcquisition = 0; @@ -2843,6 +2865,7 @@ uint32_t kUnnest2 = 0xFFFE +(0xE0DD << 16 ); //#define kUnnest2 0xFFFE +(0xE0DD //float intenScalePhilips = 0.0; char acquisitionDateTimeTxt[kDICOMStr] = ""; bool isEncapsulatedData = false; + int multiBandFactor = 0; int encapsulatedDataFragments = 0; int encapsulatedDataFragmentStart = 0; //position of first FFFE,E000 for compressed images int encapsulatedDataImageStart = 0; //position of 7FE0,0010 for compressed images (where actual image start should be start of first fragment) @@ -3096,12 +3119,27 @@ uint32_t kUnnest2 = 0xFFFE +(0xE0DD << 16 ); //#define kUnnest2 0xFFFE +(0xE0DD case kPatientID : dcmStr (lLength, &buffer[lPos], d.patientID); break; + case kPatientBirthDate : + dcmStr (lLength, &buffer[lPos], d.patientBirthDate); + break; + case kPatientSex : + d.patientSex = toupper(buffer[lPos]); //first character is either 'R'ow or 'C'ol + break; + case kPatientAge : + dcmStr (lLength, &buffer[lPos], d.patientAge); + break; + case kPatientWeight : + d.patientWeight = dcmStrFloat(lLength, &buffer[lPos]); + break; case kStationName : dcmStr (lLength, &buffer[lPos], d.stationName); break; case kSeriesDescription: { dcmStr (lLength, &buffer[lPos], d.seriesDescription); break; } + case kInstitutionalDepartmentName: + dcmStr (lLength, &buffer[lPos], d.institutionalDepartmentName); + break; case kManufacturersModelName : dcmStr (lLength, &buffer[lPos], d.manufacturersModelName); break; @@ -3192,6 +3230,9 @@ uint32_t kUnnest2 = 0xFFFE +(0xE0DD << 16 ); //#define kUnnest2 0xFFFE +(0xE0DD case kInPlanePhaseEncodingDirection: d.phaseEncodingRC = toupper(buffer[lPos]); //first character is either 'R'ow or 'C'ol break; + case kSAR: + d.SAR = dcmStrFloat(lLength, &buffer[lPos]); + break; case kStudyID: dcmStr (lLength, &buffer[lPos], d.studyID); break; @@ -3264,7 +3305,7 @@ uint32_t kUnnest2 = 0xFFFE +(0xE0DD << 16 ); //#define kUnnest2 0xFFFE +(0xE0DD d.fieldStrength = dcmStrFloat(lLength, &buffer[lPos]); break; case kZSpacing : - zSpacing = dcmStrFloat(lLength, &buffer[lPos]); + d.zSpacing = dcmStrFloat(lLength, &buffer[lPos]); break; case kPhaseEncodingSteps : d.phaseEncodingSteps = dcmStrInt(lLength, &buffer[lPos]); @@ -3275,6 +3316,9 @@ uint32_t kUnnest2 = 0xFFFE +(0xE0DD << 16 ); //#define kUnnest2 0xFFFE +(0xE0DD case kPhaseFieldofView : d.phaseFieldofView = dcmStrFloat(lLength, &buffer[lPos]); break; + case kPixelBandwidth : + d.pixelBandwidth = dcmStrFloat(lLength, &buffer[lPos]); + break; case kAcquisitionMatrix : if (lLength == 8) { uint16_t acquisitionMatrix[4]; @@ -3320,6 +3364,7 @@ uint32_t kUnnest2 = 0xFFFE +(0xE0DD << 16 ); //#define kUnnest2 0xFFFE +(0xE0DD break; case kZThick : d.xyzMM[3] = dcmStrFloat(lLength, &buffer[lPos]); + d.zThick = d.xyzMM[3]; break; case kCoilSiemens : { if (d.manufacturer == kMANUFACTURER_SIEMENS) { @@ -3342,6 +3387,13 @@ uint32_t kUnnest2 = 0xFFFE +(0xE0DD << 16 ); //#define kUnnest2 0xFFFE +(0xE0DD d.accelFactPE = (float)strtof(accelStr, &ptr); if (*ptr != '\0') d.accelFactPE = 0.0; + //between slice accel + dcmStr (lLength, &buffer[lPos], accelStr); + dcmStrDigitsOnlyKey('s', accelStr); //e.g. if "p2s4" return "4", if "p2" return "" + multiBandFactor = (int)strtol(accelStr, &ptr, 10); + if (*ptr != '\0') + multiBandFactor = 0.0; + //printMessage("p%gs%d\n", d.accelFactPE, multiBandFactor); break; } case kLocationsInAcquisition : d.locationsInAcquisition = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); @@ -3358,9 +3410,9 @@ uint32_t kUnnest2 = 0xFFFE +(0xE0DD << 16 ); //#define kUnnest2 0xFFFE +(0xE0DD d.numberOfDynamicScans = dcmStrInt(lLength, &buffer[lPos]); break; case kMRAcquisitionType: //detect 3D acquisition: we can reorient these without worrying about slice time correct or BVEC/BVAL orientation - #ifndef myDisableReorient3dToOrtho + if (lLength > 1) d.is2DAcq = (buffer[lPos]=='2') && (toupper(buffer[lPos+1]) == 'D'); if (lLength > 1) d.is3DAcq = (buffer[lPos]=='3') && (toupper(buffer[lPos+1]) == 'D'); - #endif + //dcmStr (lLength, &buffer[lPos], d.mrAcquisitionType); break; case kBodyPartExamined : { dcmStr (lLength, &buffer[lPos], d.bodyPartExamined); @@ -3637,8 +3689,8 @@ uint32_t kUnnest2 = 0xFFFE +(0xE0DD << 16 ); //#define kUnnest2 0xFFFE +(0xE0DD d.locationsInAcquisition = imagesInAcquisition; //e.g. if 72 slices acquired but interpolated as 144 if ((d.manufacturer == kMANUFACTURER_GE) && (d.locationsInAcquisition == 0)) d.locationsInAcquisition = locationsInAcquisitionGE; - if (zSpacing > 0) - d.xyzMM[3] = zSpacing; //use zSpacing if provided: depending on vendor, kZThick may or may not include a slice gap + if (d.zSpacing > 0) + d.xyzMM[3] = d.zSpacing; //use zSpacing if provided: depending on vendor, kZThick may or may not include a slice gap //printMessage("patientPositions = %d XYZT = %d slicePerVol = %d numberOfDynamicScans %d\n",patientPositionNum,d.xyzDim[3], d.locationsInAcquisition, d.numberOfDynamicScans); if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (patientPositionNum > d.xyzDim[3])) printMessage("Please check slice thicknesses: Philips R3.2.2 bug can disrupt estimation (%d positions reported for %d slices)\n",patientPositionNum, d.xyzDim[3]); //Philips reported different positions for each slice! @@ -3717,6 +3769,8 @@ uint32_t kUnnest2 = 0xFFFE +(0xE0DD << 16 ); //#define kUnnest2 0xFFFE +(0xE0DD printError("Unable to convert DTI [recompile with increased kMaxDTI4D] detected=%d, max = %d\n", d.CSA.numDti, kMaxDTI4D); d.CSA.numDti = 0; } + if (multiBandFactor > d.CSA.multiBandFactor) + d.CSA.multiBandFactor = multiBandFactor; //SMS reported in 0051,1011 but not CSA header //d.isValid = false; //debug only - will not create output! #ifndef myLoadWholeFileToReadHeader fclose(file); diff --git a/console/nii_dicom.h b/console/nii_dicom.h index ee874b94..c5b176f0 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -38,7 +38,7 @@ extern "C" { #define kCCsuf " CompilerNA" //unknown compiler! #endif -#define kDCMvers "v1.0.20170923" kDCMsuf kCCsuf +#define kDCMvers "v1.0.20171103" kDCMsuf kCCsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic static const int kMaxDTI4D = 4096; //maximum number of DTI directions for 4D (Philips) images, also maximum number of 3D slices for Philips 3D and 4D images @@ -113,16 +113,17 @@ static const int kCompressRLE = 4; //run length encoding long seriesNum; int xyzDim[5]; int modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, patientPositionNumPhilips, coilNum, echoNum, sliceOrient,numberOfDynamicScans, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,patientPositionSequentialRepeats,locationsInAcquisition, compressionScheme; - float phaseFieldofView, accelFactPE, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; + float patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; float radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) float ecat_isotope_halflife, ecat_dosage; double dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; - bool isSegamiOasis, isDerived, isXRay, isMultiEcho, isSlicesSpatiallySequentialPhilips, isValid, is3DAcq, isExplicitVR, isLittleEndian, isPlanarRGB, isSigned, isHasPhase,isHasMagnitude,isHasMixed, isFloat, isResampled; - char phaseEncodingRC; - char scanOptions[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionAddress[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[kDICOMStr], imageType[kDICOMStr], manufacturersModelName[kDICOMStr], patientID[kDICOMStr], patientOrient[kDICOMStr], patientName[kDICOMStr],seriesDescription[kDICOMStr], studyID[kDICOMStr], sequenceName[kDICOMStr], protocolName[kDICOMStr],sequenceVariant[kDICOMStr],scanningSequence[kDICOMStr], birthDate[kDICOMStr], gender[kDICOMStr], age[kDICOMStr], studyDate[kDICOMStr],studyTime[kDICOMStr]; + //char mrAcquisitionType[kDICOMStr] + char scanOptions[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionAddress[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[kDICOMStr], imageType[kDICOMStr], institutionalDepartmentName[kDICOMStr], manufacturersModelName[kDICOMStr], patientID[kDICOMStr], patientOrient[kDICOMStr], patientName[kDICOMStr],seriesDescription[kDICOMStr], studyID[kDICOMStr], sequenceName[kDICOMStr], protocolName[kDICOMStr],sequenceVariant[kDICOMStr],scanningSequence[kDICOMStr], patientBirthDate[kDICOMStr], patientAge[kDICOMStr], studyDate[kDICOMStr],studyTime[kDICOMStr]; char imageComments[kDICOMStrLarge]; struct TCSAdata CSA; + bool isSegamiOasis, isDerived, isXRay, isMultiEcho, isSlicesSpatiallySequentialPhilips, isValid, is3DAcq, is2DAcq, isExplicitVR, isLittleEndian, isPlanarRGB, isSigned, isHasPhase,isHasMagnitude,isHasMixed, isFloat, isResampled; + char phaseEncodingRC, patientSex; }; size_t nii_ImgBytes(struct nifti_1_header hdr); diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index bb61f979..475449f7 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -93,8 +93,9 @@ void dropFilenameFromPath(char *path) { // if (strlen(path) == 0) { //file name did not specify path, assume relative path and return current working directory //strcat (path,"."); //relative path - use cwd <- not sure if this works on Windows! char cwd[1024]; - getcwd(cwd, sizeof(cwd)); - strcat (path,cwd); + char* ok = getcwd(cwd, sizeof(cwd)); + if (ok !=NULL) + strcat (path,cwd); } } @@ -337,9 +338,9 @@ void nii_SaveText(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, strcat (txtname,".txt"); //printMessage("Saving text %s\n",txtname); FILE *fp = fopen(txtname, "w"); - fprintf(fp, "%s\tField Strength:\t%g\tProtocolName:\t%s\tScanningSequence00180020:\t%s\tTE:\t%g\tTR:\t%g\tSeriesNum:\t%ld\tAcquNum:\t%d\tImageNum:\t%d\tImageComments:\t%s\tDateTime:\t%f\tName:\t%s\tConvVers:\t%s\tDoB:\t%s\tGender:\t%s\tAge:\t%s\tDimXYZT:\t%d\t%d\t%d\t%d\tCoil:\t%d\tEchoNum:\t%d\tOrient(6)\t%g\t%g\t%g\t%g\t%g\t%g\tbitsAllocated\t%d\tInputName\t%s\n", + fprintf(fp, "%s\tField Strength:\t%g\tProtocolName:\t%s\tScanningSequence00180020:\t%s\tTE:\t%g\tTR:\t%g\tSeriesNum:\t%ld\tAcquNum:\t%d\tImageNum:\t%d\tImageComments:\t%s\tDateTime:\t%f\tName:\t%s\tConvVers:\t%s\tDoB:\t%s\tGender:\t%c\tAge:\t%s\tDimXYZT:\t%d\t%d\t%d\t%d\tCoil:\t%d\tEchoNum:\t%d\tOrient(6)\t%g\t%g\t%g\t%g\t%g\t%g\tbitsAllocated\t%d\tInputName\t%s\n", pathoutname, d.fieldStrength, d.protocolName, d.scanningSequence, d.TE, d.TR, d.seriesNum, d.acquNum, d.imageNum, d.imageComments, - d.dateTime, d.patientName, kDCMvers, d.birthDate, d.gender, d.age, h->dim[1], h->dim[2], h->dim[3], h->dim[4], + d.dateTime, d.patientName, kDCMvers, d.patientBirthDate, d.patientSex, d.patientAge, h->dim[1], h->dim[2], h->dim[3], h->dim[4], d.coilNum,d.echoNum, d.orient[1], d.orient[2], d.orient[3], d.orient[4], d.orient[5], d.orient[6], d.bitsAllocated, dcmname); fclose(fp); @@ -490,19 +491,25 @@ int phoenixOffsetCSASeriesHeader(unsigned char *buff, int lLength) { return 0; } // phoenixOffsetCSASeriesHeader() -void siemensCsaAscii(const char * filename, int csaOffset, int csaLength, float* phaseOversampling, float* phaseResolution, int* baseResolution, int* interp, int* partialFourier, int* echoSpacing, int* parallelReductionFactorInPlane, char* coilID, char* consistencyInfo, char* coilElements, char* pulseSequenceDetails, char* fmriExternalInfo) { +void siemensCsaAscii(const char * filename, int csaOffset, int csaLength, float* delayTimeInTR, float* phaseOversampling, float* phaseResolution, float* txRefAmp, float* shimSetting, int* baseResolution, int* interp, int* partialFourier, int* echoSpacing, int* parallelReductionFactorInPlane, char* coilID, char* consistencyInfo, char* coilElements, char* pulseSequenceDetails, char* fmriExternalInfo, char * protocolName) { //reads ASCII portion of CSASeriesHeaderInfo and returns lEchoTrainDuration or lEchoSpacing value // returns 0 if no value found + *delayTimeInTR = 0.0; *phaseOversampling = 0.0; *phaseResolution = 0.0; + *txRefAmp = 0.0; *baseResolution = 0; *interp = 0; *partialFourier = 0; *echoSpacing = 0; + for (int i = 0; i < 8; i++) + shimSetting[i] = 0.0; strcpy(coilID, ""); strcpy(consistencyInfo, ""); strcpy(coilElements, ""); strcpy(pulseSequenceDetails, ""); + strcpy(fmriExternalInfo, ""); + strcpy(protocolName, ""); if ((csaOffset < 0) || (csaLength < 8)) return; FILE * pFile = fopen ( filename, "rb" ); if(pFile==NULL) return; @@ -557,10 +564,41 @@ void siemensCsaAscii(const char * filename, int csaOffset, int csaLength, float readKeyStr(keyStrSeq, keyPos, csaLengthTrim, pulseSequenceDetails); char keyStrExt[] = "FmriExternalInfo"; readKeyStr(keyStrExt, keyPos, csaLengthTrim, fmriExternalInfo); - char keyStrPhase[] = "sKSpace.dPhaseResolution"; - *phaseResolution = readKeyFloat(keyStrPhase, keyPos, csaLengthTrim); + char keyStrPn[] = "tProtocolName"; + readKeyStr(keyStrPn, keyPos, csaLengthTrim, protocolName); + char keyStrDelay[] = "lDelayTimeInTR"; + *delayTimeInTR = readKeyFloat(keyStrDelay, keyPos, csaLengthTrim); char keyStrOver[] = "sKSpace.dPhaseOversamplingForDialog"; *phaseOversampling = readKeyFloat(keyStrOver, keyPos, csaLengthTrim); + char keyStrPhase[] = "sKSpace.dPhaseResolution"; + *phaseResolution = readKeyFloat(keyStrPhase, keyPos, csaLengthTrim); + char keyStrAmp[] = "sTXSPEC.asNucleusInfo[0].flReferenceAmplitude"; + *txRefAmp = readKeyFloat(keyStrAmp, keyPos, csaLengthTrim); + //lower order shims: newer sequences + char keyStrSh0[] = "sGRADSPEC.asGPAData[0].lOffsetX"; + shimSetting[0] = readKeyFloat(keyStrSh0, keyPos, csaLengthTrim); + char keyStrSh1[] = "sGRADSPEC.asGPAData[0].lOffsetY"; + shimSetting[1] = readKeyFloat(keyStrSh1, keyPos, csaLengthTrim); + char keyStrSh2[] = "sGRADSPEC.asGPAData[0].lOffsetZ"; + shimSetting[2] = readKeyFloat(keyStrSh2, keyPos, csaLengthTrim); + //lower order shims: older sequences + char keyStrSh0s[] = "sGRADSPEC.lOffsetX"; + if (shimSetting[0] == 0.0) shimSetting[0] = readKeyFloat(keyStrSh0s, keyPos, csaLengthTrim); + char keyStrSh1s[] = "sGRADSPEC.lOffsetY"; + if (shimSetting[1] == 0.0) shimSetting[1] = readKeyFloat(keyStrSh1s, keyPos, csaLengthTrim); + char keyStrSh2s[] = "sGRADSPEC.lOffsetZ"; + if (shimSetting[2] == 0.0) shimSetting[2] = readKeyFloat(keyStrSh2s, keyPos, csaLengthTrim); + //higher order shims: older sequences + char keyStrSh3[] = "sGRADSPEC.alShimCurrent[0]"; + shimSetting[3] = readKeyFloat(keyStrSh3, keyPos, csaLengthTrim); + char keyStrSh4[] = "sGRADSPEC.alShimCurrent[1]"; + shimSetting[4] = readKeyFloat(keyStrSh4, keyPos, csaLengthTrim); + char keyStrSh5[] = "sGRADSPEC.alShimCurrent[2]"; + shimSetting[5] = readKeyFloat(keyStrSh5, keyPos, csaLengthTrim); + char keyStrSh6[] = "sGRADSPEC.alShimCurrent[3]"; + shimSetting[6] = readKeyFloat(keyStrSh6, keyPos, csaLengthTrim); + char keyStrSh7[] = "sGRADSPEC.alShimCurrent[4]"; + shimSetting[7] = readKeyFloat(keyStrSh7, keyPos, csaLengthTrim); } fclose (pFile); free (buffer); @@ -641,6 +679,7 @@ void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, }; json_Str(fp, "\t\"ManufacturersModelName\": \"%s\",\n", d.manufacturersModelName); json_Str(fp, "\t\"InstitutionName\": \"%s\",\n", d.institutionName); + json_Str(fp, "\t\"InstitutionalDepartmentName\": \"%s\",\n", d.institutionalDepartmentName); json_Str(fp, "\t\"InstitutionAddress\": \"%s\",\n", d.institutionAddress); json_Str(fp, "\t\"DeviceSerialNumber\": \"%s\",\n", d.deviceSerialNumber ); json_Str(fp, "\t\"StationName\": \"%s\",\n", d.stationName ); @@ -650,12 +689,20 @@ void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, json_Str(fp, "\t\"ReferringPhysicianName\": \"%s\",\n", d.referringPhysicianName); json_Str(fp, "\t\"StudyID\": \"%s\",\n", d.studyID); //Next lines directly reveal patient identity - // json_Str(fp, "\t\"PatientName\": \"%s\",\n", d.patientName); - // json_Str(fp, "\t\"PatientID\": \"%s\",\n", d.patientID); + json_Str(fp, "\t\"PatientName\": \"%s\",\n", d.patientName); + json_Str(fp, "\t\"PatientID\": \"%s\",\n", d.patientID); + if (d.patientSex != '?') fprintf(fp, "\t\"PatientSex\": \"%c\",\n", d.patientSex); + json_Float(fp, "\t\"PatientWeight\": %g,\n", d.patientWeight); + //d.patientBirthDate //convert from DICOM YYYYMMDD to JSON + //d.patientAge //4-digit Age String: nnnD, nnnW, nnnM, nnnY; } json_Str(fp, "\t\"BodyPartExamined\": \"%s\",\n", d.bodyPartExamined); + json_Str(fp, "\t\"PatientPosition\": \"%s\",\n", d.patientOrient); // 0018,5100 = PatientPosition in DICOM json_Str(fp, "\t\"ProcedureStepDescription\": \"%s\",\n", d.procedureStepDescription); json_Str(fp, "\t\"SoftwareVersions\": \"%s\",\n", d.softwareVersions); + //json_Str(fp, "\t\"MRAcquisitionType\": \"%s\",\n", d.mrAcquisitionType); + if (d.is2DAcq) fprintf(fp, "\t\"MRAcquisitionType\": \"2D\",\n"); + if (d.is3DAcq) fprintf(fp, "\t\"MRAcquisitionType\": \"3D\",\n"); json_Str(fp, "\t\"SeriesDescription\": \"%s\",\n", d.seriesDescription); json_Str(fp, "\t\"ProtocolName\": \"%s\",\n", d.protocolName); json_Str(fp, "\t\"ScanningSequence\": \"%s\",\n", d.scanningSequence); @@ -678,6 +725,7 @@ void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, } if (d.isDerived) //DICOM is derived image or non-spatial file (sounds, etc) fprintf(fp, "\t\"RawImage\": false,\n"); + if (d.seriesNum > 0) fprintf(fp, "\t\"SeriesNumber\": %ld,\n", d.seriesNum); //Chris Gorgolewski: BIDS standard specifies ISO8601 date-time format (Example: 2016-07-06T12:49:15.679688) //Lines below directly save DICOM values if (d.acquisitionTime > 0.0 && d.acquisitionDate > 0.0){ @@ -726,6 +774,12 @@ void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, //CT parameters if ((d.TE > 0.0) && (d.isXRay)) fprintf(fp, "\t\"XRayExposure\": %g,\n", d.TE ); //MRI parameters + if (!d.isXRay) { //with CT scans, slice thickness often varies + //beware, not used correctly by all vendors https://public.kitware.com/pipermail/insight-users/2005-September/014711.html + json_Float(fp, "\t\"SliceThickness\": %g,\n", d.zThick ); + json_Float(fp, "\t\"SpacingBetweenSlices\": %g,\n", d.zSpacing); + } + json_Float(fp, "\t\"SAR\": %g,\n", d.SAR ); if (d.echoNum > 1) 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 ); json_Float(fp, "\t\"RepetitionTime\": %g,\n", d.TR / 1000.0 ); @@ -737,9 +791,9 @@ void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, #ifdef myReadAsciiCsa if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.SeriesHeader_offset > 0) && (d.CSA.SeriesHeader_length > 0)) { int baseResolution, interpInt, partialFourier, echoSpacing, parallelReductionFactorInPlane; - float phaseResolution; - char fmriExternalInfo[kDICOMStr], coilID[kDICOMStr], consistencyInfo[kDICOMStr], coilElements[kDICOMStr], pulseSequenceDetails[kDICOMStr]; - siemensCsaAscii(filename, d.CSA.SeriesHeader_offset, d.CSA.SeriesHeader_length, &phaseOversampling, &phaseResolution, &baseResolution, &interpInt, &partialFourier, &echoSpacing, ¶llelReductionFactorInPlane, coilID, consistencyInfo, coilElements, pulseSequenceDetails, fmriExternalInfo); + float delayTimeInTR, phaseResolution, txRefAmp, shimSetting[8]; + char protocolName[kDICOMStr], fmriExternalInfo[kDICOMStr], coilID[kDICOMStr], consistencyInfo[kDICOMStr], coilElements[kDICOMStr], pulseSequenceDetails[kDICOMStr]; + siemensCsaAscii(filename, d.CSA.SeriesHeader_offset, d.CSA.SeriesHeader_length, &delayTimeInTR, &phaseOversampling, &phaseResolution, &txRefAmp, shimSetting, &baseResolution, &interpInt, &partialFourier, &echoSpacing, ¶llelReductionFactorInPlane, coilID, consistencyInfo, coilElements, pulseSequenceDetails, fmriExternalInfo, protocolName); if (partialFourier > 0) { //https://github.com/ismrmrd/siemens_to_ismrmrd/blob/master/parameter_maps/IsmrmrdParameterMap_Siemens_EPI_FLASHREF.xsl if (partialFourier == 1) pf = 0.5; // 4/8 @@ -753,6 +807,20 @@ void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, fprintf(fp, "\t\"Interpolation2D\": %d,\n", interp); } if (baseResolution > 0) fprintf(fp, "\t\"BaseResolution\": %d,\n", baseResolution ); + if (shimSetting[0] != 0.0) { + fprintf(fp, "\t\"ShimSetting\": [\n"); + for (int i = 0; i < 8; i++) { + if (i != 0) + fprintf(fp, ",\n"); + fprintf(fp, "\t\t%g", shimSetting[i]); + } + fprintf(fp, "\t],\n"); + } + //DelayTimeInTR + // https://groups.google.com/forum/#!topic/bids-discussion/nmg1BOVH1SU + // https://groups.google.com/forum/#!topic/bids-discussion/seD7AtJfaFE + json_Float(fp, "\t\"DelayTime\": %g,\n", delayTimeInTR/ 1000000.0); //DelayTimeInTR usec -> sec + json_Float(fp, "\t\"TxRefAmp\": %g,\n", txRefAmp); json_Float(fp, "\t\"PhaseResolution\": %g,\n", phaseResolution); json_Float(fp, "\t\"PhaseOversampling\": %g,\n", phaseOversampling); //usec -> sec json_Float(fp, "\t\"VendorReportedEchoSpacing\": %g,\n", echoSpacing / 1000000.0); //usec -> sec @@ -763,6 +831,8 @@ void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, json_Str(fp, "\t\"ReceiveCoilActiveElements\": \"%s\",\n", coilElements); json_Str(fp, "\t\"PulseSequenceDetails\": \"%s\",\n", pulseSequenceDetails); json_Str(fp, "\t\"FmriExternalInfo\": \"%s\",\n", fmriExternalInfo); + if (strlen(d.protocolName) < 1) //insert protocol name if it exists in CSA but not DICOM header: https://github.com/nipy/heudiconv/issues/80 + json_Str(fp, "\t\"ProtocolName\": \"%s\",\n", protocolName); json_Str(fp, "\t\"ConsistencyInfo\": \"%s\",\n", consistencyInfo); if (parallelReductionFactorInPlane > 0) {//AccelFactorPE -> phase encoding if (d.accelFactPE < 1.0) { //value not found in DICOM header, but WAS found in CSA ascii @@ -797,7 +867,6 @@ void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, reconMatrixPE = h->dim[1]; } if (reconMatrixPE > 0) fprintf(fp, "\t\"ReconMatrixPE\": %d,\n", reconMatrixPE ); - double bandwidthPerPixelPhaseEncode = d.bandwidthPerPixelPhaseEncode; if (bandwidthPerPixelPhaseEncode == 0.0) bandwidthPerPixelPhaseEncode = d.CSA.bandwidthPerPixelPhaseEncode; @@ -839,6 +908,7 @@ void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, 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)) fprintf(fp, "\t\"DwellTime\": %g,\n", d.dwellTime * 1E-9); // Phase encoding polarity @@ -866,14 +936,41 @@ void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, // Slice Timing if (d.CSA.sliceTiming[0] >= 0.0) { fprintf(fp, "\t\"SliceTiming\": [\n"); - for (int i = 0; i < kMaxEPI3D; i++) { - if (d.CSA.sliceTiming[i] < 0.0) break; - if (i != 0) - fprintf(fp, ",\n"); - fprintf(fp, "\t\t%g", d.CSA.sliceTiming[i] / 1000.0 ); + if (d.CSA.protocolSliceNumber1 > 1) { + //https://github.com/rordenlab/dcm2niix/issues/40 + //equivalent to dicm2nii "s.SliceTiming = s.SliceTiming(end:-1:1);" + int mx = 0; + for (int i = 0; i < kMaxEPI3D; i++) { + if (d.CSA.sliceTiming[i] < 0.0) break; + mx++; + } + mx--; + for (int i = mx; i >= 0; i--) { + if (d.CSA.sliceTiming[i] < 0.0) break; + if (i != mx) + fprintf(fp, ",\n"); + fprintf(fp, "\t\t%g", d.CSA.sliceTiming[i] / 1000.0 ); + } + } else { + for (int i = 0; i < kMaxEPI3D; i++) { + if (d.CSA.sliceTiming[i] < 0.0) break; + if (i != 0) + fprintf(fp, ",\n"); + fprintf(fp, "\t\t%g", d.CSA.sliceTiming[i] / 1000.0 ); + } } fprintf(fp, "\t],\n"); } + //DICOM orientation and phase encoding: useful for 3D undistortion. Original DICOM values: DICOM not NIfTI space, ignores if 3D image re-oriented + fprintf(fp, "\t\"ImageOrientationPatientDICOM\": [\n"); + for (int i = 1; i < 7; i++) { + if (i != 1) + fprintf(fp, ",\n"); + fprintf(fp, "\t\t%g", d.orient[i]); + } + fprintf(fp, "\t],\n"); + if (d.phaseEncodingRC == 'C') fprintf(fp, "\t\"InPlanePhaseEncodingDirectionDICOM\": \"COL\",\n" ); + if (d.phaseEncodingRC == 'R') fprintf(fp, "\t\"InPlanePhaseEncodingDirectionDICOM\": \"ROW\",\n" ); // Finish up with info on the conversion tool fprintf(fp, "\t\"ConversionSoftware\": \"dcm2niix\",\n"); fprintf(fp, "\t\"ConversionSoftwareVersion\": \"%s\"\n", kDCMvers ); @@ -1744,7 +1841,9 @@ int nii_saveNII(char * niiFilename, struct nifti_1_header hdr, unsigned char* im } else printMessage("compression failed %s\n",command); #else //if win else linux - system(command); + int ret = system(command); + if (ret == -1) + printWarning("Failed to execute: %s\n",command); #endif //else linux printMessage("compress: %s\n",command); } @@ -2182,6 +2281,7 @@ void checkSliceTiming(struct TDICOMdata * d, struct TDICOMdata * d1) { if (d->CSA.sliceTiming[i] > maxT) maxT = d->CSA.sliceTiming[i]; } if ((minT != maxT) && (maxT <= d->TR)) return; //looks fine + if ((minT == maxT) && (d->is3DAcq)) return; //fine: 3D EPI if ((minT == maxT) && (d->CSA.multiBandFactor == d->CSA.mosaicSlices)) return; //fine: all slices single excitation if ((strlen(d->seriesDescription) > 0) && (strstr(d->seriesDescription, "SBRef") != NULL)) return; //fine: single-band calibration data, the slice timing WILL exceed the TR //check if 2nd image has valud slice timing @@ -2355,11 +2455,13 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis free(imgM); return EXIT_FAILURE; } + checkSliceTiming(&dcmList[indx0], &dcmList[indx1]); int sliceDir = 0; if (hdr0.dim[3] > 1)sliceDir = headerDcm2Nii2(dcmList[dcmSort[0].indx],dcmList[dcmSort[nConvert-1].indx] , &hdr0, true); //UNCOMMENT NEXT TWO LINES TO RE-ORDER MOSAIC WHERE CSA's protocolSliceNumber does not start with 1 if (dcmList[dcmSort[0].indx].CSA.protocolSliceNumber1 > 1) { - printWarning("Weird CSA 'ProtocolSliceNumber' (%d): SPATIAL, SLICE-ORDER AND DTI TRANSFORMS UNTESTED\n", dcmList[dcmSort[0].indx].CSA.protocolSliceNumber1); + printWarning("Weird CSA 'ProtocolSliceNumber' (System/Miscellaneous/ImageNumbering reversed): VALIDATE SLICETIMING AND BVECS\n"); + //https://www.healthcare.siemens.com/siemens_hwem-hwem_ssxa_websites-context-root/wcm/idc/groups/public/@global/@imaging/@mri/documents/download/mdaz/nzmy/~edisp/mri_60_graessner-01646277.pdf //see https://github.com/neurolabusc/dcm2niix/issues/40 sliceDir = -1; //not sure how to handle negative determinants? } @@ -2367,7 +2469,7 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis 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! } - checkSliceTiming(&dcmList[indx0], &dcmList[indx1]); + //move before headerDcm2Nii2 checkSliceTiming(&dcmList[indx0], &dcmList[indx1]); //nii_SaveBIDS(pathoutname, dcmList[dcmSort[0].indx], opts, dti4D, &hdr0, nameList->str[dcmSort[0].indx]); nii_SaveBIDS(pathoutname, dcmList[dcmSort[0].indx], opts, &hdr0, nameList->str[dcmSort[0].indx]); if (opts.isOnlyBIDS) { @@ -3151,4 +3253,4 @@ void saveIniFile (struct TDCMopts opts) { fclose(fp); } //saveIniFile() -#endif \ No newline at end of file +#endif diff --git a/console/nii_foreign.cpp b/console/nii_foreign.cpp index b1666d66..5219fc40 100644 --- a/console/nii_foreign.cpp +++ b/console/nii_foreign.cpp @@ -175,14 +175,24 @@ PACK( typedef struct { //read list matrix ecat_list_hdr lhdr; fseek(f, 512, SEEK_SET); - fread(&lhdr, sizeof(lhdr), 1, f); + size_t nRead = fread(&lhdr, sizeof(lhdr), 1, f); + if (nRead != 1) { + printMessage("Error reading ECAT file\n"); + fclose(f); + return NULL; + } if (swapEndian) nifti_swap_4bytes(128, &lhdr.hdr[0]); //offset to first image int img_StartBytes = lhdr.r[0][1] * 512; //load image header for first image fseek(f, img_StartBytes - 512, SEEK_SET); //image header is block immediately before image ecat_img_hdr ihdr; - fread(&ihdr, sizeof(ihdr), 1, f); + nRead = fread(&ihdr, sizeof(ihdr), 1, f); + if (nRead != 1) { + printMessage("Error reading ECAT file\n"); + fclose(f); + return NULL; + } if (swapEndian) { nifti_swap_2bytes(5, &ihdr.data_type); nifti_swap_4bytes(5, &ihdr.x_offset); @@ -218,7 +228,12 @@ PACK( typedef struct { while ((lhdr.hdr[0]+lhdr.hdr[3]) == 31) { //while valid list if (num_vol > 0) { //read the next list fseek(f, 512 * (lhdr.hdr[1] -1), SEEK_SET); - fread(&lhdr, 512, 1, f); + nRead =fread(&lhdr, 512, 1, f); + if (nRead != 1) { + printMessage("Error reading ECAT file\n"); + fclose(f); + return NULL; + } if (swapEndian) nifti_swap_4bytes(128, &lhdr.hdr[0]); } if ((lhdr.hdr[0]+lhdr.hdr[3]) != 31) break; //if valid list @@ -226,7 +241,12 @@ PACK( typedef struct { for (int k = 0; k < lhdr.hdr[3]; k++) { //check images' ecat_img_hdr matches first fseek(f, (lhdr.r[k][1]-1) * 512, SEEK_SET); //image header is block immediately before image - fread(&ihdrN, sizeof(ihdrN), 1, f); + nRead = fread(&ihdrN, sizeof(ihdrN), 1, f); + if (nRead != 1) { + printMessage("Error reading ECAT file\n"); + fclose(f); + return NULL; + } if (swapEndian) { nifti_swap_2bytes(5, &ihdrN.data_type); nifti_swap_4bytes(5, &ihdrN.x_offset); @@ -286,7 +306,12 @@ PACK( typedef struct { float * img32 = (float*) img; for (int v = 0; v < num_vol; v++) { fseek(f, imgOffsets[v] * 512, SEEK_SET); - fread( &imgIn[0], 1, bytesPerVolumeIn, f); + nRead = fread( &imgIn[0], 1, bytesPerVolumeIn, f); + if (nRead != 1) { + printMessage("Error reading ECAT file\n"); + fclose(f); + return NULL; + } if (swapEndian) nifti_swap_2bytes(num_vox, imgIn); int volOffset = v * num_vox; diff --git a/dcm_qa b/dcm_qa index 911b5c4e..c7bab67f 160000 --- a/dcm_qa +++ b/dcm_qa @@ -1 +1 @@ -Subproject commit 911b5c4eae42c9ad88c2358b3ae850ed5b1b96cf +Subproject commit c7bab67f14105031776e194d47961749f264e209 diff --git a/old_nii_SaveBIDS.cpp b/old_nii_SaveBIDS.cpp deleted file mode 100644 index a055e184..00000000 --- a/old_nii_SaveBIDS.cpp +++ /dev/null @@ -1,274 +0,0 @@ -void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char * filename) { -//https://docs.google.com/document/d/1HFUkAEE-pB-angVcYe6pf_-fVf4sCpOHKesUvfb8Grc/edit# -// Generate Brain Imaging Data Structure (BIDS) info -// sidecar JSON file (with the same filename as the .nii.gz file, but with .json extension). -// we will use %g for floats since exponents are allowed -// we will not set the locale, so decimal separator is always a period, as required -// https://www.ietf.org/rfc/rfc4627.txt - if (!opts.isCreateBIDS) return; - char txtname[2048] = {""}; - strcpy (txtname,pathoutname); - strcat (txtname,".json"); - FILE *fp = fopen(txtname, "w"); - fprintf(fp, "{\n"); - switch (d.modality) { - case kMODALITY_CR: - fprintf(fp, "\t\"Modality\": \"CR\",\n" ); - break; - case kMODALITY_CT: - fprintf(fp, "\t\"Modality\": \"CT\",\n" ); - break; - case kMODALITY_MR: - fprintf(fp, "\t\"Modality\": \"MR\",\n" ); - break; - case kMODALITY_PT: - fprintf(fp, "\t\"Modality\": \"PT\",\n" ); - break; - case kMODALITY_US: - fprintf(fp, "\t\"Modality\": \"US\",\n" ); - break; - }; - switch (d.manufacturer) { - case kMANUFACTURER_SIEMENS: - fprintf(fp, "\t\"Manufacturer\": \"Siemens\",\n" ); - break; - case kMANUFACTURER_GE: - fprintf(fp, "\t\"Manufacturer\": \"GE\",\n" ); - break; - case kMANUFACTURER_PHILIPS: - fprintf(fp, "\t\"Manufacturer\": \"Philips\",\n" ); - break; - case kMANUFACTURER_TOSHIBA: - fprintf(fp, "\t\"Manufacturer\": \"Toshiba\",\n" ); - break; - }; - fprintf(fp, "\t\"ManufacturersModelName\": \"%s\",\n", d.manufacturersModelName ); - if (!opts.isAnonymizeBIDS) { - if (strlen(d.seriesInstanceUID) > 0) - fprintf(fp, "\t\"SeriesInstanceUID\": \"%s\",\n", d.seriesInstanceUID ); - if (strlen(d.studyInstanceUID) > 0) - fprintf(fp, "\t\"StudyInstanceUID\": \"%s\",\n", d.studyInstanceUID ); - if (strlen(d.referringPhysicianName) > 0) - fprintf(fp, "\t\"ReferringPhysicianName\": \"%s\",\n", d.referringPhysicianName ); - if (strlen(d.studyID) > 0) - fprintf(fp, "\t\"StudyID\": \"%s\",\n", d.studyID ); - //Next lines directly reveal patient identity - //if (strlen(d.patientName) > 0) - // fprintf(fp, "\t\"PatientName\": \"%s\",\n", d.patientName ); - //if (strlen(d.patientID) > 0) - // fprintf(fp, "\t\"PatientID\": \"%s\",\n", d.patientID ); - } - #ifdef myReadAsciiCsa - if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.SeriesHeader_offset > 0) && (d.CSA.SeriesHeader_length > 0)) { - //&& (strlen(d.scanningSequence) > 1) && (d.scanningSequence[0] == 'E') && (d.scanningSequence[1] == 'P')) { //for EPI scans only - int partialFourier, echoSpacing, echoTrainDuration, epiFactor, parallelReductionFactorInPlane; - char fmriExternalInfo[kDICOMStr], coilID[kDICOMStr], consistencyInfo[kDICOMStr], coilElements[kDICOMStr], pulseSequenceDetails[kDICOMStr]; - epiFactor = siemensCsaAscii(filename, d.CSA.SeriesHeader_offset, d.CSA.SeriesHeader_length, &partialFourier, &echoSpacing, &echoTrainDuration, ¶llelReductionFactorInPlane, coilID, consistencyInfo, coilElements, pulseSequenceDetails, fmriExternalInfo); - //printMessage("ES %d ETD %d EPI %d\n", echoSpacing, echoTrainDuration, epiFactor); - if (partialFourier > 0) { - //https://github.com/ismrmrd/siemens_to_ismrmrd/blob/master/parameter_maps/IsmrmrdParameterMap_Siemens_EPI_FLASHREF.xsl - float pf = 1.0f; - if (partialFourier == 1) pf = 0.5; - if (partialFourier == 2) pf = 0.75; - if (partialFourier == 4) pf = 0.875; - fprintf(fp, "\t\"PartialFourier\": %g,\n", pf); - } - if (echoSpacing > 0) - fprintf(fp, "\t\"EchoSpacing\": %g,\n", echoSpacing / 1000000.0); //usec -> sec - if (echoTrainDuration > 0) - fprintf(fp, "\t\"EchoTrainDuration\": %g,\n", echoTrainDuration / 1000000.0); //usec -> sec - if (epiFactor > 0) - fprintf(fp, "\t\"EPIFactor\": %d,\n", epiFactor); - if (strlen(coilID) > 0) - fprintf(fp, "\t\"ReceiveCoilName\": \"%s\",\n", coilID); - if (strlen(coilElements) > 0) - fprintf(fp, "\t\"ReceiveCoilActiveElements\": \"%s\",\n", coilElements); - if (strlen(pulseSequenceDetails) > 0) - fprintf(fp, "\t\"PulseSequenceDetails\": \"%s\",\n", pulseSequenceDetails); - if (strlen(fmriExternalInfo) > 0) - fprintf(fp, "\t\"FmriExternalInfo\": \"%s\",\n", fmriExternalInfo); - if (strlen(consistencyInfo) > 0) - fprintf(fp, "\t\"ConsistencyInfo\": \"%s\",\n", consistencyInfo); - if (parallelReductionFactorInPlane > 0) {//AccelFactorPE -> phase encoding - if (d.accelFactPE < 1.0) d.accelFactPE = parallelReductionFactorInPlane; //value found in ASCII but not in DICOM (0051,1011) - if (parallelReductionFactorInPlane != round(d.accelFactPE)) - printWarning("ParallelReductionFactorInPlane reported in DICOM [0051,1011] (%g) does not match CSA series value %g\n", round(d.accelFactPE), parallelReductionFactorInPlane); - } - } - #endif - if (d.CSA.multiBandFactor > 1) //AccelFactorSlice - fprintf(fp, "\t\"MultibandAccelerationFactor\": %d,\n", d.CSA.multiBandFactor); - if (strlen(d.imageComments) > 0) - fprintf(fp, "\t\"ImageComments\": \"%s\",\n", d.imageComments); - if (strlen(opts.imageComments) > 0) - fprintf(fp, "\t\"ConversionComments\": \"%s\",\n", opts.imageComments); - if (d.echoTrainLength > 1) //>1 as for Siemens EPI this is 1, Siemens uses EPI factor http://mriquestions.com/echo-planar-imaging.html - fprintf(fp, "\t\"EchoTrainLength\": %d,\n", d.echoTrainLength); - if (d.echoNum > 1) - fprintf(fp, "\t\"EchoNumber\": %d,\n", d.echoNum); - if (d.isDerived) //DICOM is derived image or non-spatial file (sounds, etc) - fprintf(fp, "\t\"RawImage\": false,\n"); - if (d.acquNum > 0) - fprintf(fp, "\t\"AcquisitionNumber\": %d,\n", d.acquNum); - if (strlen(d.institutionName) > 0) - fprintf(fp, "\t\"InstitutionName\": \"%s\",\n", d.institutionName ); - if (strlen(d.institutionAddress) > 0) - fprintf(fp, "\t\"InstitutionAddress\": \"%s\",\n", d.institutionAddress ); - if (strlen(d.deviceSerialNumber) > 0) - fprintf(fp, "\t\"DeviceSerialNumber\": \"%s\",\n", d.deviceSerialNumber ); - if (strlen(d.stationName) > 0) - fprintf(fp, "\t\"StationName\": \"%s\",\n", d.stationName ); - if (strlen(d.scanOptions) > 0) - fprintf(fp, "\t\"ScanOptions\": \"%s\",\n", d.scanOptions ); - if (strlen(d.softwareVersions) > 0) - fprintf(fp, "\t\"SoftwareVersions\": \"%s\",\n", d.softwareVersions ); - if (strlen(d.procedureStepDescription) > 0) - fprintf(fp, "\t\"ProcedureStepDescription\": \"%s\",\n", d.procedureStepDescription ); - if (strlen(d.scanningSequence) > 0) - fprintf(fp, "\t\"ScanningSequence\": \"%s\",\n", d.scanningSequence ); - if (strlen(d.sequenceVariant) > 0) - fprintf(fp, "\t\"SequenceVariant\": \"%s\",\n", d.sequenceVariant ); - if (strlen(d.seriesDescription) > 0) - fprintf(fp, "\t\"SeriesDescription\": \"%s\",\n", d.seriesDescription ); - if (strlen(d.bodyPartExamined) > 0) - fprintf(fp, "\t\"BodyPartExamined\": \"%s\",\n", d.bodyPartExamined ); - if (strlen(d.protocolName) > 0) - fprintf(fp, "\t\"ProtocolName\": \"%s\",\n", d.protocolName ); - if (strlen(d.sequenceName) > 0) - fprintf(fp, "\t\"SequenceName\": \"%s\",\n", d.sequenceName ); - if (strlen(d.imageType) > 0) { - fprintf(fp, "\t\"ImageType\": [\""); - bool isSep = false; - for (int i = 0; i < strlen(d.imageType); i++) { - if (d.imageType[i] != '_') { - if (isSep) - fprintf(fp, "\", \""); - isSep = false; - fprintf(fp, "%c", d.imageType[i]); - } else - isSep = true; - } - fprintf(fp, "\"],\n"); - } - //Chris Gorgolewski: BIDS standard specifies ISO8601 date-time format (Example: 2016-07-06T12:49:15.679688) - //Lines below directly save DICOM values - if (d.acquisitionTime > 0.0 && d.acquisitionDate > 0.0){ - long acquisitionDate = d.acquisitionDate; - double acquisitionTime = d.acquisitionTime; - char acqDateTimeBuf[64]; - //snprintf(acqDateTimeBuf, sizeof acqDateTimeBuf, "%+08ld%+08f", acquisitionDate, acquisitionTime); - snprintf(acqDateTimeBuf, sizeof acqDateTimeBuf, "%+08ld%+013.5f", acquisitionDate, acquisitionTime); //CR 20170404 add zero pad so 1:23am appears as +012300.00000 not +12300.00000 - //printMessage("acquisitionDateTime %s\n",acqDateTimeBuf); - int ayear,amonth,aday,ahour,amin; - double asec; - int count = 0; - sscanf(acqDateTimeBuf, "%5d%2d%2d%3d%2d%lf%n", &ayear, &amonth, &aday, &ahour, &amin, &asec, &count); //CR 20170404 %lf not %f for double precision - //printf("-%02d-%02dT%02d:%02d:%02.6f\",\n", amonth, aday, ahour, amin, asec); - if (count) { // ISO 8601 specifies a sign must exist for distant years. - //report time of the day only format, https://www.cs.tut.fi/~jkorpela/iso8601.html - fprintf(fp, "\t\"AcquisitionTime\": \"%02d:%02d:%02.6f\",\n",ahour, amin, asec); - //report date and time together - if (!opts.isAnonymizeBIDS) { - fprintf(fp, "\t\"AcquisitionDateTime\": "); - fprintf(fp, (ayear >= 0 && ayear <= 9999) ? "\"%4d" : "\"%+4d", ayear); - fprintf(fp, "-%02d-%02dT%02d:%02d:%02.6f\",\n", amonth, aday, ahour, amin, asec); - - } - } //if (count) - } //if acquisitionTime and acquisitionDate recorded - // if (d.acquisitionTime > 0.0) fprintf(fp, "\t\"AcquisitionTime\": %f,\n", d.acquisitionTime ); - // if (d.acquisitionDate > 0.0) fprintf(fp, "\t\"AcquisitionDate\": %8.0f,\n", d.acquisitionDate ); - //if conditionals: the following values are required for DICOM MRI, but not available for CT - if ((d.intenScalePhilips != 0) || (d.manufacturer == kMANUFACTURER_PHILIPS)) { //for details, see PhilipsPrecise() - fprintf(fp, "\t\"PhilipsRescaleSlope\": %g,\n", d.intenScale ); - fprintf(fp, "\t\"PhilipsRescaleIntercept\": %g,\n", d.intenIntercept ); - fprintf(fp, "\t\"PhilipsScaleSlope\": %g,\n", d.intenScalePhilips ); - fprintf(fp, "\t\"UsePhilipsFloatNotDisplayScaling\": %d,\n", opts.isPhilipsFloatNotDisplayScaling); - } - //PET ISOTOPE MODULE ATTRIBUTES - if (d.radionuclidePositronFraction > 0.0) fprintf(fp, "\t\"RadionuclidePositronFraction\": %g,\n", d.radionuclidePositronFraction ); - if (d.radionuclideTotalDose > 0.0) fprintf(fp, "\t\"RadionuclideTotalDose\": %g,\n", d.radionuclideTotalDose ); - if (d.radionuclideHalfLife > 0.0) fprintf(fp, "\t\"RadionuclideHalfLife\": %g,\n", d.radionuclideHalfLife ); - if (d.doseCalibrationFactor > 0.0) fprintf(fp, "\t\"DoseCalibrationFactor\": %g,\n", d.doseCalibrationFactor ); - //MRI parameters - if (d.fieldStrength > 0.0) fprintf(fp, "\t\"MagneticFieldStrength\": %g,\n", d.fieldStrength ); - if (d.flipAngle > 0.0) fprintf(fp, "\t\"FlipAngle\": %g,\n", d.flipAngle ); - if ((d.TE > 0.0) && (!d.isXRay)) fprintf(fp, "\t\"EchoTime\": %g,\n", d.TE / 1000.0 ); - if ((d.TE > 0.0) && (d.isXRay)) fprintf(fp, "\t\"XRayExposure\": %g,\n", d.TE ); - if (d.TR > 0.0) fprintf(fp, "\t\"RepetitionTime\": %g,\n", d.TR / 1000.0 ); - if (d.TI > 0.0) fprintf(fp, "\t\"InversionTime\": %g,\n", d.TI / 1000.0 ); - if (d.ecat_isotope_halflife > 0.0) fprintf(fp, "\t\"IsotopeHalfLife\": %g,\n", d.ecat_isotope_halflife); - if (d.ecat_dosage > 0.0) fprintf(fp, "\t\"Dosage\": %g,\n", d.ecat_dosage); - double bandwidthPerPixelPhaseEncode = d.bandwidthPerPixelPhaseEncode; - int phaseEncodingLines = d.phaseEncodingLines; - if ((phaseEncodingLines == 0) && (h->dim[2] > 0) && (h->dim[1] > 0)) { - if (h->dim[2] == h->dim[2]) //phase encoding does not matter - phaseEncodingLines = h->dim[2]; - else if (d.phaseEncodingRC =='R') - phaseEncodingLines = h->dim[2]; - else if (d.phaseEncodingRC =='C') - phaseEncodingLines = h->dim[1]; - } - if (bandwidthPerPixelPhaseEncode == 0.0) - bandwidthPerPixelPhaseEncode = d.CSA.bandwidthPerPixelPhaseEncode; - if (phaseEncodingLines > 0.0) fprintf(fp, "\t\"PhaseEncodingLines\": %d,\n", phaseEncodingLines ); - if (bandwidthPerPixelPhaseEncode > 0.0) - fprintf(fp, "\t\"BandwidthPerPixelPhaseEncode\": %g,\n", bandwidthPerPixelPhaseEncode ); - double effectiveEchoSpacing = 0.0; - if ((phaseEncodingLines > 0) && (bandwidthPerPixelPhaseEncode > 0.0)) - effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * phaseEncodingLines) ; - if (d.effectiveEchoSpacingGE > 0.0) - effectiveEchoSpacing = d.effectiveEchoSpacingGE / 1000000.0; - if (effectiveEchoSpacing > 0.0) - fprintf(fp, "\t\"EffectiveEchoSpacing\": %g,\n", effectiveEchoSpacing); - //FSL definition is start of first line until start of last line, so n-1 unless accelerated in-plane acquisition - // to check: partial Fourier, iPAT, etc. - int fencePost = 1; - if (d.accelFactPE > 1.0) - fencePost = (int)round(d.accelFactPE); //e.g. if 64 lines with iPAT=2, we want time from start of first until start of 62nd effective line - if ((d.phaseEncodingSteps > 1) && (effectiveEchoSpacing > 0.0)) - fprintf(fp, "\t\"TotalReadoutTime\": %g,\n", effectiveEchoSpacing * ((float)d.phaseEncodingSteps - fencePost)); - if (d.accelFactPE > 1.0) { - fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); - if (effectiveEchoSpacing > 0.0) - fprintf(fp, "\t\"TrueEchoSpacing\": %g,\n", effectiveEchoSpacing * d.accelFactPE); - } - if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.dwellTime > 0)) - fprintf(fp, "\t\"DwellTime\": %g,\n", d.dwellTime * 1E-9); - if (d.CSA.sliceTiming[0] >= 0.0) { - fprintf(fp, "\t\"SliceTiming\": [\n"); - for (int i = 0; i < kMaxEPI3D; i++) { - if (d.CSA.sliceTiming[i] < 0.0) break; - if (i != 0) - fprintf(fp, ",\n"); - fprintf(fp, "\t\t%g", d.CSA.sliceTiming[i] / 1000.0 ); - } - fprintf(fp, "\t],\n"); - } - if (((d.phaseEncodingRC == 'R') || (d.phaseEncodingRC == 'C')) && (!d.is3DAcq) && ((d.CSA.phaseEncodingDirectionPositive == 1) || (d.CSA.phaseEncodingDirectionPositive == 0))) { - if (d.phaseEncodingRC == 'C') //Values should be "R"ow, "C"olumn or "?"Unknown - fprintf(fp, "\t\"PhaseEncodingDirection\": \"j"); - else if (d.phaseEncodingRC == 'R') - fprintf(fp, "\t\"PhaseEncodingDirection\": \"i"); - else - fprintf(fp, "\t\"PhaseEncodingDirection\": \"?"); - //phaseEncodingDirectionPositive has one of three values: UNKNOWN (-1), NEGATIVE (0), POSITIVE (1) - //However, DICOM and NIfTI are reversed in the j (ROW) direction - //Equivalent to dicm2nii's "if flp(iPhase), phPos = ~phPos; end" - //for samples see https://github.com/rordenlab/dcm2niix/issues/125 - if (d.CSA.phaseEncodingDirectionPositive == -1) - fprintf(fp, "?"); //unknown - else if ((d.CSA.phaseEncodingDirectionPositive == 0) && (d.phaseEncodingRC != 'C')) - fprintf(fp, "-"); - else if ((d.phaseEncodingRC == 'C') && (d.CSA.phaseEncodingDirectionPositive == 1) && (opts.isFlipY)) - fprintf(fp, "-"); - else if ((d.phaseEncodingRC == 'C') && (d.CSA.phaseEncodingDirectionPositive == 0) && (!opts.isFlipY)) - fprintf(fp, "-"); - fprintf(fp, "\",\n"); - } //only save PhaseEncodingDirection if BOTH direction and POLARITY are known - fprintf(fp, "\t\"ConversionSoftware\": \"dcm2niix\",\n"); - fprintf(fp, "\t\"ConversionSoftwareVersion\": \"%s\"\n", kDCMvers ); - //fprintf(fp, "\t\"DicomConversion\": [\"dcm2niix\", \"%s\"]\n", kDCMvers ); - fprintf(fp, "}\n"); - fclose(fp); -}// nii_SaveBIDS()