From e0e15a9ce549c20ec15530f1b60d74de01b0576e Mon Sep 17 00:00:00 2001 From: Tashrif Billah Date: Fri, 6 Nov 2020 23:13:59 -0500 Subject: [PATCH 01/22] correct name and source for PNL conversion library --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b9d2bb1e..1ebe4d1b 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,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. - - [PNL-nipype](https://github.com/pnlbwh/Dummy-PNL-nipype) is a Python script 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. + - [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. - [pydcm2niix is a Python module for working with dcm2niix](https://github.com/jstutters/pydcm2niix). - [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. - [qsm](https://github.com/CAIsr/qsm) Quantitative Susceptibility Mapping software. From cb9948702936b0982adf6a5cebaa98e6a5d42064 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Tue, 17 Nov 2020 16:17:34 +0000 Subject: [PATCH 02/22] Remove redundant underscores before creating subdirectories --- console/nii_dicom_batch.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 0b0a6a0f..6e227938 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -2714,6 +2714,18 @@ int nii_createFilename(struct TDICOMdata dcm, char * niiFilename, struct TDCMopt appendChar[0] = kPathSeparator; if ((strlen(pth) > 0) && (pth[strlen(pth)-1] != kPathSeparator) && (outname[0] != kPathSeparator)) strcat (baseoutname,appendChar); + + //remove redundant underscores + int len = strlen(outname); + int outpos = 0; + for (int inpos = 0; inpos < len; inpos ++) { + if ((outpos > 0) && (outname[inpos] == '_') && (outname[outpos-1] == '_')) + continue; + outname[outpos] = outname[inpos]; + outpos++; + } + outname[outpos] = 0; + //Allow user to specify new folders, e.g. "-f dir/%p" or "-f %s/%p/%m" // These folders are created if they do not exist char *sep = strchr(outname, kPathSeparator); @@ -2741,16 +2753,6 @@ int nii_createFilename(struct TDICOMdata dcm, char * niiFilename, struct TDCMopt strcat (newdir,ch); } } - //remove redundant underscores - int len = strlen(outname); - int outpos = 0; - for (int inpos = 0; inpos < len; inpos ++) { - if ((outpos > 0) && (outname[inpos] == '_') && (outname[outpos-1] == '_')) - continue; - outname[outpos] = outname[inpos]; - outpos++; - } - outname[outpos] = 0; //printMessage("path='%s' name='%s'\n", pathoutname, outname); //make sure outname is unique strcat (baseoutname,outname); From c57c37109f9e315d337038aa07a6c468438a1106 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 17 Nov 2020 13:48:10 -0500 Subject: [PATCH 03/22] Only report b-value for GE data with 0043,1039 if EPI (0018,9018) (https://github.com/rordenlab/dcm2niix/issues/449) --- console/nii_dicom.cpp | 3 --- console/nii_dicom_batch.cpp | 12 +++++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 9a6d77a4..160285aa 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -6708,9 +6708,6 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); } //printMessage(" tag=%04x,%04x length=%u pos=%ld %c%c nest=%d\n", groupElement & 65535,groupElement>>16, lLength, lPos,vr[0], vr[1], nest); #endif lPos = lPos + (lLength); - //printMessage("%d\n",d.imageStart); - //printMessage(" DWI bxyz %g %g %g %g %d\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3], d.CSA.numDti); - } //while d.imageStart == 0 free (buffer); if (d.bitsStored < 0) d.isValid = false; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 0b0a6a0f..eb1d3ea9 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -190,6 +190,7 @@ void geCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx, int isV //COL then if swap the x and y value and reverse the sign on the z value. //If the phase encoding is not COL, then just reverse the sign on the x value. if ((d->manufacturer != kMANUFACTURER_GE) && (d->manufacturer != kMANUFACTURER_CANON)) return; + if ((!d->isEPI) && (d->CSA.numDti == 1)) d->CSA.numDti = 0; //issue449 if (d->CSA.numDti < 1) return; if ((toupper(d->patientOrient[0])== 'H') && (toupper(d->patientOrient[1])== 'F') && (toupper(d->patientOrient[2])== 'S')) ; //participant was head first supine @@ -1635,7 +1636,7 @@ tse3d: T2*/ } //only save PhaseEncodingDirection if BOTH direction and POLARITY are known //Slice Timing UIH or GE >>>> //in theory, we should also report XA10 slice times here, but see series 24 of https://github.com/rordenlab/dcm2niix/issues/236 - if ((d.modality != kMODALITY_PT) && (!d.is3DAcq) && (d.CSA.sliceTiming[0] >= 0.0)) { + if ((d.modality != kMODALITY_CT) && (d.modality != kMODALITY_PT) && (!d.is3DAcq) && (d.CSA.sliceTiming[0] >= 0.0)) { fprintf(fp, "\t\"SliceTiming\": [\n"); for (int i = 0; i < h->dim[3]; i++) { if (i != 0) @@ -2011,6 +2012,10 @@ int * nii_saveDTI(char pathoutname[],int nConvert, struct TDCMsort dcmSort[],str dcmList[indx0].CSA.numDti = numDti; //warning structure not changed outside scope! geCorrectBvecs(&dcmList[indx0],sliceDir, vx, opts.isVerbose); siemensPhilipsCorrectBvecs(&dcmList[indx0],sliceDir, vx, opts.isVerbose); + if (dcmList[indx0].CSA.numDti < 1) { //issue449 + free(vx); + return NULL; + } if (!opts.isFlipY ) { //!FLIP_Y&& (dcmList[indx0].CSA.mosaicSlices < 2) mosaics are always flipped in the Y direction for (int i = 0; i < (numDti); i++) { if (fabs(vx[i].V[2]) > FLT_EPSILON) @@ -5011,7 +5016,12 @@ int sliceTimingCore(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struct dcmList[dcmSort[0].indx].CSA.protocolSliceNumber1 = -1; } sliceTimingGE(d0, filename, opts, hdr, dcmSort, dcmList); + //ensure slice times have variability reverseSliceTiming(d0, verbose, hdr->dim[3]); + bool allSame = true; + for (int i = 0; i < hdr->dim[3]; i++) + if (!isSameFloatGE(d0->CSA.sliceTiming[i], d0->CSA.sliceTiming[0])) allSame = false; + if (allSame) d0->CSA.sliceTiming[0] = - 1.0; return sliceDir; } //sliceTiming() From ede3f49d4d78584f6694d6f2441480b7fc0d0b50 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 18 Nov 2020 08:17:08 -0500 Subject: [PATCH 04/22] Fix CMake for Apple Silicon (note similar change required for CloudFlare zlib) --- console/CMakeLists.txt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index e28566c9..0445c6fa 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -53,10 +53,15 @@ endif() # Compiler dependent flags include (CheckCXXCompilerFlag) if(UNIX) - check_cxx_compiler_flag(-msse2 HAS_SSE2) - if(HAS_SSE2) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2 -mfpmath=sse") - endif() + check_c_compiler_flag(-march=armv8-a+crc ARM_CRC) + if(ARM_CRC) + # wrong answer for Apple Silicon: check_cxx_compiler_flag(-msse2 HAS_SSE2) + else() + check_cxx_compiler_flag(-msse2 HAS_SSE2) + if(HAS_SSE2) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2 -mfpmath=sse") + endif() + endif() endif() set(PROGRAMS dcm2niix) From ffa8435b4c28367d3e0166d5cd6c0dd5233fd109 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 18 Nov 2020 13:49:30 -0500 Subject: [PATCH 05/22] Apple. M1. use C++ not. C --- console/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index 0445c6fa..05a16fbc 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -53,7 +53,7 @@ endif() # Compiler dependent flags include (CheckCXXCompilerFlag) if(UNIX) - check_c_compiler_flag(-march=armv8-a+crc ARM_CRC) + check_cxx_compiler_flag(-march=armv8-a+crc ARM_CRC) if(ARM_CRC) # wrong answer for Apple Silicon: check_cxx_compiler_flag(-msse2 HAS_SSE2) else() From 70f5fdf83a76413211cb0e6f5cc2a65afb6a2e86 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 24 Nov 2020 15:43:10 -0500 Subject: [PATCH 06/22] Use 1st study time if multiple times provided. --- 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 160285aa..902812f8 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -5216,7 +5216,8 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); dcmStr(lLength, &buffer[lPos], seriesTimeTxt); break; case kStudyTime : - dcmStr(lLength, &buffer[lPos], d.studyTime); + if (strlen(d.studyTime) < 2) + dcmStr(lLength, &buffer[lPos], d.studyTime); break; case kPatientName : dcmStr(lLength, &buffer[lPos], d.patientName); From d6acc85fad9bae989dde373b4bd3c32fc4374f2d Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 27 Nov 2020 06:29:45 -0500 Subject: [PATCH 07/22] When -n is specified, only save BIDS for requested series (https://github.com/rordenlab/dcm2niix/issues/453) --- console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 41 +++++++++++++++++-------------------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 3fa01c4a..1574821d 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.20201102" +#define kDCMdate "v1.0.20201127" #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 dd78fd8f..bd93b0e1 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -5509,8 +5509,25 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc free(imgM); return EXIT_FAILURE; } - //issue377(dcmList[indx0], &hdr0); return EXIT_SUCCESS; - nii_SaveBIDSX(pathoutname, dcmList[dcmSort[0].indx], opts, &hdr0, nameList->str[dcmSort[0].indx], dti4D); + + // skip converting if user has specified one or more series, but has not specified this one + if (opts.numSeries > 0) { //issue453: moved to before saveBIDS + int i = 0; + 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; + for (; i < opts.numSeries; i++) { + if (isSameDouble(opts.seriesNumber[i], seriesNum)) + break; + } + if (i == opts.numSeries) + return EXIT_SUCCESS; + } + if (opts.numSeries >= 0) //issue453 + 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 free(imgM); @@ -5572,26 +5589,6 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc 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! - } - // skip converting if user has specified one or more series, but has not specified this one - if (opts.numSeries > 0) { - int i = 0; - //double seriesNum = (double) dcmList[dcmSort[0].indx].seriesNum; - 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; - for (; i < opts.numSeries; i++) { - if (isSameDouble(opts.seriesNumber[i], seriesNum)) { - //if (opts.seriesNumber[i] == dcmList[dcmSort[0].indx].seriesNum) { - break; - } - } - if (i == opts.numSeries) { - return EXIT_SUCCESS; - } } nii_saveText(pathoutname, dcmList[dcmSort[0].indx], opts, &hdr0, nameList->str[indx]); int numADC = 0; From 25e314a68b1b0ffd956360465075d2fc19887805 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 2 Dec 2020 07:26:05 -0500 Subject: [PATCH 08/22] Single file mode memory allocation (https://github.com/rordenlab/dcm2niix/issues/454) --- console/nii_dicom_batch.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index bd93b0e1..d1b6ef3d 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -4996,8 +4996,9 @@ int sliceTimingCore(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struct //uint64_t indx0 = dcmSort[0].indx; //uint64_t indx1 = dcmSort[1].indx; struct TDICOMdata * d0 = &dcmList[dcmSort[0].indx]; - uint64_t indx1 = dcmSort[1].indx; - if (nConvert < 2) indx1 = dcmSort[0].indx; + uint64_t indx1 = dcmSort[0].indx; + if (nConvert > 1) //use 2nd volume as CMRR bug can create bogus slice timing in first volume + indx1 = dcmSort[1].indx; struct TDICOMdata * d1 = &dcmList[indx1]; //oldSliceTimingGE(dcmSort, dcmList, hdr, verbose, filename, nConvert); sliceTimingUIH(dcmSort, dcmList, hdr, verbose, filename, nConvert); @@ -6166,12 +6167,13 @@ int singleDICOM(struct TDCMopts* opts, char *fname) { nameList.str[nameList.numItems] = (char *)malloc(strlen(fname)+1); strcpy(nameList.str[nameList.numItems],fname); nameList.numItems++; - struct TDCMsort dcmSort[1]; + TDCMsort * dcmSort = (TDCMsort *)malloc(sizeof(TDCMsort)); dcmList[0].converted2NII = 1; dcmList[0] = readDICOMv(nameList.str[0], opts->isVerbose, opts->compressFlag, &dti4D); //ignore compile warning - memory only freed on first of 2 passes fillTDCMsort(dcmSort[0], 0, dcmList[0]); int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, &dti4D); freeNameList(nameList); + free(dcmSort); return ret; }// singleDICOM() From 8725ec5b51486282e2aef37f66fc28558352f033 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 2 Dec 2020 13:21:45 -0500 Subject: [PATCH 09/22] leak (https://github.com/rordenlab/dcm2niix/issues/454) --- console/nii_dicom_batch.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index d1b6ef3d..b4928481 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -6174,6 +6174,7 @@ int singleDICOM(struct TDCMopts* opts, char *fname) { int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, &dti4D); freeNameList(nameList); free(dcmSort); + free(dcmList); return ret; }// singleDICOM() From 14a66ae069c27e8588d95872e169acad2db68183 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 4 Dec 2020 06:20:58 -0500 Subject: [PATCH 10/22] Clear RepetitionTimeExcitation (https://github.com/rordenlab/dcm2niix/issues/457) --- console/nii_dicom.cpp | 4 ---- console/nii_dicom_batch.cpp | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 902812f8..e9c0743c 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -1653,8 +1653,6 @@ dti4D->volumeOnsetTime[0] = -1; dti4D->decayFactor[0] = -1; dti4D->frameDuration[0] = -1; dti4D->intenScale[0] = 0.0; -dti4D->repetitionTimeExcitation = 0.0; -dti4D->repetitionTimeInversion = 0.0; strcpy(d.protocolName, ""); //erase dummy with empty strcpy(d.seriesDescription, ""); //erase dummy with empty strcpy(d.sequenceName, ""); //erase dummy with empty @@ -4064,8 +4062,6 @@ struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, stru dti4D->decayFactor[0] = -1; dti4D->frameDuration[0] = -1; dti4D->intenScale[0] = 0.0; - dti4D->repetitionTimeExcitation = 0.0; - dti4D->repetitionTimeInversion = 0.0; struct TVolumeDiffusion volDiffusion = initTVolumeDiffusion(&d, dti4D); struct stat s; if( stat(fname,&s) == 0 ) { diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index b4928481..113c4873 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1670,6 +1670,7 @@ dti4D->volumeOnsetTime[0] = -1; dti4D->decayFactor[0] = -1; dti4D->intenScale[0] = 0.0; dti4D->repetitionTimeExcitation = 0.0; +dti4D->repetitionTimeInversion = 0.0; nii_SaveBIDSX(pathoutname, d, opts, h, filename, dti4D); }// nii_SaveBIDSX() @@ -5099,6 +5100,8 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc 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" #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 8acdc7bcac48272d878162e04a877b939b78ed45 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Mon, 7 Dec 2020 12:51:29 -0500 Subject: [PATCH 11/22] Convert DICOM series where bit allocated (0028,0100) varies across slices (https://github.com/rordenlab/dcm2niix/issues/458) --- console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 1574821d..810877ba 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.20201127" +#define kDCMdate "v1.0.20201207" #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 113c4873..ddeb2864 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -2308,15 +2308,16 @@ bool intensityScaleVaries(int nConvert, struct TDCMsort dcmSort[],struct TDICOMd //some Siemens PET scanners generate 16-bit images where slice has its own scaling factor. // since NIfTI provides a single scaling factor for each file, these images require special consideration if (nConvert < 2) return false; - bool iVaries = false; + int dt = dcmList[dcmSort[0].indx].bitsAllocated; float iScale = dcmList[dcmSort[0].indx].intenScale; float iInter = dcmList[dcmSort[0].indx].intenIntercept; for (int i = 1; i < nConvert; i++) { //stack additional images uint64_t indx = dcmSort[i].indx; - if (fabs (dcmList[indx].intenScale - iScale) > FLT_EPSILON) iVaries = true; - if (fabs (dcmList[indx].intenIntercept- iInter) > FLT_EPSILON) iVaries = true; + if (dcmList[indx].bitsAllocated != dt) return true; + if (fabs (dcmList[indx].intenScale - iScale) > FLT_EPSILON) return true; + if (fabs (dcmList[indx].intenIntercept- iInter) > FLT_EPSILON) return true; } - return iVaries; + return false; } //intensityScaleVaries() /*unsigned char * nii_bgr2rgb(unsigned char* bImg, struct nifti_1_header *hdr) { @@ -5094,7 +5095,7 @@ void loadOverlay(char* imgname, unsigned char * img, int offset, int x, int y, i int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts, struct TDTI4D *dti4D, int segVol) { bool iVaries = intensityScaleVaries(nConvert,dcmSort,dcmList); - float *sliceMMarray = NULL; //only used if slices are not equidistant + 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; @@ -5513,7 +5514,6 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc free(imgM); return EXIT_FAILURE; } - // skip converting if user has specified one or more series, but has not specified this one if (opts.numSeries > 0) { //issue453: moved to before saveBIDS int i = 0; @@ -5735,6 +5735,8 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc #endif imgM = removeADC(&hdr0, imgM, numADC); #ifndef USING_R + if (iVaries) + printMessage("Saving as 32-bit float (slope, intercept or bits allocated varies).\n"); if (opts.isSaveNRRD) returnCode = nii_saveNRRD(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx], dti4D, dcmList[indx0].CSA.numDti); else if (opts.isSave3D) @@ -6000,13 +6002,13 @@ int isSameFloatDouble (double a, double b) { } struct TWarnings { //generate a warning only once per set - bool acqNumVaries, bitDepthVaries, dateTimeVaries, echoVaries, triggerVaries, phaseVaries, coilVaries, forceStackSeries, seriesUidVaries, nameVaries, nameEmpty, orientVaries; + bool acqNumVaries, dimensionVaries, dateTimeVaries, echoVaries, triggerVaries, phaseVaries, coilVaries, forceStackSeries, seriesUidVaries, nameVaries, nameEmpty, orientVaries; }; TWarnings setWarnings() { TWarnings r; r.acqNumVaries = false; - r.bitDepthVaries = false; + r.dimensionVaries = false; r.dateTimeVaries = false; r.phaseVaries = false; r.echoVaries = false; @@ -6062,10 +6064,10 @@ bool isSameSet (struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts* opt if ((isSameStudyInstanceUID) && (d1.isXA10A) && (d2.isXA10A)) isSameTime = true; //kludge XA10A 0008,0030 incorrect https://github.com/rordenlab/dcm2niix/issues/236 if ((!isSameStudyInstanceUID) && (!isSameTime)) return false; - if ((d1.bitsAllocated != d2.bitsAllocated) || (d1.xyzDim[1] != d2.xyzDim[1]) || (d1.xyzDim[2] != d2.xyzDim[2]) || (d1.xyzDim[3] != d2.xyzDim[3]) ) { - if (!warnings->bitDepthVaries) - printMessage("Slices not stacked: dimensions or bit-depth varies\n"); - warnings->bitDepthVaries = true; + if ( (d1.xyzDim[1] != d2.xyzDim[1]) || (d1.xyzDim[2] != d2.xyzDim[2]) || (d1.xyzDim[3] != d2.xyzDim[3]) ) { + if (!warnings->dimensionVaries) + printMessage("Slices not stacked: dimensions vary across slices\n"); + warnings->dimensionVaries = true; return false; } #ifndef myIgnoreStudyTime From f827a591336a2ee2ae5c4861b97095a60ed48d2a Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Mon, 7 Dec 2020 15:37:48 -0500 Subject: [PATCH 12/22] Ignore LocationsInAcquisition (0020,1002) if number of slices is not evenly divisible with this value (https://github.com/rordenlab/dcm2niix/issues/459) --- console/nii_dicom.cpp | 1 - console/nii_dicom_batch.cpp | 9 +++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index e9c0743c..51550369 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -6895,7 +6895,6 @@ if (d.isHasPhase) if (!isnan(patientPositionStartPhilips[1])) //for Philips data without for (int k = 0; k < 4; k++) d.patientPosition[k] = patientPositionStartPhilips[k]; - //printMessage("%d %g\n", d.imageNum, sliceLocation); //shame this tag is optional if (isVerbose) { printMessage("DICOM file: %s\n", fname); printMessage(" patient position (0020,0032)\t%g\t%g\t%g\n", d.patientPosition[1],d.patientPosition[2],d.patientPosition[3]); diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index ddeb2864..31d502eb 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -5190,8 +5190,13 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc dcmList[indx0].locationsInAcquisition = dcmList[indx0].locationsInAcquisitionConflict; } } - if ((nAcq == 1 ) && (dcmList[indx0].locationsInAcquisition > 0)) nAcq = nConvert/dcmList[indx0].locationsInAcquisition; - if (nAcq < 2 ) { + if ((nConvert > 1) && (nAcq == 1 ) && (dcmList[indx0].locationsInAcquisition > 0) ){ + if ((nConvert % dcmList[indx0].locationsInAcquisition) == 0) + nAcq = nConvert / dcmList[indx0].locationsInAcquisition; + 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++; From 7f9b266f416b7575e9f6728dacba07f7a77cfd5a Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 9 Dec 2020 08:57:51 -0500 Subject: [PATCH 13/22] PAR/REC field map calibrated in Hz given name _fieldmaphz (https://github.com/rordenlab/dcm2niix/issues/455) --- console/nii_dicom.cpp | 23 +++++++++++++++++------ console/nii_dicom.h | 4 ++-- console/nii_dicom_batch.cpp | 11 ++++++++--- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 51550369..ea295aaa 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -848,6 +848,7 @@ struct TDICOMdata clear_dicom_data() { d.overlayStart[i] = 0; d.isHasOverlay = false; d.isPrivateCreatorRemap = false; + d.isRealIsPhaseMapHz = false; d.numberOfImagesInGridUIH = 0; d.phaseEncodingRC = '?'; d.patientSex = '?'; @@ -2017,9 +2018,10 @@ int kbval = 33; //V3: 27 } if (cols[kImageType] == 0) d.isHasMagnitude = true; if (cols[kImageType] != 0) d.isHasPhase = true; - if ((isSameFloat(cols[kImageType],18)) && (!isTypeWarning)) { - printWarning("Field map in Hz will be saved as the 'real' image.\n"); - isTypeWarning = true; + if (isSameFloat(cols[kImageType],18)) { + //printWarning("Field map in Hz will be saved as the 'real' image.\n"); + //isTypeWarning = true; + d.isRealIsPhaseMapHz = true; } else if (((cols[kImageType] < 0.0) || (cols[kImageType] > 4.0)) && (!isTypeWarning)) { printError("Unknown type %g: not magnitude[0], real[1], imaginary[2] or phase[3].\n", cols[kImageType]); isTypeWarning = true; @@ -2107,6 +2109,10 @@ int kbval = 33; //V3: 27 bool isReal = (cols[kImageType] == 1); bool isImaginary = (cols[kImageType] == 2); bool isPhase = (cols[kImageType] == 3); + if (cols[kImageType] == 18) { + isReal = true; + d.isRealIsPhaseMapHz = true; + } if (cols[kImageType] == 4) { if (!isType4Warning) { printWarning("Unknown image type (4). Be aware the 'phase' image is of an unknown type.\n"); @@ -2114,8 +2120,13 @@ int kbval = 33; //V3: 27 } isPhase = true; //2019 } - if ((cols[kImageType] < 0.0) || (cols[kImageType] > 3.0)) + if ((cols[kImageType] != 18) && ((cols[kImageType] < 0.0) || (cols[kImageType] > 3.0))) { + if (!isType4Warning) { + printWarning("Unknown image type (%g). Be aware the 'phase' image is of an unknown type.\n", round(cols[kImageType])); + isType4Warning = true; + } isReal = true; //<- this is not correct, kludge for bug in ROGERS_20180526_WIP_B0_NS_8_1.PAR + } if (isReal) vol += num3DExpected; if (isImaginary) vol += (2*num3DExpected); if (isPhase) vol += (3*num3DExpected); @@ -5171,7 +5182,7 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); //issue 256: Philips files report real ComplexImageComponent but Magnitude ImageType https://github.com/rordenlab/dcm2niix/issues/256 isPhase = false; isReal = false; - isImaginary = false; + isImaginary = false; isMagnitude = false; //see Table C.8-85 http://dicom.nema.org/medical/Dicom/2017c/output/chtml/part03/sect_C.8.13.3.html if ((buffer[lPos]=='R') && (toupper(buffer[lPos+1]) == 'E')) @@ -5185,7 +5196,7 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); //not mutually exclusive: possible for Philips enhanced DICOM to store BOTH magnitude and phase in the same image if (isPhase) d.isHasPhase = true; if (isReal) d.isHasReal = true; - if (isImaginary) d.isHasImaginary = true; + if (isImaginary) d.isHasImaginary = true; if (isMagnitude) d.isHasMagnitude = true; break; case kAcquisitionContrast: diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 810877ba..8acd9a7a 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -139,7 +139,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; bool isReal[kMaxDTI4D]; bool isImaginary[kMaxDTI4D]; bool isPhase[kMaxDTI4D]; - float repetitionTimeExcitation, repetitionTimeInversion; + float repetitionTimeExcitation, repetitionTimeInversion; }; #ifdef _MSC_VER //Microsoft nomenclature for packed structures is different... @@ -192,7 +192,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; char institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge]; uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; struct TCSAdata CSA; - bool isPrivateCreatorRemap, isHasOverlay, isEPI, isIR, isPartialFourier, isDiffusion, isVectorFromBMatrix, isRawDataStorage, isGrayscaleSoftcopyPresentationState, isStackableSeries, isCoilVaries, isNonParallelSlices, isSegamiOasis, isXA10A, isScaleOrTEVaries, isScaleVariesEnh, isDerived, isXRay, isMultiEcho, isValid, is3DAcq, is2DAcq, isExplicitVR, isLittleEndian, isPlanarRGB, isSigned, isHasPhase, isHasImaginary, isHasReal, isHasMagnitude,isHasMixed, isFloat, isResampled, isLocalizer; + bool isRealIsPhaseMapHz, isPrivateCreatorRemap, isHasOverlay, isEPI, isIR, isPartialFourier, isDiffusion, isVectorFromBMatrix, isRawDataStorage, isGrayscaleSoftcopyPresentationState, isStackableSeries, isCoilVaries, isNonParallelSlices, isSegamiOasis, isXA10A, isScaleOrTEVaries, isScaleVariesEnh, isDerived, isXRay, isMultiEcho, isValid, is3DAcq, is2DAcq, isExplicitVR, isLittleEndian, isPlanarRGB, isSigned, isHasPhase, isHasImaginary, isHasReal, isHasMagnitude,isHasMixed, isFloat, isResampled, isLocalizer; char phaseEncodingRC, patientSex; }; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 31d502eb..4c593e78 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1140,6 +1140,8 @@ tse3d: T2*/ fprintf(fp,"\", \"REAL"); if ((d.isHasImaginary) && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_IMAGINARY_") == NULL)) ) fprintf(fp,"\", \"IMAGINARY"); + if ((d.isRealIsPhaseMapHz))// && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_IMAGINARY_") == NULL)) ) + fprintf(fp,"\", \"FIELDMAPHZ"); fprintf(fp, "\"],\n"); } if (d.isDerived) //DICOM is derived image or non-spatial file (sounds, etc) @@ -2667,7 +2669,10 @@ int nii_createFilename(struct TDICOMdata dcm, char * niiFilename, struct TDCMopt if ((isAddNamePostFixes) && (dcm.isHasImaginary)) { strcat (outname,"_imaginary"); //has phase map } - if ((isAddNamePostFixes) && (dcm.isHasReal)) { + if ((isAddNamePostFixes) && (dcm.isHasReal) && (dcm.isRealIsPhaseMapHz)) { + strcat (outname,"_fieldmaphz"); //has field map + } + if ((isAddNamePostFixes) && (dcm.isHasReal) && (!dcm.isRealIsPhaseMapHz)) { strcat (outname,"_real"); //has phase map } if ((isAddNamePostFixes) && (dcm.isHasPhase)) { @@ -6090,9 +6095,9 @@ bool isSameSet (struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts* opt //} 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))) { + if ((d1.isHasImaginary != d2.isHasImaginary) || (d1.isHasPhase != d2.isHasPhase) || (d1.isHasReal != d2.isHasReal)) { if (!warnings->phaseVaries) - printMessage("Slices not stacked: some are phase/real/imaginary maps, others are not. Instances %d %d\n", d1.imageNum, d2.imageNum); + printMessage("Slices not stacked: some are phase/real/imaginary/phase maps, others are not. Instances %d %d\n", d1.imageNum, d2.imageNum); warnings->phaseVaries = true; return false; } From 2b96ee9fa26a4045434675b421d41725024937c9 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Thu, 10 Dec 2020 11:11:32 -0500 Subject: [PATCH 14/22] DICOM field map calibrated in Hz given name _fieldmaphz (https://github.com/rordenlab/dcm2niix/issues/455) --- console/nii_dicom.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index ea295aaa..0b642fbb 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -5074,6 +5074,8 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); //d.isDerived = true; //this would have 'i- y' skip MoCo images isMoCo = true; } + if ((slen > 5) && strstr(d.imageType, "B0") && strstr(d.imageType, "MAP")) + d.isRealIsPhaseMapHz = true; if((slen > 5) && strstr(d.imageType, "_ADC_") ) d.isDerived = true; if((slen > 5) && strstr(d.imageType, "_TRACEW_") ) From aa5dfdab76326f51db963f562d091b3d2f9653fb Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Thu, 10 Dec 2020 13:21:18 -0500 Subject: [PATCH 15/22] Fix PAR/REC ADC detection (https://github.com/rordenlab/dcm2niix/issues/462) --- FILENAMING.md | 1 + console/nii_dicom.cpp | 3 ++- console/nii_dicom_batch.cpp | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/FILENAMING.md b/FILENAMING.md index 4b79224c..620f9030 100644 --- a/FILENAMING.md +++ b/FILENAMING.md @@ -49,6 +49,7 @@ In general dcm2niix creates images with 3D dimensions, or 4 dimensions when the Some post-fixes are specific to Philips DICOMs - _ADC Philips specific case. A DWI image where derived isotropic, ADC or trace volume was appended to the series. Since this image will disrupt subsequent processing, and because subsequent processing (dwidenoise, topup, eddy) will yield better derived images, dcm2niix will also create an additional image without this volume. Therefore, the _ADC file should typically be discarded. If you want dcm2niix to discard these useless derived images, use the ignore feature ('-i y'). + - _fieldmaphz unwrapped B0 field map (in Hz) generated by a Philips scanner. Suggests Image Type (0008,0008) includes the terms 'B0' and 'MAP'. - _Raw Philips XX_* DICOMs (Raw Data Storage). - _PS Philips PS_* DICOMs (Grayscale Softcopy Presentation State). diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 0b642fbb..d85bcf9f 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -1969,7 +1969,7 @@ int kbval = 33; //V3: 27 } //diskSlice ++; bool isADC = false; - if ((maxNumberOfGradientOrients >= 2) && (cols[kbval] > 50) && isSameFloat(0.0, cols[kv1]) && isSameFloat(0.0, cols[kv2]) && isSameFloat(0.0, cols[kv2]) ) { + if ((maxNumberOfGradientOrients >= 2) && (cols[kbval] > 50) && isSameFloat(0.0, cols[kv1]) && isSameFloat(0.0, cols[kv2]) && isSameFloat(0.0, cols[kv3]) ) { isADC = true; ADCwarning = true; } @@ -2447,6 +2447,7 @@ int kbval = 33; //V3: 27 d.imageStart = 0; if (d.CSA.numDti >= kMaxDTI4D) { printError("Unable to convert DTI [increase kMaxDTI4D] found %d directions\n", d.CSA.numDti); + printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n", d.xyzDim[3], maxNumberOfGradientOrients,maxNumberOfDiffusionValues, maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); d.CSA.numDti = 0; }; //check if dimensions vary diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 4c593e78..3e577421 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -6119,7 +6119,7 @@ bool isSameSet (struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts* opt } if (d1.coilCrc != d2.coilCrc) { if (!warnings->coilVaries) - printMessage("Slices not stacked: coil varies\n"); + printMessage("Slices not stacked: coil varies '%s' vs '%s'\n", d1.coilName, d2.coilName); warnings->coilVaries = true; *isCoilVaries = true; return false; From 28f63229fc3eb061548c0974b794c3e502fced52 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 11 Dec 2020 12:26:39 -0500 Subject: [PATCH 16/22] Prevent dti4D overflow --- console/nii_dicom.cpp | 23 +++++++++++++---------- console/nii_dicom.h | 6 +++++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index d85bcf9f..fa313140 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -1973,7 +1973,6 @@ int kbval = 33; //V3: 27 isADC = true; ADCwarning = true; } - //printMessage(">>%d %d\n", (int)cols[kSlice], diskSlice); if (numSlice2D < 1) { d.xyzMM[1] = cols[kXmm]; d.xyzMM[2] = cols[kYmm]; @@ -2138,7 +2137,7 @@ int kbval = 33; //V3: 27 free (cols); return d; } - // dti4D->S[vol].V[0] = cols[kbval]; + // dti4D->S[vol].V[0] = cols[kbval]; //dti4D->gradDynVol[vol] = gradDynVol; dti4D->TE[vol] = cols[kTEcho]; if (isSameFloatGE(cols[kTEcho], 0)) @@ -2161,7 +2160,7 @@ int kbval = 33; //V3: 27 if ((vol+1) > d.CSA.numDti) d.CSA.numDti = vol+1; } - if (numSlice2D < kMaxSlice2D) {//issue 363: intensity can vary with each 2D slice of 4D volume + if (numSlice2D < kMaxDTI4D) {//issue 363: intensity can vary with each 2D slice of 4D volume //printf("%d %g %g\n", numSlice2D, cols[kRI], cols[kRS]); dti4D->intenIntercept[numSlice2D] = cols[kRI]; dti4D->intenScale[numSlice2D] = cols[kRS]; @@ -2188,6 +2187,14 @@ int kbval = 33; //V3: 27 printError("Invalid PAR format header (unable to detect version or slices) %s\n", parname); return d; } + if (numSlice2D > kMaxSlice2D) { //check again after reading, as top portion of header does not report image types or isotropics + printError("Increase kMaxSlice2D from %d to at least %d (or use dicm2nii).\n", kMaxSlice2D, numSlice2D); + return d; + } + if (numSlice2D > kMaxDTI4D) { + printError("Increase kMaxDTI4D from %d to at least %d (or use dicm2nii).\n", kMaxDTI4D, numSlice2D); + return d; + } d.manufacturer = kMANUFACTURER_PHILIPS; d.isValid = true; d.isSigned = true; @@ -2212,10 +2219,6 @@ int kbval = 33; //V3: 27 } if (d.CSA.numDti > 0) d.CSA.numDti = maxVol; //e.g. gradient 2 can skip B=0 but include isotropic //remove unused slices - this will happen if unless we have all 4 image types: real, imag, mag, phase - if (numSlice2D > kMaxSlice2D) { //check again after reading, as top portion of header does not report image types or isotropics - printError("Increase kMaxSlice2D from %d to at least %d (or use dicm2nii).\n", kMaxSlice2D, numSlice2D); - d.isValid = false; - } int slice = 0; for (int i = 0; i < kMaxSlice2D; i++) { if (dti4D->sliceOrder[i] > -1) { //this slice was populated @@ -2251,7 +2254,7 @@ int kbval = 33; //V3: 27 dti4D->intenScalePhilips[i] = tmp.intenScalePhilips[j]; } } - d.isScaleOrTEVaries = true; + d.isScaleOrTEVaries = true; if (numSlice2D > kMaxSlice2D) { printError("Overloaded slice re-ordering. Number of slices (%d) exceeds kMaxSlice2D (%d)\n", numSlice2D, kMaxSlice2D); dti4D->sliceOrder[0] = -1; @@ -2278,14 +2281,14 @@ int kbval = 33; //V3: 27 printWarning("Reported TR=%gms, measured TR=%gms (prospect. motion corr.?)\n", d.TR, TRms); d.TR = TRms; } - if ((isTypeWarning) && ((numSlice2D % num2DExpected) != 0) && ((numSlice2D % d.xyzDim[3]) == 0) ) { + if ((isTypeWarning) && ((numSlice2D % num2DExpected) != 0) && ((numSlice2D % d.xyzDim[3]) == 0) ) { num2DExpected = numSlice2D; } if ( ((numSlice2D % num2DExpected) != 0) && ((numSlice2D % d.xyzDim[3]) == 0) ) { num2DExpected = d.xyzDim[3] * (int)(numSlice2D / d.xyzDim[3]); if (!ADCwarning) printWarning("More volumes than described in header (ADC or isotropic?)\n"); } - if ((numSlice2D % num2DExpected) != 0) { + if ((numSlice2D % num2DExpected) != 0) { printMessage("Found %d slices, but expected divisible by %d: slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d %s\n", numSlice2D, num2DExpected, d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes,maxNumberOfLabels, parname); diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 8acd9a7a..2bc99dfc 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -54,8 +54,12 @@ extern "C" { #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic -static const int kMaxDTI4D = 18000; //maximum number of DTI directions for 4D (Philips) images, also maximum number of 3D slices for Philips 3D and 4D images static const int kMaxSlice2D = 64000; //maximum number of 2D slices in 4D (Philips) images +#if defined(_WIN64) || defined(_WIN32) //do not exceed windows stack +static const int kMaxDTI4D = 18000; //maximum number of DTI directions for 4D (Philips) images, also maximum number of 3D slices for Philips 3D and 4D images +#else +static const int kMaxDTI4D = 18000; //issue460: maximum number of DTI directions for 4D (Philips) images, also maximum number of 3D slices for Philips 3D and 4D images +#endif #define kDICOMStr 66 //64 characters plus NULL https://github.com/rordenlab/dcm2niix/issues/268 #define kDICOMStrLarge 256 From 28b72c239f08bacd6bcf80c2e1cb9e48bc7d91d2 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 11 Dec 2020 13:52:18 -0500 Subject: [PATCH 17/22] Support huge PAR/REC files (https://github.com/rordenlab/dcm2niix/issues/460) --- console/nii_dicom.cpp | 6 ++--- console/nii_dicom.h | 6 +---- console/nii_dicom_batch.cpp | 49 +++++++++++++++++++++---------------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index fa313140..d7c9ce03 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -1647,7 +1647,7 @@ int isSameFloatGE (float a, float b) { return (fabs (a - b) <= 0.0001); } -struct TDICOMdata nii_readParRec (char * parname, int isVerbose, struct TDTI4D *dti4D, bool isReadPhase) { +struct TDICOMdata nii_readParRec (char * parname, int isVerbose, struct TDTI4D *dti4D, bool isReadPhase) { struct TDICOMdata d = clear_dicom_data(); dti4D->sliceOrder[0] = -1; dti4D->volumeOnsetTime[0] = -1; @@ -1768,7 +1768,7 @@ int kbval = 33; //V3: 27 * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes; num2DExpected = d.xyzDim[3] * num3DExpected; if ((num2DExpected ) >= kMaxSlice2D) { - printError("Use dicm2nii or increase kMaxDTI4D to be more than %d\n", num2DExpected); + printError("Use dicm2nii or increase kMaxSlice2D to be more than %d\n", num2DExpected); printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n", d.xyzDim[3], maxNumberOfGradientOrients,maxNumberOfDiffusionValues, maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); @@ -2191,7 +2191,7 @@ int kbval = 33; //V3: 27 printError("Increase kMaxSlice2D from %d to at least %d (or use dicm2nii).\n", kMaxSlice2D, numSlice2D); return d; } - if (numSlice2D > kMaxDTI4D) { + if (numSlice2D > kMaxDTI4D) { //since issue460, kMaxSlice2D == kMaxSlice4D, so we should never get here printError("Increase kMaxDTI4D from %d to at least %d (or use dicm2nii).\n", kMaxDTI4D, numSlice2D); return d; } diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 2bc99dfc..76ca0383 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -55,11 +55,7 @@ extern "C" { static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic static const int kMaxSlice2D = 64000; //maximum number of 2D slices in 4D (Philips) images -#if defined(_WIN64) || defined(_WIN32) //do not exceed windows stack -static const int kMaxDTI4D = 18000; //maximum number of DTI directions for 4D (Philips) images, also maximum number of 3D slices for Philips 3D and 4D images -#else -static const int kMaxDTI4D = 18000; //issue460: maximum number of DTI directions for 4D (Philips) images, also maximum number of 3D slices for Philips 3D and 4D images -#endif +static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI directions for 4D (Philips) images, also maximum number of 2D slices for Enhanced DICOM and PAR/REC #define kDICOMStr 66 //64 characters plus NULL https://github.com/rordenlab/dcm2niix/issues/268 #define kDICOMStrLarge 256 diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 3e577421..2ab943ce 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -3467,8 +3467,10 @@ void swapEndian(struct nifti_1_header* hdr, unsigned char* im, bool isNative) { int nii_saveNII(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts, struct TDICOMdata d) { if (opts.isOnlyBIDS) return EXIT_SUCCESS; if (opts.isSaveNRRD) { - struct TDTI4D dti4D; - return nii_saveNRRD(niiFilename, hdr, im, opts, d, &dti4D, 0); + struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); + int ret = nii_saveNRRD(niiFilename, hdr, im, opts, d, dti4D, 0); + free(dti4D); + return ret; } hdr.vox_offset = 352; size_t imgsz = nii_ImgBytes(hdr); @@ -5832,8 +5834,7 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis } } //bvec/bval saved for each series (real, phase, magnitude, imaginary) https://github.com/rordenlab/dcm2niix/issues/219 - TDTI4D dti4Ds; - dti4Ds = *dti4D; + TDTI4D dti4Ds = *dti4D; bool isHasDti = (dcmList[indx].CSA.numDti > 0); if ((isHasDti) && (dcmList[indx].CSA.numDti == dcmList[indx].xyzDim[4])) { int nDti = 0; @@ -6174,7 +6175,7 @@ int singleDICOM(struct TDCMopts* opts, char *fname) { return 0; } struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc( sizeof(struct TDICOMdata)); - struct TDTI4D dti4D; + struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); struct TSearchList nameList; nameList.maxItems = 1; // larger requires more memory, smaller more passes nameList.str = (char **) malloc((nameList.maxItems+1) * sizeof(char *)); //reserve one pointer (32 or 64 bits) per potential file @@ -6184,11 +6185,12 @@ int singleDICOM(struct TDCMopts* opts, char *fname) { nameList.numItems++; TDCMsort * dcmSort = (TDCMsort *)malloc(sizeof(TDCMsort)); dcmList[0].converted2NII = 1; - dcmList[0] = readDICOMv(nameList.str[0], opts->isVerbose, opts->compressFlag, &dti4D); //ignore compile warning - memory only freed on first of 2 passes + dcmList[0] = readDICOMv(nameList.str[0], opts->isVerbose, opts->compressFlag, dti4D); //ignore compile warning - memory only freed on first of 2 passes fillTDCMsort(dcmSort[0], 0, dcmList[0]); - int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, &dti4D); + int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, dti4D); freeNameList(nameList); - free(dcmSort); + free(dti4D); + free(dcmSort); free(dcmList); return ret; }// singleDICOM() @@ -6229,7 +6231,7 @@ int textDICOM(struct TDCMopts* opts, char *fname) { #endif TDCMsort * dcmSort = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc(nConvert * sizeof(struct TDICOMdata)); - struct TDTI4D dti4D; + struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); struct TSearchList nameList; nameList.maxItems = nConvert; // larger requires more memory, smaller more passes nameList.str = (char **) malloc((nameList.maxItems+1) * sizeof(char *)); //reserve one pointer (32 or 64 bits) per potential file @@ -6243,15 +6245,16 @@ int textDICOM(struct TDCMopts* opts, char *fname) { nameList.str[nameList.numItems] = (char *)malloc(strlen(dcmname)+1); strcpy(nameList.str[nameList.numItems],dcmname); nameList.numItems++; - dcmList[nConvert] = readDICOMv(nameList.str[nConvert], opts->isVerbose, opts->compressFlag, &dti4D); //ignore compile warning - memory only freed on first of 2 passes + dcmList[nConvert] = readDICOMv(nameList.str[nConvert], opts->isVerbose, opts->compressFlag, dti4D); //ignore compile warning - memory only freed on first of 2 passes fillTDCMsort(dcmSort[nConvert], nConvert, dcmList[nConvert]); nConvert ++; } fclose(fp); qsort(dcmSort, nConvert, sizeof(struct TDCMsort), compareTDCMsort); //sort based on series and image numbers.... - int ret = saveDcm2Nii(nConvert, dcmSort, dcmList, &nameList, *opts, &dti4D); + int ret = saveDcm2Nii(nConvert, dcmSort, dcmList, &nameList, *opts, dti4D); free(dcmSort); free(dcmList); + free(dti4D); freeNameList(nameList); return ret; }//textDICOM() @@ -6364,12 +6367,13 @@ int convert_parRec(char * fnm, struct TDCMopts opts) { strcpy(nameList.str[0], fnm); //nameList.str[0] = (char *)malloc(strlen(opts.indir)+1); //strcpy(nameList.str[0],opts.indir); - TDTI4D dti4D; - dcmList[0] = nii_readParRec(nameList.str[0], opts.isVerbose, &dti4D, false); + struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); + dcmList[0] = nii_readParRec(nameList.str[0], opts.isVerbose, dti4D, false); struct TDCMsort dcmSort[1]; dcmSort[0].indx = 0; if (dcmList[0].isValid) - ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, opts, &dti4D); + ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, opts, dti4D); + free(dti4D); free(dcmList);//if (nConvertTotal == 0) if (nameList.numItems < 1) printMessage("No valid PAR/REC files were found\n"); @@ -6655,7 +6659,7 @@ int nii_loadDirCore(char *indir, struct TDCMopts* opts) { progressPct = reportProgress(progressPct, kStage1Frac); //proportion correct, 0..100 // struct TDICOMdata dcmList [nameList.numItems]; //<- this exhausts the stack for large arrays struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc(nameList.numItems * sizeof(struct TDICOMdata)); - struct TDTI4D dti4D; + struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); int nConvertTotal = 0; bool compressionWarning = false; bool convertError = false; @@ -6674,13 +6678,13 @@ int nii_loadDirCore(char *indir, struct TDCMopts* opts) { convertError = true; continue; } - dcmList[i] = readDICOMv(nameList.str[i], opts->isVerbose, opts->compressFlag, &dti4D); //ignore compile warning - memory only freed on first of 2 passes + dcmList[i] = readDICOMv(nameList.str[i], opts->isVerbose, opts->compressFlag, dti4D); //ignore compile warning - memory only freed on first of 2 passes //if (!dcmList[i].isValid) printf(">>>>Not a valid DICOM %s\n", nameList.str[i]); - if ((dcmList[i].isValid) && ((dti4D.sliceOrder[0] >= 0) || (dcmList[i].CSA.numDti > 1))) { //4D dataset: dti4D arrays require huge amounts of RAM - write this immediately + if ((dcmList[i].isValid) && ((dti4D->sliceOrder[0] >= 0) || (dcmList[i].CSA.numDti > 1))) { //4D dataset: dti4D arrays require huge amounts of RAM - write this immediately struct TDCMsort dcmSort[1]; fillTDCMsort(dcmSort[0], i, dcmList[i]); dcmList[i].converted2NII = 1; - int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, &dti4D); + int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, dti4D); if (ret == EXIT_SUCCESS) nConvertTotal++; else @@ -6698,7 +6702,9 @@ int nii_loadDirCore(char *indir, struct TDCMopts* opts) { start = clock(); #endif if (opts->isRenameNotConvert) { - return EXIT_SUCCESS; + free(dcmList); + free(dti4D); + return EXIT_SUCCESS; } #ifdef USING_R if (opts->isScanOnly) { @@ -6783,7 +6789,7 @@ int nii_loadDirCore(char *indir, struct TDCMopts* opts) { else //nConvert = removeDuplicatesVerbose(nConvert, dcmSort, &nameList); nConvert = removeDuplicates(nConvert, dcmSort); - int ret = saveDcm2Nii(nConvert, dcmSort, dcmList, &nameList, *opts, &dti4D); + int ret = saveDcm2Nii(nConvert, dcmSort, dcmList, &nameList, *opts, dti4D); if (ret == EXIT_SUCCESS) nConvertTotal += nConvert; else @@ -6843,7 +6849,7 @@ int nii_loadDirCore(char *indir, struct TDCMopts* opts) { nConvert = removeDuplicatesVerbose(nConvert, dcmSort, &nameList); else nConvert = removeDuplicates(nConvert, dcmSort); - int ret = saveDcm2Nii(nConvert, dcmSort, dcmList, &nameList, *opts, &dti4D); + int ret = saveDcm2Nii(nConvert, dcmSort, dcmList, &nameList, *opts, dti4D); if (ret == EXIT_SUCCESS) nConvertTotal += nConvert; else @@ -6864,6 +6870,7 @@ int nii_loadDirCore(char *indir, struct TDCMopts* opts) { #endif if (opts->isProgress) progressPct = reportProgress(progressPct, 1); //proportion correct, 0..100 free(dcmList); + free(dti4D); freeNameList(nameList); if (convertError) { if (nConvertTotal == 0) From d7976467d6e4f8ce59fbd5accf68df4aed98fbfa Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 11 Dec 2020 16:22:38 -0500 Subject: [PATCH 18/22] Retain Philips Scaling Values for images where scaling does not vary but volumes must be separated (https://github.com/rordenlab/dcm2niix/issues/461) --- console/nii_dicom_batch.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 2ab943ce..e7bec953 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -5851,7 +5851,18 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis } //save each series bool isScaleVariesEnh = dcmList[indx].isScaleVariesEnh; //issue363: any variation in any image + float intenScale = dcmList[indx].intenScale; + float intenIntercept = dcmList[indx].intenIntercept; + float intenScalePhilips = dcmList[indx].intenScalePhilips; + float RWVIntercept = dcmList[indx].RWVIntercept; + float RWVScale = dcmList[indx].RWVScale; for (int s = 1; s <= series; s++) { + //issue461: assert these values as saveDcm2NiiCore modifies them when it applies Philips scaling + dcmList[indx].intenScale = intenScale; + dcmList[indx].intenIntercept = intenIntercept; + dcmList[indx].intenScalePhilips = intenScalePhilips; + dcmList[indx].RWVIntercept = RWVIntercept; + dcmList[indx].RWVScale = RWVScale; for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) { //for each volume if (dti4D->gradDynVol[i] == s) { //dti4D->gradDynVol[i] = s; @@ -5892,18 +5903,12 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis if (dti4Ds.intenScale[i] != dti4Ds.intenScale[0]) dcmList[indx].isScaleVariesEnh = true; if (dti4Ds.intenScalePhilips[i] != dti4Ds.intenScalePhilips[0]) dcmList[indx].isScaleVariesEnh = true; } - //in case scale doe not vary dcmList[indx].intenScale = dti4Ds.intenScale[0]; dcmList[indx].intenIntercept = dti4Ds.intenIntercept[0]; dcmList[indx].intenScalePhilips = dti4Ds.intenScalePhilips[0]; dcmList[indx].RWVIntercept = dti4Ds.RWVIntercept[0]; dcmList[indx].RWVScale = dti4Ds.RWVScale[0]; - } //if isScaleVariesEnh - - //if (dcmList[indx].isScaleVariesEnh) printf("Varies\n"); - //if (!dcmList[indx].isScaleVariesEnh) printf("no Varies\n"); - //printf("%g %g %g\n", dcmList[indx].intenScale, dcmList[indx].intenIntercept, dcmList[indx].intenScalePhilips); - //continue; + } if (s > 1) dcmList[indx].CSA.numDti = 0; //only save bvec for first type (magnitude) int ret2 = saveDcm2NiiCore(nConvert, dcmSort, dcmList, nameList, opts, &dti4Ds, s); if (ret2 != EXIT_SUCCESS) ret = ret2; //return EXIT_SUCCESS only if ALL are successful From 95b18f979d642f523c91ada2604fcad10770f434 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 11 Dec 2020 16:25:05 -0500 Subject: [PATCH 19/22] Bump version date --- console/nii_dicom.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 76ca0383..20fb26e4 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.20201207" +#define kDCMdate "v1.0.20201211" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic From 8b99a984c9f796b412c6874f26d529d3aad5ded7 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 16 Dec 2020 05:32:52 -0500 Subject: [PATCH 20/22] Increase details for series stacking, enhance merge override mode (https://github.com/rordenlab/dcm2niix/issues/463) --- console/nii_dicom.cpp | 9 +++++ console/nii_dicom.h | 4 +-- console/nii_dicom_batch.cpp | 66 +++++++++++++++++++++++++++++-------- 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index d7c9ce03..6b158fc7 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -5017,6 +5017,15 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); } else if ((compressFlag != kCompressNone) && (strcmp(transferSyntax, "1.2.840.10008.1.2.4.90") == 0)) { d.compressionScheme = kCompressYes; //printMessage("JPEG2000 Lossless support is new: please validate conversion\n"); + } else if ((strcmp(transferSyntax, "1.2.840.10008.1.2.1.99") == 0)){ + //n.b. Deflate compression applied applies to the encoding of the **entire** DICOM Data Set, not just image data + // see https://www.medicalconnections.co.uk/kb/Transfer-Syntax/ + //#ifndef myDisableZLib + //d.compressionScheme = kCompressDeflate; + //#else + printWarning("Unsupported transfer syntax '%s' (inflate files with 'dcmconv +te gz.dcm raw.dcm' or 'gdcmconv -w gz.dcm raw.dcm)'\n",transferSyntax); + d.imageStart = 1;//abort as invalid (imageStart MUST be >128) + //#endif } else if ((compressFlag != kCompressNone) && (strcmp(transferSyntax, "1.2.840.10008.1.2.4.91") == 0)) { d.compressionScheme = kCompressYes; //printMessage("JPEG2000 support is new: please validate conversion\n"); diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 20fb26e4..5360cdd2 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,11 +50,11 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20201211" +#define kDCMdate "v1.0.20201216" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic -static const int kMaxSlice2D = 64000; //maximum number of 2D slices in 4D (Philips) images +static const int kMaxSlice2D = 65535; //issue460 maximum number of 2D slices in 4D (Philips) images static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI directions for 4D (Philips) images, also maximum number of 2D slices for Enhanced DICOM and PAR/REC #define kDICOMStr 66 //64 characters plus NULL https://github.com/rordenlab/dcm2niix/issues/268 diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index e7bec953..c3f6160d 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -6018,14 +6018,18 @@ int isSameFloatDouble (double a, double b) { } struct TWarnings { //generate a warning only once per set - bool acqNumVaries, dimensionVaries, dateTimeVaries, echoVaries, triggerVaries, phaseVaries, coilVaries, forceStackSeries, seriesUidVaries, nameVaries, nameEmpty, orientVaries; + bool manufacturerVaries, modalityVaries, derivedVaries, acqNumVaries, dimensionVaries, dateTimeVaries, studyUidVaries, echoVaries, triggerVaries, phaseVaries, coilVaries, forceStackSeries, seriesUidVaries, nameVaries, nameEmpty, orientVaries; }; TWarnings setWarnings() { TWarnings r; + r.manufacturerVaries = false; + r.modalityVaries = false; + r.derivedVaries = false; r.acqNumVaries = false; r.dimensionVaries = false; r.dateTimeVaries = false; + r.studyUidVaries = false; r.phaseVaries = false; r.echoVaries = false; r.triggerVaries = false; @@ -6041,14 +6045,29 @@ TWarnings setWarnings() { bool isSameSet (struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts* opts, struct TWarnings* warnings, bool *isMultiEcho, bool *isNonParallelSlices, bool *isCoilVaries) { //returns true if d1 and d2 should be stacked together as a single output if (!d1.isValid) return false; - if (!d2.isValid) return false; - if (d1.modality != d2.modality) return false; //do not stack MR and CT data! - if (d1.isDerived != d2.isDerived) return false; //do not stack raw and derived image types + if (!d2.isValid) return false; + if ((opts->isVerbose) && (d1.seriesNum == d2.seriesNum)) { + //one would never want to combine in these situations: only raise warning for verbose modes to help troubleshooting + if ((d1.manufacturer != d2.manufacturer) && (!warnings->manufacturerVaries)) { + printMessage("Volumes not stacked: manufacturer varies.\n"); + warnings->manufacturerVaries = true; + } + if ((d1.modality != d2.modality) && (!warnings->modalityVaries)) { + printMessage("Volumes not stacked: modality varies.\n"); + warnings->modalityVaries = true; + } + if ((d1.isDerived != d2.isDerived) && (!warnings->derivedVaries)) { + printMessage("Volumes not stacked: derived varies.\n"); + warnings->derivedVaries = true; + } + } if (d1.manufacturer != d2.manufacturer) return false; //do not stack data from different vendors - bool isForceStackSeries = false; + if (d1.modality != d2.modality) return false; //do not stack MR and CT data! + if (d1.isDerived != d2.isDerived) return false; //do not stack raw and derived image types + bool isForceStackSeries = false; if ((opts->isForceStackDCE) && (d1.isStackableSeries) && (d2.isStackableSeries) && (d1.seriesNum != d2.seriesNum)) { if (!warnings->forceStackSeries) - printMessage("Siemens volumes stacked despite varying series number (use '-m o' to turn off merging).\n"); + printMessage("Volumes stacked despite varying series number (use '-m o' to turn off merging).\n"); warnings->forceStackSeries = true; isForceStackSeries = true; } @@ -6079,8 +6098,20 @@ bool isSameSet (struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts* opt bool isSameTime = isSameFloatDouble(d1.dateTime, d2.dateTime); if ((isSameStudyInstanceUID) && (d1.isXA10A) && (d2.isXA10A)) isSameTime = true; //kludge XA10A 0008,0030 incorrect https://github.com/rordenlab/dcm2niix/issues/236 - if ((!isSameStudyInstanceUID) && (!isSameTime)) return false; - if ( (d1.xyzDim[1] != d2.xyzDim[1]) || (d1.xyzDim[2] != d2.xyzDim[2]) || (d1.xyzDim[3] != d2.xyzDim[3]) ) { + bool isDimensionVaries = ( (d1.xyzDim[1] != d2.xyzDim[1]) || (d1.xyzDim[2] != d2.xyzDim[2]) || (d1.xyzDim[3] != d2.xyzDim[3]) ); + if ((!isSameStudyInstanceUID) && (!isSameTime)) { + if (opts->isForceStackDCE) { + if (!warnings->studyUidVaries) + printMessage("Slices stacked despite Study Date/Time (0008,0020;0008,0030) and Study UID (0020,000E) variation %12.12f ~= %12.12f\n", d1.dateTime, d2.dateTime); + warnings->studyUidVaries = true; + } else { + if (!warnings->studyUidVaries) + printMessage("Slices not stacked: Study Date/Time (0008,0020;0008,0030) and Study UID (0020,000E) varies %12.12f ~= %12.12f\n", d1.dateTime, d2.dateTime); + warnings->studyUidVaries = true; + return false; + } + } + if (isDimensionVaries) { if (!warnings->dimensionVaries) printMessage("Slices not stacked: dimensions vary across slices\n"); warnings->dimensionVaries = true; @@ -6089,7 +6120,7 @@ bool isSameSet (struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts* opt #ifndef myIgnoreStudyTime if (!isSameTime) { //beware, some vendors incorrectly store Image Time (0008,0033) as Study Time (0008,0030). if (!warnings->dateTimeVaries) - printMessage("Slices not stacked: Study Date/Time (0008,0020 / 0008,0030) varies %12.12f ~= %12.12f\n", d1.dateTime, d2.dateTime); + printMessage("Slices not stacked: Study Date/Time (0008,0020;0008,0030) varies %12.12f ~= %12.12f\n", d1.dateTime, d2.dateTime); warnings->dateTimeVaries = true; return false; } @@ -6124,11 +6155,18 @@ bool isSameSet (struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts* opt return false; } if (d1.coilCrc != d2.coilCrc) { - if (!warnings->coilVaries) - printMessage("Slices not stacked: coil varies '%s' vs '%s'\n", d1.coilName, d2.coilName); - warnings->coilVaries = true; - *isCoilVaries = true; - return false; + if (opts->isForceStackDCE) { + if (!warnings->coilVaries) + printMessage("Slices stacked despite coil variation '%s' vs '%s'\n", d1.coilName, d2.coilName); + warnings->coilVaries = true; + *isCoilVaries = true; + } else { + if (!warnings->coilVaries) + printMessage("Slices not stacked: coil varies '%s' vs '%s'\n", d1.coilName, d2.coilName); + warnings->coilVaries = true; + *isCoilVaries = true; + return false; + } } if ((strlen(d1.protocolName) < 1) && (strlen(d2.protocolName) < 1)) { if (!warnings->nameEmpty) From 829f541d686997f6c98d4bf309791daef3c28f68 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 23 Dec 2020 08:35:23 -0500 Subject: [PATCH 21/22] MacOS notarization, minor tweaks --- console/nii_dicom.cpp | 49 ++++++++++++++++------------- console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 8 +++-- console/notarize.sh | 63 +++++++++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 25 deletions(-) create mode 100755 console/notarize.sh diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 6b158fc7..e187cf82 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -1238,7 +1238,8 @@ void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3D // Nov 1, 2018 wrote: // If you have an interleaved dataset we can more definitively validate this formula (aka sliceTime(i) - min(sliceTimes())). if (minTimeValue < 0) { - printWarning("Adjusting for negative MosaicRefAcqTimes (issue 271).\n"); + //printWarning("Adjusting for negative MosaicRefAcqTimes (issue 271).\n"); //if uncommented, overwhelming number of warnings (one per DICOM input), better once per series + CSA->sliceTiming[kMaxEPI3D-1] = -2.0; //issue 271: flag for unified warning for (int z = 0; z < itemsOK; z++) CSA->sliceTiming[z] = CSA->sliceTiming[z] - minTimeValue; } @@ -7022,29 +7023,31 @@ if (d.isHasPhase) int j = 0; if (d.xyzDim[3] > 1) j = 1; for (int i = 0; i < d.xyzDim[4]; i++) { + int slice = j+(i * d.xyzDim[3]); //dti4D->gradDynVol[i] = 0; //only PAR/REC - dti4D->TE[i] = dcmDim[j+(i * d.xyzDim[3])].TE; - dti4D->isPhase[i] = dcmDim[j+(i * d.xyzDim[3])].isPhase; - dti4D->isReal[i] = dcmDim[j+(i * d.xyzDim[3])].isReal; - dti4D->isImaginary[i] = dcmDim[j+(i * d.xyzDim[3])].isImaginary; - dti4D->triggerDelayTime[i] = dcmDim[j+(i * d.xyzDim[3])].triggerDelayTime; - dti4D->S[i].V[0] = dcmDim[j+(i * d.xyzDim[3])].V[0]; - dti4D->S[i].V[1] = dcmDim[j+(i * d.xyzDim[3])].V[1]; - dti4D->S[i].V[2] = dcmDim[j+(i * d.xyzDim[3])].V[2]; - dti4D->S[i].V[3] = dcmDim[j+(i * d.xyzDim[3])].V[3]; + dti4D->TE[i] = dcmDim[slice].TE; + dti4D->isPhase[i] = dcmDim[slice].isPhase; + dti4D->isReal[i] = dcmDim[slice].isReal; + dti4D->isImaginary[i] = dcmDim[slice].isImaginary; + dti4D->triggerDelayTime[i] = dcmDim[slice].triggerDelayTime; + dti4D->S[i].V[0] = dcmDim[slice].V[0]; + dti4D->S[i].V[1] = dcmDim[slice].V[1]; + dti4D->S[i].V[2] = dcmDim[slice].V[2]; + dti4D->S[i].V[3] = dcmDim[slice].V[3]; //printf("te=\t%g\tscl=\t%g\tintercept=\t%g\n",dti4D->TE[i], dti4D->intenScale[i],dti4D->intenIntercept[i]); if ((!isSameFloatGE(dti4D->TE[i],0.0)) && (dti4D->TE[i] != d.TE)) isTEvaries = true; if (dti4D->isPhase[i] != isPhase) d.isScaleOrTEVaries = true; if (dti4D->triggerDelayTime[i] != d.triggerDelayTime) d.isScaleOrTEVaries = true; if (dti4D->isReal[i] != isReal) d.isScaleOrTEVaries = true; if (dti4D->isImaginary[i] != isImaginary) d.isScaleOrTEVaries = true; - //dti4D->intenScale[i] = dcmDim[j+(i * d.xyzDim[3])].intenScale; - //dti4D->intenIntercept[i] = dcmDim[j+(i * d.xyzDim[3])].intenIntercept; - //dti4D->intenScalePhilips[i] = dcmDim[j+(i * d.xyzDim[3])].intenScalePhilips; - //dti4D->RWVIntercept[i] = dcmDim[j+(i * d.xyzDim[3])].RWVIntercept; - //dti4D->RWVScale[i] = dcmDim[j+(i * d.xyzDim[3])].RWVScale; - //if (dti4D->intenScale[i] != d.intenScale) isScaleVaries = true; - //if (dti4D->intenIntercept[i] != d.intenIntercept) isScaleVaries = true; + /*Philips can vary intensity scalings for separate slices within a volume! + dti4D->intenScale[i] = dcmDim[slice].intenScale; + dti4D->intenIntercept[i] = dcmDim[slice].intenIntercept; + dti4D->intenScalePhilips[i] = dcmDim[slice].intenScalePhilips; + dti4D->RWVIntercept[i] = dcmDim[slice].RWVIntercept; + dti4D->RWVScale[i] = dcmDim[slice].RWVScale; + if (dti4D->intenScale[i] != d.intenScale) isScaleVaries = true; + if (dti4D->intenIntercept[i] != d.intenIntercept) isScaleVaries = true;*/ } if((isScaleVaries) || (isTEvaries)) d.isScaleOrTEVaries = true; if (isTEvaries) d.isMultiEcho = true; @@ -7055,8 +7058,10 @@ if (d.isHasPhase) }*/ if ((isVerbose) && (d.isScaleOrTEVaries)) { printMessage("Parameters vary across 3D volumes packed in single DICOM file:\n"); - for (int i = 0; i < d.xyzDim[4]; i++) - printMessage(" %d TE=%g Slope=%g Inter=%g PhilipsScale=%g Phase=%d\n", i, dti4D->TE[i], dti4D->intenScale[i], dti4D->intenIntercept[i], dti4D->intenScalePhilips[i], dti4D->isPhase[i] ); + for (int i = 0; i < d.xyzDim[4]; i++) { + int slice = (i * d.xyzDim[3]); + printMessage(" %d TE=%g Slope=%g Inter=%g PhilipsScale=%g Phase=%d\n", i, dti4D->TE[i], dti4D->intenScale[slice], dti4D->intenIntercept[slice], dti4D->intenScalePhilips[slice], dti4D->isPhase[i] ); + } } } if ((d.xyzDim[3] == maxInStackPositionNumber) && (maxInStackPositionNumber > 1) && (d.zSpacing <= 0.0)) { @@ -7249,7 +7254,9 @@ if (d.isHasPhase) } // readDICOM() struct TDICOMdata readDICOM(char * fname) { - TDTI4D unused; - return readDICOMv(fname, false, kCompressSupport, &unused); + struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); //unused + TDICOMdata ret = readDICOMv(fname, false, kCompressSupport, dti4D); + free(dti4D); + return ret; } // readDICOM() diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 5360cdd2..c31a24a9 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.20201216" +#define kDCMdate "v1.0.20201223" #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 c3f6160d..bf6695ab 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -4467,6 +4467,8 @@ void checkSliceTiming(struct TDICOMdata * d, struct TDICOMdata * d1, int verbose while ((nSlices < kMaxEPI3D) && (d->CSA.sliceTiming[nSlices] >= 0.0)) nSlices++; if (nSlices < 1) return; + if (d->CSA.sliceTiming[kMaxEPI3D-1] < 1.0) + printWarning("Adjusting for negative MosaicRefAcqTimes (issue 271).\n"); bool isSliceTimeHHMMSS = (d->manufacturer == kMANUFACTURER_UIH); if (isForceSliceTimeHHMMSS) isSliceTimeHHMMSS = true; //if (d->isXA10A) isSliceTimeHHMMSS = true; //for XA10 use TimeAfterStart 0x0021,0x1104 -> Siemens de-identification can corrupt acquisition ties https://github.com/rordenlab/dcm2niix/issues/236 @@ -5516,9 +5518,11 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc printMessage("|%d|%s\n", i, nameList->str[dcmSort[i].indx]); } #endif + if (strlen(dcmList[dcmSort[0].indx].protocolName) < 1) //beware: tProtocolName can vary within a series "t1+AF8-mpr+AF8-ns+AF8-sag+AF8-p2+AF8-iso" vs "T1_mprage_ns_sag_p2_iso 1.0mm_192" + rescueProtocolName(&dcmList[dcmSort[0].indx], nameList->str[dcmSort[0].indx]); //move before headerDcm2Nii2 checkSliceTiming(&dcmList[indx0], &dcmList[indx1]); char pathoutname[2048] = {""}; - if (nii_createFilename(dcmList[dcmSort[0].indx], pathoutname, opts) == EXIT_FAILURE) { + if (nii_createFilename(dcmList[dcmSort[0].indx], pathoutname, opts) == EXIT_FAILURE) { //make sure call is subsequent to rescueProtocolName() free(imgM); return EXIT_FAILURE; } @@ -5580,8 +5584,6 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc free(img4D); saveAs3D = false; } - if (strlen(dcmList[dcmSort[0].indx].protocolName) < 1) //beware: tProtocolName can vary within a series "t1+AF8-mpr+AF8-ns+AF8-sag+AF8-p2+AF8-iso" vs "T1_mprage_ns_sag_p2_iso 1.0mm_192" - rescueProtocolName(&dcmList[dcmSort[0].indx], nameList->str[dcmSort[0].indx]); // Prevent these DICOM files from being reused. for(int i = 0; i < nConvert; ++i) dcmList[dcmSort[i].indx].converted2NII = 1; diff --git a/console/notarize.sh b/console/notarize.sh new file mode 100755 index 00000000..9a61a0b7 --- /dev/null +++ b/console/notarize.sh @@ -0,0 +1,63 @@ +#!/bin/bash +set -e + +#com.${COMPANY_NAME}.${APP_NAME} e.g. com.mricro.niimath +COMPANY_NAME=mycompany +APP_NAME=dcm2niix +APP_SPECIFIC_PASSWORD=abcd-efgh-ijkl-mnop +APPLE_ID_USER=myname@gmail.com +APPLE_ID_INSTALL="Developer ID Installer: My Name" +APPLE_ID_APP="Developer ID Application: My Name" + +if [[ "$APPLE_ID_USER" == "myname@gmail.com" ]] +then + echo "You need to set your personal IDs and password" + exit 1 +fi + +g++ -O3 -sectcreate __TEXT __info_plist Info.plist -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 dcm2niixX86 -DmyDisableOpenJPEG -target x86_64-apple-macos10.12 -mmacosx-version-min=10.12 +g++ -O3 -sectcreate __TEXT __info_plist Info.plist -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 dcm2niixARM -DmyDisableOpenJPEG -target arm64-apple-macos11 -mmacosx-version-min=11.0 +# Create the universal binary. +strip ./dcm2niixARM; strip ./dcm2niixX86 +lipo -create -output ${APP_NAME} dcm2niixARM dcm2niixX86 +rm ./dcm2niixARM; rm ./dcm2niixX86 +# Create a staging area for the installer package. +mkdir -p usr/local/bin +# Move the binary into the staging area. +mv ${APP_NAME} usr/local/bin +# Sign the binary. +codesign --timestamp --options=runtime -s "${APPLE_ID_APP}" -v usr/local/bin/${APP_NAME} +# Build the package. +pkgbuild --identifier "com.${COMPANY_NAME}.${APP_NAME}.pkg" --sign "${APPLE_ID_INSTALL}" --timestamp --root usr/local --install-location /usr/local/ ${APP_NAME}.pkg +# Submit the package to the notarization service. + +xcrun altool --notarize-app --primary-bundle-id "com.${COMPANY_NAME}.${APP_NAME}.pkg" --username $APPLE_ID_USER --password $APP_SPECIFIC_PASSWORD --file ${APP_NAME}.pkg --output-format xml > upload_log_file.txt + +# now we need to query apple's server to the status of notarization +# when the "xcrun altool --notarize-app" command is finished the output plist +# will contain a notarization-upload->RequestUUID key which we can use to check status +echo "Checking status..." +sleep 50 +REQUEST_UUID=`/usr/libexec/PlistBuddy -c "Print :notarization-upload:RequestUUID" upload_log_file.txt` +while true; do + xcrun altool --notarization-info $REQUEST_UUID -u $APPLE_ID_USER -p $APP_SPECIFIC_PASSWORD --output-format xml > request_log_file.txt + # parse the request plist for the notarization-info->Status Code key which will + # be set to "success" if the package was notarized + STATUS=`/usr/libexec/PlistBuddy -c "Print :notarization-info:Status" request_log_file.txt` + if [ "$STATUS" != "in progress" ]; then + break + fi + # echo $STATUS + echo "$STATUS" + sleep 10 +done + +# download the log file to view any issues +/usr/bin/curl -o log_file.txt `/usr/libexec/PlistBuddy -c "Print :notarization-info:LogFileURL" request_log_file.txt` + +# staple +echo "Stapling..." +xcrun stapler staple ${APP_NAME}.pkg +xcrun stapler validate ${APP_NAME}.pkg + +open log_file.txt From 0587941bb939fa46843917740f2c652a4219911f Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 23 Dec 2020 09:43:16 -0500 Subject: [PATCH 22/22] Overflow for Siemens data [missing protocol name] (https://github.com/rordenlab/dcm2niix/issues/466) --- console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/console/nii_dicom.h b/console/nii_dicom.h index c31a24a9..cdfbeb05 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.20201223" +#define kDCMdate "v1.0.20201224" #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 bf6695ab..8473eb06 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -968,6 +968,8 @@ void rescueProtocolName(struct TDICOMdata *d, const char * filename) { char protocolName[kDICOMStrLarge], fmriExternalInfo[kDICOMStrLarge], coilID[kDICOMStrLarge], consistencyInfo[kDICOMStrLarge], coilElements[kDICOMStrLarge], pulseSequenceDetails[kDICOMStrLarge], wipMemBlock[kDICOMStrLarge]; TCsaAscii csaAscii; siemensCsaAscii(filename, &csaAscii, d->CSA.SeriesHeader_offset, d->CSA.SeriesHeader_length, shimSetting, coilID, consistencyInfo, coilElements, pulseSequenceDetails, fmriExternalInfo, protocolName, wipMemBlock); + if (strlen(protocolName) >= kDICOMStr) + protocolName[kDICOMStr-1] = 0; strcpy(d->protocolName, protocolName); #endif }