From ccf263d22c8d895117558c398891bc1235f7a255 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Sat, 3 Apr 2021 08:53:14 -0400 Subject: [PATCH 01/95] ignore_trigger_times options (https://github.com/rordenlab/dcm2niix/issues/499) --- console/main_console.cpp | 4 ++++ console/nii_dicom.cpp | 23 ++++++++++++++++++++++- console/nii_dicom.h | 10 ++++++++-- console/nii_dicom_batch.cpp | 18 ++++++++++++++++-- console/nii_dicom_batch.h | 2 +- 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/console/main_console.cpp b/console/main_console.cpp index 2adc4982..8e91a324 100644 --- a/console/main_console.cpp +++ b/console/main_console.cpp @@ -131,6 +131,7 @@ void showHelp(const char * argv[], struct TDCMopts opts) { #endif printf(" --big-endian : byte order (y/n/o, default o) [y=big-end, n=little-end, o=optimal/native]\n"); printf(" --progress : report progress (y/n, default n)\n"); + printf(" --ignore_trigger_times : disregard values in 0018,1060 and 0020,9153\n"); printf(" --terse : omit filename post-fixes (can cause overwrites)\n"); printf(" --version : report version\n"); printf(" --xml : Slicer format features\n"); @@ -286,6 +287,9 @@ int main(int argc, const char * argv[]) opts.isSaveNativeEndian = false; printf("NIfTI data will be little-endian\n"); } + } else if ( ! strcmp(argv[i], "--ignore_trigger_times")) { + opts.isIgnoreTriggerTimes = true; + printf("ignore_trigger_times may have unintended consequences (issue 499)\n"); } else if ( ! strcmp(argv[i], "--terse")) { opts.isAddNamePostFixes = false; } else if ( ! strcmp(argv[i], "--version")) { diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 9194a637..aeba3728 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -4094,7 +4094,10 @@ int compareTDCMdimRev(void const *item1, void const *item2) { #endif // USING_R -struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) { +struct TDICOMdata readDICOMx(char * fname, struct TDCMprefs* prefs, struct TDTI4D *dti4D) { +//struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) { + int isVerbose = prefs->isVerbose; + int compressFlag = prefs->compressFlag; struct TDICOMdata d = clear_dicom_data(); d.imageNum = 0; //not set strcpy(d.protocolName, ""); //erase dummy with empty @@ -5641,6 +5644,7 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); sqDepth00189114 = sqDepth - 1; break; case kTriggerDelayTime: { //0x0020+uint32_t(0x9153<< 16 ) //FD + if (prefs->isIgnoreTriggerTimes) break;//issue499 if (d.manufacturer != kMANUFACTURER_PHILIPS) break; //if (isVerbose < 2) break; double trigger = dcmFloatDouble(lLength, &buffer[lPos],d.isLittleEndian); @@ -5839,6 +5843,7 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); d.imagingFrequency = dcmStrFloat(lLength, &buffer[lPos]); break; case kTriggerTime: { + if (prefs->isIgnoreTriggerTimes) break;//issue499 //untested method to detect slice timing for GE PSD “epi” with multiphase option // will not work for current PSD “epiRT” (BrainWave RT, fMRI/DTI package provided by Medical Numerics) if ((d.manufacturer != kMANUFACTURER_GE) && (d.manufacturer != kMANUFACTURER_PHILIPS)) break; //issue384 @@ -7316,6 +7321,22 @@ if (d.isHasPhase) return d; } // readDICOM() +void setDefaultPrefs (struct TDCMprefs *prefs) { + prefs->isVerbose = false; + prefs->compressFlag = kCompressSupport; + prefs->isIgnoreTriggerTimes = false; +} + +struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) { + struct TDCMprefs prefs; + setDefaultPrefs(&prefs); + prefs.isVerbose = isVerbose; + prefs.compressFlag = compressFlag; + TDICOMdata ret = readDICOMx(fname, &prefs, dti4D); + return ret; +} + + struct TDICOMdata readDICOM(char * fname) { struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); //unused TDICOMdata ret = readDICOMv(fname, false, kCompressSupport, dti4D); diff --git a/console/nii_dicom.h b/console/nii_dicom.h index fa3605bf..b4957346 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.20210317" +#define kDCMdate "v1.0.20210403" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic @@ -195,12 +195,18 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; bool isRealIsPhaseMapHz, isPrivateCreatorRemap, isHasOverlay, isEPI, isIR, isPartialFourier, isDiffusion, isVectorFromBMatrix, isRawDataStorage, isGrayscaleSoftcopyPresentationState, isStackableSeries, isCoilVaries, isNonParallelSlices, isBVecWorldCoordinates, isSegamiOasis, isXA10A, isScaleOrTEVaries, isScaleVariesEnh, isDerived, isXRay, isMultiEcho, isValid, is3DAcq, is2DAcq, isExplicitVR, isLittleEndian, isPlanarRGB, isSigned, isHasPhase, isHasImaginary, isHasReal, isHasMagnitude,isHasMixed, isFloat, isResampled, isLocalizer; char phaseEncodingRC, patientSex; }; + struct TDCMprefs { + int isVerbose, compressFlag, isIgnoreTriggerTimes; + }; size_t nii_ImgBytes(struct nifti_1_header hdr); + void setDefaultPrefs (struct TDCMprefs *prefs); int isSameFloatGE (float a, float b); void getFileNameX( char *pathParent, const char *path, int maxLen); struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D); - struct TDICOMdata readDICOM(char * fname); + struct TDICOMdata readDICOMx(char * fname, struct TDCMprefs* prefs, struct TDTI4D *dti4D); + + struct TDICOMdata readDICOM(char * fname); struct TDICOMdata clear_dicom_data(void); struct TDICOMdata nii_readParRec (char * parname, int isVerbose, struct TDTI4D *dti4D, bool isReadPhase); unsigned char * nii_flipY(unsigned char* bImg, struct nifti_1_header *h); diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 32233b78..5843637a 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -176,6 +176,13 @@ bool is_exe(const char* path) { //requires #include }// is_dir() #endif +void opts2Prefs (struct TDCMopts* opts, struct TDCMprefs *prefs) { + setDefaultPrefs(prefs); + prefs->isVerbose = opts->isVerbose; + prefs->compressFlag = opts->compressFlag; + prefs->isIgnoreTriggerTimes = opts->isIgnoreTriggerTimes; +} + void geCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx, int isVerbose){ //0018,1312 phase encoding is either in row or column direction //0043,1039 (or 0043,a039). b value (as the first number in the string). @@ -6234,6 +6241,8 @@ int singleDICOM(struct TDCMopts* opts, char *fname) { struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc( sizeof(struct TDICOMdata)); struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); struct TSearchList nameList; + struct TDCMprefs prefs; + opts2Prefs (opts, &prefs); 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 nameList.numItems = 0; @@ -6242,7 +6251,8 @@ 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] = readDICOMx(nameList.str[0], &prefs, 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); freeNameList(nameList); @@ -6678,6 +6688,8 @@ int nii_loadDirCore(char *indir, struct TDCMopts* opts) { // 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 *)malloc(sizeof(struct TDTI4D)); + struct TDCMprefs prefs; + opts2Prefs (opts, &prefs); int nConvertTotal = 0; bool compressionWarning = false; bool convertError = false; @@ -6696,7 +6708,8 @@ 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] = readDICOMx(nameList.str[i], &prefs, 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 (opts->isIgnoreSeriesInstanceUID) dcmList[i].seriesUidCrc = dcmList[i].seriesNum; //if (!dcmList[i].isValid) printf(">>>>Not a valid DICOM %s\n", nameList.str[i]); @@ -7248,6 +7261,7 @@ void setDefaultOpts (struct TDCMopts *opts, const char * argv[]) { //either "set opts->isSaveNativeEndian = true; opts->isAddNamePostFixes = true; //e.g. "_e2" added for second echo opts->isTestx0021x105E = false; //GE test slice times stored in 0021,105E + opts->isIgnoreTriggerTimes = false; opts->isSaveNRRD = false; opts->isPipedGz = false; //e.g. pipe data directly to pigz instead of saving uncompressed to disk opts->isSave3D = false; diff --git a/console/nii_dicom_batch.h b/console/nii_dicom_batch.h index 39e66c89..d62c16b1 100644 --- a/console/nii_dicom_batch.h +++ b/console/nii_dicom_batch.h @@ -35,7 +35,7 @@ extern "C" { #define MAX_NUM_SERIES 16 struct TDCMopts { - bool isTestx0021x105E, isAddNamePostFixes, isSaveNativeEndian, isSaveNRRD, isOneDirAtATime, isRenameNotConvert, isSave3D, isGz, isPipedGz, isFlipY, isCreateBIDS, isSortDTIbyBVal, isAnonymizeBIDS, isOnlyBIDS, isCreateText, isForceOnsetTimes,isIgnoreDerivedAnd2D, isPhilipsFloatNotDisplayScaling, isTiltCorrect, isRGBplanar, isOnlySingleFile, isForceStackDCE, isIgnoreSeriesInstanceUID, isRotate3DAcq, isCrop; + bool isIgnoreTriggerTimes, isTestx0021x105E, isAddNamePostFixes, isSaveNativeEndian, isSaveNRRD, isOneDirAtATime, isRenameNotConvert, isSave3D, isGz, isPipedGz, isFlipY, isCreateBIDS, isSortDTIbyBVal, isAnonymizeBIDS, isOnlyBIDS, isCreateText, isForceOnsetTimes,isIgnoreDerivedAnd2D, isPhilipsFloatNotDisplayScaling, isTiltCorrect, isRGBplanar, isOnlySingleFile, isForceStackDCE, isIgnoreSeriesInstanceUID, isRotate3DAcq, isCrop; int isMaximize16BitRange, isForceStackSameSeries, nameConflictBehavior, isVerbose, isProgress, compressFlag, dirSearchDepth, gzLevel; //support for compressed data 0=none, char filename[512], outdir[512], indir[512], pigzname[512], optsname[512], indirParent[512], imageComments[24]; double seriesNumber[MAX_NUM_SERIES]; //requires double must store -1 (report but do not convert) as well as seriesUidCrc (uint32) From 15756e9cebcee5f87a02bc596290181068c493ce Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Fri, 13 Nov 2020 17:43:31 +0000 Subject: [PATCH 02/95] Fencing in required macro --- console/nii_dicom_batch.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 5843637a..ca6bc079 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -3399,6 +3399,7 @@ int nii_saveNRRD(char * niiFilename, struct nifti_1_header hdr, unsigned char* i return pigz_File(fname, opts, imgsz); } // nii_saveNRRD() +#endif #ifndef max #define max(a,b) \ @@ -3463,6 +3464,8 @@ void removeSclSlopeInter(struct nifti_1_header* hdr, unsigned char* img) { //printWarning("NRRD unable to record scl_slope/scl_inter %g/%g\n", hdr->scl_slope, hdr->scl_inter); } +#ifndef USING_R + void swapEndian(struct nifti_1_header* hdr, unsigned char* im, bool isNative) { //swap endian from big->little or little->big // must be told which is native to detect datatype and number of voxels From 99268a72c760664d1c85f8c7104dd6e90af76935 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Fri, 13 Nov 2020 18:55:00 +0000 Subject: [PATCH 03/95] Also adding template specialisation for float, and using special cases to reduce boilerplate --- console/nii_dicom_batch.cpp | 54 +++++++++++++------------------------ 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index ca6bc079..feafa101 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -3000,37 +3000,23 @@ void nii_saveAttributes (struct TDICOMdata &data, struct nifti_1_header &header, case kMANUFACTURER_HITACHI: images->addAttribute("manufacturer", "Hitachi"); break; case kMANUFACTURER_CANON: images->addAttribute("manufacturer", "Canon"); break; } - if (strlen(data.manufacturersModelName) > 0) - images->addAttribute("scannerModelName", data.manufacturersModelName); - if (strlen(data.imageType) > 0) - images->addAttribute("imageType", data.imageType); + images->addAttribute("scannerModelName", data.manufacturersModelName); + images->addAttribute("imageType", data.imageType); if (data.seriesNum > 0) images->addAttribute("seriesNumber", int(data.seriesNum)); - if (strlen(data.seriesDescription) > 0) - images->addAttribute("seriesDescription", data.seriesDescription); - if (strlen(data.sequenceName) > 0) - images->addAttribute("sequenceName", data.sequenceName); - if (strlen(data.protocolName) > 0) - images->addAttribute("protocolName", data.protocolName); - if (strlen(data.studyDate) >= 8 && strcmp(data.studyDate,"00000000") != 0) - images->addDateAttribute("studyDate", data.studyDate); - if (strlen(data.studyTime) > 0 && strncmp(data.studyTime,"000000",6) != 0) - images->addAttribute("studyTime", data.studyTime); - if (data.fieldStrength > 0.0) - images->addAttribute("fieldStrength", data.fieldStrength); - if (data.flipAngle > 0.0) - images->addAttribute("flipAngle", data.flipAngle); - if (data.TE > 0.0) - images->addAttribute("echoTime", data.TE); - if (data.TR > 0.0) - images->addAttribute("repetitionTime", data.TR); - if (data.TI > 0.0) - images->addAttribute("inversionTime", data.TI); + images->addAttribute("seriesDescription", data.seriesDescription); + images->addAttribute("sequenceName", data.sequenceName); + images->addAttribute("protocolName", data.protocolName); + images->addDateAttribute("studyDate", data.studyDate); + images->addAttribute("studyTime", data.studyTime); + images->addAttribute("fieldStrength", data.fieldStrength); + images->addAttribute("flipAngle", data.flipAngle); + images->addAttribute("echoTime", data.TE); + images->addAttribute("repetitionTime", data.TR); + images->addAttribute("inversionTime", data.TI); if (!data.isXRay) { - if (data.zThick > 0.0) - images->addAttribute("sliceThickness", data.zThick); - if (data.zSpacing > 0.0) - images->addAttribute("sliceSpacing", data.zSpacing); + images->addAttribute("sliceThickness", data.zThick); + images->addAttribute("sliceSpacing", data.zSpacing); } if (data.CSA.multiBandFactor > 1) images->addAttribute("multibandFactor", data.CSA.multiBandFactor); @@ -3099,10 +3085,8 @@ void nii_saveAttributes (struct TDICOMdata &data, struct nifti_1_header &header, images->addAttribute("sliceTiming", sliceTimes); } - if (strlen(data.patientID) > 0) - images->addAttribute("patientIdentifier", data.patientID); - if (strlen(data.patientName) > 0) - images->addAttribute("patientName", data.patientName); + images->addAttribute("patientIdentifier", data.patientID); + images->addAttribute("patientName", data.patientName); if (strlen(data.patientBirthDate) >= 8 && strcmp(data.patientBirthDate,"00000000") != 0) images->addDateAttribute("patientBirthDate", data.patientBirthDate); if (strlen(data.patientAge) > 0 && strcmp(data.patientAge,"000Y") != 0) @@ -3111,10 +3095,8 @@ void nii_saveAttributes (struct TDICOMdata &data, struct nifti_1_header &header, images->addAttribute("patientSex", "F"); else if (data.patientSex == 'M') images->addAttribute("patientSex", "M"); - if (data.patientWeight > 0.0) - images->addAttribute("patientWeight", data.patientWeight); - if (strlen(data.imageComments) > 0) - images->addAttribute("comments", data.imageComments); + images->addAttribute("patientWeight", data.patientWeight); + images->addAttribute("comments", data.imageComments); } #else From 5234347fa2af1d33e6954d71303cb9b7ba82a511 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Tue, 17 Nov 2020 12:19:17 +0000 Subject: [PATCH 04/95] Check date and time values before storing attributes --- console/nii_dicom_batch.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index feafa101..598a6e44 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -3008,7 +3008,7 @@ void nii_saveAttributes (struct TDICOMdata &data, struct nifti_1_header &header, images->addAttribute("sequenceName", data.sequenceName); images->addAttribute("protocolName", data.protocolName); images->addDateAttribute("studyDate", data.studyDate); - images->addAttribute("studyTime", data.studyTime); + images->addTimeAttribute("studyTime", data.studyTime); images->addAttribute("fieldStrength", data.fieldStrength); images->addAttribute("flipAngle", data.flipAngle); images->addAttribute("echoTime", data.TE); @@ -3087,8 +3087,7 @@ void nii_saveAttributes (struct TDICOMdata &data, struct nifti_1_header &header, images->addAttribute("patientIdentifier", data.patientID); images->addAttribute("patientName", data.patientName); - if (strlen(data.patientBirthDate) >= 8 && strcmp(data.patientBirthDate,"00000000") != 0) - images->addDateAttribute("patientBirthDate", data.patientBirthDate); + images->addDateAttribute("patientBirthDate", data.patientBirthDate); if (strlen(data.patientAge) > 0 && strcmp(data.patientAge,"000Y") != 0) images->addAttribute("patientAge", data.patientAge); if (data.patientSex == 'F') From 20531eb2fc1a63613b7751bb4cdd3c75de0372a4 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Tue, 17 Nov 2020 20:10:51 +0000 Subject: [PATCH 05/95] Resolving outstanding printf() calls --- console/nii_dicom.cpp | 6 +++--- console/nii_dicom_batch.cpp | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index aeba3728..9780145c 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -4968,7 +4968,7 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); privateCreatorRemaps[nRemaps] = privateCreatorRemap; //printf("new remapping %04x,%04x -> %04x,%04x\n", privateCreatorMask & 65535, privateCreatorMask >> 16, privateCreatorRemap & 65535, privateCreatorRemap >> 16); if (isVerbose > 1) - printf("new remapping (%d) %04x,%02xxy -> %04x,%02xxy\n", nRemaps, privateCreatorMask & 65535, privateCreatorMask >> 24, privateCreatorRemap & 65535, privateCreatorRemap >> 24); + printMessage("new remapping (%d) %04x,%02xxy -> %04x,%02xxy\n", nRemaps, privateCreatorMask & 65535, privateCreatorMask >> 24, privateCreatorRemap & 65535, privateCreatorRemap >> 24); nRemaps += 1; //for (int i = 0; i < nRemaps; i++) // printf(" %d = %04x,%02xxy -> %04x,%02xxy\n", i, privateCreatorMasks[i] & 65535, privateCreatorMasks[i] >> 24, privateCreatorRemaps[i] & 65535, privateCreatorRemaps[i] >> 24); @@ -4983,13 +4983,13 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); remappedGroupElement = privateCreatorRemaps[i] + (groupElement & 0x00FF0000); if (remappedGroupElement == 0) goto skipRemap; if (isVerbose > 1) - printf("remapping %04x,%04x -> %04x,%04x\n", groupElement & 65535, groupElement >> 16, remappedGroupElement & 65535, remappedGroupElement >> 16); + printMessage("remapping %04x,%04x -> %04x,%04x\n", groupElement & 65535, groupElement >> 16, remappedGroupElement & 65535, remappedGroupElement >> 16); groupElement = remappedGroupElement; } skipRemap: #endif // salvageAgfa if ((lLength % 2) != 0) { //https://www.nitrc.org/forum/forum.php?thread_id=11827&forum_id=4703 - printf("Illegal DICOM tag %04x,%04x (odd element length %d): %s\n", groupElement & 65535,groupElement>>16, lLength, fname); + printMessage("Illegal DICOM tag %04x,%04x (odd element length %d): %s\n", groupElement & 65535,groupElement>>16, lLength, fname); //proper to return here, but we can carry on as a hail mary // d.isValid = false; //return d; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 598a6e44..6b0717c9 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -307,8 +307,10 @@ void siemensPhilipsCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI for (int v= 0; v < 4; v++) if (vx[i].V[v] == -0.0f) vx[i].V[v] = 0.0f; //remove sign from values that are virtually zero } +#ifndef USING_R for (int i = 0; i < 3; i++) printf("%g = %g %g %g\n", vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); +#endif return; } //https://github.com/rordenlab/dcm2niix/issues/225 if ((toupper(d->patientOrient[0])== 'H') && (toupper(d->patientOrient[1])== 'F') && (toupper(d->patientOrient[2])== 'S')) From 11238da7fe4af49262137f1c616268dea029d3a3 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Tue, 17 Nov 2020 20:29:46 +0000 Subject: [PATCH 06/95] Specialising templated member functions isn't standard C++, so moving validation into a standalone function --- console/nii_dicom_batch.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 6b0717c9..b526b855 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -3048,16 +3048,14 @@ void nii_saveAttributes (struct TDICOMdata &data, struct nifti_1_header &header, effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * reconMatrixPE); if (data.effectiveEchoSpacingGE > 0.0) effectiveEchoSpacing = data.effectiveEchoSpacingGE / 1000000.0; - - if (effectiveEchoSpacing > 0.0) - images->addAttribute("effectiveEchoSpacing", effectiveEchoSpacing); + + images->addAttribute("effectiveEchoSpacing", effectiveEchoSpacing); if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0)) images->addAttribute("effectiveReadoutTime", effectiveEchoSpacing * (reconMatrixPE - 1.0)); - if (data.pixelBandwidth > 0.0) - images->addAttribute("pixelBandwidth", data.pixelBandwidth); + images->addAttribute("pixelBandwidth", data.pixelBandwidth); if ((data.manufacturer == kMANUFACTURER_SIEMENS) && (data.dwellTime > 0)) images->addAttribute("dwellTime", data.dwellTime * 1e-9); - + // Phase encoding polarity // We only save these attributes if both direction and polarity are known bool isSkipPhaseEncodingAxis = data.is3DAcq; From 64974d6a525b585056053e0393d144c411dfed69 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Wed, 18 Nov 2020 09:52:54 +0000 Subject: [PATCH 07/95] Try re-enabling reading ASCII CSA, without which the QA battery doesn't all pass --- console/nii_dicom_batch.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index b526b855..b900395f 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -387,9 +387,7 @@ void nii_saveText(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, fclose(fp); }// nii_saveText() -#ifndef USING_R #define myReadAsciiCsa -#endif #ifdef myReadAsciiCsa //read from the ASCII portion of the Siemens CSA series header From 5c3c9af3f692e5d7fa901ce99e871127ea473dd2 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Wed, 18 Nov 2020 09:55:12 +0000 Subject: [PATCH 08/95] Using R_FINITE --- console/nii_dicom_batch.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index b900395f..3afe953d 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -69,6 +69,9 @@ #undef isnan #define isnan ISNAN + +#undef isfinite +#define isfinite R_FINITE #endif #define newTilt From cab3efb6c38671e59f8f6d2e47af625e2737baf0 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Wed, 18 Nov 2020 10:09:14 +0000 Subject: [PATCH 09/95] kForeignPathSeparator is no longer defined, so use '/' explicitly --- console/nii_dicom_batch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 3afe953d..08b32cd0 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -2764,7 +2764,7 @@ int nii_createFilename(struct TDICOMdata dcm, char * niiFilename, struct TDCMopt #if defined(USING_R) && (defined(_WIN64) || defined(_WIN32)) // R also uses forward slash on Windows, so allow it here if (!sep) - sep = strchr(outname, kForeignPathSeparator); + sep = strchr(outname, '/'); #endif if (sep) { char newdir[2048] = {""}; From 9732da09eee219b9042aa4907c198ae47a4429f4 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Wed, 18 Nov 2020 13:48:01 +0000 Subject: [PATCH 10/95] Fencing out apparently unused function --- console/nii_dicom_batch.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 08b32cd0..5ea15406 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1685,6 +1685,8 @@ tse3d: T2*/ fclose(fp); }// nii_SaveBIDSX() +#ifndef USING_R + void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char * filename) { struct TDTI4D *dti4D; dti4D->sliceOrder[0] = -1; @@ -1696,6 +1698,8 @@ dti4D->repetitionTimeInversion = 0.0; nii_SaveBIDSX(pathoutname, d, opts, h, filename, dti4D); }// nii_SaveBIDSX() +#endif + bool isADCnotDTI(TDTI bvec) { //returns true if bval!=0 but all bvecs == 0 (Philips code for derived ADC image) return ((!isSameFloat(bvec.V[0],0.0f)) && //not a B-0 image ((isSameFloat(bvec.V[1],0.0f)) && (isSameFloat(bvec.V[2],0.0f)) && (isSameFloat(bvec.V[3],0.0f)) ) ); From d7730d1a81658dcd35adeec690b643f640576604 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Sat, 27 Mar 2021 23:43:13 +0000 Subject: [PATCH 11/95] Bringing echo spacing calculations into line with mainline code --- console/nii_dicom_batch.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 5ea15406..4c70be59 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -3051,11 +3051,16 @@ void nii_saveAttributes (struct TDICOMdata &data, struct nifti_1_header &header, double effectiveEchoSpacing = 0.0; if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode > 0.0)) effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * reconMatrixPE); - if (data.effectiveEchoSpacingGE > 0.0) - effectiveEchoSpacing = data.effectiveEchoSpacingGE / 1000000.0; + if (data.effectiveEchoSpacingGE > 0.0) { + double roundFactor = data.isPartialFourier ? 4.0 : 2.0; + double totalReadoutTime = ((ceil(1.0/roundFactor * data.phaseEncodingLines / data.accelFactPE) * roundFactor) - 1.0) * data.effectiveEchoSpacingGE * 0.000001; + effectiveEchoSpacing = totalReadoutTime / (reconMatrixPE - 1); + } images->addAttribute("effectiveEchoSpacing", effectiveEchoSpacing); - if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0)) + if (data.manufacturer == kMANUFACTURER_UIH) + images->addAttribute("effectiveReadoutTime", data.acquisitionDuration / 1000.0); + else if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0)) images->addAttribute("effectiveReadoutTime", effectiveEchoSpacing * (reconMatrixPE - 1.0)); images->addAttribute("pixelBandwidth", data.pixelBandwidth); if ((data.manufacturer == kMANUFACTURER_SIEMENS) && (data.dwellTime > 0)) From d2ec9269a98e7f699866a28e3c6436bb95e68343 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Sun, 28 Mar 2021 00:14:45 +0000 Subject: [PATCH 12/95] Ensuring variables are initialised before passing their addresses --- console/nii_dicom_batch.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 4c70be59..4965327c 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -6749,8 +6749,8 @@ int nii_loadDirCore(char *indir, struct TDCMopts* opts) { bool matched = false; // If the file matches an existing series, add it to the corresponding file list for (int j = 0; j < opts->series.size(); j++) { - bool isMultiEchoUnused, isNonParallelSlices, isCoilVaries; - if (isSameSet(opts->series[j].representativeData, dcmList[i], opts, &warnings, &isMultiEchoUnused, &isNonParallelSlices, &isCoilVaries)) { + bool isMultiEcho = false, isNonParallelSlices = false, isCoilVaries = false; + if (isSameSet(opts->series[j].representativeData, dcmList[i], opts, &warnings, &isMultiEcho, &isNonParallelSlices, &isCoilVaries)) { opts->series[j].files.push_back(nameList.str[i]); matched = true; break; From a2d230b50d0ed0f643d02c2897adbcca0f7a2669 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Sun, 28 Mar 2021 18:14:21 +0100 Subject: [PATCH 13/95] Using standard min and max functions with R --- console/nii_dicom_batch.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 4965327c..308200c9 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -3392,6 +3392,18 @@ int nii_saveNRRD(char * niiFilename, struct nifti_1_header hdr, unsigned char* i #endif +#ifdef USING_R + +#ifndef max + #define max(a,b) std::max(a,b) +#endif + +#ifndef min + #define min(a,b) std::min(a,b) +#endif + +#else + #ifndef max #define max(a,b) \ ({ __typeof__ (a) _a = (a); \ @@ -3406,6 +3418,8 @@ int nii_saveNRRD(char * niiFilename, struct nifti_1_header hdr, unsigned char* i _a < _b ? _a : _b; }) #endif +#endif + void removeSclSlopeInter(struct nifti_1_header* hdr, unsigned char* img) { //NRRD does not have scl_slope scl_inter. Adjust data if possible // https://discourse.slicer.org/t/preserve-image-rescale-and-slope-when-saving-in-nrrd-file/13357 From 10ef23051dabc80896ca675b8d4c48becc41e568 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Sun, 28 Mar 2021 21:13:22 +0100 Subject: [PATCH 14/95] Fixing buffer overflow --- console/nii_dicom_batch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 308200c9..6b03db1d 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -4694,7 +4694,7 @@ void readSoftwareVersionsGE(char softwareVersionsGE[], int verbose,char geVersio sepStart += 1; len = 11; char * versionString = (char *)malloc(sizeof(char) * len); - versionString[len] =0; + versionString[len-1] =0; memcpy(versionString, sepStart, len); int ver1, ver2, ver3; char c1, c2, c3, c4; From b5ab2a1a9b1096853cc603afbe1ca8a9e6acb0ed Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Sun, 28 Mar 2021 21:17:16 +0100 Subject: [PATCH 15/95] Updating configure/source files to not be specific to a particular version of OpenJPEG --- console/nii_dicom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 9780145c..02d30f16 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -263,7 +263,7 @@ unsigned char * nii_loadImgCoreOpenJPEG(char* imgname, struct nifti_1_header hdr opj_destroy_codec(codec); return ret; } -#endif //if +#endif //myDisableOpenJPEG #ifndef M_PI #define M_PI 3.14159265358979323846 From 2378ce0fe3509f0f6ac80a226fdc02037d52e027 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Thu, 1 Apr 2021 22:10:06 +0100 Subject: [PATCH 16/95] Removing self-assignment to silence clang warning --- console/nii_dicom_batch.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 6b03db1d..c4cd00d8 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -823,12 +823,12 @@ int geProtocolBlock(const char * filename, int geOffset, int geLength, int isV bool isFCOMMENT = ((flags & 0x10) == 0x10); uint32_t hdrSz = 10; if (isFNAME) {//skip null-terminated string FNAME - for (hdrSz = hdrSz; hdrSz < cmpSz; hdrSz++) + for (; hdrSz < cmpSz; hdrSz++) if (pCmp[hdrSz] == 0) break; hdrSz++; } if (isFCOMMENT) {//skip null-terminated string COMMENT - for (hdrSz = hdrSz; hdrSz < cmpSz; hdrSz++) + for (; hdrSz < cmpSz; hdrSz++) if (pCmp[hdrSz] == 0) break; hdrSz++; } From 34dd89fd1045e58217f5db6e47ed36ad27c83095 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Thu, 1 Apr 2021 22:12:17 +0100 Subject: [PATCH 17/95] Using float literals to silence warning on Solaris --- console/nii_dicom_batch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index c4cd00d8..b4a4678d 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -3424,7 +3424,7 @@ void removeSclSlopeInter(struct nifti_1_header* hdr, unsigned char* img) { //NRRD does not have scl_slope scl_inter. Adjust data if possible // https://discourse.slicer.org/t/preserve-image-rescale-and-slope-when-saving-in-nrrd-file/13357 if (isSameFloat(hdr->scl_inter,0.0) && isSameFloat(hdr->scl_slope,1.0)) return; - if ((!isSameFloat(fmod(hdr->scl_inter, 1.0),0.0)) || (!isSameFloat(fmod(hdr->scl_slope, 1.0),0.0))) return; + if ((!isSameFloat(fmod(hdr->scl_inter, 1.0f),0.0)) || (!isSameFloat(fmod(hdr->scl_slope, 1.0f),0.0))) return; int nVox = 1; for (int i = 1; i < 8; i++) if (hdr->dim[i] > 1) nVox = nVox * hdr->dim[i]; From df0ff6cd40a96ea73202d1ffee0bb336bc5ee222 Mon Sep 17 00:00:00 2001 From: Jon Clayden Date: Thu, 1 Apr 2021 23:52:51 +0100 Subject: [PATCH 18/95] Adding cast to avoid template argument resolution problem with certain compilers --- console/nii_dicom_batch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index b4a4678d..9e201345 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -4734,7 +4734,7 @@ void sliceTimingGE_Testx0021x105E(struct TDICOMdata * d, struct TDCMopts opts, s float mxErr = 0.0; for (int v = 0; v < hdr->dim[3]; v++) { sliceTiming[v] = (sliceTiming[v] - mn) * 1000.0; //subtract offset, convert sec -> ms - mxErr = max(mxErr, fabs(sliceTiming[v] - d->CSA.sliceTiming[v])); + mxErr = max(mxErr, float(fabs(sliceTiming[v] - d->CSA.sliceTiming[v]))); } printMessage("Slice Timing Error between calculated and RTIA timer(0021,105E): %gms\n", mxErr); if ((mxErr < 1.0) && (opts.isVerbose < 1)) return; From b9252b5672fbe93887653f14e683ba9e54f302a2 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Sat, 10 Apr 2021 08:30:47 -0400 Subject: [PATCH 19/95] Detect GE direct fieldmaps (https://github.com/rordenlab/dcm2niix/issues/501) --- console/nii_dicom.cpp | 10 ++++++++++ console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 3 +++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 02d30f16..59e37951 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -4350,6 +4350,7 @@ const uint32_t kEffectiveTE = 0x0018+ (0x9082 << 16); #define kFloatPixelPaddingValue 0x0028+(0x0122 << 16 ) // https://github.com/rordenlab/dcm2niix/issues/262 #define kIntercept 0x0028+(0x1052 << 16 ) #define kSlope 0x0028+(0x1053 << 16 ) +//#define kRescaleType 0x0028+(0x1053 << 16 ) //LO e.g. for Philips Fieldmap: [Hz] //#define kSpectroscopyDataPointColumns 0x0028+(0x9002 << 16 ) //IS #define kGeiisFlag 0x0029+(0x0010 << 16 ) //warn user if dreaded GEIIS was used to process image #define kCSAImageHeaderInfo 0x0029+(0x1010 << 16 ) @@ -4527,6 +4528,7 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); bool isSwitchToBigEndian = false; bool isAtFirstPatientPosition = false; //for 3d and 4d files: flag is true for slices at same position as first slice bool isMosaic = false; + bool isGEfieldMap = false; //issue501 int patientPositionNum = 0; float B0Philips = -1.0; float vRLPhilips = 0.0; @@ -5544,6 +5546,8 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); if (strcmp(epiStr, "EPI2") == 0){ d.internalepiVersionGE = 2; //-1 = not epi, 1 = EPI, 2 = EPI2 } + if ((strcmp(epiStr, "EFGRE3D") == 0) || (strcmp(epiStr, "B0map") == 0)) + isGEfieldMap = true; //issue501 break; } case kBandwidthPerPixelPhaseEncode: @@ -6839,6 +6843,12 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); //printMessage("Issue 373: Check for ZIP2 Factor: %d SliceThickness+SliceGap: %f, SpacingBetweenSlices: %f \n", zipFactor, d.xyzMM[3], d.zSpacing); locationsInAcquisitionGE *= zipFactor; // Multiply number of slices by ZIP factor. Do this prior to checking for conflict below (?). } + if (isGEfieldMap) { //issue501 : to do check zip factor + //Volume 1) derived phase field map [Hz] and 2) magnitude volume. + d.isDerived = (d.imageNum <= locationsInAcquisitionGE); //first volume + d.isRealIsPhaseMapHz = d.isDerived; + d.isHasReal = d.isDerived; + } /* SAH.end */ if (locationsInAcquisitionGE < d.locationsInAcquisition) { d.locationsInAcquisitionConflict = d.locationsInAcquisition; diff --git a/console/nii_dicom.h b/console/nii_dicom.h index b4957346..78962342 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.20210403" +#define kDCMdate "v1.0.20210410" #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 9e201345..954d1360 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1201,6 +1201,9 @@ tse3d: T2*/ fprintf(fp, "\t\"PhilipsScaleSlope\": %g,\n", d.intenScalePhilips ); fprintf(fp, "\t\"UsePhilipsFloatNotDisplayScaling\": %d,\n", opts.isPhilipsFloatNotDisplayScaling); } + //https://bids-specification--622.org.readthedocs.build/en/622/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#case-3-direct-field-mapping + if ((d.isRealIsPhaseMapHz) && (d.isHasReal)) + fprintf(fp, "\t\"Units\": \"Hz\",\n"); // //PET ISOTOPE MODULE ATTRIBUTES json_Str(fp, "\t\"Radiopharmaceutical\": \"%s\",\n", d.radiopharmaceutical); json_Float(fp, "\t\"RadionuclidePositronFraction\": %g,\n", d.radionuclidePositronFraction ); From c8874a6fa15e72b7a25ac270dd46abf3cc10cbc5 Mon Sep 17 00:00:00 2001 From: Jaemin Shin Date: Sat, 10 Apr 2021 11:49:18 -0400 Subject: [PATCH 20/95] Detect GE direct fieldmaps Update --- console/nii_dicom.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 59e37951..49175a29 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -5531,6 +5531,9 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); if (strstr(epiStr, "epiRT") != NULL){ d.epiVersionGE = 1; //-1 = not epi, 0 = epi, 1 = epiRT } + if (strcmp(epiStr, "3db0map") == 0){ + isGEfieldMap = true; //issue501 + } break; } case kInternalPulseSequenceNameGE : { //LO 'EPI'(gradient echo)/'EPI2'(spin echo): @@ -5546,8 +5549,9 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); if (strcmp(epiStr, "EPI2") == 0){ d.internalepiVersionGE = 2; //-1 = not epi, 1 = EPI, 2 = EPI2 } - if ((strcmp(epiStr, "EFGRE3D") == 0) || (strcmp(epiStr, "B0map") == 0)) + if (strcmp(epiStr, "B0map") == 0){ isGEfieldMap = true; //issue501 + } break; } case kBandwidthPerPixelPhaseEncode: From 64b4afdb2705860d9f9d729ff24a415e2352a310 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Thu, 15 Apr 2021 09:18:07 -0400 Subject: [PATCH 21/95] Handle repeated patient position (0020, 0032) entries (https://github.com/rordenlab/dcm2niix/issues/506) --- console/nii_dicom.cpp | 4 +++- console/nii_dicom_batch.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 49175a29..c4bace5b 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -6865,8 +6865,10 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); if (d.zSpacing > 0.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])) + if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (patientPositionNum > d.xyzDim[3])) { + d.CSA.numDti = d.xyzDim[3]; //issue506 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! + } if ((d.imageStart > 144) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1)) d.isValid = true; //if ((d.imageStart > 144) && (d.xyzDim[1] >= 1) && (d.xyzDim[2] >= 1) && (d.xyzDim[4] > 1)) //Spectroscopy diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 954d1360..be00908c 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1213,7 +1213,7 @@ tse3d: T2*/ json_Float(fp, "\t\"IsotopeHalfLife\": %g,\n", d.ecat_isotope_halflife); json_Float(fp, "\t\"Dosage\": %g,\n", d.ecat_dosage); json_Str(fp, "\t\"ConvolutionKernel\": \"%s\",\n", d.convolutionKernel); - json_Str(fp, "\t\"Unit\": \"%s\",\n", d.unitsPT); + json_Str(fp, "\t\"Units\": \"%s\",\n", d.unitsPT); //https://github.com/bids-standard/bids-specification/pull/773 json_Str(fp, "\t\"DecayCorrection\": \"%s\",\n", d.decayCorrection); json_Str(fp, "\t\"AttenuationCorrectionMethod\": \"%s\",\n", d.attenuationCorrectionMethod); json_Str(fp, "\t\"ReconstructionMethod\": \"%s\",\n", d.reconstructionMethod); From d8a46aad7109332d0723c519dc5d4aa98e7d91af Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 16 Apr 2021 09:18:26 -0400 Subject: [PATCH 22/95] vox_offset as Float32 for huge DICOM headers (https://github.com/rordenlab/dcm2niix/issues/507) --- console/nii_dicom.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index c4bace5b..c89b9278 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -2747,9 +2747,10 @@ void conv12bit16bit(unsigned char * img, struct nifti_1_header hdr) { } } //conv12bit16bit() -unsigned char * nii_loadImgCore(char* imgname, struct nifti_1_header hdr, int bitsAllocated) { +unsigned char * nii_loadImgCore(char* imgname, struct nifti_1_header hdr, int bitsAllocated, int imageStart32) { size_t imgsz = nii_ImgBytes(hdr); size_t imgszRead = imgsz; + size_t imageStart = imageStart32; if (bitsAllocated == 12) imgszRead = round(imgsz * 0.75); FILE *file = fopen(imgname , "rb"); @@ -2759,18 +2760,14 @@ unsigned char * nii_loadImgCore(char* imgname, struct nifti_1_header hdr, int bi } fseek(file, 0, SEEK_END); long fileLen=ftell(file); - if (fileLen < (imgszRead+(long) hdr.vox_offset)) { - //previously (fileLen < (imgszRead+hdr.vox_offset)) - // FileSize < (ImageSize+HeaderSize): 42399788 < (42398702+1086) - // FileSize < (ImageSize+HeaderSize): 42399788 < ( 42399792.00) - //note hdr.vox_offset is a float, and without a type-cast it can lead to unusual values + if (fileLen < (imgszRead + imageStart)) { + //note hdr.vox_offset is a float: issue507 //https://www.nitrc.org/forum/message.php?msg_id=27155 - printMessage("FileSize < (ImageSize+HeaderSize): %ld < (%zu+%ld) \n", fileLen, imgszRead, (long)hdr.vox_offset); - //printMessage("FileSize < (ImageSize+HeaderSize): %ld < (%zu) \n", fileLen, imgszRead+(long)hdr.vox_offset); + printMessage("FileSize < (ImageSize+HeaderSize): %ld < (%zu+%zu) \n", fileLen, imgszRead, imageStart); printWarning("File not large enough to store image data: %s\n", imgname); return NULL; } - fseek(file, (long) hdr.vox_offset, SEEK_SET); + fseek(file, (long) imageStart, SEEK_SET); unsigned char *bImg = (unsigned char *)malloc(imgsz); //int i = 0; //while (bImg[i] == 0) i++; @@ -3670,7 +3667,7 @@ unsigned char * nii_loadImgXL(char* imgname, struct nifti_1_header *hdr, struct 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, dcm.imageStart); if (img == NULL) return img; if ((dcm.compressionScheme == kCompressNone) && (dcm.isLittleEndian != littleEndianPlatform()) && (hdr->bitpix > 8)) img = nii_byteswap(img, hdr); From 0d4ea8b51006b1ecb5ca860dc338a090fd4b1ee9 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Mon, 26 Apr 2021 12:38:12 -0400 Subject: [PATCH 23/95] Report if Protocol Data Block GE (0025,101B) is missing. --- console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 78962342..c2073b1b 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.20210410" +#define kDCMdate "v1.0.20210426" #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 be00908c..a50877ec 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -4752,6 +4752,11 @@ void sliceTimingGE(struct TDICOMdata * d, const char * filename, struct TDCMopts if ((d->is3DAcq) || (d->isLocalizer) || (hdr->dim[4] < 2)) return; //no need for slice times if (hdr->dim[3] < 2) return; if (d->manufacturer != kMANUFACTURER_GE) return; + if ((d->protocolBlockStartGE < 128) || (d->protocolBlockLengthGE < 10)) { + d->CSA.sliceTiming[0] = -1; + printWarning("Unable to determine GE Slice timing, no Protocol Data Block GE (0025,101B): %s\n", filename); + return; + } //start version check: float geMajorVersion = 0; int geMajorVersionInt = 0, geMinorVersionInt = 0, geReleaseVersionInt = 0; @@ -4786,6 +4791,7 @@ void sliceTimingGE(struct TDICOMdata * d, const char * filename, struct TDCMopts //printWarning("Using GE Protocol Data Block for BIDS data (beware: new feature)\n"); int ok = geProtocolBlock(filename, d->protocolBlockStartGE, d->protocolBlockLengthGE, opts.isVerbose, &sliceOrderGE, &viewOrderGE, &mbAccel, &nSlices, &groupDelay, ioptGE); if (ok != EXIT_SUCCESS) { + d->CSA.sliceTiming[0] = -1; printWarning("Unable to estimate slice times: issue decoding GE protocol block.\n"); return; } From 7038255816fe606865fd624a687bcf5dc0eb88a5 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 5 May 2021 08:39:43 -0400 Subject: [PATCH 24/95] Reduce false alarms for warning 'Adjusting for negative MosaicRefAcqTimes (issue 271).' --- console/nii_dicom_batch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index a50877ec..ff00ee3a 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -4496,7 +4496,7 @@ 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) + if (d->CSA.sliceTiming[kMaxEPI3D-1] < -1.0) //the value -2.0 is used as a flag for negative MosaicRefAcqTimes in checkSliceTimes(), see issue 271 printWarning("Adjusting for negative MosaicRefAcqTimes (issue 271).\n"); bool isSliceTimeHHMMSS = (d->manufacturer == kMANUFACTURER_UIH); if (isForceSliceTimeHHMMSS) isSliceTimeHHMMSS = true; From 0b44e6f444d7c5a1c430814a64e68158a90ee269 Mon Sep 17 00:00:00 2001 From: "Eric A. Borisch" Date: Wed, 26 May 2021 16:19:05 -0500 Subject: [PATCH 25/95] Include MacPorts option for installation. MacPorts has dcm2niix available; a +docs variant is available for those desiring a man page, and it is installed with openjpeg and batch support. Pre-compiled binaries are available for ease of installation with the default MacPorts configuration. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 45e63545..0499a83f 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ There are a couple ways to install dcm2niix * `curl -fLO https://github.com/rordenlab/dcm2niix/releases/latest/download/dcm2niix_mac_arm.pkg` * `curl -fLO https://github.com/rordenlab/dcm2niix/releases/latest/download/dcm2niix_win.zip` - [MRIcroGL (NITRC)](https://www.nitrc.org/projects/mricrogl) or [MRIcroGL (GitHub)](https://github.com/rordenlab/MRIcroGL12/releases) includes dcm2niix that can be run from the command line or from the graphical user interface (select the Import menu item). The Linux version of dcm2niix is compiled on a [holy build box](https://github.com/phusion/holy-build-box), so it should run on any Linux distribution. - - If you have a MacOS computer with Homebrew you can run `brew install dcm2niix`. + - If you have a MacOS computer with Homebrew or MacPorts you can run `brew install dcm2niix` or `sudo port install dcm2niix`, respectively. - If you have Conda, [`conda install -c conda-forge dcm2niix`](https://anaconda.org/conda-forge/dcm2niix) on Linux, MacOS or Windows. - On Debian Linux computers you can run `sudo apt-get install dcm2niix`. @@ -58,7 +58,7 @@ It is often easier to download and install a precompiled version. However, you c Ubuntu: `sudo apt-get install cmake pkg-config` -MacOS: `brew install cmake pkg-config` +MacOS: `brew install cmake pkg-config` or `sudo port install cmake pkgconfig` **Basic build:** ```bash From 478a18efa10d2dac94228ab4073d257926d29467 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Mon, 31 May 2021 10:15:20 -0400 Subject: [PATCH 26/95] Require finite TotalReadoutTime (https://github.com/rordenlab/dcm2niix/issues/512) --- console/nii_dicom.cpp | 22 +++++++++++++++++++++- console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index c89b9278..ebfd5c0f 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -7046,6 +7046,7 @@ if (d.isHasPhase) if ((numDimensionIndexValues > 1) && (numDimensionIndexValues == numberOfFrames)) { //Philips enhanced datasets can have custom slice orders and pack images with different TE, Phase/Magnitude/Etc. int maxVariableItem = 0; + int nVariableItems = 0; if (true) { // int mn[MAX_NUMBER_OF_DIMENSIONS]; int mx[MAX_NUMBER_OF_DIMENSIONS]; @@ -7056,7 +7057,10 @@ if (d.isHasPhase) if (mx[j] < dcmDim[i].dimIdx[j]) mx[j] = dcmDim[i].dimIdx[j]; if (mn[j] > dcmDim[i].dimIdx[j]) mn[j] = dcmDim[i].dimIdx[j]; } - if (mx[j] != mn[j]) maxVariableItem = j; + if (mx[j] != mn[j]) { + maxVariableItem = j; + nVariableItems ++; + } } if (isVerbose > 1) { printMessage(" DimensionIndexValues (0020,9157), dimensions with variability:\n"); @@ -7071,6 +7075,22 @@ if (d.isHasPhase) if (dimensionIndexPointerCounter > 0) for(size_t i = 0; i < dimensionIndexPointerCounter; i++) if (dimensionIndexPointer[i] == kInStackPositionNumber) stackPositionItem = i; + if ((d.manufacturer == kMANUFACTURER_CANON) && (nVariableItems == 1) && (d.xyzDim[4] > 1)) { + //WARNING: Canon CANON V6.0SP2001* (0018,9005) = "AX fMRI" strangely sets TemporalPositionIndex(0020,9128) as 1 for all volumes: (0020,9157) and (0020,9128) are INCORRECT! + printf("Invalid enhanced DICOM created by Canon: Only single dimension in DimensionIndexValues (0020,9157) varies, for 4D file (e.g. BOTH space and time should vary)\n"); + printf("%d %d\n", stackPositionItem, maxVariableItem); + int stackTimeItem = 0; + if (stackPositionItem == 0) { + maxVariableItem ++; + stackTimeItem ++; //e.g. slot 0 = space, slot 1 = time + } + int vol = 0; + for (int i = 0; i < numDimensionIndexValues; i++) { + if (1 == dcmDim[i].dimIdx[stackPositionItem]) vol ++; + dcmDim[i].dimIdx[stackTimeItem] = vol; + //printf("vol %d slice %d\n", dcmDim[i].dimIdx[stackTimeItem], dcmDim[i].dimIdx[stackPositionItem]); + } + } //Kuldge for corrupted CANON 0020,9157 //sort dimensions #ifdef USING_R if (stackPositionItem < maxVariableItem) diff --git a/console/nii_dicom.h b/console/nii_dicom.h index c2073b1b..9befedd2 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.20210426" +#define kDCMdate "v1.0.20210531" #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 ff00ee3a..49770647 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1614,7 +1614,7 @@ tse3d: T2*/ // Other than the use of (n-1), the value is basically just 1.0/bandwidthPerPixelPhaseEncode. // https://github.com/rordenlab/dcm2niix/issues/130 if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0) && (d.manufacturer != kMANUFACTURER_UIH)) - fprintf(fp, "\t\"TotalReadoutTime\": %g,\n", effectiveEchoSpacing * (reconMatrixPE - 1.0)); + json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", effectiveEchoSpacing * (reconMatrixPE - 1.0)); //do not store INF issue 512 if (d.manufacturer == kMANUFACTURER_UIH) //https://github.com/rordenlab/dcm2niix/issues/225 json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", d.acquisitionDuration / 1000.0); json_Float(fp, "\t\"PixelBandwidth\": %g,\n", d.pixelBandwidth ); From 1cfbd7a6850b2933cafe9919dbf5e3da3dc5bf3b Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 4 Jun 2021 10:02:50 -0400 Subject: [PATCH 27/95] Community contribution --- CONTRIBUTE.md | 19 +++++++++++++++++++ README.md | 4 ++++ console/nii_dicom_batch.cpp | 1 - 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTE.md diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md new file mode 100644 index 00000000..1826820c --- /dev/null +++ b/CONTRIBUTE.md @@ -0,0 +1,19 @@ +#### dcm2niix is a community effort + +Like the [Brain Imaging Data Structure](https://bids.neuroimaging.io/get_involved.html), which it supports, dcm2niix is developed by the community for the community and everybody can become a part of the community. + +The easiest way to contribute to dcm2niix is to ask questions you have by [generating Github issues](https://github.com/rordenlab/dcm2niix/issues) or [asking a question on the NITRC forim](https://www.nitrc.org/forum/?group_id=880). + +The code is open source, and you can share your improvements by [creating a pull request](https://github.com/rordenlab/dcm2niix/pulls). +dcm2niix is a community project that has benefitted from many [contrbutors](https://github.com/rordenlab/dcm2niix/graphs/contributors). + +The INCF suggests indicating who is responsible for maintaining software for [stability and support](https://incf.org/incf-standards-review-criteria-v20). Therefore, below we indicate several active contributors and their primary domain of expertise. However, this list is not comprehensive, and it is noted that the project has been supported by contributions from many users. This list does not reflect magnitude of prior contributions, rather it is a non-exhaustive list of members who are actively maintaining the project. + + - Jon Clayden: (@jonclayden): [R Deployment](https://github.com/jonclayden/divest) + - Ningfei Li : (@ningfei) CMake, AppVeyor, Travis + - Yaroslav O. Halchenko: (@yarikoptic) Debian distributions + - Taylor Hanayik (@hanayik): FSL integration + - Michael Harms (@mharms): Advanced modalities + - Roger D Newman-Norlund (@rogiedodgie): User support + - Rob Reid (@captainnova): Clinical modalities + - Chris Rorden (@neurolabusc): General development, user support \ No newline at end of file diff --git a/README.md b/README.md index 45e63545..90d3e0fc 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,10 @@ DICOM provides many ways to store/compress image data, known as [transfer syntax [See releases](https://github.com/rordenlab/dcm2niix/releases) for recent release notes. [See the VERSIONS.md file for details on earlier releases](./VERSIONS.md). +## Contribute + +dcm2niix is developed by the community for the community and everybody can become a part of the [community](./CONTRIBUTE.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`. diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 49770647..380d2f9b 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1276,7 +1276,6 @@ tse3d: T2*/ if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) fprintf(fp, "\t\"PhaseEncodingPolarityGE\": \"Unflipped\",\n" ); if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED) fprintf(fp, "\t\"PhaseEncodingPolarityGE\": \"Flipped\",\n" ); } - float delayTimeInTR = -0.01; #ifdef myReadAsciiCsa if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.SeriesHeader_offset > 0) && (d.CSA.SeriesHeader_length > 0)) { From d7a35b49c734898829b7ab34d098d9227363fe53 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Sun, 6 Jun 2021 08:21:16 -0400 Subject: [PATCH 28/95] Kludge for partial volumes (https://github.com/rordenlab/dcm2niix/issues/515) --- console/nii_dicom.cpp | 6 ++++++ console/nii_dicom.h | 1 + console/nii_dicom_batch.cpp | 1 + 3 files changed, 8 insertions(+) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index ebfd5c0f..54f225ae 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -6980,6 +6980,12 @@ if (d.isHasPhase) d.patientPositionLast[k] = patientPositionEndPhilips[k]; } } + if ((numberOfFrames > 1) && (locationsInAcquisitionPhilips > 0) && ((numberOfFrames % locationsInAcquisitionPhilips) != 0)) { //issue515 + printWarning("Number of frames (%d) not divisible by locations in acquisition (2001,1018) %d (issue 515)\n", numberOfFrames, locationsInAcquisitionPhilips); + d.xyzDim[4] = d.xyzDim[3] / locationsInAcquisitionPhilips; + d.xyzDim[3] = locationsInAcquisitionPhilips; + d.xyzDim[0] = numberOfFrames; + } if ((B0Philips >= 0) && (d.CSA.numDti == 0)) { d.CSA.dtiV[0] = B0Philips; d.CSA.numDti = 1; diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 9befedd2..51fea310 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -99,6 +99,7 @@ static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI dire #define kEXIT_OUTPUT_FOLDER_READ_ONLY 7 #define kEXIT_SOME_OK_SOME_BAD 8 #define kEXIT_RENAME_ERROR 9 +#define kEXIT_INCOMPLETE_VOLUMES_FOUND 10 //issue 515 static const int kSliceOrientUnknown = 0; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 380d2f9b..04291012 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -5825,6 +5825,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc nii_saveAttributes(dcmList[dcmSort[0].indx], hdr0, opts, nameList->str[dcmSort[0].indx]); #endif free(imgM); + if (dcmList[dcmSort[0].indx].xyzDim[0] > 1) returnCode = kEXIT_INCOMPLETE_VOLUMES_FOUND; //issue515 return returnCode;//EXIT_SUCCESS; }// saveDcm2NiiCore() From a3239e225e89e4ea65c0132afcf1ccd1f64e24d5 Mon Sep 17 00:00:00 2001 From: Lucas Mitrak Date: Thu, 10 Jun 2021 10:26:33 -0400 Subject: [PATCH 29/95] Display correct OpenJPEG library directory * Change displayed OpenJPEG directory from hard coded 2.1 to actual Currently, when -DUSE_OPENJPEG=true is set in cmake, the OpenJPEG_DIR variable is hard coded to ${OPENJPEG_LIBDIR}/openjpeg-2.1, no matter the actual OpenJPEG version that is installed. This causes the message `message("-- Using OpenJPEG library from ${OpenJPEG_DIR}")` to always display `Using OpenJPEG library from /usr/lib64/openjpeg-2.1` This message is incorrect because version 2.1 of OpenJPEG may not be in use, and also the directory `/usr/lib64/openjpeg-2.1` may not exist at all. In addition, this can be confusing because the message above it can display `Found libopenjp2, version 2.4.0`, which is the correct version of OpenJPEG. As stated in the COMPILE.md, "Some JPEG2000 DICOM images can not be decoded by the default compilation of OpenJPEG library after version 2.1.0". This suggests that versions after OpenJPEG-2.1 can still be used, even with limited functionality. Therefore, the cmake message should display the correct library directory. If the desired behavior is to warn users who are compiling the library with versions past 2.1 of the limit functionality, then a message should be displayed as a warning or OpenJPEG version 2.1 should be required. Whatever the case, a non-hard coded OpenJPEG version should be displayed which takes advantage of cmake's _VERSION special variable [1]. If this commit is not merged, the package will still compile correctly because this is seemingly only an aesthetic change because the correct location is ultimately used; however, incorrect and confusing information will be displayed to the user. If this commit is merged, then the correct OpenJPEG version is used in the message. This commit was written, tested, and submitted by Lucas Mitrak. [1] https://cmake.org/cmake/help/latest/module/FindPkgConfig.html --- SuperBuild/SuperBuild.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SuperBuild/SuperBuild.cmake b/SuperBuild/SuperBuild.cmake index 9b6fe36f..caf2d932 100644 --- a/SuperBuild/SuperBuild.cmake +++ b/SuperBuild/SuperBuild.cmake @@ -68,7 +68,7 @@ if(USE_OPENJPEG) endif() if(OPENJPEG_FOUND AND NOT ${OPENJPEG_INCLUDE_DIRS} MATCHES "gdcmopenjpeg") - set(OpenJPEG_DIR ${OPENJPEG_LIBDIR}/openjpeg-2.1 CACHE PATH "Path to OpenJPEG configuration file" FORCE) + set(OpenJPEG_DIR ${OPENJPEG_LIBDIR}/openjpeg-${OPENJPEG_VERSION} CACHE PATH "Path to OpenJPEG configuration file" FORCE) message("-- Using OpenJPEG library from ${OpenJPEG_DIR}") else() if(${OPENJPEG_INCLUDE_DIRS} MATCHES "gdcmopenjpeg") From f36569312d0126c45ab1e617490519b77e569fbd Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 11 Jun 2021 10:48:57 -0400 Subject: [PATCH 30/95] Describe JSON fields (https://github.com/rordenlab/dcm2niix/issues/516) --- BIDS/README.md | 306 +++++++++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTE.md | 2 +- 2 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 BIDS/README.md diff --git a/BIDS/README.md b/BIDS/README.md new file mode 100644 index 00000000..19960e71 --- /dev/null +++ b/BIDS/README.md @@ -0,0 +1,306 @@ +## About + +dcm2niix is designed to convert complicated DICOM images in to simple NIfTI images. However, the NIfTI images are unable to store much meta data that might be useful for analyses. Therefore, dcm2nii can create Brain Imaging Data Structure ([BIDS](https://bids.neuroimaging.io)) files that retain useful information. These are simple human-readable text files in the [JSON](https://en.wikipedia.org/wiki/JSON) format. The tables below provide details regarding the JSON fields generated by dcm2niix. Some of these are defined by the BIDS standard, while others are unique to dcm2niix. + +The Notes section of the tables uses the following abreviations + + - G : GE manufacturer only. + - P : Philips manufacturer only. + - S : Siemens manufacturer only. + + + +##### Constants + +These fields should be the same for all images acquired on a specific scanner. + +|Field|Notes|Comments| +|-----|-----|-----| +|ImagingFrequency| | | +|Manufacturer| | | +|MagneticFieldStrength| | | +|Modality| | | +|PulseSequenceName*G| | | +|InternalPulseSequenceName*G| | | +|ManufacturersModelName| | | +|InstitutionName| | | +|InstitutionalDepartmentName| | | +|InstitutionAddress| | | +|DeviceSerialNumber| | | +|StationName| | | + +##### Private Information + +These fields contain personally identifiable information. By default dcm2niix will create anonymized files without these fields. The option `-ba n` will retain private information. + + + +|Field|Notes|Comments| +|-----|-----|-----| +|SeriesInstanceUID| | From DICOM [0020,000E](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0020,000E)) | +|StudyInstanceUID| | | +|ReferringPhysicianName| | | +|StudyID| | | +|PatientName| | | +|PatientID| | | +|AccessionNumber| | | +|PatientBirthDate| | | +|PatientWeight| | | +|AcquisitionDateTime| | | + +##### Series Information + +Fields common to all scans, regardless of modality or manufacturer. + +|Field|Notes|Comments| +|-----|-----|-----| +|BodyPartExamined| | | +|PatientPosition| | | +|ProcedureStepDescription| | | +|SoftwareVersions| | | +|SeriesDescription| | | +|ProtocolName| | | +|ScanningSequence| | | +|SequenceVariant| | | +|ScanOptions| | | +|SequenceName| | | +|ImageType| | | +|AcquisitionTime| | | +|AcquisitionNumber| | | +|ImageComments| | | +|ConversionComments| | | + +##### Philips Sequence Information + +Data unique to Philips, including [custom intensity scaling](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3998685/) + + +|Field|Notes|Comments| +|-----|-----|-----| +|TriggerDelayTime| P | | +|PhilipsRWVSlope | P | | +|PhilipsRWVIntercept | P | | +|PhilipsRescaleSlope| P | | +|PhilipsRescaleIntercept| P | | +|PhilipsScaleSlope| P | | +|UsePhilipsFloatNotDisplayScaling| P | | + +##### Positron Emission Tomography Isotope Module Attributes + +PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.9.2.html). See BIDS [BEP009 Positron Emission Tomography](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). + +|Field|Notes|Comments| +|-----|-----|-----| +|Radiopharmaceutical| | | +|RadionuclidePositronFraction| | | +|RadionuclideTotalDose| | | +|RadionuclideHalfLife| | | +|DoseCalibrationFactor| | | +|IsotopeHalfLife| | | +|Dosage| | | +|ConvolutionKernel| | | +|Units https://github.com/bids-standard/bids-specification/pull/773| | | +|DecayCorrection| | | +|AttenuationCorrectionMethod| | | +|ReconstructionMethod| | | +|DecayFactor| | | +|FrameTimesStart| | | +|FrameDuration| | | + +##### Computerized Tomography + +Fields specific to CT scans. + +|Field|Notes|Comments| +|-----|-----|-----| +|XRayExposure| | Relative x-ray exposure from DICOM [0018,1405](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,1405))| + +##### Magnetic Resonance Imaging + +Fields specific to MRI scans. + +|Field|Notes|Comments| +|-----|-----|-----| +|MRAcquisitionType| |`2D` or `3D` | +|Units| | `Hz` only for field maps| +|SliceThickness| | | +|SpacingBetweenSlices| | | +|SAR| | | +|NumberOfAverages| | | +|EchoNumber| | Only for multi-echo series| +|EchoTime| | | +|RepetitionTime| | | +|RepetitionTimeExcitation| | | +|RepetitionTimeInversion| | | +|InversionTime| | | +|FlipAngle| | | +|PhaseEncodingDirectionDisplayed| | | +|PhaseEncodingPolarityGE| G | | + + +##### Arterial Spin Labeling + +Siemens 2D pCASL [ep2d_pcasl](http://www.loft-lab.org). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). + + +|Field|Notes|Comments| +|-----|-----|-----| +|LabelOffset| S | | +|PostLabelDelay| S | | +|NumRFBlocks| S | | +|RFGap| S | | +|MeanGzx10| S | | +|PhiAdjust| S | | + +##### Arterial Spin Labeling + +Siemens 3D pCASL [tgse_pcasl](http://www.loft-lab.org). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). + +|Field|Notes|Comments| +|-----|-----|-----| +|RFGap| S | | +|MeanGzx10| S | | +|T1| S | | + +##### Arterial Spin Labeling + +Siemens 2D PASL product sequence (`ep2d_pasl`). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). + + +|Field|Notes|Comments| +|-----|-----|-----| +|InversionTime| S | | +|SaturationStopTime| S | | + +##### Arterial Spin Labeling + +Siemens 2D PASL product sequence (`tgse_pasl`). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). +|Field|Notes|Comments| +|-----|-----|-----| +|BolusDuration| S | | +|InversionTime| S | | + +##### Arterial Spin Labeling + +Siemens 2D [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). + +|Field|Notes|Comments| +|-----|-----|-----| +|PostInversionDelay| S | | +|PostLabelDelay| S | | + +##### Arterial Spin Labeling + +Siemens 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). + + +|Field|Notes|Comments| +|-----|-----|-----| +|InversionTime| S | | +|BolusDuration| S | | +|TagRFFlipAngle| S | | +|TagRFDuration| S | | +|TagRFSeparation| S | | +|MeanTagGradient| S | | +|TagGradientAmplitude| S | | +|TagDuration| S | | +|MaximumT1Opt| S | | +|InitialPostLabelDelay| S | | +|sWipMemBlockAdFree*| S | Multiple values | +|TagPlaneDThickness| S | | +|TagPlaneUlShape| S | | +|TagPlaneSPositionDTra| S | | +|TagPlaneSNormalDTra| S | | + +##### Arterial Spin Labeling + +Siemens 3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`jw_tgse_VEPCASL`). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). + +|Field|Notes|Comments| +|-----|-----|-----| +|TagRFFlipAngle| S | | +|TagRFDuration| S | | +|TagRFSeparation| S | | +|MaximumT1Opt| S | | +|Tag*| S | One value for each [post-label delays](https://fsl.fmrib.ox.ac.uk/fslcourse/lectures/practicals/ASLpractical/index.html) | + +##### Magnetic Resonance Imaging (Siemens V*) + +Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). + + +|Field|Notes|Comments| +|-----|-----|-----| +|EchoTime1| | For Fieldmaps created with two echo times| +|EchoTime2| | For Fieldmaps created with two echo times| +|PartialFourier| S | Partial fourier fraction, e.g. image with 6/8 pF will report `0.75` | +|Interpolation2D| S | If present, slices interpolated within plane| +|Interpolation3D| S | If present, image interpolated in all spatial dimensions| +|BaseResolution| S | | +|ShimSetting| S | | +|DiffusionScheme| | `Monopolar` or `Bipolar`| +|DelayTime| S | | +|TxRefAmp| S | | +|PhaseResolution| S | | +|PhaseOversampling| S | | +|VendorReportedEchoSpacing| S | | +|ReceiveCoilName| S | | +|ReceiveCoilActiveElements| S | | +|CoilString| S | | +|PulseSequenceDetails| S | | +|FmriExternalInfo| S | | +|WipMemBlock| S | | +|ProtocolName| S | | +|RefLinesPE| S | | +|ConsistencyInfo| S | Sequence software version| + +##### Magnetic Resonance Imaging (Siemens XA) + +Fields specific to Siemens XA-series MRI systems (Sola, Vida). + +|Field|Notes|Comments| +|-----|-----|-----| +|ReceiveCoilActiveElements| S | | +|CoilString| S | | +|PartialFourier| S | | + +##### Magnetic Resonance Imaging + +Fields found in typical MRI sequences. + +|Field|Notes|Comments| +|-----|-----|-----| +|MultibandAccelerationFactor| | Multiple 2D EPI slices in 3D volume acquired simultaneously(`HyperBand`, `SMS`)| +|PercentPhaseFOV| | | +|PercentSampling| | | +|EchoTrainLength| | | +|PartialFourierEnabled|P|This field will report `YES`, for fraction see `PartialFourier` | +|PhaseEncodingStepsNoPartialFourier|P| Does not take partial Fourier into account| +|PhaseEncodingSteps| | | +|AcquisitionMatrixPE| | | +|BandwidthPerPixelPhaseEncode| | | +|ParallelReductionFactorInPlane| | Partial sampling of k-space (`SENSE`, `GRAPPA`)| +|ParallelReductionOutOfPlane| | | +|WaterFatShift| | | +|EstimatedEffectiveEchoSpacing| | | +|EstimatedTotalReadoutTime| | | +|EffectiveEchoSpacing| | | +|DerivedVendorReportedEchoSpacing| | | +|TotalReadoutTime| | [FSL's definition](https://github.com/rordenlab/dcm2niix/tree/master/GE) of time to read a 2D EPI slice| +|PixelBandwidth| | | +|DwellTime| | | +|PhaseEncodingAxis| |Only populated if polarity of phase encoding is not known. Values `j` or `i`. see `PhaseEncodingDirection ` | +|PhaseEncodingDirection| | Phase axis and polarity. Values `j`,`i`,`j-`,`i-` | +|SliceTiming| | One value for each slice in EPI volume| +|ImageOrientationPatientDICOM| | | +|InPlanePhaseEncodingDirectionDICOM| | | + + +##### Conversion Tool + +Details of software used to convert DICOM to NIfTI/BIDS. + +|Field|Notes|Comments| +|-----|-----|-----| +|ConversionSoftware| |`dcm2niix` | +|ConversionSoftwareVersion| | Based on date of release, e.g. [`v1.0.20210317`](https://github.com/rordenlab/dcm2niix/releases)| diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md index 1826820c..14d21671 100644 --- a/CONTRIBUTE.md +++ b/CONTRIBUTE.md @@ -2,7 +2,7 @@ Like the [Brain Imaging Data Structure](https://bids.neuroimaging.io/get_involved.html), which it supports, dcm2niix is developed by the community for the community and everybody can become a part of the community. -The easiest way to contribute to dcm2niix is to ask questions you have by [generating Github issues](https://github.com/rordenlab/dcm2niix/issues) or [asking a question on the NITRC forim](https://www.nitrc.org/forum/?group_id=880). +The easiest way to contribute to dcm2niix is to ask questions you have by [generating Github issues](https://github.com/rordenlab/dcm2niix/issues) or [asking a question on the NITRC forum](https://www.nitrc.org/forum/?group_id=880). The code is open source, and you can share your improvements by [creating a pull request](https://github.com/rordenlab/dcm2niix/pulls). dcm2niix is a community project that has benefitted from many [contrbutors](https://github.com/rordenlab/dcm2niix/graphs/contributors). From e7bbc1b4733f7ae2c25e16cb9352db8cc9caab9f Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 11 Jun 2021 10:52:33 -0400 Subject: [PATCH 31/95] Typo --- BIDS/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/BIDS/README.md b/BIDS/README.md index 19960e71..8c417612 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -89,6 +89,8 @@ Data unique to Philips, including [custom intensity scaling](https://www.ncbi.nl PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.9.2.html). See BIDS [BEP009 Positron Emission Tomography](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). +This is a [work in progress](https://github.com/bids-standard/bids-specification/pull/773). + |Field|Notes|Comments| |-----|-----|-----| |Radiopharmaceutical| | | @@ -99,7 +101,7 @@ PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/curre |IsotopeHalfLife| | | |Dosage| | | |ConvolutionKernel| | | -|Units https://github.com/bids-standard/bids-specification/pull/773| | | +|Units| | | |DecayCorrection| | | |AttenuationCorrectionMethod| | | |ReconstructionMethod| | | From a61cc57a9669ec28706d21d2f7a07df72e70f596 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Fri, 11 Jun 2021 10:56:28 -0400 Subject: [PATCH 32/95] Typo --- BIDS/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index 8c417612..d6cb7ff2 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -20,8 +20,8 @@ These fields should be the same for all images acquired on a specific scanner. |Manufacturer| | | |MagneticFieldStrength| | | |Modality| | | -|PulseSequenceName*G| | | -|InternalPulseSequenceName*G| | | +|PulseSequenceName| G | | +|InternalPulseSequenceName| G | | |ManufacturersModelName| | | |InstitutionName| | | |InstitutionalDepartmentName| | | From 6bcfa6d0c45727cb81e3e25507cd977e96db7847 Mon Sep 17 00:00:00 2001 From: Rob Reid Date: Fri, 11 Jun 2021 11:27:00 -0500 Subject: [PATCH 33/95] Edits the BIDS description in README.md --- BIDS/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index d6cb7ff2..31050cea 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -1,12 +1,12 @@ ## About -dcm2niix is designed to convert complicated DICOM images in to simple NIfTI images. However, the NIfTI images are unable to store much meta data that might be useful for analyses. Therefore, dcm2nii can create Brain Imaging Data Structure ([BIDS](https://bids.neuroimaging.io)) files that retain useful information. These are simple human-readable text files in the [JSON](https://en.wikipedia.org/wiki/JSON) format. The tables below provide details regarding the JSON fields generated by dcm2niix. Some of these are defined by the BIDS standard, while others are unique to dcm2niix. +dcm2niix is designed to convert complicated DICOM images in to simple NIfTI images. However, the NIfTI images are unable to store much metadata that might be useful for analyses. Therefore, dcm2niix can create Brain Imaging Data Structure ([BIDS](https://bids.neuroimaging.io)) files that retain useful information. These are simple human-readable text files in the [JSON](https://en.wikipedia.org/wiki/JSON) format. The tables below provide details regarding the JSON fields generated by dcm2niix. Some of these are defined by the BIDS standard, while others are unique to dcm2niix. -The Notes section of the tables uses the following abreviations +The Manufacturer section of the tables uses the following abbreviations - - G : GE manufacturer only. - - P : Philips manufacturer only. - - S : Siemens manufacturer only. + - G : GE only. + - P : Philips only. + - S : Siemens only. @@ -14,9 +14,8 @@ The Notes section of the tables uses the following abreviations These fields should be the same for all images acquired on a specific scanner. -|Field|Notes|Comments| -|-----|-----|-----| -|ImagingFrequency| | | +|Field|Manufacturer|Comments| +|-----+------------+-----| |Manufacturer| | | |MagneticFieldStrength| | | |Modality| | | @@ -135,6 +134,7 @@ Fields specific to MRI scans. |RepetitionTimeExcitation| | | |RepetitionTimeInversion| | | |InversionTime| | | +|ImagingFrequency| | | |FlipAngle| | | |PhaseEncodingDirectionDisplayed| | | |PhaseEncodingPolarityGE| G | | From 9b21ba5cc2987913748b8609d9275799901e8d0a Mon Sep 17 00:00:00 2001 From: Rob Reid Date: Fri, 11 Jun 2021 11:28:11 -0500 Subject: [PATCH 34/95] Should have run preview --- BIDS/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BIDS/README.md b/BIDS/README.md index 31050cea..4c5ff362 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -15,7 +15,7 @@ The Manufacturer section of the tables uses the following abbreviations These fields should be the same for all images acquired on a specific scanner. |Field|Manufacturer|Comments| -|-----+------------+-----| +|-----|------------|-----| |Manufacturer| | | |MagneticFieldStrength| | | |Modality| | | From 86143cee700a6536a6df95bb0aab4bf9f822c2dd Mon Sep 17 00:00:00 2001 From: Rob Reid Date: Fri, 11 Jun 2021 12:02:43 -0500 Subject: [PATCH 35/95] Edits as far as common MRI. --- BIDS/README.md | 185 +++++++++++++++++++++++++------------------------ 1 file changed, 94 insertions(+), 91 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index 4c5ff362..0aadac88 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -8,81 +8,84 @@ The Manufacturer section of the tables uses the following abbreviations - P : Philips only. - S : Siemens only. +The Defined By column uses +- B : The [BIDS](https://bids.neuroimaging.io) standard +- D : dcm2niix (often, but not always, these come directly from the DICOM header without transformation) ##### Constants These fields should be the same for all images acquired on a specific scanner. -|Field|Manufacturer|Comments| -|-----|------------|-----| -|Manufacturer| | | -|MagneticFieldStrength| | | -|Modality| | | -|PulseSequenceName| G | | -|InternalPulseSequenceName| G | | -|ManufacturersModelName| | | -|InstitutionName| | | -|InstitutionalDepartmentName| | | -|InstitutionAddress| | | -|DeviceSerialNumber| | | -|StationName| | | +|Field|Unit|Manufacturer|Comments|Defined By| +|-----|----|------------|--------|----------| +|Manufacturer| | | | | +|MagneticFieldStrength| T | | | | +|Modality| | | | | +|PulseSequenceName| | G | | | +|InternalPulseSequenceName| | G | | | +|ManufacturersModelName| | | | | +|InstitutionName| | | | | +|InstitutionalDepartmentName| | | | | +|InstitutionAddress| | | | | +|DeviceSerialNumber| | | | | +|StationName| | | | | ##### Private Information These fields contain personally identifiable information. By default dcm2niix will create anonymized files without these fields. The option `-ba n` will retain private information. - -|Field|Notes|Comments| -|-----|-----|-----| -|SeriesInstanceUID| | From DICOM [0020,000E](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0020,000E)) | -|StudyInstanceUID| | | -|ReferringPhysicianName| | | -|StudyID| | | -|PatientName| | | -|PatientID| | | -|AccessionNumber| | | -|PatientBirthDate| | | -|PatientWeight| | | -|AcquisitionDateTime| | | +|Field|Unit|Manufacturer|Comments|Defined By| +|-----|----|------------|--------|----------| +|SeriesInstanceUID| | | From DICOM [0020,000E](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0020,000E)) | | +|StudyInstanceUID| | | | +|ReferringPhysicianName| | | | +|StudyID| | | | +|PatientName| | | | +|PatientID| | | | +|AccessionNumber| | | | +|PatientBirthDate| | | | +|PatientWeight| nominally kg | | | +|AcquisitionDateTime| | | | ##### Series Information Fields common to all scans, regardless of modality or manufacturer. -|Field|Notes|Comments| -|-----|-----|-----| -|BodyPartExamined| | | -|PatientPosition| | | -|ProcedureStepDescription| | | -|SoftwareVersions| | | -|SeriesDescription| | | -|ProtocolName| | | -|ScanningSequence| | | -|SequenceVariant| | | -|ScanOptions| | | -|SequenceName| | | -|ImageType| | | -|AcquisitionTime| | | -|AcquisitionNumber| | | -|ImageComments| | | -|ConversionComments| | | +|Field|Unit|Manufacturer|Comments|Defined By| +|-----|----|------------|--------|----------| +|BodyPartExamined| | | | | +|PatientPosition| | | | | +|ProcedureStepDescription| | | | | +|SoftwareVersions| | | | | +|SeriesDescription| | | | | +|ProtocolName| | | | | +|ScanningSequence| | | | | +|SequenceVariant| | | | | +|ScanOptions| | | | | +|SequenceName| | | | | +|ImageType| list | | | | +|AcquisitionTime| | | | | +|AcquisitionNumber| | | | | +|ImageComments| | | | | +|ConversionSoftware| | | Name of the program that converted from DICOM to NIFTI | | +|ConversionSoftwareVersion| | | | | + ##### Philips Sequence Information Data unique to Philips, including [custom intensity scaling](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3998685/) - -|Field|Notes|Comments| -|-----|-----|-----| -|TriggerDelayTime| P | | -|PhilipsRWVSlope | P | | -|PhilipsRWVIntercept | P | | -|PhilipsRescaleSlope| P | | -|PhilipsRescaleIntercept| P | | -|PhilipsScaleSlope| P | | -|UsePhilipsFloatNotDisplayScaling| P | | +|Field|Unit|Manufacturer|Comments|Defined By| +|-----|----|------------|--------|----------| +|TriggerDelayTime| s? | P | | +|PhilipsRWVSlope | | P | | +|PhilipsRWVIntercept | | P | | +|PhilipsRescaleSlope| | P | | +|PhilipsRescaleIntercept| | P | | +|PhilipsScaleSlope| | P | | +|UsePhilipsFloatNotDisplayScaling| | P | | ##### Positron Emission Tomography Isotope Module Attributes @@ -90,54 +93,54 @@ PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/curre This is a [work in progress](https://github.com/bids-standard/bids-specification/pull/773). -|Field|Notes|Comments| -|-----|-----|-----| -|Radiopharmaceutical| | | -|RadionuclidePositronFraction| | | -|RadionuclideTotalDose| | | -|RadionuclideHalfLife| | | -|DoseCalibrationFactor| | | -|IsotopeHalfLife| | | -|Dosage| | | -|ConvolutionKernel| | | -|Units| | | -|DecayCorrection| | | -|AttenuationCorrectionMethod| | | -|ReconstructionMethod| | | -|DecayFactor| | | -|FrameTimesStart| | | -|FrameDuration| | | +|Field|Unit|Manufacturer|Comments|Defined By| +|-----|----|------------|--------|----------| +|Radiopharmaceutical| | | | | +|RadionuclidePositronFraction| | | | | +|RadionuclideTotalDose| | | | | +|RadionuclideHalfLife| | | | | +|DoseCalibrationFactor| | | | | +|IsotopeHalfLife| s? | | | | +|Dosage| | | | | +|ConvolutionKernel| | | | | +|Units| | | | | +|DecayCorrection| | | | | +|AttenuationCorrectionMethod| | | | | +|ReconstructionMethod| | | | | +|DecayFactor| | | | | +|FrameTimesStart| s | | | | +|FrameDuration| s | | | | ##### Computerized Tomography Fields specific to CT scans. -|Field|Notes|Comments| -|-----|-----|-----| -|XRayExposure| | Relative x-ray exposure from DICOM [0018,1405](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,1405))| +|Field|Unit|Manufacturer|Comments|Defined By| +|-----|----|------------|--------|----------| +|XRayExposure| | | | Relative x-ray exposure from DICOM [0018,1405](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,1405))| | ##### Magnetic Resonance Imaging Fields specific to MRI scans. -|Field|Notes|Comments| -|-----|-----|-----| -|MRAcquisitionType| |`2D` or `3D` | -|Units| | `Hz` only for field maps| -|SliceThickness| | | -|SpacingBetweenSlices| | | -|SAR| | | -|NumberOfAverages| | | -|EchoNumber| | Only for multi-echo series| -|EchoTime| | | -|RepetitionTime| | | -|RepetitionTimeExcitation| | | -|RepetitionTimeInversion| | | -|InversionTime| | | -|ImagingFrequency| | | -|FlipAngle| | | -|PhaseEncodingDirectionDisplayed| | | -|PhaseEncodingPolarityGE| G | | +|Field|Unit|Manufacturer|Comments|Defined By| +|-----|----|------------|--------|----------| +|MRAcquisitionType| | | `2D` or `3D` | | +|Units| | | `Hz` only for field maps| | +|SliceThickness| mm | | | D | +|SpacingBetweenSlices| mm | | | D | +|SAR| | | | | +|NumberOfAverages| | | | | +|EchoNumber| | | Only for multi-echo series| | +|EchoTime| s | | | | +|RepetitionTime| s | | | | +|RepetitionTimeExcitation| s | | | | +|RepetitionTimeInversion| s | | | | +|InversionTime| s | | | | +|ImagingFrequency| MHz | | | D? | +|FlipAngle| degrees | | | | +|PhaseEncodingDirection| | | i, j | | +|PhaseEncodingPolarityGE| | G | | D | ##### Arterial Spin Labeling From d461c4007f28810a5dff755df87db05d0f6a378f Mon Sep 17 00:00:00 2001 From: Rob Reid Date: Fri, 11 Jun 2021 14:10:06 -0500 Subject: [PATCH 36/95] Consolidating ASL --- BIDS/README.md | 108 +++++++++++++++++++++++-------------------------- 1 file changed, 50 insertions(+), 58 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index 0aadac88..73b6162c 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -1,6 +1,21 @@ ## About -dcm2niix is designed to convert complicated DICOM images in to simple NIfTI images. However, the NIfTI images are unable to store much metadata that might be useful for analyses. Therefore, dcm2niix can create Brain Imaging Data Structure ([BIDS](https://bids.neuroimaging.io)) files that retain useful information. These are simple human-readable text files in the [JSON](https://en.wikipedia.org/wiki/JSON) format. The tables below provide details regarding the JSON fields generated by dcm2niix. Some of these are defined by the BIDS standard, while others are unique to dcm2niix. +dcm2niix is designed to convert complicated DICOM images in to simple NIfTI +images. However, the NIfTI images are unable to store much metadata that might +be useful for analyses. Therefore, dcm2niix can create Brain Imaging Data +Structure ([BIDS](https://bids.neuroimaging.io)) files that retain useful +information. These are simple human-readable text files in the +[JSON](https://en.wikipedia.org/wiki/JSON) format. The tables below provide +details regarding the JSON fields generated by dcm2niix. Some of these are +defined by the BIDS standard, while others are unique to dcm2niix. + +Note that dcm2niix cannot provide information that is not in the DICOM header. +Common reasons for absent fields are: + - irrelevance to the scan type, e.g. MagneticFieldStrength for CT + - it was removed from the DICOM during anonymization, possibly by accident or overzealousness + - difficulty interpreting the storage format used by the manufacturer + - the manufacturer simply neglected to write it + The Manufacturer section of the tables uses the following abbreviations @@ -17,19 +32,23 @@ The Defined By column uses These fields should be the same for all images acquired on a specific scanner. -|Field|Unit|Manufacturer|Comments|Defined By| -|-----|----|------------|--------|----------| -|Manufacturer| | | | | -|MagneticFieldStrength| T | | | | -|Modality| | | | | -|PulseSequenceName| | G | | | -|InternalPulseSequenceName| | G | | | -|ManufacturersModelName| | | | | -|InstitutionName| | | | | -|InstitutionalDepartmentName| | | | | -|InstitutionAddress| | | | | -|DeviceSerialNumber| | | | | -|StationName| | | | | +| Field | Unit | Manufacturer | Comments | Defined By | +|-----------------------------|------|--------------|----------------------|------------| +| Manufacturer | | | DICOM tag 0008, 0070 | B | +| ManufacturersModelName | | | DICOM tag 0008, 1090 | B | +| DeviceSerialNumber | | | DICOM tag 0018, 1000 | B | +| StationName | | | DICOM tag 0008, 1010 | B | +| SoftwareVersions | | | DICOM tag 0018, 1020 | B | +| MagneticFieldStrength | T | | DICOM tag 0018, 0087 | B | +| Modality | | | | | +| PulseSequenceName | | G | | | +| InternalPulseSequenceName | | G | | | +| ManufacturersModelName | | | | | +| InstitutionName | | | | | +| InstitutionalDepartmentName | | | | | +| InstitutionAddress | | | | | +| DeviceSerialNumber | | | | | +| StationName | | | | | ##### Private Information @@ -144,50 +163,23 @@ Fields specific to MRI scans. ##### Arterial Spin Labeling - -Siemens 2D pCASL [ep2d_pcasl](http://www.loft-lab.org). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). - - -|Field|Notes|Comments| -|-----|-----|-----| -|LabelOffset| S | | -|PostLabelDelay| S | | -|NumRFBlocks| S | | -|RFGap| S | | -|MeanGzx10| S | | -|PhiAdjust| S | | - -##### Arterial Spin Labeling - -Siemens 3D pCASL [tgse_pcasl](http://www.loft-lab.org). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). - -|Field|Notes|Comments| -|-----|-----|-----| -|RFGap| S | | -|MeanGzx10| S | | -|T1| S | | - -##### Arterial Spin Labeling - -Siemens 2D PASL product sequence (`ep2d_pasl`). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). - - -|Field|Notes|Comments| -|-----|-----|-----| -|InversionTime| S | | -|SaturationStopTime| S | | - -##### Arterial Spin Labeling - -Siemens 2D PASL product sequence (`tgse_pasl`). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). -|Field|Notes|Comments| -|-----|-----|-----| -|BolusDuration| S | | -|InversionTime| S | | - -##### Arterial Spin Labeling - -Siemens 2D [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). +See BIDS [BEP005 Arterial Spin +Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). + +| Field | Unit | Manufacturer | Comments | Defined By | +|--------------------|------|--------------|------------------------------------------------|------------| +| LabelOffset | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | +| PostLabelDelay | s | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | +| NumRFBlocks | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | +| RFGap | | S | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org), | B | +| MeanGzx10 | | S | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | B | +| PhiAdjust | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | +| InversionTime | s | S | 2D pASL product (`ep2d_pasl`, `tgse_pasl`) | B | +| SaturationStopTime | s | S | 2D pASL product (`ep2d_pasl`) | B | +| BolusDuration | s | S | 2D pASL product (`tgse_pasl`) | B | +| MeanGzx10 | | S | 3D pCASL [tgse_pcasl](http://www.loft-lab.org) | B | + +Siemens 2D [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`). |Field|Notes|Comments| |-----|-----|-----| From daf9b3ba8c3beb2a22420f9c60acfe886748467b Mon Sep 17 00:00:00 2001 From: Rob Reid Date: Fri, 11 Jun 2021 14:38:45 -0500 Subject: [PATCH 37/95] Finishes ASL consolidation --- BIDS/README.md | 80 +++++++++++++++++--------------------------------- 1 file changed, 27 insertions(+), 53 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index 73b6162c..5e45a3dc 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -166,60 +166,34 @@ Fields specific to MRI scans. See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). -| Field | Unit | Manufacturer | Comments | Defined By | -|--------------------|------|--------------|------------------------------------------------|------------| -| LabelOffset | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | -| PostLabelDelay | s | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | -| NumRFBlocks | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | -| RFGap | | S | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org), | B | -| MeanGzx10 | | S | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | B | -| PhiAdjust | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | -| InversionTime | s | S | 2D pASL product (`ep2d_pasl`, `tgse_pasl`) | B | -| SaturationStopTime | s | S | 2D pASL product (`ep2d_pasl`) | B | -| BolusDuration | s | S | 2D pASL product (`tgse_pasl`) | B | -| MeanGzx10 | | S | 3D pCASL [tgse_pcasl](http://www.loft-lab.org) | B | - -Siemens 2D [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`). +| Field | Unit | Manufacturer | Comments | Defined By | +|-----------------------|-----------------|--------------|----------------------------------------------------------------------------------------------------------------------------------|------------| +| LabelOffset | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | +| PostLabelDelay | s | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org), [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | B | +| PostInversionDelay | s | S | 2D [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | B | +| NumRFBlocks | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | +| RFGap | | S | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org), | B | +| MeanGzx10 | | S | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | B | +| PhiAdjust | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | +| InversionTime | s | S | 2D pASL product (`ep2d_pasl`, `tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| SaturationStopTime | s | S | 2D pASL product (`ep2d_pasl`) | B | +| BolusDuration | s | S | 2D pASL product (`tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| MeanGzx10 | | S | 3D pCASL [tgse_pcasl](http://www.loft-lab.org) | B | +| TagRFFlipAngle | degrees | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | +| TagRFDuration | s | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | +| TagRFSeparation | | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | +| MeanTagGradient | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagGradientAmplitude | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagDuration | s | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| MaximumT1Opt | | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | +| InitialPostLabelDelay | s | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| sWipMemBlockAdFree* | multiple values | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagPlaneDThickness | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagPlaneUlShape | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagPlaneSPositionDTra | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagPlaneSNormalDTra | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| Tag* (if not explicitly listed) | One value for each | S | [Post-label delays](https://fsl.fmrib.ox.ac.uk/fslcourse/lectures/practicals/ASLpractical/index.html), 3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`jw_tgse_VEPCASL`) | B | -|Field|Notes|Comments| -|-----|-----|-----| -|PostInversionDelay| S | | -|PostLabelDelay| S | | - -##### Arterial Spin Labeling - -Siemens 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). - - -|Field|Notes|Comments| -|-----|-----|-----| -|InversionTime| S | | -|BolusDuration| S | | -|TagRFFlipAngle| S | | -|TagRFDuration| S | | -|TagRFSeparation| S | | -|MeanTagGradient| S | | -|TagGradientAmplitude| S | | -|TagDuration| S | | -|MaximumT1Opt| S | | -|InitialPostLabelDelay| S | | -|sWipMemBlockAdFree*| S | Multiple values | -|TagPlaneDThickness| S | | -|TagPlaneUlShape| S | | -|TagPlaneSPositionDTra| S | | -|TagPlaneSNormalDTra| S | | - -##### Arterial Spin Labeling - -Siemens 3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`jw_tgse_VEPCASL`). See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). - -|Field|Notes|Comments| -|-----|-----|-----| -|TagRFFlipAngle| S | | -|TagRFDuration| S | | -|TagRFSeparation| S | | -|MaximumT1Opt| S | | -|Tag*| S | One value for each [post-label delays](https://fsl.fmrib.ox.ac.uk/fslcourse/lectures/practicals/ASLpractical/index.html) | ##### Magnetic Resonance Imaging (Siemens V*) From 783c9b6a67a7568fa1b5c930f83a7e5442584a81 Mon Sep 17 00:00:00 2001 From: Rob Reid Date: Fri, 11 Jun 2021 15:30:46 -0500 Subject: [PATCH 38/95] Fills in the CSA section a bit --- BIDS/README.md | 103 ++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index 5e45a3dc..a7a1693b 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -166,64 +166,63 @@ Fields specific to MRI scans. See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). -| Field | Unit | Manufacturer | Comments | Defined By | -|-----------------------|-----------------|--------------|----------------------------------------------------------------------------------------------------------------------------------|------------| -| LabelOffset | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | -| PostLabelDelay | s | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org), [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | B | -| PostInversionDelay | s | S | 2D [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | B | -| NumRFBlocks | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | -| RFGap | | S | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org), | B | -| MeanGzx10 | | S | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | B | -| PhiAdjust | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | -| InversionTime | s | S | 2D pASL product (`ep2d_pasl`, `tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| SaturationStopTime | s | S | 2D pASL product (`ep2d_pasl`) | B | -| BolusDuration | s | S | 2D pASL product (`tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| MeanGzx10 | | S | 3D pCASL [tgse_pcasl](http://www.loft-lab.org) | B | -| TagRFFlipAngle | degrees | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | -| TagRFDuration | s | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | -| TagRFSeparation | | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | -| MeanTagGradient | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagGradientAmplitude | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagDuration | s | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| MaximumT1Opt | | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | -| InitialPostLabelDelay | s | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| sWipMemBlockAdFree* | multiple values | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagPlaneDThickness | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagPlaneUlShape | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagPlaneSPositionDTra | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagPlaneSNormalDTra | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| Tag* (if not explicitly listed) | One value for each | S | [Post-label delays](https://fsl.fmrib.ox.ac.uk/fslcourse/lectures/practicals/ASLpractical/index.html), 3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`jw_tgse_VEPCASL`) | B | +| Field | Unit | Manufacturer | Comments | Defined By | +|---------------------------------|--------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| +| LabelOffset | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | +| PostLabelDelay | s | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org), [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | B | +| PostInversionDelay | s | S | 2D [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | B | +| NumRFBlocks | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | +| RFGap | | S | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | B | +| MeanGzx10 | | S | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | B | +| PhiAdjust | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | +| InversionTime | s | S | 2D pASL product (`ep2d_pasl`, `tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| SaturationStopTime | s | S | 2D pASL product (`ep2d_pasl`) | B | +| BolusDuration | s | S | 2D pASL product (`tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| MeanGzx10 | | S | 3D pCASL [tgse_pcasl](http://www.loft-lab.org) | B | +| TagRFFlipAngle | degrees | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | +| TagRFDuration | s | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | +| TagRFSeparation | | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | +| MeanTagGradient | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagGradientAmplitude | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagDuration | s | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| MaximumT1Opt | | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | +| InitialPostLabelDelay | s | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| sWipMemBlockAdFree* | multiple values | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagPlaneDThickness | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagPlaneUlShape | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagPlaneSPositionDTra | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagPlaneSNormalDTra | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| Tag* (if not explicitly listed) | One value for each | S | [Post-label delays](https://fsl.fmrib.ox.ac.uk/fslcourse/lectures/practicals/ASLpractical/index.html), 3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`jw_tgse_VEPCASL`) | B | ##### Magnetic Resonance Imaging (Siemens V*) Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). - -|Field|Notes|Comments| -|-----|-----|-----| -|EchoTime1| | For Fieldmaps created with two echo times| -|EchoTime2| | For Fieldmaps created with two echo times| -|PartialFourier| S | Partial fourier fraction, e.g. image with 6/8 pF will report `0.75` | -|Interpolation2D| S | If present, slices interpolated within plane| -|Interpolation3D| S | If present, image interpolated in all spatial dimensions| -|BaseResolution| S | | -|ShimSetting| S | | -|DiffusionScheme| | `Monopolar` or `Bipolar`| -|DelayTime| S | | -|TxRefAmp| S | | -|PhaseResolution| S | | -|PhaseOversampling| S | | -|VendorReportedEchoSpacing| S | | -|ReceiveCoilName| S | | -|ReceiveCoilActiveElements| S | | -|CoilString| S | | -|PulseSequenceDetails| S | | -|FmriExternalInfo| S | | -|WipMemBlock| S | | -|ProtocolName| S | | -|RefLinesPE| S | | -|ConsistencyInfo| S | Sequence software version| +| Field | Unit | Manufacturer | Comments | Defined By | +|---------------------------|-------------------------------------------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| +| EchoTime1 | s | S | For Fieldmaps created with two echo times | D | +| EchoTime2 | s | S | For Fieldmaps created with two echo times | D | +| PartialFourier | | S | Partial fourier fraction, e.g. image with 6/8 pF will report `0.75` | D | +| Interpolation2D | | S | If present, slices interpolated within plane | D | +| Interpolation3D | | S | If present, image interpolated in all spatial dimensions | D | +| BaseResolution | integer | S | # of acquisition lines? | D | +| ShimSetting | DAC for 1st order terms, mA for 2nd order terms | S | The 1st 3 values are the 1st order shims from sGRADSPEC.lOffset{X,Y,Z}. The next 5 are 2nd order shims from sGRADSPEC.alShimCurrent[0-4]. Converting to the first order terms to uT/m may be possible using the "gradient sensitivities". Converting the second order terms to uT/m^2 requires information not stored in the DICOM header, but they are fixed for a given shim hardware configuration. | D | +| DiffusionScheme | | S | `Monopolar` or `Bipolar` | D | +| DelayTime | s | S | | D | +| TxRefAmp | V | S | | D | +| PhaseResolution | fraction | S | | D | +| PhaseOversampling | | S | | D | +| VendorReportedEchoSpacing | | S | | D | +| ReceiveCoilName | | S | e.g. "HeadNeck\_64" | D | +| ReceiveCoilActiveElements | | S | Given as groups of channels, instead of individual ones, e.g. "HC1-7" for HeadNeck\_64. | D | +| CoilString | | S | | D | +| PulseSequenceDetails | | S | pulse sequence label, e.g. "%SiemensSeq%\\\\tgse\_pasl" | D | +| FmriExternalInfo | | S | | D | +| WipMemBlock | | S | | D | +| ProtocolName | | S | Check SeriesDescription - they might be switched around | D | +| RefLinesPE | | S | # of reference lines in the phase encoding direction for acceleration (GRAPPA) | D | +| ConsistencyInfo | | S | The more complete software version, e.g. VE11C or VE11E instead of just VE11. | D | ##### Magnetic Resonance Imaging (Siemens XA) From ac0e0580e842659cd9adbf7d9af0d28f02349f70 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Sat, 12 Jun 2021 11:39:40 -0400 Subject: [PATCH 39/95] Further BIDS details (https://github.com/rordenlab/dcm2niix/issues/516) --- BIDS/README.md | 348 ++++++++++++++++++------------------ console/nii_dicom.cpp | 5 + console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 4 +- 4 files changed, 185 insertions(+), 174 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index a7a1693b..d7366246 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -19,7 +19,7 @@ Common reasons for absent fields are: The Manufacturer section of the tables uses the following abbreviations - - G : GE only. + - G : General Electric (GE) only. - P : Philips only. - S : Siemens only. @@ -28,143 +28,186 @@ The Defined By column uses - B : The [BIDS](https://bids.neuroimaging.io) standard - D : dcm2niix (often, but not always, these come directly from the DICOM header without transformation) +The Unit column uses + +The Defined By column uses + +- deg : degrees +- f : fraction +- kg : Kilogram +- list : list of text strings +- MBq : MegaBecquerels +- MHz : Megahertz +- mm : millimeter +- s : seconds +- T : Tesla + +MBq + ##### Constants -These fields should be the same for all images acquired on a specific scanner. +These fields should be the same for all images acquired on a specific scanner using a specific DICOM to BIDS conversion tool. | Field | Unit | Manufacturer | Comments | Defined By | |-----------------------------|------|--------------|----------------------|------------| -| Manufacturer | | | DICOM tag 0008, 0070 | B | -| ManufacturersModelName | | | DICOM tag 0008, 1090 | B | -| DeviceSerialNumber | | | DICOM tag 0018, 1000 | B | -| StationName | | | DICOM tag 0008, 1010 | B | -| SoftwareVersions | | | DICOM tag 0018, 1020 | B | -| MagneticFieldStrength | T | | DICOM tag 0018, 0087 | B | -| Modality | | | | | -| PulseSequenceName | | G | | | -| InternalPulseSequenceName | | G | | | -| ManufacturersModelName | | | | | -| InstitutionName | | | | | -| InstitutionalDepartmentName | | | | | -| InstitutionAddress | | | | | -| DeviceSerialNumber | | | | | -| StationName | | | | | +| Manufacturer | | | DICOM tag 0008,0070 | B | +| DeviceSerialNumber | | | DICOM tag 0018,1000 | B | +| StationName | | | DICOM tag 0008,1010 | B | +| SoftwareVersions | | | DICOM tag 0018,1020 | B | +| Modality | | | DICOM tag 0008,1060 | D | +| ManufacturersModelName | | | DICOM tag 0008,1090 | B | +| InstitutionName | | | DICOM tag 0008,0080 | B | +| InstitutionalDepartmentName | | | DICOM tag 0008,1040 | B | +| InstitutionAddress | | | DICOM tag 0008,0081 | B | +| DeviceSerialNumber | | | DICOM tag 0018,1000 | B | +| StationName | | | DICOM tag 0008,1010 | B | +| ConversionSoftware | | | e.g. `dcm2niix` | D | +| ConversionSoftwareVersion | | | e.g. `v1.0.20210317` | D | ##### Private Information These fields contain personally identifiable information. By default dcm2niix will create anonymized files without these fields. The option `-ba n` will retain private information. - -|Field|Unit|Manufacturer|Comments|Defined By| -|-----|----|------------|--------|----------| -|SeriesInstanceUID| | | From DICOM [0020,000E](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0020,000E)) | | -|StudyInstanceUID| | | | -|ReferringPhysicianName| | | | -|StudyID| | | | -|PatientName| | | | -|PatientID| | | | -|AccessionNumber| | | | -|PatientBirthDate| | | | -|PatientWeight| nominally kg | | | -|AcquisitionDateTime| | | | +| Field | Unit | Manufacturer | Comments | Defined By | +|------------------------|------|--------------|----------------------|------------| +| SeriesInstanceUID | | | DICOM tag 0020,000E | D | +| StudyInstanceUID | | | DICOM tag 0020,000D | D | +| ReferringPhysicianName | | | DICOM tag 0008,0090 | D | +| StudyID | | | DICOM tag 0020,0010 | D | +| PatientName | | | DICOM tag 0010,0010 | D | +| PatientID | | | DICOM tag 0010,0020 | D | +| AccessionNumber | | | DICOM tag 0008,0050 | D | +| PatientBirthDate | | | DICOM tag 0010,0030 | D | +| PatientWeight | kg | | DICOM tag 0010,1030 | D | +| AcquisitionDateTime | | | DICOM tag 0008,002A | D | ##### Series Information -Fields common to all scans, regardless of modality or manufacturer. - -|Field|Unit|Manufacturer|Comments|Defined By| -|-----|----|------------|--------|----------| -|BodyPartExamined| | | | | -|PatientPosition| | | | | -|ProcedureStepDescription| | | | | -|SoftwareVersions| | | | | -|SeriesDescription| | | | | -|ProtocolName| | | | | -|ScanningSequence| | | | | -|SequenceVariant| | | | | -|ScanOptions| | | | | -|SequenceName| | | | | -|ImageType| list | | | | -|AcquisitionTime| | | | | -|AcquisitionNumber| | | | | -|ImageComments| | | | | -|ConversionSoftware| | | Name of the program that converted from DICOM to NIFTI | | -|ConversionSoftwareVersion| | | | | - +| Field | Unit | Manufacturer | Comments | Defined By | +|--------------------------|------|--------------|----------------------|------------| +| BodyPartExamined | | | DICOM tag 0018,0015 | D | +| PatientPosition | | | DICOM tag 0020,0032 | D | +| ProcedureStepDescription | | | DICOM tag 0040,0254 | D | +| SoftwareVersions | | | DICOM tag 0020,1020 | B | +| SeriesDescription | | | DICOM tag 0008,103E | D | +| ProtocolName | | | DICOM tag 0018,1030 | D | +| ScanningSequence | | | DICOM tag 0018,0020 | D | +| SequenceVariant | | | DICOM tag 0018,0021 | D | +| ScanOptions | | | DICOM tag 0018,0022 | D | +| SequenceName | | | DICOM tag 0018,0024 | D | +| ImageType | list | | DICOM tag 0008,0008 | D | +| AcquisitionTime | | | DICOM tag 0008,0032 | D | +| AcquisitionNumber | | | DICOM tag 0020,0012 | D | +| ImageComments | | | DICOM tag 0020,4000 | D | ##### Philips Sequence Information Data unique to Philips, including [custom intensity scaling](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3998685/) -|Field|Unit|Manufacturer|Comments|Defined By| -|-----|----|------------|--------|----------| -|TriggerDelayTime| s? | P | | -|PhilipsRWVSlope | | P | | -|PhilipsRWVIntercept | | P | | -|PhilipsRescaleSlope| | P | | -|PhilipsRescaleIntercept| | P | | -|PhilipsScaleSlope| | P | | -|UsePhilipsFloatNotDisplayScaling| | P | | +| Field | Unit | Manufacturer | Comments | Defined By | +|------------------------------------|------|--------------|-----------------------------------|------------| +| TriggerDelayTime | s | P | DICOM tag 0020,9153 or 0018,1060 | D | +| PhilipsRWVSlope | | P | DICOM tag 0040,9225 | D | +| PhilipsRWVIntercept | | P | DICOM tag 0040,9224 | D | +| PhilipsRescaleSlope | | P | DICOM tag 0028,1053 | D | +| PhilipsRescaleIntercept | | P | DICOM tag 0028,1052 | D | +| PhilipsScaleSlope | | P | DICOM tag 2005,100E | D | +| UsePhilipsFloatNotDisplayScaling | | P | dcm2niix option `-p y` or `-p n` | D | +| PartialFourierEnabled | | P | `YES`, n.b. `PartialFourier` | D | +| PhaseEncodingStepsNoPartialFourier | | P | | D | +| WaterFatShift | | P | DICOM tag 2001,1022 | D | + + +##### General Electric Sequence Information + +Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Determining these fields from a DICOM image requires decoding the compressed Protocol Data Block (0025,101B). + + +| Field | Unit | Manufacturer | Comments | Defined By | +|-----------------------------|------|--------------|--------------------------|------------| +| PulseSequenceName | | G | `epi` or `epiRT` | | +| InternalPulseSequenceName | | G | `EPI` or `EPI2` | | +| PhaseEncodingPolarityGE | | G | `Unflipped` or `Flipped` | D | ##### Positron Emission Tomography Isotope Module Attributes -PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.9.2.html). See BIDS [BEP009 Positron Emission Tomography](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). - -This is a [work in progress](https://github.com/bids-standard/bids-specification/pull/773). - -|Field|Unit|Manufacturer|Comments|Defined By| -|-----|----|------------|--------|----------| -|Radiopharmaceutical| | | | | -|RadionuclidePositronFraction| | | | | -|RadionuclideTotalDose| | | | | -|RadionuclideHalfLife| | | | | -|DoseCalibrationFactor| | | | | -|IsotopeHalfLife| s? | | | | -|Dosage| | | | | -|ConvolutionKernel| | | | | -|Units| | | | | -|DecayCorrection| | | | | -|AttenuationCorrectionMethod| | | | | -|ReconstructionMethod| | | | | -|DecayFactor| | | | | -|FrameTimesStart| s | | | | -|FrameDuration| s | | | | +PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.9.2.html). See BIDS [BEP009 Positron Emission Tomography](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). Be aware that many of the fields required by BEP009 are not stored in DICOM or ECAT files. Since dcm2niix is limited to the information provided by the input files, it will generate JSON fields that reflect the meta data stored in the source images. + +The term ECAT in the comments suggests that values are defined by the [ECAT7](http://www.turkupetcentre.net/petanalysis/format_image_ecat.html) format. Therefore, these fields will not be populated for DICOM data. + + +| Field | Unit | Manufacturer | Comments | Defined By | +|-----------------------------|------|--------------|-----------------------------|------------| +|Radiopharmaceutical | | | DICOM tag 0018,0031 or ECAT | D | +|RadionuclidePositronFraction | f | | DICOM tag 0018,1076 | D | +|RadionuclideTotalDose | MBq | | DICOM tag 0018,1074 | D | +|RadionuclideHalfLife | s | | DICOM tag 0018,1075 | D | +|DoseCalibrationFactor | | | DICOM tag 0054,1322 | D | +|IsotopeHalfLife | | | ECAT | D | +|Dosage | | | ECAT | D | +|ConvolutionKernel | | | DICOM tag 0018,1210 | D | +|Units | | | DICOM tag 0054,1001 | D | +|DecayCorrection | | | DICOM tag 0054,1102 | D | +|AttenuationCorrectionMethod | | | DICOM tag 0054,1101 | D | +|ReconstructionMethod | | | DICOM tag 0054,1103 | D | +|DecayFactor | | | DICOM tag 0054,1321 | D | +|FrameTimesStart | s | | DICOM tags 0008,0022 | D | +|FrameDuration | s | | DICOM tag 0018,1242 | D | ##### Computerized Tomography Fields specific to CT scans. -|Field|Unit|Manufacturer|Comments|Defined By| -|-----|----|------------|--------|----------| -|XRayExposure| | | | Relative x-ray exposure from DICOM [0018,1405](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,1405))| | +|Field |Unit |Manufacturer|Comments |Defined By| +|------------|-----|------------|--------------------|----------| +|XRayExposure| mAs | | DICOM tag 0018,1152| D | ##### Magnetic Resonance Imaging Fields specific to MRI scans. -|Field|Unit|Manufacturer|Comments|Defined By| -|-----|----|------------|--------|----------| -|MRAcquisitionType| | | `2D` or `3D` | | -|Units| | | `Hz` only for field maps| | -|SliceThickness| mm | | | D | -|SpacingBetweenSlices| mm | | | D | -|SAR| | | | | -|NumberOfAverages| | | | | -|EchoNumber| | | Only for multi-echo series| | -|EchoTime| s | | | | -|RepetitionTime| s | | | | -|RepetitionTimeExcitation| s | | | | -|RepetitionTimeInversion| s | | | | -|InversionTime| s | | | | -|ImagingFrequency| MHz | | | D? | -|FlipAngle| degrees | | | | -|PhaseEncodingDirection| | | i, j | | -|PhaseEncodingPolarityGE| | G | | D | - +| Field | Unit | Manufacturer | Comments | Defined By | +|------------------------------------|------|--------------|-------------------------------|------------| +| MagneticFieldStrength | T | | DICOM tag 0018,0087 | B | +| MRAcquisitionType | | | DICOM tag 0018,0023 | | +| Units | | | `Hz` (field maps) | B | +| SliceThickness | mm | | [nb](http://dclunie.blogspot.com/2013/10/how-thick-am-i-sad-story-of-lonely-slice.html) | D | +| SpacingBetweenSlices | mm | | [nb](http://dclunie.blogspot.com/2013/10/how-thick-am-i-sad-story-of-lonely-slice.html) | D | +| SAR | | | DICOM tag 0018,1316 | D | +| NumberOfAverages | | | DICOM tag 0018,0083 | D | +| EchoNumber | | | Only multi-echo series | D | +| EchoTime | s | | DICOM tag 0018,0081 | D | +| RepetitionTime | s | | DICOM tag 0018,0080 | D | +| RepetitionTimeExcitation | s | | | D | +| RepetitionTimeInversion | s | | | D | +| InversionTime | s | | DICOM tag 0018,0082 | D | +| ImagingFrequency | MHz | | DICOM tag 0018,0084 | D | +| FlipAngle | deg | | DICOM tag 0018,1314 | B | +| MultibandAccelerationFactor | | | aka `SMS`, `HyperBand` | B | +| PercentPhaseFOV | | | DICOM tag 0018,0094 | D | +| PercentSampling | | | DICOM tag 0018,0093 | D | +| EchoTrainLength | | | DICOM tag 0018,0091 | D | +| PhaseEncodingSteps | | | DICOM tag 0018,0089 | D | +| AcquisitionMatrixPE | | | | D | +| ParallelReductionFactorInPlane | | | aka `SENSE`, `GRAPPA` | B | +| ParallelAcquisitionTechnique | | | DICOM tag 0018, 9078 | B | +| ParallelReductionOutOfPlane | | | | D | +| EstimatedEffectiveEchoSpacing | s | | | D | +| EstimatedTotalReadoutTime | s | | | D | +| EffectiveEchoSpacing | s | | | D | +| DerivedVendorReportedEchoSpacing | s | | | D | +| TotalReadoutTime | | | FSL definition | B | +| PixelBandwidth | | | DICOM tag 0018,0095 | D | +| PhaseEncodingAxis | | | When polarity unknown | B | +| PhaseEncodingDirection | | | When polarity known | B | +| SliceTiming | s | | | B | +| ImageOrientationPatientDICOM | | | DICOM tag 0020,0037 | D | +| InPlanePhaseEncodingDirectionDICOM | | | | D | +| ReceiveCoilName | | | DICOM tag 0018,1250 | B | ##### Arterial Spin Labeling See BIDS [BEP005 Arterial Spin -Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). +Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). All these values are specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). | Field | Unit | Manufacturer | Comments | Defined By | |---------------------------------|--------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| @@ -199,78 +242,39 @@ Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). -| Field | Unit | Manufacturer | Comments | Defined By | -|---------------------------|-------------------------------------------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| -| EchoTime1 | s | S | For Fieldmaps created with two echo times | D | -| EchoTime2 | s | S | For Fieldmaps created with two echo times | D | -| PartialFourier | | S | Partial fourier fraction, e.g. image with 6/8 pF will report `0.75` | D | -| Interpolation2D | | S | If present, slices interpolated within plane | D | -| Interpolation3D | | S | If present, image interpolated in all spatial dimensions | D | -| BaseResolution | integer | S | # of acquisition lines? | D | -| ShimSetting | DAC for 1st order terms, mA for 2nd order terms | S | The 1st 3 values are the 1st order shims from sGRADSPEC.lOffset{X,Y,Z}. The next 5 are 2nd order shims from sGRADSPEC.alShimCurrent[0-4]. Converting to the first order terms to uT/m may be possible using the "gradient sensitivities". Converting the second order terms to uT/m^2 requires information not stored in the DICOM header, but they are fixed for a given shim hardware configuration. | D | -| DiffusionScheme | | S | `Monopolar` or `Bipolar` | D | -| DelayTime | s | S | | D | -| TxRefAmp | V | S | | D | -| PhaseResolution | fraction | S | | D | -| PhaseOversampling | | S | | D | -| VendorReportedEchoSpacing | | S | | D | -| ReceiveCoilName | | S | e.g. "HeadNeck\_64" | D | -| ReceiveCoilActiveElements | | S | Given as groups of channels, instead of individual ones, e.g. "HC1-7" for HeadNeck\_64. | D | -| CoilString | | S | | D | -| PulseSequenceDetails | | S | pulse sequence label, e.g. "%SiemensSeq%\\\\tgse\_pasl" | D | -| FmriExternalInfo | | S | | D | -| WipMemBlock | | S | | D | -| ProtocolName | | S | Check SeriesDescription - they might be switched around | D | -| RefLinesPE | | S | # of reference lines in the phase encoding direction for acceleration (GRAPPA) | D | -| ConsistencyInfo | | S | The more complete software version, e.g. VE11C or VE11E instead of just VE11. | D | +| Field | Unit | Manufacturer | Comments | Defined By | +|------------------------------|-------------------------------------------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| +| EchoTime1 | s | S | For Fieldmaps created with two echo times | D | +| EchoTime2 | s | S | For Fieldmaps created with two echo times | D | +| PartialFourier | f | S | Partial fourier fraction, e.g. image with 6/8 pF will report `0.75`. Different tags depending on manufacturer | B | +| Interpolation2D | | S | If present, slices interpolated within plane | D | +| Interpolation3D | | S | If present, image interpolated in all spatial dimensions | D | +| BaseResolution | integer | S | # of acquisition lines? | D | +| ShimSetting | DAC for 1st order terms, mA for 2nd order terms | S | The 1st 3 values are the 1st order shims from sGRADSPEC.lOffset{X,Y,Z}. The next 5 are 2nd order shims from sGRADSPEC.alShimCurrent[0-4]. Converting to the first order terms to uT/m may be possible using the "gradient sensitivities". Converting the second order terms to uT/m^2 requires information not stored in the DICOM header, but they are fixed for a given shim hardware configuration. | D | +| DiffusionScheme | | S | `Monopolar` or `Bipolar` | D | +| DelayTime | s | S | | D | +| TxRefAmp | V | S | | D | +| PhaseResolution | f | S | | D | +| PhaseOversampling | | S | | D | +| VendorReportedEchoSpacing | | S | | B | +| ReceiveCoilActiveElements | | S | Given as groups of channels, instead of individual ones, e.g. "HC1-7" for HeadNeck\_64. | D | +| CoilString | | S | May or may not match `ReceiveCoilName` | D | +| PulseSequenceDetails | | S | pulse sequence label, e.g. "%SiemensSeq%\\\\tgse\_pasl" | D | +| FmriExternalInfo | | S | | D | +| WipMemBlock | | S | | D | +| ProtocolName | | S | Check SeriesDescription - they might be switched around | D | +| RefLinesPE | | S | # of reference lines in the phase encoding direction for acceleration (GRAPPA) | D | +| ConsistencyInfo | | S | The more complete software version, e.g. VE11C or VE11E instead of just VE11. | D | +| DwellTime | | S | DICOM tag 0019,1018 | B | +| BandwidthPerPixelPhaseEncode | | S | | D | ##### Magnetic Resonance Imaging (Siemens XA) Fields specific to Siemens XA-series MRI systems (Sola, Vida). -|Field|Notes|Comments| -|-----|-----|-----| -|ReceiveCoilActiveElements| S | | -|CoilString| S | | -|PartialFourier| S | | +|Field | Manufacturer |Comments | +|------------------------------|--------------|--------------------| +| ReceiveCoilActiveElements | S | DICOM tag 0021,114F| +| BandwidthPerPixelPhaseEncode | S | DICOM tag 0021,1153| -##### Magnetic Resonance Imaging -Fields found in typical MRI sequences. - -|Field|Notes|Comments| -|-----|-----|-----| -|MultibandAccelerationFactor| | Multiple 2D EPI slices in 3D volume acquired simultaneously(`HyperBand`, `SMS`)| -|PercentPhaseFOV| | | -|PercentSampling| | | -|EchoTrainLength| | | -|PartialFourierEnabled|P|This field will report `YES`, for fraction see `PartialFourier` | -|PhaseEncodingStepsNoPartialFourier|P| Does not take partial Fourier into account| -|PhaseEncodingSteps| | | -|AcquisitionMatrixPE| | | -|BandwidthPerPixelPhaseEncode| | | -|ParallelReductionFactorInPlane| | Partial sampling of k-space (`SENSE`, `GRAPPA`)| -|ParallelReductionOutOfPlane| | | -|WaterFatShift| | | -|EstimatedEffectiveEchoSpacing| | | -|EstimatedTotalReadoutTime| | | -|EffectiveEchoSpacing| | | -|DerivedVendorReportedEchoSpacing| | | -|TotalReadoutTime| | [FSL's definition](https://github.com/rordenlab/dcm2niix/tree/master/GE) of time to read a 2D EPI slice| -|PixelBandwidth| | | -|DwellTime| | | -|PhaseEncodingAxis| |Only populated if polarity of phase encoding is not known. Values `j` or `i`. see `PhaseEncodingDirection ` | -|PhaseEncodingDirection| | Phase axis and polarity. Values `j`,`i`,`j-`,`i-` | -|SliceTiming| | One value for each slice in EPI volume| -|ImageOrientationPatientDICOM| | | -|InPlanePhaseEncodingDirectionDICOM| | | - - -##### Conversion Tool - -Details of software used to convert DICOM to NIfTI/BIDS. - -|Field|Notes|Comments| -|-----|-----|-----| -|ConversionSoftware| |`dcm2niix` | -|ConversionSoftwareVersion| | Based on date of release, e.g. [`v1.0.20210317`](https://github.com/rordenlab/dcm2niix/releases)| diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 54f225ae..a8bf5815 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -740,6 +740,7 @@ struct TDICOMdata clear_dicom_data() { strcpy(d.coilElements, ""); strcpy(d.radiopharmaceutical, ""); strcpy(d.convolutionKernel, ""); + strcpy(d.parallelAcquisitionTechnique, ""); strcpy(d.unitsPT, ""); strcpy(d.decayCorrection, ""); strcpy(d.attenuationCorrectionMethod, ""); @@ -4258,6 +4259,7 @@ struct TDICOMdata readDICOMx(char * fname, struct TDCMprefs* prefs, struct TDTI4 #define kAcquisitionDuration 0x0018+uint32_t(0x9073<< 16 ) //FD //#define kFrameAcquisitionDateTime 0x0018+uint32_t(0x9074<< 16 ) //DT "20181019212528.232500" #define kDiffusionDirectionality 0x0018+uint32_t(0x9075<< 16 ) // NONE, ISOTROPIC, or DIRECTIONAL +#define kParallelAcquisitionTechnique 0x0018+uint32_t(0x9078<< 16 ) //CS: SENSE, SMASH #define kInversionTimes 0x0018+uint32_t(0x9079<< 16 ) //FD #define kPartialFourier 0x0018+uint32_t(0x9081<< 16 ) //CS const uint32_t kEffectiveTE = 0x0018+ (0x9082 << 16); @@ -5402,6 +5404,9 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); if (strcmp(dir, "ISOTROPIC") == 0) isPhilipsDerived = true; break; } + case kParallelAcquisitionTechnique: //CS + dcmStr(lLength, &buffer[lPos], d.parallelAcquisitionTechnique); + break; case kInversionTimes : {//issue 380 if ((lLength < 8) || ((lLength % 8) != 0)) break; d.TI = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 51fea310..3579154a 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -188,7 +188,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; float frameDuration, ecat_isotope_halflife, ecat_dosage; float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. double acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; - char radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr]; + char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr]; char coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], scanOptions[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[kDICOMStr], imageType[kDICOMStr], institutionalDepartmentName[kDICOMStr], manufacturersModelName[kDICOMStr], patientID[kDICOMStr], patientOrient[kDICOMStr], patientName[kDICOMStr], accessionNumber[kDICOMStr], seriesDescription[kDICOMStr], studyID[kDICOMStr], sequenceName[kDICOMStr], protocolName[kDICOMStr],sequenceVariant[kDICOMStr],scanningSequence[kDICOMStr], patientBirthDate[kDICOMStr], patientAge[kDICOMStr], studyDate[kDICOMStr],studyTime[kDICOMStr]; char institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge]; uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 04291012..99e623a8 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1546,7 +1546,9 @@ tse3d: T2*/ bandwidthPerPixelPhaseEncode = d.CSA.bandwidthPerPixelPhaseEncode; json_Float(fp, "\t\"BandwidthPerPixelPhaseEncode\": %g,\n", bandwidthPerPixelPhaseEncode ); //if ((!d.is3DAcq) && (d.accelFactPE > 1.0)) fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); - if (d.accelFactPE > 1.0) fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); //https://github.com/rordenlab/dcm2niix/issues/314 + if (d.accelFactPE > 1.0) fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); + json_Str(fp, "\t\"ParallelAcquisitionTechnique\": \"%s\",\n", d.parallelAcquisitionTechnique); + //https://github.com/rordenlab/dcm2niix/issues/314 if (d.accelFactOOP > 1.0) fprintf(fp, "\t\"ParallelReductionOutOfPlane\": %g,\n", d.accelFactOOP); //EffectiveEchoSpacing // Siemens bandwidthPerPixelPhaseEncode already accounts for the effects of parallel imaging, From e0e7d8881288987fb71a13167facf650859868b6 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Sat, 12 Jun 2021 11:48:08 -0400 Subject: [PATCH 40/95] Typos --- BIDS/README.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index d7366246..e2833d1e 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -42,8 +42,6 @@ The Defined By column uses - s : seconds - T : Tesla -MBq - ##### Constants These fields should be the same for all images acquired on a specific scanner using a specific DICOM to BIDS conversion tool. @@ -115,7 +113,7 @@ Data unique to Philips, including [custom intensity scaling](https://www.ncbi.nl | UsePhilipsFloatNotDisplayScaling | | P | dcm2niix option `-p y` or `-p n` | D | | PartialFourierEnabled | | P | `YES`, n.b. `PartialFourier` | D | | PhaseEncodingStepsNoPartialFourier | | P | | D | -| WaterFatShift | | P | DICOM tag 2001,1022 | D | +| WaterFatShift | | P | DICOM tag 2001,1022 | D | ##### General Electric Sequence Information @@ -125,8 +123,8 @@ Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Deter | Field | Unit | Manufacturer | Comments | Defined By | |-----------------------------|------|--------------|--------------------------|------------| -| PulseSequenceName | | G | `epi` or `epiRT` | | -| InternalPulseSequenceName | | G | `EPI` or `EPI2` | | +| PulseSequenceName | | G | `epi` or `epiRT` | D | +| InternalPulseSequenceName | | G | `EPI` or `EPI2` | D | | PhaseEncodingPolarityGE | | G | `Unflipped` or `Flipped` | D | ##### Positron Emission Tomography Isotope Module Attributes @@ -158,9 +156,9 @@ The term ECAT in the comments suggests that values are defined by the [ECAT7](ht Fields specific to CT scans. -|Field |Unit |Manufacturer|Comments |Defined By| -|------------|-----|------------|--------------------|----------| -|XRayExposure| mAs | | DICOM tag 0018,1152| D | +|Field |Unit |Manufacturer|Comments |Defined By| +|------------|-----|------------|---------------------|----------| +|XRayExposure| mAs | | DICOM tag 0018,1152 | D | ##### Magnetic Resonance Imaging @@ -272,9 +270,9 @@ Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio Fields specific to Siemens XA-series MRI systems (Sola, Vida). -|Field | Manufacturer |Comments | -|------------------------------|--------------|--------------------| -| ReceiveCoilActiveElements | S | DICOM tag 0021,114F| -| BandwidthPerPixelPhaseEncode | S | DICOM tag 0021,1153| +|Field | Manufacturer |Comments | Defined By | +|------------------------------|--------------|--------------------|------------| +| ReceiveCoilActiveElements | S | DICOM tag 0021,114F| D | +| BandwidthPerPixelPhaseEncode | S | DICOM tag 0021,1153| D | From 423a1c1aab289e068fa3c41f232587426603e4ec Mon Sep 17 00:00:00 2001 From: Rob Reid Date: Sun, 13 Jun 2021 12:04:04 -0500 Subject: [PATCH 41/95] Possibly obsolete commit --- BIDS/README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index a7a1693b..347be8c1 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -197,7 +197,8 @@ Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html ##### Magnetic Resonance Imaging (Siemens V*) -Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). +Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, +Trio, Skyra, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). | Field | Unit | Manufacturer | Comments | Defined By | |---------------------------|-------------------------------------------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| @@ -226,13 +227,13 @@ Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio ##### Magnetic Resonance Imaging (Siemens XA) -Fields specific to Siemens XA-series MRI systems (Sola, Vida). +Fields (possibly?) specific to Siemens XA-series MRI systems (Sola, Vida). -|Field|Notes|Comments| -|-----|-----|-----| -|ReceiveCoilActiveElements| S | | -|CoilString| S | | -|PartialFourier| S | | +| Field | Unit | Manufacturer | Comments | Defined By | +|---------------------------|------|--------------|---------------|------------| +| ReceiveCoilActiveElements | | S | See Siemens V | D | +| CoilString | | S | See Siemens V | D | +| PartialFourier | | S | | D | ##### Magnetic Resonance Imaging From 3f5f0e958b22fa48b2deafe877975cfe17810af0 Mon Sep 17 00:00:00 2001 From: Rob Reid Date: Sun, 13 Jun 2021 12:33:28 -0500 Subject: [PATCH 42/95] Removes Manufacturer column --- BIDS/README.md | 354 ++++++++++++++++++++++++------------------------- 1 file changed, 177 insertions(+), 177 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index 20114560..ac3e5772 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -30,90 +30,90 @@ The Defined By column uses The Unit column uses -The Defined By column uses - - deg : degrees - f : fraction - kg : Kilogram - list : list of text strings - MBq : MegaBecquerels - MHz : Megahertz +- mA : milliAmperes - mm : millimeter - s : seconds - T : Tesla +- V : Volts ##### Constants These fields should be the same for all images acquired on a specific scanner using a specific DICOM to BIDS conversion tool. -| Field | Unit | Manufacturer | Comments | Defined By | -|-----------------------------|------|--------------|----------------------|------------| -| Manufacturer | | | DICOM tag 0008,0070 | B | -| DeviceSerialNumber | | | DICOM tag 0018,1000 | B | -| StationName | | | DICOM tag 0008,1010 | B | -| SoftwareVersions | | | DICOM tag 0018,1020 | B | -| Modality | | | DICOM tag 0008,1060 | D | -| ManufacturersModelName | | | DICOM tag 0008,1090 | B | -| InstitutionName | | | DICOM tag 0008,0080 | B | -| InstitutionalDepartmentName | | | DICOM tag 0008,1040 | B | -| InstitutionAddress | | | DICOM tag 0008,0081 | B | -| DeviceSerialNumber | | | DICOM tag 0018,1000 | B | -| StationName | | | DICOM tag 0008,1010 | B | -| ConversionSoftware | | | e.g. `dcm2niix` | D | -| ConversionSoftwareVersion | | | e.g. `v1.0.20210317` | D | +| Field | Unit | Comments | Defined By | +|-----------------------------|------|----------------------|------------| +| Manufacturer | | DICOM tag 0008,0070 | B | +| DeviceSerialNumber | | DICOM tag 0018,1000 | B | +| StationName | | DICOM tag 0008,1010 | B | +| SoftwareVersions | | DICOM tag 0018,1020 | B | +| Modality | | DICOM tag 0008,1060 | D | +| ManufacturersModelName | | DICOM tag 0008,1090 | B | +| InstitutionName | | DICOM tag 0008,0080 | B | +| InstitutionalDepartmentName | | DICOM tag 0008,1040 | B | +| InstitutionAddress | | DICOM tag 0008,0081 | B | +| DeviceSerialNumber | | DICOM tag 0018,1000 | B | +| StationName | | DICOM tag 0008,1010 | B | +| ConversionSoftware | | e.g. `dcm2niix` | D | +| ConversionSoftwareVersion | | e.g. `v1.0.20210317` | D | ##### Private Information These fields contain personally identifiable information. By default dcm2niix will create anonymized files without these fields. The option `-ba n` will retain private information. -| Field | Unit | Manufacturer | Comments | Defined By | -|------------------------|------|--------------|----------------------|------------| -| SeriesInstanceUID | | | DICOM tag 0020,000E | D | -| StudyInstanceUID | | | DICOM tag 0020,000D | D | -| ReferringPhysicianName | | | DICOM tag 0008,0090 | D | -| StudyID | | | DICOM tag 0020,0010 | D | -| PatientName | | | DICOM tag 0010,0010 | D | -| PatientID | | | DICOM tag 0010,0020 | D | -| AccessionNumber | | | DICOM tag 0008,0050 | D | -| PatientBirthDate | | | DICOM tag 0010,0030 | D | -| PatientWeight | kg | | DICOM tag 0010,1030 | D | -| AcquisitionDateTime | | | DICOM tag 0008,002A | D | +| Field | Unit | Comments | Defined By | +|------------------------|------|---------------------|------------| +| SeriesInstanceUID | | DICOM tag 0020,000E | D | +| StudyInstanceUID | | DICOM tag 0020,000D | D | +| ReferringPhysicianName | | DICOM tag 0008,0090 | D | +| StudyID | | DICOM tag 0020,0010 | D | +| PatientName | | DICOM tag 0010,0010 | D | +| PatientID | | DICOM tag 0010,0020 | D | +| AccessionNumber | | DICOM tag 0008,0050 | D | +| PatientBirthDate | | DICOM tag 0010,0030 | D | +| PatientWeight | kg | DICOM tag 0010,1030 | D | +| AcquisitionDateTime | | DICOM tag 0008,002A | D | ##### Series Information -| Field | Unit | Manufacturer | Comments | Defined By | -|--------------------------|------|--------------|----------------------|------------| -| BodyPartExamined | | | DICOM tag 0018,0015 | D | -| PatientPosition | | | DICOM tag 0020,0032 | D | -| ProcedureStepDescription | | | DICOM tag 0040,0254 | D | -| SoftwareVersions | | | DICOM tag 0020,1020 | B | -| SeriesDescription | | | DICOM tag 0008,103E | D | -| ProtocolName | | | DICOM tag 0018,1030 | D | -| ScanningSequence | | | DICOM tag 0018,0020 | D | -| SequenceVariant | | | DICOM tag 0018,0021 | D | -| ScanOptions | | | DICOM tag 0018,0022 | D | -| SequenceName | | | DICOM tag 0018,0024 | D | -| ImageType | list | | DICOM tag 0008,0008 | D | -| AcquisitionTime | | | DICOM tag 0008,0032 | D | -| AcquisitionNumber | | | DICOM tag 0020,0012 | D | -| ImageComments | | | DICOM tag 0020,4000 | D | +| Field | Unit | Comments | Defined By | +|--------------------------|------|---------------------|------------| +| BodyPartExamined | | DICOM tag 0018,0015 | D | +| PatientPosition | | DICOM tag 0020,0032 | D | +| ProcedureStepDescription | | DICOM tag 0040,0254 | D | +| SoftwareVersions | | DICOM tag 0020,1020 | B | +| SeriesDescription | | DICOM tag 0008,103E | D | +| ProtocolName | | DICOM tag 0018,1030 | D | +| ScanningSequence | | DICOM tag 0018,0020 | D | +| SequenceVariant | | DICOM tag 0018,0021 | D | +| ScanOptions | | DICOM tag 0018,0022 | D | +| SequenceName | | DICOM tag 0018,0024 | D | +| ImageType | list | DICOM tag 0008,0008 | D | +| AcquisitionTime | | DICOM tag 0008,0032 | D | +| AcquisitionNumber | | DICOM tag 0020,0012 | D | +| ImageComments | | DICOM tag 0020,4000 | D | ##### Philips Sequence Information Data unique to Philips, including [custom intensity scaling](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3998685/) -| Field | Unit | Manufacturer | Comments | Defined By | -|------------------------------------|------|--------------|-----------------------------------|------------| -| TriggerDelayTime | s | P | DICOM tag 0020,9153 or 0018,1060 | D | -| PhilipsRWVSlope | | P | DICOM tag 0040,9225 | D | -| PhilipsRWVIntercept | | P | DICOM tag 0040,9224 | D | -| PhilipsRescaleSlope | | P | DICOM tag 0028,1053 | D | -| PhilipsRescaleIntercept | | P | DICOM tag 0028,1052 | D | -| PhilipsScaleSlope | | P | DICOM tag 2005,100E | D | -| UsePhilipsFloatNotDisplayScaling | | P | dcm2niix option `-p y` or `-p n` | D | -| PartialFourierEnabled | | P | `YES`, n.b. `PartialFourier` | D | -| PhaseEncodingStepsNoPartialFourier | | P | | D | -| WaterFatShift | | P | DICOM tag 2001,1022 | D | +| Field | Unit | Comments | Defined By | +|------------------------------------|------|----------------------------------|------------| +| TriggerDelayTime | s | DICOM tag 0020,9153 or 0018,1060 | D | +| PhilipsRWVSlope | | DICOM tag 0040,9225 | D | +| PhilipsRWVIntercept | | DICOM tag 0040,9224 | D | +| PhilipsRescaleSlope | | DICOM tag 0028,1053 | D | +| PhilipsRescaleIntercept | | DICOM tag 0028,1052 | D | +| PhilipsScaleSlope | | DICOM tag 2005,100E | D | +| UsePhilipsFloatNotDisplayScaling | | dcm2niix option `-p y` or `-p n` | D | +| PartialFourierEnabled | | `YES`, n.b. `PartialFourier` | D | +| PhaseEncodingStepsNoPartialFourier | | | D | +| WaterFatShift | | DICOM tag 2001,1022 | D | ##### General Electric Sequence Information @@ -121,11 +121,11 @@ Data unique to Philips, including [custom intensity scaling](https://www.ncbi.nl Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Determining these fields from a DICOM image requires decoding the compressed Protocol Data Block (0025,101B). -| Field | Unit | Manufacturer | Comments | Defined By | -|-----------------------------|------|--------------|--------------------------|------------| -| PulseSequenceName | | G | `epi` or `epiRT` | D | -| InternalPulseSequenceName | | G | `EPI` or `EPI2` | D | -| PhaseEncodingPolarityGE | | G | `Unflipped` or `Flipped` | D | +| Field | Unit | Comments | Defined By | +|---------------------------|------|--------------------------|------------| +| PulseSequenceName | | `epi` or `epiRT` | D | +| InternalPulseSequenceName | | `EPI` or `EPI2` | D | +| PhaseEncodingPolarityGE | | `Unflipped` or `Flipped` | D | ##### Positron Emission Tomography Isotope Module Attributes @@ -134,106 +134,106 @@ PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/curre The term ECAT in the comments suggests that values are defined by the [ECAT7](http://www.turkupetcentre.net/petanalysis/format_image_ecat.html) format. Therefore, these fields will not be populated for DICOM data. -| Field | Unit | Manufacturer | Comments | Defined By | -|-----------------------------|------|--------------|-----------------------------|------------| -|Radiopharmaceutical | | | DICOM tag 0018,0031 or ECAT | D | -|RadionuclidePositronFraction | f | | DICOM tag 0018,1076 | D | -|RadionuclideTotalDose | MBq | | DICOM tag 0018,1074 | D | -|RadionuclideHalfLife | s | | DICOM tag 0018,1075 | D | -|DoseCalibrationFactor | | | DICOM tag 0054,1322 | D | -|IsotopeHalfLife | | | ECAT | D | -|Dosage | | | ECAT | D | -|ConvolutionKernel | | | DICOM tag 0018,1210 | D | -|Units | | | DICOM tag 0054,1001 | D | -|DecayCorrection | | | DICOM tag 0054,1102 | D | -|AttenuationCorrectionMethod | | | DICOM tag 0054,1101 | D | -|ReconstructionMethod | | | DICOM tag 0054,1103 | D | -|DecayFactor | | | DICOM tag 0054,1321 | D | -|FrameTimesStart | s | | DICOM tags 0008,0022 | D | -|FrameDuration | s | | DICOM tag 0018,1242 | D | +| Field | Unit | Comments | Defined By | +|------------------------------|------|-----------------------------|------------| +| Radiopharmaceutical | | DICOM tag 0018,0031 or ECAT | D | +| RadionuclidePositronFraction | f | DICOM tag 0018,1076 | D | +| RadionuclideTotalDose | MBq | DICOM tag 0018,1074 | D | +| RadionuclideHalfLife | s | DICOM tag 0018,1075 | D | +| DoseCalibrationFactor | | DICOM tag 0054,1322 | D | +| IsotopeHalfLife | | ECAT | D | +| Dosage | | ECAT | D | +| ConvolutionKernel | | DICOM tag 0018,1210 | D | +| Units | | DICOM tag 0054,1001 | D | +| DecayCorrection | | DICOM tag 0054,1102 | D | +| AttenuationCorrectionMethod | | DICOM tag 0054,1101 | D | +| ReconstructionMethod | | DICOM tag 0054,1103 | D | +| DecayFactor | | DICOM tag 0054,1321 | D | +| FrameTimesStart | s | DICOM tags 0008,0022 | D | +| FrameDuration | s | DICOM tag 0018,1242 | D | ##### Computerized Tomography Fields specific to CT scans. -|Field |Unit |Manufacturer|Comments |Defined By| -|------------|-----|------------|---------------------|----------| -|XRayExposure| mAs | | DICOM tag 0018,1152 | D | +| Field | Unit | Comments | Defined By | +|--------------|------|---------------------|------------| +| XRayExposure | mAs | DICOM tag 0018,1152 | D | ##### Magnetic Resonance Imaging Fields specific to MRI scans. -| Field | Unit | Manufacturer | Comments | Defined By | -|------------------------------------|------|--------------|-------------------------------|------------| -| MagneticFieldStrength | T | | DICOM tag 0018,0087 | B | -| MRAcquisitionType | | | DICOM tag 0018,0023 | | -| Units | | | `Hz` (field maps) | B | -| SliceThickness | mm | | [nb](http://dclunie.blogspot.com/2013/10/how-thick-am-i-sad-story-of-lonely-slice.html) | D | -| SpacingBetweenSlices | mm | | [nb](http://dclunie.blogspot.com/2013/10/how-thick-am-i-sad-story-of-lonely-slice.html) | D | -| SAR | | | DICOM tag 0018,1316 | D | -| NumberOfAverages | | | DICOM tag 0018,0083 | D | -| EchoNumber | | | Only multi-echo series | D | -| EchoTime | s | | DICOM tag 0018,0081 | D | -| RepetitionTime | s | | DICOM tag 0018,0080 | D | -| RepetitionTimeExcitation | s | | | D | -| RepetitionTimeInversion | s | | | D | -| InversionTime | s | | DICOM tag 0018,0082 | D | -| ImagingFrequency | MHz | | DICOM tag 0018,0084 | D | -| FlipAngle | deg | | DICOM tag 0018,1314 | B | -| MultibandAccelerationFactor | | | aka `SMS`, `HyperBand` | B | -| PercentPhaseFOV | | | DICOM tag 0018,0094 | D | -| PercentSampling | | | DICOM tag 0018,0093 | D | -| EchoTrainLength | | | DICOM tag 0018,0091 | D | -| PhaseEncodingSteps | | | DICOM tag 0018,0089 | D | -| AcquisitionMatrixPE | | | | D | -| ParallelReductionFactorInPlane | | | aka `SENSE`, `GRAPPA` | B | -| ParallelAcquisitionTechnique | | | DICOM tag 0018, 9078 | B | -| ParallelReductionOutOfPlane | | | | D | -| EstimatedEffectiveEchoSpacing | s | | | D | -| EstimatedTotalReadoutTime | s | | | D | -| EffectiveEchoSpacing | s | | | D | -| DerivedVendorReportedEchoSpacing | s | | | D | -| TotalReadoutTime | | | FSL definition | B | -| PixelBandwidth | | | DICOM tag 0018,0095 | D | -| PhaseEncodingAxis | | | When polarity unknown | B | -| PhaseEncodingDirection | | | When polarity known | B | -| SliceTiming | s | | | B | -| ImageOrientationPatientDICOM | | | DICOM tag 0020,0037 | D | -| InPlanePhaseEncodingDirectionDICOM | | | | D | -| ReceiveCoilName | | | DICOM tag 0018,1250 | B | +| Field | Unit | Comments | Defined By | +|------------------------------------|------|-----------------------------------------------------------------------------------------|------------| +| MagneticFieldStrength | T | DICOM tag 0018,0087 | B | +| MRAcquisitionType | | DICOM tag 0018,0023 | | +| Units | | `Hz` (field maps) | B | +| SliceThickness | mm | [nb](http://dclunie.blogspot.com/2013/10/how-thick-am-i-sad-story-of-lonely-slice.html) | D | +| SpacingBetweenSlices | mm | [nb](http://dclunie.blogspot.com/2013/10/how-thick-am-i-sad-story-of-lonely-slice.html) | D | +| SAR | | DICOM tag 0018,1316 | D | +| NumberOfAverages | | DICOM tag 0018,0083 | D | +| EchoNumber | | Only multi-echo series | D | +| EchoTime | s | DICOM tag 0018,0081 | D | +| RepetitionTime | s | DICOM tag 0018,0080 | D | +| RepetitionTimeExcitation | s | | D | +| RepetitionTimeInversion | s | | D | +| InversionTime | s | DICOM tag 0018,0082 | D | +| ImagingFrequency | MHz | DICOM tag 0018,0084 | D | +| FlipAngle | deg | DICOM tag 0018,1314 | B | +| MultibandAccelerationFactor | | aka `SMS`, `HyperBand` | B | +| PercentPhaseFOV | | DICOM tag 0018,0094 | D | +| PercentSampling | | DICOM tag 0018,0093 | D | +| EchoTrainLength | | DICOM tag 0018,0091 | D | +| PhaseEncodingSteps | | DICOM tag 0018,0089 | D | +| AcquisitionMatrixPE | | | D | +| ParallelReductionFactorInPlane | | aka `SENSE`, `GRAPPA` | B | +| ParallelAcquisitionTechnique | | DICOM tag 0018, 9078 | B | +| ParallelReductionOutOfPlane | | | D | +| EstimatedEffectiveEchoSpacing | s | | D | +| EstimatedTotalReadoutTime | s | | D | +| EffectiveEchoSpacing | s | | D | +| DerivedVendorReportedEchoSpacing | s | | D | +| TotalReadoutTime | s | [FSL definition](https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=fsl;d2c47aa4.1606) | B | +| PixelBandwidth | Hz | DICOM tag 0018,0095 | D | +| PhaseEncodingAxis | | When polarity unknown | B | +| PhaseEncodingDirection | | When polarity known | B | +| SliceTiming | s | | B | +| ImageOrientationPatientDICOM | | DICOM tag 0020,0037 | D | +| InPlanePhaseEncodingDirectionDICOM | | | D | +| ReceiveCoilName | | DICOM tag 0018,1250 | B | ##### Arterial Spin Labeling See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). All these values are specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). -| Field | Unit | Manufacturer | Comments | Defined By | -|---------------------------------|--------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| -| LabelOffset | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | -| PostLabelDelay | s | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org), [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | B | -| PostInversionDelay | s | S | 2D [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | B | -| NumRFBlocks | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | -| RFGap | | S | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | B | -| MeanGzx10 | | S | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | B | -| PhiAdjust | | S | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | -| InversionTime | s | S | 2D pASL product (`ep2d_pasl`, `tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| SaturationStopTime | s | S | 2D pASL product (`ep2d_pasl`) | B | -| BolusDuration | s | S | 2D pASL product (`tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| MeanGzx10 | | S | 3D pCASL [tgse_pcasl](http://www.loft-lab.org) | B | -| TagRFFlipAngle | degrees | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | -| TagRFDuration | s | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | -| TagRFSeparation | | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | -| MeanTagGradient | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagGradientAmplitude | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagDuration | s | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| MaximumT1Opt | | S | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | -| InitialPostLabelDelay | s | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| sWipMemBlockAdFree* | multiple values | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagPlaneDThickness | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagPlaneUlShape | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagPlaneSPositionDTra | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagPlaneSNormalDTra | | S | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| Tag* (if not explicitly listed) | One value for each | S | [Post-label delays](https://fsl.fmrib.ox.ac.uk/fslcourse/lectures/practicals/ASLpractical/index.html), 3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`jw_tgse_VEPCASL`) | B | +| Field | Unit | Comments | Defined By | +|---------------------------------|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| +| LabelOffset | | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | +| PostLabelDelay | s | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org), [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | B | +| PostInversionDelay | s | 2D [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | B | +| NumRFBlocks | | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | +| RFGap | | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | B | +| MeanGzx10 | | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | B | +| PhiAdjust | | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | +| InversionTime | s | 2D pASL product (`ep2d_pasl`, `tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| SaturationStopTime | s | 2D pASL product (`ep2d_pasl`) | B | +| BolusDuration | s | 2D pASL product (`tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| MeanGzx10 | | 3D pCASL [tgse_pcasl](http://www.loft-lab.org) | B | +| TagRFFlipAngle | deg | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | +| TagRFDuration | s | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | +| TagRFSeparation | | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | +| MeanTagGradient | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagGradientAmplitude | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagDuration | s | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| MaximumT1Opt | | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | +| InitialPostLabelDelay | s | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| sWipMemBlockAdFree* | multiple values | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagPlaneDThickness | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagPlaneUlShape | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagPlaneSPositionDTra | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| TagPlaneSNormalDTra | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | +| Tag* (if not explicitly listed) | One value for each | [Post-label delays](https://fsl.fmrib.ox.ac.uk/fslcourse/lectures/practicals/ASLpractical/index.html), 3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`jw_tgse_VEPCASL`) | B | ##### Magnetic Resonance Imaging (Siemens V*) @@ -241,39 +241,39 @@ Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Skyra, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). -| Field | Unit | Manufacturer | Comments | Defined By | -|------------------------------|-------------------------------------------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| -| EchoTime1 | s | S | For Fieldmaps created with two echo times | D | -| EchoTime2 | s | S | For Fieldmaps created with two echo times | D | -| PartialFourier | f | S | Partial fourier fraction, e.g. image with 6/8 pF will report `0.75`. Different tags depending on manufacturer | B | -| Interpolation2D | | S | If present, slices interpolated within plane | D | -| Interpolation3D | | S | If present, image interpolated in all spatial dimensions | D | -| BaseResolution | integer | S | # of acquisition lines? | D | -| ShimSetting | DAC for 1st order terms, mA for 2nd order terms | S | The 1st 3 values are the 1st order shims from sGRADSPEC.lOffset{X,Y,Z}. The next 5 are 2nd order shims from sGRADSPEC.alShimCurrent[0-4]. Converting to the first order terms to uT/m may be possible using the "gradient sensitivities". Converting the second order terms to uT/m^2 requires information not stored in the DICOM header, but they are fixed for a given shim hardware configuration. | D | -| DiffusionScheme | | S | `Monopolar` or `Bipolar` | D | -| DelayTime | s | S | | D | -| TxRefAmp | V | S | | D | -| PhaseResolution | f | S | | D | -| PhaseOversampling | | S | | D | -| VendorReportedEchoSpacing | | S | | B | -| ReceiveCoilActiveElements | | S | Given as groups of channels, instead of individual ones, e.g. "HC1-7" for HeadNeck\_64. | D | -| CoilString | | S | May or may not match `ReceiveCoilName` | D | -| PulseSequenceDetails | | S | pulse sequence label, e.g. "%SiemensSeq%\\\\tgse\_pasl" | D | -| FmriExternalInfo | | S | | D | -| WipMemBlock | | S | | D | -| ProtocolName | | S | Check SeriesDescription - they might be switched around | D | -| RefLinesPE | | S | # of reference lines in the phase encoding direction for acceleration (GRAPPA) | D | -| ConsistencyInfo | | S | The more complete software version, e.g. VE11C or VE11E instead of just VE11. | D | -| DwellTime | | S | DICOM tag 0019,1018 | B | -| BandwidthPerPixelPhaseEncode | | S | | D | +| Field | Unit | Comments | Defined By | +|------------------------------|------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| +| EchoTime1 | s | For Fieldmaps created with two echo times | D | +| EchoTime2 | s | For Fieldmaps created with two echo times | D | +| PartialFourier | f | Partial fourier fraction, e.g. image with 6/8 pF will report `0.75`. Different tags depending on manufacturer | B | +| Interpolation2D | | If present, slices interpolated within plane | D | +| Interpolation3D | | If present, image interpolated in all spatial dimensions | D | +| BaseResolution | integer | # of acquisition lines? | D | +| ShimSetting | DAC for 1st order terms, mA for 2nd order terms | The 1st 3 values are the 1st order shims from sGRADSPEC.lOffset{X,Y,Z}. The next 5 are 2nd order shims from sGRADSPEC.alShimCurrent[0-4]. Converting to the first order terms to uT/m may be possible using the "gradient sensitivities". Converting the second order terms to uT/m^2 requires information not stored in the DICOM header, but they are fixed for a given shim hardware configuration. | D | +| DiffusionScheme | | `Monopolar` or `Bipolar` | D | +| DelayTime | s | | D | +| TxRefAmp | V | | D | +| PhaseResolution | f | | D | +| PhaseOversampling | | | D | +| VendorReportedEchoSpacing | | | B | +| ReceiveCoilActiveElements | | Given as groups of channels, instead of individual ones, e.g. "HC1-7" for HeadNeck\_64. | D | +| CoilString | | May or may not match `ReceiveCoilName` | D | +| PulseSequenceDetails | | pulse sequence label, e.g. "%SiemensSeq%\\\\tgse\_pasl" | D | +| FmriExternalInfo | | | D | +| WipMemBlock | | | D | +| ProtocolName | | Check SeriesDescription - they might be switched around | D | +| RefLinesPE | | # of reference lines in the phase encoding direction for acceleration (GRAPPA) | D | +| ConsistencyInfo | | The more complete software version, e.g. VE11C or VE11E instead of just VE11. | D | +| DwellTime | | DICOM tag 0019,1018 | B | +| BandwidthPerPixelPhaseEncode | [Hz](https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=fsl;d2c47aa4.1606) | | D | ##### Magnetic Resonance Imaging (Siemens XA) -Fields (possibly?) specific to Siemens XA-series MRI systems (Sola, Vida). +Fields specific to Siemens XA-series MRI systems (Sola, Vida). -|Field | Manufacturer |Comments | Defined By | -|------------------------------|--------------|--------------------|------------| -| ReceiveCoilActiveElements | S | DICOM tag 0021,114F| D | -| BandwidthPerPixelPhaseEncode | S | DICOM tag 0021,1153| D | +| Field | Unit | Comments | Defined By | +|------------------------------|------|---------------------|------------| +| ReceiveCoilActiveElements | | DICOM tag 0021,114F | D | +| BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0021,1153 | D | From db7bf60d26cd85b5a06c4bc47407be1885f067d2 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Mon, 14 Jun 2021 08:52:17 -0400 Subject: [PATCH 43/95] Reorder --- BIDS/README.md | 319 +++++++++++++++++++++++++------------------------ 1 file changed, 161 insertions(+), 158 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index ac3e5772..5fc16603 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -1,34 +1,20 @@ ## About -dcm2niix is designed to convert complicated DICOM images in to simple NIfTI -images. However, the NIfTI images are unable to store much metadata that might -be useful for analyses. Therefore, dcm2niix can create Brain Imaging Data -Structure ([BIDS](https://bids.neuroimaging.io)) files that retain useful -information. These are simple human-readable text files in the -[JSON](https://en.wikipedia.org/wiki/JSON) format. The tables below provide -details regarding the JSON fields generated by dcm2niix. Some of these are -defined by the BIDS standard, while others are unique to dcm2niix. - -Note that dcm2niix cannot provide information that is not in the DICOM header. -Common reasons for absent fields are: - - irrelevance to the scan type, e.g. MagneticFieldStrength for CT - - it was removed from the DICOM during anonymization, possibly by accident or overzealousness - - difficulty interpreting the storage format used by the manufacturer - - the manufacturer simply neglected to write it - - -The Manufacturer section of the tables uses the following abbreviations - - - G : General Electric (GE) only. - - P : Philips only. - - S : Siemens only. - -The Defined By column uses +dcm2niix is designed to convert complicated DICOM images in to simple NIfTI images. However, the NIfTI images are unable to store much metadata that might be useful for analyses. Therefore, dcm2niix can create Brain Imaging Data Structure ([BIDS](https://bids.neuroimaging.io)) files that retain useful information. These are simple human-readable text files in the [JSON](https://en.wikipedia.org/wiki/JSON) format. The tables below provide details regarding the JSON fields generated by dcm2niix. Some of these are defined by the BIDS standard, while others are unique to dcm2niix. + +Note that dcm2niix cannot provide information that is not in the DICOM header. Common reasons for absent fields are: + + - Irrelevance to the scan type, e.g. MagneticFieldStrength for CT + - It was removed from the DICOM during anonymization, possibly by accident or overzealousness + - Difficulty interpreting the storage format used by the manufacturer + - The manufacturer simply neglected to write it + +The Defined By column uses: - B : The [BIDS](https://bids.neuroimaging.io) standard - D : dcm2niix (often, but not always, these come directly from the DICOM header without transformation) -The Unit column uses +The Unit column uses: - deg : degrees - f : fraction @@ -42,9 +28,13 @@ The Unit column uses - T : Tesla - V : Volts -##### Constants +## Global Fields + +These fields are present regardless of modality (e.g. MR, CT, PET). -These fields should be the same for all images acquired on a specific scanner using a specific DICOM to BIDS conversion tool. +##### Global Constants + +These fields should be the same for all images acquired on a specific scanner. | Field | Unit | Comments | Defined By | |-----------------------------|------|----------------------|------------| @@ -62,24 +52,9 @@ These fields should be the same for all images acquired on a specific scanner us | ConversionSoftware | | e.g. `dcm2niix` | D | | ConversionSoftwareVersion | | e.g. `v1.0.20210317` | D | -##### Private Information - -These fields contain personally identifiable information. By default dcm2niix will create anonymized files without these fields. The option `-ba n` will retain private information. - -| Field | Unit | Comments | Defined By | -|------------------------|------|---------------------|------------| -| SeriesInstanceUID | | DICOM tag 0020,000E | D | -| StudyInstanceUID | | DICOM tag 0020,000D | D | -| ReferringPhysicianName | | DICOM tag 0008,0090 | D | -| StudyID | | DICOM tag 0020,0010 | D | -| PatientName | | DICOM tag 0010,0010 | D | -| PatientID | | DICOM tag 0010,0020 | D | -| AccessionNumber | | DICOM tag 0008,0050 | D | -| PatientBirthDate | | DICOM tag 0010,0030 | D | -| PatientWeight | kg | DICOM tag 0010,1030 | D | -| AcquisitionDateTime | | DICOM tag 0008,002A | D | +##### Global Series Information -##### Series Information +These fields are present regardless of modality (e.g. MR, CT, PET). | Field | Unit | Comments | Defined By | |--------------------------|------|---------------------|------------| @@ -98,61 +73,29 @@ These fields contain personally identifiable information. By default dcm2niix wi | AcquisitionNumber | | DICOM tag 0020,0012 | D | | ImageComments | | DICOM tag 0020,4000 | D | -##### Philips Sequence Information +##### Global Private Information -Data unique to Philips, including [custom intensity scaling](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3998685/) - -| Field | Unit | Comments | Defined By | -|------------------------------------|------|----------------------------------|------------| -| TriggerDelayTime | s | DICOM tag 0020,9153 or 0018,1060 | D | -| PhilipsRWVSlope | | DICOM tag 0040,9225 | D | -| PhilipsRWVIntercept | | DICOM tag 0040,9224 | D | -| PhilipsRescaleSlope | | DICOM tag 0028,1053 | D | -| PhilipsRescaleIntercept | | DICOM tag 0028,1052 | D | -| PhilipsScaleSlope | | DICOM tag 2005,100E | D | -| UsePhilipsFloatNotDisplayScaling | | dcm2niix option `-p y` or `-p n` | D | -| PartialFourierEnabled | | `YES`, n.b. `PartialFourier` | D | -| PhaseEncodingStepsNoPartialFourier | | | D | -| WaterFatShift | | DICOM tag 2001,1022 | D | - - -##### General Electric Sequence Information - -Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Determining these fields from a DICOM image requires decoding the compressed Protocol Data Block (0025,101B). - - -| Field | Unit | Comments | Defined By | -|---------------------------|------|--------------------------|------------| -| PulseSequenceName | | `epi` or `epiRT` | D | -| InternalPulseSequenceName | | `EPI` or `EPI2` | D | -| PhaseEncodingPolarityGE | | `Unflipped` or `Flipped` | D | - -##### Positron Emission Tomography Isotope Module Attributes +These fields contain personally identifiable information. By default dcm2niix will create anonymized files without these fields. The option `-ba n` will retain private information. -PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.9.2.html). See BIDS [BEP009 Positron Emission Tomography](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). Be aware that many of the fields required by BEP009 are not stored in DICOM or ECAT files. Since dcm2niix is limited to the information provided by the input files, it will generate JSON fields that reflect the meta data stored in the source images. +| Field | Unit | Comments | Defined By | +|------------------------|------|---------------------|------------| +| SeriesInstanceUID | | DICOM tag 0020,000E | D | +| StudyInstanceUID | | DICOM tag 0020,000D | D | +| ReferringPhysicianName | | DICOM tag 0008,0090 | D | +| StudyID | | DICOM tag 0020,0010 | D | +| PatientName | | DICOM tag 0010,0010 | D | +| PatientID | | DICOM tag 0010,0020 | D | +| AccessionNumber | | DICOM tag 0008,0050 | D | +| PatientBirthDate | | DICOM tag 0010,0030 | D | +| PatientWeight | kg | DICOM tag 0010,1030 | D | +| AcquisitionDateTime | | DICOM tag 0008,002A | D | -The term ECAT in the comments suggests that values are defined by the [ECAT7](http://www.turkupetcentre.net/petanalysis/format_image_ecat.html) format. Therefore, these fields will not be populated for DICOM data. +## Modality Fields +These fields are specific to modality (e.g. MR, CT, PET). -| Field | Unit | Comments | Defined By | -|------------------------------|------|-----------------------------|------------| -| Radiopharmaceutical | | DICOM tag 0018,0031 or ECAT | D | -| RadionuclidePositronFraction | f | DICOM tag 0018,1076 | D | -| RadionuclideTotalDose | MBq | DICOM tag 0018,1074 | D | -| RadionuclideHalfLife | s | DICOM tag 0018,1075 | D | -| DoseCalibrationFactor | | DICOM tag 0054,1322 | D | -| IsotopeHalfLife | | ECAT | D | -| Dosage | | ECAT | D | -| ConvolutionKernel | | DICOM tag 0018,1210 | D | -| Units | | DICOM tag 0054,1001 | D | -| DecayCorrection | | DICOM tag 0054,1102 | D | -| AttenuationCorrectionMethod | | DICOM tag 0054,1101 | D | -| ReconstructionMethod | | DICOM tag 0054,1103 | D | -| DecayFactor | | DICOM tag 0054,1321 | D | -| FrameTimesStart | s | DICOM tags 0008,0022 | D | -| FrameDuration | s | DICOM tag 0018,1242 | D | -##### Computerized Tomography +##### Modality Computerized Tomography Fields specific to CT scans. @@ -160,7 +103,7 @@ Fields specific to CT scans. |--------------|------|---------------------|------------| | XRayExposure | mAs | DICOM tag 0018,1152 | D | -##### Magnetic Resonance Imaging +##### Modality Magnetic Resonance Imaging Fields specific to MRI scans. @@ -186,10 +129,10 @@ Fields specific to MRI scans. | PercentSampling | | DICOM tag 0018,0093 | D | | EchoTrainLength | | DICOM tag 0018,0091 | D | | PhaseEncodingSteps | | DICOM tag 0018,0089 | D | -| AcquisitionMatrixPE | | | D | +| AcquisitionMatrixPE | | DICOM tag 0018,9231 (aka PhaseEncodingLines) | D | | ParallelReductionFactorInPlane | | aka `SENSE`, `GRAPPA` | B | | ParallelAcquisitionTechnique | | DICOM tag 0018, 9078 | B | -| ParallelReductionOutOfPlane | | | D | +| ParallelReductionOutOfPlane | | DICOM tag 0018,9155 (for GE, consider Asset R Factors 0043,1083) | D | | EstimatedEffectiveEchoSpacing | s | | D | | EstimatedTotalReadoutTime | s | | D | | EffectiveEchoSpacing | s | | D | @@ -200,80 +143,140 @@ Fields specific to MRI scans. | PhaseEncodingDirection | | When polarity known | B | | SliceTiming | s | | B | | ImageOrientationPatientDICOM | | DICOM tag 0020,0037 | D | -| InPlanePhaseEncodingDirectionDICOM | | | D | +| InPlanePhaseEncodingDirectionDICOM | | DICOM tag 0018,1312 | D | | ReceiveCoilName | | DICOM tag 0018,1250 | B | -##### Arterial Spin Labeling +##### Modality Positron Emission Tomography Isotope Module Attributes + +PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.9.2.html). See BIDS [BEP009 Positron Emission Tomography](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). Be aware that many of the fields required by BEP009 are not stored in DICOM or ECAT files. Since dcm2niix is limited to the information provided by the input files, it will generate JSON fields that reflect the meta data stored in the source images. + +The term ECAT in the comments suggests that values are defined by the [ECAT7](http://www.turkupetcentre.net/petanalysis/format_image_ecat.html) format. Therefore, these fields will not be populated for DICOM data. + +| Field | Unit | Comments | Defined By | +|------------------------------|------|-----------------------------|------------| +| Radiopharmaceutical | | DICOM tag 0018,0031 or ECAT | D | +| RadionuclidePositronFraction | f | DICOM tag 0018,1076 | D | +| RadionuclideTotalDose | MBq | DICOM tag 0018,1074 | D | +| RadionuclideHalfLife | s | DICOM tag 0018,1075 | D | +| DoseCalibrationFactor | | DICOM tag 0054,1322 | D | +| IsotopeHalfLife | | ECAT | D | +| Dosage | | ECAT | D | +| ConvolutionKernel | | DICOM tag 0018,1210 | D | +| Units | | DICOM tag 0054,1001 | D | +| DecayCorrection | | DICOM tag 0054,1102 | D | +| AttenuationCorrectionMethod | | DICOM tag 0054,1101 | D | +| ReconstructionMethod | | DICOM tag 0054,1103 | D | +| DecayFactor | | DICOM tag 0054,1321 | D | +| FrameTimesStart | s | DICOM tags 0008,0022 | D | +| FrameDuration | s | DICOM tag 0018,1242 | D | + +## Manufacturer Fields + +These fields are specific to manufacturer (e.g. GE, Philips, Siemens). For further manufacturer details see: + + - [Canon (Toshiba)](https://github.com/rordenlab/dcm2niix/tree/master/Canon) + - [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE) + - [Philips](https://github.com/rordenlab/dcm2niix/tree/master/Philips) + - [Siemens](https://github.com/rordenlab/dcm2niix/tree/master/Siemens) + - [UIH](https://github.com/rordenlab/dcm2niix/tree/master/UIH) + +##### Manufacturer General Electric + +Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Determining these fields from a DICOM image requires decoding the compressed Protocol Data Block (0025,101B). + +| Field | Unit | Comments | Defined By | +|---------------------------|------|--------------------------|------------| +| PulseSequenceName | | `epi` or `epiRT` | D | +| InternalPulseSequenceName | | `EPI` or `EPI2` | D | +| PhaseEncodingPolarityGE | | `Unflipped` or `Flipped` | D | + +##### Manufacturer Philips + +Data unique to Philips, including [custom intensity scaling](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3998685/) + +| Field | Unit | Comments | Defined By | +|------------------------------------|------|----------------------------------|------------| +| TriggerDelayTime | s | DICOM tag 0020,9153 or 0018,1060 | D | +| PhilipsRWVSlope | | DICOM tag 0040,9225 | D | +| PhilipsRWVIntercept | | DICOM tag 0040,9224 | D | +| PhilipsRescaleSlope | | DICOM tag 0028,1053 | D | +| PhilipsRescaleIntercept | | DICOM tag 0028,1052 | D | +| PhilipsScaleSlope | | DICOM tag 2005,100E | D | +| UsePhilipsFloatNotDisplayScaling | | dcm2niix option `-p y` or `-p n` | D | +| PartialFourierEnabled | | DICOM tag 0018,9081, `YES` | D | +| PhaseEncodingStepsNoPartialFourier | | DICOM tag 0018,9231 | D | +| WaterFatShift | | DICOM tag 2001,1022 | D | + +##### Manufacturer Siemens (Arterial Spin Labeling) + See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). All these values are specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). | Field | Unit | Comments | Defined By | |---------------------------------|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| -| LabelOffset | | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | -| PostLabelDelay | s | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org), [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | B | -| PostInversionDelay | s | 2D [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | B | -| NumRFBlocks | | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | -| RFGap | | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | B | -| MeanGzx10 | | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | B | -| PhiAdjust | | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | B | -| InversionTime | s | 2D pASL product (`ep2d_pasl`, `tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| SaturationStopTime | s | 2D pASL product (`ep2d_pasl`) | B | -| BolusDuration | s | 2D pASL product (`tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| MeanGzx10 | | 3D pCASL [tgse_pcasl](http://www.loft-lab.org) | B | -| TagRFFlipAngle | deg | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | -| TagRFDuration | s | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | -| TagRFSeparation | | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | -| MeanTagGradient | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagGradientAmplitude | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagDuration | s | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| MaximumT1Opt | | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | B | -| InitialPostLabelDelay | s | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| sWipMemBlockAdFree* | multiple values | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagPlaneDThickness | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagPlaneUlShape | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagPlaneSPositionDTra | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| TagPlaneSNormalDTra | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | B | -| Tag* (if not explicitly listed) | One value for each | [Post-label delays](https://fsl.fmrib.ox.ac.uk/fslcourse/lectures/practicals/ASLpractical/index.html), 3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`jw_tgse_VEPCASL`) | B | - - -##### Magnetic Resonance Imaging (Siemens V*) - -Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, -Trio, Skyra, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). - -| Field | Unit | Comments | Defined By | -|------------------------------|------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| -| EchoTime1 | s | For Fieldmaps created with two echo times | D | -| EchoTime2 | s | For Fieldmaps created with two echo times | D | -| PartialFourier | f | Partial fourier fraction, e.g. image with 6/8 pF will report `0.75`. Different tags depending on manufacturer | B | -| Interpolation2D | | If present, slices interpolated within plane | D | -| Interpolation3D | | If present, image interpolated in all spatial dimensions | D | -| BaseResolution | integer | # of acquisition lines? | D | -| ShimSetting | DAC for 1st order terms, mA for 2nd order terms | The 1st 3 values are the 1st order shims from sGRADSPEC.lOffset{X,Y,Z}. The next 5 are 2nd order shims from sGRADSPEC.alShimCurrent[0-4]. Converting to the first order terms to uT/m may be possible using the "gradient sensitivities". Converting the second order terms to uT/m^2 requires information not stored in the DICOM header, but they are fixed for a given shim hardware configuration. | D | -| DiffusionScheme | | `Monopolar` or `Bipolar` | D | -| DelayTime | s | | D | -| TxRefAmp | V | | D | -| PhaseResolution | f | | D | -| PhaseOversampling | | | D | -| VendorReportedEchoSpacing | | | B | -| ReceiveCoilActiveElements | | Given as groups of channels, instead of individual ones, e.g. "HC1-7" for HeadNeck\_64. | D | -| CoilString | | May or may not match `ReceiveCoilName` | D | -| PulseSequenceDetails | | pulse sequence label, e.g. "%SiemensSeq%\\\\tgse\_pasl" | D | -| FmriExternalInfo | | | D | -| WipMemBlock | | | D | -| ProtocolName | | Check SeriesDescription - they might be switched around | D | -| RefLinesPE | | # of reference lines in the phase encoding direction for acceleration (GRAPPA) | D | -| ConsistencyInfo | | The more complete software version, e.g. VE11C or VE11E instead of just VE11. | D | -| DwellTime | | DICOM tag 0019,1018 | B | -| BandwidthPerPixelPhaseEncode | [Hz](https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=fsl;d2c47aa4.1606) | | D | - -##### Magnetic Resonance Imaging (Siemens XA) +| LabelOffset | | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | D | +| PostLabelDelay | s | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org), [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | D | +| PostInversionDelay | s | 2D [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | D | +| NumRFBlocks | | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | D | +| RFGap | | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | D | +| MeanGzx10 | | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | D | +| PhiAdjust | | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | D | +| InversionTime | s | 2D pASL product (`ep2d_pasl`, `tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | +| SaturationStopTime | s | 2D pASL product (`ep2d_pasl`) | D | +| BolusDuration | s | 2D pASL product (`tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | +| MeanGzx10 | | 3D pCASL [tgse_pcasl](http://www.loft-lab.org) | D | +| TagRFFlipAngle | deg | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | D | +| TagRFDuration | s | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | D | +| TagRFSeparation | | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | D | +| MeanTagGradient | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | +| TagGradientAmplitude | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | +| TagDuration | s | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | +| MaximumT1Opt | | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | D | +| InitialPostLabelDelay | s | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | +| sWipMemBlockAdFree* | multiple values | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | +| TagPlaneDThickness | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | +| TagPlaneUlShape | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | +| TagPlaneSPositionDTra | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | +| TagPlaneSNormalDTra | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | +| Tag* (if not explicitly listed) | One value for each | [Post-label delays](https://fsl.fmrib.ox.ac.uk/fslcourse/lectures/practicals/ASLpractical/index.html), 3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`jw_tgse_VEPCASL`) | D | + +##### Manufacturer Siemens Magnetic Resonance Imaging (V*) + +Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Skyra, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). + +| Field | Unit | Comments | Defined By | +|------------------------------|-------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| +| EchoTime1 | s | For Fieldmaps created with two echo times | B | +| EchoTime2 | s | For Fieldmaps created with two echo times | B | +| PartialFourier | f | Partial fourier fraction, e.g. image with 6/8 pF will report `0.75`. Different tags depending on manufacturer | B | +| Interpolation2D | | If present, slices interpolated within plane | D | +| Interpolation3D | | If present, image interpolated in all spatial dimensions | D | +| BaseResolution | integer | # of acquisition lines? | D | +| ShimSetting | DAC for 1st order terms, mA for 2nd order terms | The 1st 3 values are the 1st order shims from sGRADSPEC.lOffset{X,Y,Z}. The next 5 are 2nd order shims from sGRADSPEC.alShimCurrent[0-4]. Converting to the first order terms to uT/m may be possible using the "gradient sensitivities". Converting the second order terms to uT/m^2 requires information not stored in the DICOM header, but they are fixed for a given shim hardware configuration. | D | +| DiffusionScheme | | `Monopolar` or `Bipolar` | D | +| DelayTime | s | | D | +| TxRefAmp | V | | D | +| PhaseResolution | f | | D | +| PhaseOversampling | | | D | +| VendorReportedEchoSpacing | | | B | +| ReceiveCoilActiveElements | | Given as groups of channels, instead of individual ones, e.g. "HC1-7" for HeadNeck\_64. | B | +| CoilString | | May or may not match `ReceiveCoilName` | D | +| PulseSequenceDetails | | pulse sequence label, e.g. "%SiemensSeq%\\\\tgse\_pasl" | D | +| FmriExternalInfo | | | D | +| WipMemBlock | | | D | +| ProtocolName | | Check SeriesDescription - they might be switched around | D | +| RefLinesPE | | # of reference lines in the phase encoding direction for acceleration (GRAPPA) | D | +| ConsistencyInfo | | The more complete software version, e.g. VE11C or VE11E instead of just VE11. | D | +| DwellTime | | DICOM tag 0019,1018 | B | +| BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0019,1028 | D | + +##### Manufacturer Siemens Magnetic Resonance Imaging (XA) Fields specific to Siemens XA-series MRI systems (Sola, Vida). | Field | Unit | Comments | Defined By | |------------------------------|------|---------------------|------------| -| ReceiveCoilActiveElements | | DICOM tag 0021,114F | D | +| ReceiveCoilActiveElements | | DICOM tag 0021,114F | B | | BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0021,1153 | D | From 88fb50270eaff4d7b6eac243181a488e318a47f1 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Mon, 14 Jun 2021 13:41:36 -0400 Subject: [PATCH 44/95] Add BIDS field PartialFourierDirection --- BIDS/README.md | 30 ++++++++++++++++-------------- console/nii_dicom.cpp | 14 ++++++++++++++ console/nii_dicom.h | 9 ++++++++- console/nii_dicom_batch.cpp | 8 ++++++++ 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index 5fc16603..48123b75 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -64,10 +64,10 @@ These fields are present regardless of modality (e.g. MR, CT, PET). | SoftwareVersions | | DICOM tag 0020,1020 | B | | SeriesDescription | | DICOM tag 0008,103E | D | | ProtocolName | | DICOM tag 0018,1030 | D | -| ScanningSequence | | DICOM tag 0018,0020 | D | -| SequenceVariant | | DICOM tag 0018,0021 | D | -| ScanOptions | | DICOM tag 0018,0022 | D | -| SequenceName | | DICOM tag 0018,0024 | D | +| ScanningSequence | | DICOM tag 0018,0020 | B | +| SequenceVariant | | DICOM tag 0018,0021 | B | +| ScanOptions | | DICOM tag 0018,0022 | B | +| SequenceName | | DICOM tag 0018,0024 | B | | ImageType | list | DICOM tag 0008,0008 | D | | AcquisitionTime | | DICOM tag 0008,0032 | D | | AcquisitionNumber | | DICOM tag 0020,0012 | D | @@ -110,8 +110,8 @@ Fields specific to MRI scans. | Field | Unit | Comments | Defined By | |------------------------------------|------|-----------------------------------------------------------------------------------------|------------| | MagneticFieldStrength | T | DICOM tag 0018,0087 | B | -| MRAcquisitionType | | DICOM tag 0018,0023 | | -| Units | | `Hz` (field maps) | B | +| MRAcquisitionType | | DICOM tag 0018,0023 | B | +| Units | | `Hz`,`rad` (field maps) | B | | SliceThickness | mm | [nb](http://dclunie.blogspot.com/2013/10/how-thick-am-i-sad-story-of-lonely-slice.html) | D | | SpacingBetweenSlices | mm | [nb](http://dclunie.blogspot.com/2013/10/how-thick-am-i-sad-story-of-lonely-slice.html) | D | | SAR | | DICOM tag 0018,1316 | D | @@ -119,7 +119,7 @@ Fields specific to MRI scans. | EchoNumber | | Only multi-echo series | D | | EchoTime | s | DICOM tag 0018,0081 | D | | RepetitionTime | s | DICOM tag 0018,0080 | D | -| RepetitionTimeExcitation | s | | D | +| RepetitionTimeExcitation | s | DICOM tag 0018, 0080 for some manufacturers | B | | RepetitionTimeInversion | s | | D | | InversionTime | s | DICOM tag 0018,0082 | D | | ImagingFrequency | MHz | DICOM tag 0018,0084 | D | @@ -130,9 +130,10 @@ Fields specific to MRI scans. | EchoTrainLength | | DICOM tag 0018,0091 | D | | PhaseEncodingSteps | | DICOM tag 0018,0089 | D | | AcquisitionMatrixPE | | DICOM tag 0018,9231 (aka PhaseEncodingLines) | D | -| ParallelReductionFactorInPlane | | aka `SENSE`, `GRAPPA` | B | -| ParallelAcquisitionTechnique | | DICOM tag 0018, 9078 | B | -| ParallelReductionOutOfPlane | | DICOM tag 0018,9155 (for GE, consider Asset R Factors 0043,1083) | D | +| ParallelReductionFactorInPlane | | | B | +| ParallelAcquisitionTechnique | | DICOM tag 0018, 9078, aka `SENSE`, `GRAPPA` | B | +| ParallelReductionOutOfPlane | | DICOM tag 0018,9155 | D | +| PartialFourierDirection | | DICOM tag 0018,9036 | B | | EstimatedEffectiveEchoSpacing | s | | D | | EstimatedTotalReadoutTime | s | | D | | EffectiveEchoSpacing | s | | D | @@ -145,7 +146,7 @@ Fields specific to MRI scans. | ImageOrientationPatientDICOM | | DICOM tag 0020,0037 | D | | InPlanePhaseEncodingDirectionDICOM | | DICOM tag 0018,1312 | D | | ReceiveCoilName | | DICOM tag 0018,1250 | B | - + ##### Modality Positron Emission Tomography Isotope Module Attributes PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.9.2.html). See BIDS [BEP009 Positron Emission Tomography](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). Be aware that many of the fields required by BEP009 are not stored in DICOM or ECAT files. Since dcm2niix is limited to the information provided by the input files, it will generate JSON fields that reflect the meta data stored in the source images. @@ -211,6 +212,8 @@ Data unique to Philips, including [custom intensity scaling](https://www.ncbi.nl See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). All these values are specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). +Note that [many of the fields listed by BIDS](https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#_asljson-file +) do not have DICOM equivalents. | Field | Unit | Comments | Defined By | |---------------------------------|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| @@ -254,14 +257,14 @@ Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio | BaseResolution | integer | # of acquisition lines? | D | | ShimSetting | DAC for 1st order terms, mA for 2nd order terms | The 1st 3 values are the 1st order shims from sGRADSPEC.lOffset{X,Y,Z}. The next 5 are 2nd order shims from sGRADSPEC.alShimCurrent[0-4]. Converting to the first order terms to uT/m may be possible using the "gradient sensitivities". Converting the second order terms to uT/m^2 requires information not stored in the DICOM header, but they are fixed for a given shim hardware configuration. | D | | DiffusionScheme | | `Monopolar` or `Bipolar` | D | -| DelayTime | s | | D | +| DelayTime | s | Pause between EPI volumes, where TR is longer than required by TA (`sparse` imaging) | D | | TxRefAmp | V | | D | | PhaseResolution | f | | D | | PhaseOversampling | | | D | | VendorReportedEchoSpacing | | | B | | ReceiveCoilActiveElements | | Given as groups of channels, instead of individual ones, e.g. "HC1-7" for HeadNeck\_64. | B | | CoilString | | May or may not match `ReceiveCoilName` | D | -| PulseSequenceDetails | | pulse sequence label, e.g. "%SiemensSeq%\\\\tgse\_pasl" | D | +| PulseSequenceDetails | | pulse sequence label, e.g. "%SiemensSeq%\\\\tgse\_pasl" | B | | FmriExternalInfo | | | D | | WipMemBlock | | | D | | ProtocolName | | Check SeriesDescription - they might be switched around | D | @@ -279,4 +282,3 @@ Fields specific to Siemens XA-series MRI systems (Sola, Vida). | ReceiveCoilActiveElements | | DICOM tag 0021,114F | B | | BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0021,1153 | D | - diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index a8bf5815..f32c0f71 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -850,6 +850,7 @@ struct TDICOMdata clear_dicom_data() { d.maxEchoNumGE = -1; d.epiVersionGE = -1; d.internalepiVersionGE = -1; + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_UNKNOWN; d.interp3D = -1; for (int i = 0; i < kMaxOverlay; i++) d.overlayStart[i] = 0; @@ -4255,6 +4256,7 @@ struct TDICOMdata readDICOMx(char * fname, struct TDCMprefs* prefs, struct TDTI4 #define kInversionRecovery 0x0018+uint32_t(0x9009 << 16 ) //'CS' 'YES'/'NO' #define kEchoPlanarPulseSequence 0x0018+uint32_t(0x9018 << 16 ) //'CS' 'YES'/'NO' #define kRectilinearPhaseEncodeReordering 0x0018+uint32_t(0x9034 << 16) //'CS' 'REVERSE_LINEAR'/'LINEAR' +#define kPartialFourierDirection 0x0018+uint32_t(0x9036 << 16) //'CS' #define kParallelReductionFactorInPlane 0x0018+uint32_t(0x9069<< 16 ) //FD #define kAcquisitionDuration 0x0018+uint32_t(0x9073<< 16 ) //FD //#define kFrameAcquisitionDateTime 0x0018+uint32_t(0x9074<< 16 ) //DT "20181019212528.232500" @@ -5380,6 +5382,18 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); if (toupper(buffer[lPos]) == 'R') d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; break; } + case kPartialFourierDirection : { //'CS' PHASE FREQUENCY SLICE_SELECT COMBINATION + if (lLength < 2) break; + if (toupper(buffer[lPos]) == 'P') + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_PHASE; + if (toupper(buffer[lPos]) == 'F') + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_FREQUENCY; + if (toupper(buffer[lPos]) == 'S') + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT; + if (toupper(buffer[lPos]) == 'C') + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_COMBINATION; + break; } + case kParallelReductionFactorInPlane: if (d.manufacturer == kMANUFACTURER_SIEMENS) break; d.accelFactPE = dcmFloatDouble(lLength, &buffer[lPos],d.isLittleEndian); diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 3579154a..3cb68746 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -77,6 +77,13 @@ static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI dire #define kMODALITY_PT 4 #define kMODALITY_US 5 +// PartialFourierDirection 0018,9036 +#define kPARTIAL_FOURIER_DIRECTION_UNKNOWN 0 +#define kPARTIAL_FOURIER_DIRECTION_PHASE 1 +#define kPARTIAL_FOURIER_DIRECTION_FREQUENCY 2 +#define kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT 3 +#define kPARTIAL_FOURIER_DIRECTION_COMBINATION 4 + //GE phase encoding #define kGE_PHASE_ENCODING_POLARITY_UNKNOWN -1 #define kGE_PHASE_ENCODING_POLARITY_UNFLIPPED 0 @@ -181,7 +188,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; int xyzDim[5]; uint32_t coilCrc, seriesUidCrc, instanceUidCrc; int overlayStart[kMaxOverlay]; - int interp3D, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; + int partialFourierDirection, interp3D, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; float groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 99e623a8..03237565 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1518,6 +1518,14 @@ tse3d: T2*/ json_Float(fp, "\t\"PercentSampling\": %g,\n", d.percentSampling); 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); //0018,0091 Combination of partial fourier and in-plane parallel imaging + if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_PHASE) + fprintf(fp, "\t\"PartialFourierDirection\": \"PHASE\",\n" ); + if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_FREQUENCY) + fprintf(fp, "\t\"PartialFourierDirection\": \"FREQUENCY\",\n" ); + if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT) + fprintf(fp, "\t\"PartialFourierDirection\": \"SLICE_SELECT\",\n" ); + if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_COMBINATION) + fprintf(fp, "\t\"PartialFourierDirection\": \"COMBINATION\",\n" ); if ((d.phaseEncodingSteps > 0) && (d.isPartialFourier) && (d.manufacturer == kMANUFACTURER_PHILIPS)) { //issue 377 fprintf(fp, "\t\"PartialFourierEnabled\": \"YES\",\n" ); From a6c710645336ec31de3152d871291b483a12662b Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 15 Jun 2021 14:17:51 -0400 Subject: [PATCH 45/95] Add GE ASL BIDS fields --- BIDS/README.md | 4 ++++ console/nii_dicom.cpp | 31 +++++++++++++++++++++++++++++++ console/nii_dicom.h | 10 ++++++++-- console/nii_dicom_batch.cpp | 30 ++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 2 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index 48123b75..302cf6aa 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -190,6 +190,10 @@ Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Deter | PulseSequenceName | | `epi` or `epiRT` | D | | InternalPulseSequenceName | | `EPI` or `EPI2` | D | | PhaseEncodingPolarityGE | | `Unflipped` or `Flipped` | D | +| ASLContrastTechnique | | DICOM tag 0043,10A3 | D | +| ASLLabelingTechnique | | DICOM tag 0043,10A4 | D | +| LabelingDuration | s | DICOM tag 0043,10A5 | B | +| PostLabelingDelay | s | DICOM tag 0018,0082 (TI) | B | ##### Manufacturer Philips diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index f32c0f71..a3050fbf 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -850,6 +850,8 @@ struct TDICOMdata clear_dicom_data() { d.maxEchoNumGE = -1; d.epiVersionGE = -1; d.internalepiVersionGE = -1; + d.durationLabelPulseGE = -1; + d.aslFlagsGE = 0; d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_UNKNOWN; d.interp3D = -1; for (int i = 0; i < kMaxOverlay; i++) @@ -4367,6 +4369,9 @@ const uint32_t kEffectiveTE = 0x0018+ (0x9082 << 16); #define kDiffusion_bValueGE 0x0043+(0x1039 << 16 ) //IS dicm2nii's SlopInt_6_9 #define kEpiRTGroupDelayGE 0x0043+(0x107C << 16 ) //FL #define kAssetRFactorsGE 0x0043+(0x1083 << 16 ) //DS +#define kASLContrastTechniqueGE 0x0043+(0x10A3 << 16 ) //CS +#define kASLLabelingTechniqueGE 0x0043+(0x10A4 << 16 ) //LO +#define kDurationLabelPulseGE 0x0043+(0x10A5 << 16 ) //IS #define kMultiBandGE 0x0043+(0x10B6 << 16 ) //LO #define kAcquisitionMatrixText 0x0051+(0x100B << 16 ) //LO #define kCoilSiemens 0x0051+(0x100F << 16 ) @@ -6595,6 +6600,32 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); d.accelFactOOP = 1.0f / PhaseSlice[2]; break; } + case kASLContrastTechniqueGE: { //CS + if (d.manufacturer != kMANUFACTURER_GE) break; + char st[kDICOMStr]; + //aslFlagsGE + dcmStr(lLength, &buffer[lPos], st); + if (strstr(st, "CONTINUOUS") != NULL) + d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_CONTINUOUS); + else if (strstr(st, "PSEUDOCONTINUOUS") != NULL) + d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_PSEUDOCONTINUOUS); + break; + } + case kASLLabelingTechniqueGE: { //LO issue427GE + if (d.manufacturer != kMANUFACTURER_GE) break; + char st[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], st); + if (strstr(st, "3D continuous") != NULL) + d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_3DCASL); + if (strstr(st, "3D pulsed continuous") != NULL) + d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_3DPCASL); + break; + } + case kDurationLabelPulseGE: { //IS + if (d.manufacturer != kMANUFACTURER_GE) break; + d.durationLabelPulseGE = dcmStrInt(lLength, &buffer[lPos]); + break; + } case kMultiBandGE: { //LO issue427GE if (d.manufacturer != kMANUFACTURER_GE) break; //LO array: Value 1 = Multiband factor, Value 2 = Slice FOV Shift Factor, Value 3 = Calibration method diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 3cb68746..b89321fd 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -108,7 +108,13 @@ static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI dire #define kEXIT_RENAME_ERROR 9 #define kEXIT_INCOMPLETE_VOLUMES_FOUND 10 //issue 515 - +//0043,10A3 ---: PSEUDOCONTINUOUS +//0043,10A4 ---: 3D pulsed continuous ASL technique +#define kASL_FLAG_GE_3DPCASL 1 +#define kASL_FLAG_GE_3DCASL 2 +#define kASL_FLAG_GE_PSEUDOCONTINUOUS 4 +#define kASL_FLAG_GE_CONTINUOUS 8 + static const int kSliceOrientUnknown = 0; static const int kSliceOrientTra = 1; static const int kSliceOrientSag = 2; @@ -188,7 +194,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; int xyzDim[5]; uint32_t coilCrc, seriesUidCrc, instanceUidCrc; int overlayStart[kMaxOverlay]; - int partialFourierDirection, interp3D, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; + int partialFourierDirection, interp3D, aslFlagsGE, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; float groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 03237565..526eeed5 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -646,6 +646,22 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, csaAscii->phaseEncodingLines = readKey(keyStrLns, keyPos, csaLengthTrim); char keyStrUcImg[] = "sSliceArray.ucImageNumb"; csaAscii->existUcImageNumb = readKey(keyStrUcImg, keyPos, csaLengthTrim); + /* + //TODO! + + int combineMode = -1; + char keyStrCombineMode[] = "ucCoilCombineMode"; + combineMode = readKeyN1(keyStrCombineMode, keyPos, csaLengthTrim); + //BIDS CoilCombinationMethod <- Siemens 'Coil Combine Mode' CSA ucCoilCombineMode 1 = Sum of Squares, 2 = Adaptive Combine, + printf("CoilCombineMode %d\n", combineMode); + //BIDS MatrixCoilMode <- Siemens 'PAT mode' CSA ucPATMode ?? 1?= "SENSE" 2= "GRAPPA", + //sPat.ucPATMode = 2 + int patMode = -1; + char keyStrPATMode[] = "sPat.ucPATMode"; //n.b. field set even if PAT not enabled, e.g. will list SENSE for a R-factor of 1 + patMode = readKeyN1(keyStrPATMode, keyPos, csaLengthTrim); + printf("PATMODE %d\n", patMode); + + */ char keyStrUcMode[] = "sSliceArray.ucMode"; csaAscii->ucMode = readKeyN1(keyStrUcMode, keyPos, csaLengthTrim); char keyStrBase[] = "sKSpace.lBaseResolution"; @@ -1512,6 +1528,20 @@ tse3d: T2*/ //printf("PhaseLines=%d EchoTrainLength=%d SENSE=%g\n", d.phaseEncodingLines, d.echoTrainLength, d.accelFactPE); //n.b. we can not distinguish pF from SENSE/GRAPPA for UIH } #endif + //GE ASL specific tags + + if (d.aslFlagsGE & kASL_FLAG_GE_CONTINUOUS) + fprintf(fp, "\t\"ASLContrastTechnique\": \"CONTINUOUS\",\n" ); + if (d.aslFlagsGE & kASL_FLAG_GE_PSEUDOCONTINUOUS) + fprintf(fp, "\t\"ASLContrastTechnique\": \"PSEUDOCONTINUOUS\",\n" ); + if (d.aslFlagsGE & kASL_FLAG_GE_3DPCASL) + fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D pulsed continuous ASL technique\",\n" ); + if (d.aslFlagsGE & kASL_FLAG_GE_3DCASL) + fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D continuous ASL technique\",\n" ); + if (d.durationLabelPulseGE > 0) { + json_Float(fp, "\t\"LabelingDuration\": %g,\n", d.durationLabelPulseGE / 1000.0); + json_Float(fp, "\t\"PostLabelingDelay\": %g,\n", d.TI / 1000.0); //For GE ASL: InversionTime -> Post-label delay + } if ((d.CSA.multiBandFactor > 1) && (d.modality == kMODALITY_MR)) //AccelFactorSlice fprintf(fp, "\t\"MultibandAccelerationFactor\": %d,\n", d.CSA.multiBandFactor); json_Float(fp, "\t\"PercentPhaseFOV\": %g,\n", d.phaseFieldofView); From fd4a3b1b4b884d2496394b846b08557cfcd920fe Mon Sep 17 00:00:00 2001 From: Rob Reid Date: Tue, 15 Jun 2021 16:45:08 -0500 Subject: [PATCH 46/95] Adds a script to extract units from README.md --- BIDS/extract_units.py | 85 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100755 BIDS/extract_units.py diff --git a/BIDS/extract_units.py b/BIDS/extract_units.py new file mode 100755 index 00000000..234d82a0 --- /dev/null +++ b/BIDS/extract_units.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +"""extract_units.py - extract BIDS/README.md's units as json + +Usage: + extract_units.py [-e EXISTING -o OUT] [MD] + extract_units.py (-h|--help|--version) + +Arguments: + MD: A Markdown input file including tables with Field and Unit as the + first two columns. + [default: README.md] + +Options: + -h --help Show this message and exit. + --version Show version and exit. + -e EXISTING --ex=EXISTING Extract units for all the fields, and only the + fields in EXISTING, a BIDS file, + and write the output to + EXISTING.replace('.json', '_units.json') + instead of stdout. + -o OUT --out=OUT If given, save the output to this filename. + (Overrides the implicit destination of -e.) +""" +from __future__ import print_function +try: + import json_tricks as json +except ImportError: + try: + import simplejson as json + except ImportError: + # Not really compatible with json_tricks because it does not support + # primitives=True + import json +import sys + +# Please use semantic versioning (API.feature.bugfix), http://semver.org/ +__version__ = '0.0.0' + + +def extract_units(mdfn): + units = {} + intable = False + with open(mdfn) as md: + for line in md: + if line.startswith('|'): + parts = [s.strip() for s in line.split('|')[1:-1]] + if parts[:2] == ['Field', 'Unit']: + intable = True + elif intable and parts[1] and not parts[1].startswith('-'): + units[parts[0]] = parts[1] + else: + intable = False + return units + + +def main(mdfn, existing=None, outfn=None): + units = extract_units(mdfn) + if existing: + with open(existing) as f: # Can't count on having json_tricks. + used = json.load(f) + units = {k: v for k, v in units.items() if k in used} + if not outfn: + outfn = existing.replace('.json', '_units.json') + outtext = json.dumps(units, indent=2, sort_keys=True, + separators=(', ', ': ')) + if outfn: + with open(outfn, 'w') as outf: + outf.write(outtext + "\n") + else: + print(outtext) + return units + + +if __name__ == '__main__': + from docopt import docopt + + args = docopt(__doc__, version=__version__) + #print(args) + + # https://github.com/docopt/docopt/issues/214 has been open for + # almost 7 years, so it looks like docopt isn't getting default + # positional args. + output = main(args['MD'] or 'README.md', + args.get('--ex'), args.get('--out')) + sys.exit(0) From 9af237f141f5264f3f341a8d1f7f5b22782e5b53 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 16 Jun 2021 09:30:16 -0400 Subject: [PATCH 47/95] Experimental BEP001 support (https://github.com/bids-standard/bids-specification/pull/681) --- console/nii_dicom.cpp | 14 +++++++++- console/nii_dicom.h | 4 +-- console/nii_dicom_batch.cpp | 53 ++++++++++++++++++++++--------------- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index a3050fbf..c447b76a 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -853,6 +853,7 @@ struct TDICOMdata clear_dicom_data() { d.durationLabelPulseGE = -1; d.aslFlagsGE = 0; d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_UNKNOWN; + d.mtState = -1; d.interp3D = -1; for (int i = 0; i < kMaxOverlay; i++) d.overlayStart[i] = 0; @@ -4257,6 +4258,8 @@ struct TDICOMdata readDICOMx(char * fname, struct TDCMprefs* prefs, struct TDTI4 #define kPatientOrient 0x0018+(0x5100<< 16 ) //0018,5100. patient orientation - 'HFS' #define kInversionRecovery 0x0018+uint32_t(0x9009 << 16 ) //'CS' 'YES'/'NO' #define kEchoPlanarPulseSequence 0x0018+uint32_t(0x9018 << 16 ) //'CS' 'YES'/'NO' +#define kMagnetizationTransferAttribute 0x0018+uint32_t(0x9020 << 16 ) //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE' + #define kRectilinearPhaseEncodeReordering 0x0018+uint32_t(0x9034 << 16) //'CS' 'REVERSE_LINEAR'/'LINEAR' #define kPartialFourierDirection 0x0018+uint32_t(0x9036 << 16) //'CS' #define kParallelReductionFactorInPlane 0x0018+uint32_t(0x9069<< 16 ) //FD @@ -5379,7 +5382,16 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); if (toupper(buffer[lPos]) == 'Y') d.isEPI = true; break; - case kRectilinearPhaseEncodeReordering : { //'CS' [REVERSE_LINEAR],[LINEAR],[CENTRIC],[REVERSE_CENTRIC] + case kMagnetizationTransferAttribute : //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE' + if (lLength < 2) break; //https://github.com/bids-standard/bids-specification/pull/681 + if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos]) == 'F')) // OFF_RESONANCE + d.mtState = 1; //TRUE + if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos]) == 'N')) // ON_RESONANCE and NONE + d.mtState = 0; //FALSE + if ((toupper(buffer[lPos]) == 'N') && (toupper(buffer[lPos]) == 'O')) // ON_RESONANCE and NONE + d.mtState = 0; //FALSE + break; + case kRectilinearPhaseEncodeReordering : { //'CS' [REVERSE_LINEAR],[LINEAR],[CENTRIC],[REVERSE_CENTRIC] if (d.manufacturer != kMANUFACTURER_GE) break; //only found in GE software beginning with RX27 if (lLength < 2) break; if (toupper(buffer[lPos]) == 'L') diff --git a/console/nii_dicom.h b/console/nii_dicom.h index b89321fd..67d9109a 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.20210531" +#define kDCMdate "v1.0.20210616" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic @@ -194,7 +194,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; int xyzDim[5]; uint32_t coilCrc, seriesUidCrc, instanceUidCrc; int overlayStart[kMaxOverlay]; - int partialFourierDirection, interp3D, aslFlagsGE, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; + int mtState, partialFourierDirection, interp3D, aslFlagsGE, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; float groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 526eeed5..154efb48 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -570,7 +570,7 @@ int phoenixOffsetCSASeriesHeader(unsigned char *buff, int lLength) { typedef struct { float TE0, TE1, delayTimeInTR, phaseOversampling, phaseResolution, txRefAmp; int phaseEncodingLines, existUcImageNumb, ucMode, baseResolution, interp, partialFourier,echoSpacing, - difBipolar, parallelReductionFactorInPlane, refLinesPE; + difBipolar, parallelReductionFactorInPlane, refLinesPE, combineMode, patMode; float alFree[kMaxWipFree] ; float adFree[kMaxWipFree]; float alTI[kMaxWipFree]; @@ -596,6 +596,8 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, csaAscii->difBipolar = 0; //0=not assigned,1=bipolar,2=monopolar csaAscii->parallelReductionFactorInPlane = 0; csaAscii->refLinesPE = 0; + csaAscii->combineMode = 0; + csaAscii->patMode = 0; for (int i = 0; i < 8; i++) shimSetting[i] = 0.0; strcpy(coilID, ""); @@ -646,22 +648,6 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, csaAscii->phaseEncodingLines = readKey(keyStrLns, keyPos, csaLengthTrim); char keyStrUcImg[] = "sSliceArray.ucImageNumb"; csaAscii->existUcImageNumb = readKey(keyStrUcImg, keyPos, csaLengthTrim); - /* - //TODO! - - int combineMode = -1; - char keyStrCombineMode[] = "ucCoilCombineMode"; - combineMode = readKeyN1(keyStrCombineMode, keyPos, csaLengthTrim); - //BIDS CoilCombinationMethod <- Siemens 'Coil Combine Mode' CSA ucCoilCombineMode 1 = Sum of Squares, 2 = Adaptive Combine, - printf("CoilCombineMode %d\n", combineMode); - //BIDS MatrixCoilMode <- Siemens 'PAT mode' CSA ucPATMode ?? 1?= "SENSE" 2= "GRAPPA", - //sPat.ucPATMode = 2 - int patMode = -1; - char keyStrPATMode[] = "sPat.ucPATMode"; //n.b. field set even if PAT not enabled, e.g. will list SENSE for a R-factor of 1 - patMode = readKeyN1(keyStrPATMode, keyPos, csaLengthTrim); - printf("PATMODE %d\n", patMode); - - */ char keyStrUcMode[] = "sSliceArray.ucMode"; csaAscii->ucMode = readKeyN1(keyStrUcMode, keyPos, csaLengthTrim); char keyStrBase[] = "sKSpace.lBaseResolution"; @@ -685,6 +671,13 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, csaAscii->parallelReductionFactorInPlane = readKey(keyStrAF, keyPos, csaLengthTrim); char keyStrRef[] = "sPat.lRefLinesPE"; csaAscii->refLinesPE = readKey(keyStrRef, keyPos, csaLengthTrim); + char keyStrCombineMode[] = "ucCoilCombineMode"; + csaAscii->combineMode = readKeyN1(keyStrCombineMode, keyPos, csaLengthTrim); + //BIDS CoilCombinationMethod <- Siemens 'Coil Combine Mode' CSA ucCoilCombineMode 1 = Sum of Squares, 2 = Adaptive Combine, + //printf("CoilCombineMode %d\n", csaAscii->combineMode); + char keyStrPATMode[] = "sPat.ucPATMode"; //n.b. field set even if PAT not enabled, e.g. will list SENSE for a R-factor of 1 + csaAscii->patMode = readKeyN1(keyStrPATMode, keyPos, csaLengthTrim); + //printf("PATMODE %d\n", csaAscii->patMode); //char keyStrETD[] = "sFastImaging.lEchoTrainDuration"; //*echoTrainDuration = readKey(keyStrETD, keyPos, csaLengthTrim); //char keyStrEF[] = "sFastImaging.lEPIFactor"; @@ -984,6 +977,17 @@ void json_Float(FILE *fp, const char *sLabel, float sVal) { fprintf(fp, sLabel, sVal ); } //json_Float +void json_Bool(FILE *fp, const char *sLabel, int sVal) { + // json_Str(fp, "\t\"MTState\"", d.mtState); + //n.b. in JSON, true and false are lower case, whereas in Python they are capitalized + // only print 0 / 1 for false true, ignore negative values + if (sVal == 0) fprintf(fp, sLabel, "false"); + if (sVal == 1) fprintf(fp, sLabel, "true"); + + //if (sVal = 0) fprintf(" : false,\n" ); + //if (sVal = 1) fprintf(" : true,\n" ); +} //json_Bool + void rescueProtocolName(struct TDICOMdata *d, const char * filename) { //tools like gdcmanon strip protocol name (0018,1030) but for Siemens we can recover it from CSASeriesHeaderInfo (0029,1020) if ((d->manufacturer != kMANUFACTURER_SIEMENS) || (d->CSA.SeriesHeader_offset < 1) || (d->CSA.SeriesHeader_length < 1)) return; @@ -1263,9 +1267,7 @@ tse3d: T2*/ fprintf(fp, "\t\t%g", dti4D->frameDuration[i] / 1000.0 ); // from 0018,1242 ms -> sec } fprintf(fp, "\t],\n"); - } - - + } //CT parameters if ((d.TE > 0.0) && (d.isXRay)) fprintf(fp, "\t\"XRayExposure\": %g,\n", d.TE ); //MRI parameters @@ -1282,6 +1284,7 @@ tse3d: T2*/ json_Float(fp, "\t\"RepetitionTime\": %g,\n", d.TR / 1000.0 ); json_Float(fp, "\t\"RepetitionTimeExcitation\": %g,\n", dti4D->repetitionTimeExcitation); json_Float(fp, "\t\"RepetitionTimeInversion\": %g,\n", dti4D->repetitionTimeInversion); + json_Bool(fp, "\t\"MTState\": %s,\n", d.mtState); json_Float(fp, "\t\"InversionTime\": %g,\n", d.TI / 1000.0 ); json_Float(fp, "\t\"FlipAngle\": %g,\n", d.flipAngle ); bool interp = false; //2D interpolation @@ -1502,8 +1505,17 @@ tse3d: T2*/ json_Str(fp, "\t\"ProtocolName\": \"%s\",\n", protocolName); if (csaAscii.refLinesPE > 0) fprintf(fp, "\t\"RefLinesPE\": %d,\n", csaAscii.refLinesPE); + //https://github.com/bids-standard/bids-specification/pull/681#issuecomment-861767213 + if (csaAscii.combineMode == 1) + fprintf(fp, "\t\"CoilCombinationMethod\": \"Sum of Squares\",\n" ); + if (csaAscii.combineMode == 2) + fprintf(fp, "\t\"CoilCombinationMethod\": \"Adaptive Combine\",\n" ); json_Str(fp, "\t\"ConsistencyInfo\": \"%s\",\n", consistencyInfo); if (csaAscii.parallelReductionFactorInPlane > 0) {//AccelFactorPE -> phase encoding + if (csaAscii.patMode == 1) + fprintf(fp, "\t\"MatrixCoilMode\": \"SENSE\",\n" ); + if (csaAscii.patMode == 2) + fprintf(fp, "\t\"MatrixCoilMode\": \"GRAPPA\",\n" ); if (d.accelFactPE < 1.0) { //value not found in DICOM header, but WAS found in CSA ascii d.accelFactPE = csaAscii.parallelReductionFactorInPlane; //value found in ASCII but not in DICOM (0051,1011) //fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); @@ -1529,7 +1541,6 @@ tse3d: T2*/ } #endif //GE ASL specific tags - if (d.aslFlagsGE & kASL_FLAG_GE_CONTINUOUS) fprintf(fp, "\t\"ASLContrastTechnique\": \"CONTINUOUS\",\n" ); if (d.aslFlagsGE & kASL_FLAG_GE_PSEUDOCONTINUOUS) From f1480b47d7df11e86523d2e8b01a5f32630dd0ab Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 16 Jun 2021 09:48:02 -0400 Subject: [PATCH 48/95] New tags --- BIDS/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/BIDS/README.md b/BIDS/README.md index 302cf6aa..b7ce9ec8 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -146,6 +146,7 @@ Fields specific to MRI scans. | ImageOrientationPatientDICOM | | DICOM tag 0020,0037 | D | | InPlanePhaseEncodingDirectionDICOM | | DICOM tag 0018,1312 | D | | ReceiveCoilName | | DICOM tag 0018,1250 | B | +| MTState | | 0018,9020 | B | ##### Modality Positron Emission Tomography Isotope Module Attributes @@ -274,9 +275,13 @@ Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio | ProtocolName | | Check SeriesDescription - they might be switched around | D | | RefLinesPE | | # of reference lines in the phase encoding direction for acceleration (GRAPPA) | D | | ConsistencyInfo | | The more complete software version, e.g. VE11C or VE11E instead of just VE11. | D | +| CoilCombinationMethod | | Detects `Sum of Squares` and `Adaptive Combine` | B | +| MatrixCoilMode | | Detects `SENSE` and `GRAPPA` | B | | DwellTime | | DICOM tag 0019,1018 | B | | BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0019,1028 | D | + + ##### Manufacturer Siemens Magnetic Resonance Imaging (XA) Fields specific to Siemens XA-series MRI systems (Sola, Vida). From 09dfdb766db0a27c02c343e249d8cad2fac31d0b Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 16 Jun 2021 11:55:01 -0400 Subject: [PATCH 49/95] BIDS SpoilingState and SpoilingType (https://github.com/bids-standard/bids-specification/pull/681) --- BIDS/README.md | 4 +++- console/nii_dicom.cpp | 17 ++++++++++++++++- console/nii_dicom.h | 11 +++++++++-- console/nii_dicom_batch.cpp | 12 ++++++++++-- 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index b7ce9ec8..18379c85 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -147,7 +147,9 @@ Fields specific to MRI scans. | InPlanePhaseEncodingDirectionDICOM | | DICOM tag 0018,1312 | D | | ReceiveCoilName | | DICOM tag 0018,1250 | B | | MTState | | 0018,9020 | B | - +| SpoilingState | | 0018,9016 | B | +| SpoilingType | | 0018,9016 | B | + ##### Modality Positron Emission Tomography Isotope Module Attributes PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.9.2.html). See BIDS [BEP009 Positron Emission Tomography](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). Be aware that many of the fields required by BEP009 are not stored in DICOM or ECAT files. Since dcm2niix is limited to the information provided by the input files, it will generate JSON fields that reflect the meta data stored in the source images. diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index c447b76a..00e69c72 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -854,6 +854,7 @@ struct TDICOMdata clear_dicom_data() { d.aslFlagsGE = 0; d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_UNKNOWN; d.mtState = -1; + d.spoiling = kSPOILING_UNKOWN; d.interp3D = -1; for (int i = 0; i < kMaxOverlay; i++) d.overlayStart[i] = 0; @@ -4257,6 +4258,7 @@ struct TDICOMdata readDICOMx(char * fname, struct TDCMprefs* prefs, struct TDTI4 #define kSAR 0x0018+(0x1316 << 16 ) //'DS' 'SAR' #define kPatientOrient 0x0018+(0x5100<< 16 ) //0018,5100. patient orientation - 'HFS' #define kInversionRecovery 0x0018+uint32_t(0x9009 << 16 ) //'CS' 'YES'/'NO' +#define kSpoiling 0x0018+uint32_t(0x9016 << 16 ) //'CS' #define kEchoPlanarPulseSequence 0x0018+uint32_t(0x9018 << 16 ) //'CS' 'YES'/'NO' #define kMagnetizationTransferAttribute 0x0018+uint32_t(0x9020 << 16 ) //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE' @@ -5376,7 +5378,20 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); if (lLength < 2) break; if (toupper(buffer[lPos]) == 'Y') d.isIR = true; - break; + break; + case kSpoiling : // CS 0018,9016 + if (lLength < 2) break; + char sTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], sTxt); + if ((strstr(sTxt, "RF") != NULL) && (strstr(sTxt, "RF") != NULL)) + d.spoiling = kSPOILING_RF_AND_GRADIENT; + else if (strstr(sTxt, "RF") != NULL) + d.spoiling = kSPOILING_RF; + else if (strstr(sTxt, "GRADIENT") != NULL) + d.spoiling = kSPOILING_GRADIENT; + else if (strstr(sTxt, "NONE") != NULL) + d.spoiling = kSPOILING_NONE; + break; case kEchoPlanarPulseSequence : // CS [YES],[NO] if (lLength < 2) break; if (toupper(buffer[lPos]) == 'Y') diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 67d9109a..dfc6a05b 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -114,7 +114,14 @@ static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI dire #define kASL_FLAG_GE_3DCASL 2 #define kASL_FLAG_GE_PSEUDOCONTINUOUS 4 #define kASL_FLAG_GE_CONTINUOUS 8 - + +//for spoiling 0018,9016 +#define kSPOILING_UNKOWN -1 +#define kSPOILING_NONE 0 +#define kSPOILING_RF 1 +#define kSPOILING_GRADIENT 2 +#define kSPOILING_RF_AND_GRADIENT 3 + static const int kSliceOrientUnknown = 0; static const int kSliceOrientTra = 1; static const int kSliceOrientSag = 2; @@ -194,7 +201,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; int xyzDim[5]; uint32_t coilCrc, seriesUidCrc, instanceUidCrc; int overlayStart[kMaxOverlay]; - int mtState, partialFourierDirection, interp3D, aslFlagsGE, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; + int spoiling, mtState, partialFourierDirection, interp3D, aslFlagsGE, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; float groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 154efb48..a2db7b83 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -980,9 +980,9 @@ void json_Float(FILE *fp, const char *sLabel, float sVal) { void json_Bool(FILE *fp, const char *sLabel, int sVal) { // json_Str(fp, "\t\"MTState\"", d.mtState); //n.b. in JSON, true and false are lower case, whereas in Python they are capitalized - // only print 0 / 1 for false true, ignore negative values + // only print 0 and >=1 for false and true, ignore negative values if (sVal == 0) fprintf(fp, sLabel, "false"); - if (sVal == 1) fprintf(fp, sLabel, "true"); + if (sVal > 0) fprintf(fp, sLabel, "true"); //if (sVal = 0) fprintf(" : false,\n" ); //if (sVal = 1) fprintf(" : true,\n" ); @@ -1285,6 +1285,14 @@ tse3d: T2*/ json_Float(fp, "\t\"RepetitionTimeExcitation\": %g,\n", dti4D->repetitionTimeExcitation); json_Float(fp, "\t\"RepetitionTimeInversion\": %g,\n", dti4D->repetitionTimeInversion); json_Bool(fp, "\t\"MTState\": %s,\n", d.mtState); + json_Bool(fp, "\t\"SpoilingState\": %s,\n", d.spoiling); + if (d.spoiling == kSPOILING_RF) + fprintf(fp, "\t\"SpoilingType\": \"RF\",\n" ); + if (d.spoiling == kSPOILING_GRADIENT) + fprintf(fp, "\t\"SpoilingType\": \"GRADIENT\",\n" ); + if (d.spoiling == kSPOILING_RF_AND_GRADIENT) + fprintf(fp, "\t\"SpoilingType\": \"COMBINED\",\n" ); + json_Float(fp, "\t\"InversionTime\": %g,\n", d.TI / 1000.0 ); json_Float(fp, "\t\"FlipAngle\": %g,\n", d.flipAngle ); bool interp = false; //2D interpolation From 70da450f5ee74cc6fd702b51fb70d8df366cabf3 Mon Sep 17 00:00:00 2001 From: Rob Reid Date: Wed, 16 Jun 2021 13:07:18 -0500 Subject: [PATCH 50/95] Adds a brief explanation of extract_units.py to README.md --- BIDS/README.md | 47 ++++++++++++++++++++++++++++++------------- BIDS/extract_units.py | 15 +++++++------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index 302cf6aa..60f7acf6 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -1,14 +1,33 @@ ## About -dcm2niix is designed to convert complicated DICOM images in to simple NIfTI images. However, the NIfTI images are unable to store much metadata that might be useful for analyses. Therefore, dcm2niix can create Brain Imaging Data Structure ([BIDS](https://bids.neuroimaging.io)) files that retain useful information. These are simple human-readable text files in the [JSON](https://en.wikipedia.org/wiki/JSON) format. The tables below provide details regarding the JSON fields generated by dcm2niix. Some of these are defined by the BIDS standard, while others are unique to dcm2niix. - -Note that dcm2niix cannot provide information that is not in the DICOM header. Common reasons for absent fields are: +dcm2niix is designed to convert complicated DICOM images in to simple NIfTI +images. However, the NIfTI images are unable to store much metadata that might +be useful for analyses. Therefore, dcm2niix can create Brain Imaging Data +Structure ([BIDS](https://bids.neuroimaging.io)) files that retain useful +information. These are simple human-readable text files in the +[JSON](https://en.wikipedia.org/wiki/JSON) format. The tables below provide +details regarding the JSON fields generated by dcm2niix. Some of these are +defined by the BIDS standard, while others are unique to dcm2niix. + +Note that dcm2niix cannot provide information that is not in the DICOM +header. Common reasons for absent fields are: - Irrelevance to the scan type, e.g. MagneticFieldStrength for CT - It was removed from the DICOM during anonymization, possibly by accident or overzealousness - Difficulty interpreting the storage format used by the manufacturer - The manufacturer simply neglected to write it +### Formatting Note +The units listed in this document can be extracted into JSON format using +```./extract_units.py```, as long as they are in tables where the first two columns +are Field and Unit. It can also optionally extract only the units that are used +in a given BIDS sidecar - e.g. if you have a BIDS sidecar file named +```my_series.json``` from dcm2niix, you can use ```extract_units.py``` to get +```my_series_units.json```. + +Help on using ```extract_units.py``` is available from running ```extract_units.py -h```. + +### Glossary The Defined By column uses: - B : The [BIDS](https://bids.neuroimaging.io) standard @@ -32,7 +51,7 @@ The Unit column uses: These fields are present regardless of modality (e.g. MR, CT, PET). -##### Global Constants +### Global Constants These fields should be the same for all images acquired on a specific scanner. @@ -52,7 +71,7 @@ These fields should be the same for all images acquired on a specific scanner. | ConversionSoftware | | e.g. `dcm2niix` | D | | ConversionSoftwareVersion | | e.g. `v1.0.20210317` | D | -##### Global Series Information +### Global Series Information These fields are present regardless of modality (e.g. MR, CT, PET). @@ -73,7 +92,7 @@ These fields are present regardless of modality (e.g. MR, CT, PET). | AcquisitionNumber | | DICOM tag 0020,0012 | D | | ImageComments | | DICOM tag 0020,4000 | D | -##### Global Private Information +### Global Private Information These fields contain personally identifiable information. By default dcm2niix will create anonymized files without these fields. The option `-ba n` will retain private information. @@ -95,7 +114,7 @@ These fields contain personally identifiable information. By default dcm2niix wi These fields are specific to modality (e.g. MR, CT, PET). -##### Modality Computerized Tomography +### Modality Computerized Tomography Fields specific to CT scans. @@ -103,7 +122,7 @@ Fields specific to CT scans. |--------------|------|---------------------|------------| | XRayExposure | mAs | DICOM tag 0018,1152 | D | -##### Modality Magnetic Resonance Imaging +### Modality Magnetic Resonance Imaging Fields specific to MRI scans. @@ -147,7 +166,7 @@ Fields specific to MRI scans. | InPlanePhaseEncodingDirectionDICOM | | DICOM tag 0018,1312 | D | | ReceiveCoilName | | DICOM tag 0018,1250 | B | -##### Modality Positron Emission Tomography Isotope Module Attributes +### Modality Positron Emission Tomography Isotope Module Attributes PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.9.2.html). See BIDS [BEP009 Positron Emission Tomography](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). Be aware that many of the fields required by BEP009 are not stored in DICOM or ECAT files. Since dcm2niix is limited to the information provided by the input files, it will generate JSON fields that reflect the meta data stored in the source images. @@ -181,7 +200,7 @@ These fields are specific to manufacturer (e.g. GE, Philips, Siemens). For furth - [Siemens](https://github.com/rordenlab/dcm2niix/tree/master/Siemens) - [UIH](https://github.com/rordenlab/dcm2niix/tree/master/UIH) -##### Manufacturer General Electric +### Manufacturer General Electric Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Determining these fields from a DICOM image requires decoding the compressed Protocol Data Block (0025,101B). @@ -195,7 +214,7 @@ Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Deter | LabelingDuration | s | DICOM tag 0043,10A5 | B | | PostLabelingDelay | s | DICOM tag 0018,0082 (TI) | B | -##### Manufacturer Philips +### Manufacturer Philips Data unique to Philips, including [custom intensity scaling](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3998685/) @@ -212,7 +231,7 @@ Data unique to Philips, including [custom intensity scaling](https://www.ncbi.nl | PhaseEncodingStepsNoPartialFourier | | DICOM tag 0018,9231 | D | | WaterFatShift | | DICOM tag 2001,1022 | D | -##### Manufacturer Siemens (Arterial Spin Labeling) +### Manufacturer Siemens (Arterial Spin Labeling) See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). All these values are specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). @@ -247,7 +266,7 @@ Note that [many of the fields listed by BIDS](https://bids-specification.readthe | TagPlaneSNormalDTra | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | | Tag* (if not explicitly listed) | One value for each | [Post-label delays](https://fsl.fmrib.ox.ac.uk/fslcourse/lectures/practicals/ASLpractical/index.html), 3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`jw_tgse_VEPCASL`) | D | -##### Manufacturer Siemens Magnetic Resonance Imaging (V*) +### Manufacturer Siemens Magnetic Resonance Imaging (V*) Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Skyra, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). @@ -277,7 +296,7 @@ Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio | DwellTime | | DICOM tag 0019,1018 | B | | BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0019,1028 | D | -##### Manufacturer Siemens Magnetic Resonance Imaging (XA) +### Manufacturer Siemens Magnetic Resonance Imaging (XA) Fields specific to Siemens XA-series MRI systems (Sola, Vida). diff --git a/BIDS/extract_units.py b/BIDS/extract_units.py index 234d82a0..035f3b8d 100755 --- a/BIDS/extract_units.py +++ b/BIDS/extract_units.py @@ -11,15 +11,15 @@ [default: README.md] Options: - -h --help Show this message and exit. - --version Show version and exit. + -h --help Show this message and exit. + --version Show version and exit. -e EXISTING --ex=EXISTING Extract units for all the fields, and only the - fields in EXISTING, a BIDS file, - and write the output to - EXISTING.replace('.json', '_units.json') - instead of stdout. + fields in EXISTING, a BIDS file, and write the + output to + EXISTING.replace('.json', '_units.json') + instead of stdout. -o OUT --out=OUT If given, save the output to this filename. - (Overrides the implicit destination of -e.) + (Overrides the implicit destination of -e.) """ from __future__ import print_function try: @@ -75,7 +75,6 @@ def main(mdfn, existing=None, outfn=None): from docopt import docopt args = docopt(__doc__, version=__version__) - #print(args) # https://github.com/docopt/docopt/issues/214 has been open for # almost 7 years, so it looks like docopt isn't getting default From 81b14986fcf3cf6325db7472e564b88e6d0de173 Mon Sep 17 00:00:00 2001 From: Rob Reid Date: Wed, 16 Jun 2021 13:22:16 -0500 Subject: [PATCH 51/95] Adjusts header levels --- BIDS/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BIDS/README.md b/BIDS/README.md index 19a47d07..2801f90e 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -169,7 +169,7 @@ Fields specific to MRI scans. | SpoilingState | | 0018,9016 | B | | SpoilingType | | 0018,9016 | B | -##### Modality Positron Emission Tomography Isotope Module Attributes +### Modality Positron Emission Tomography Isotope Module Attributes PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.9.2.html). See BIDS [BEP009 Positron Emission Tomography](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). Be aware that many of the fields required by BEP009 are not stored in DICOM or ECAT files. Since dcm2niix is limited to the information provided by the input files, it will generate JSON fields that reflect the meta data stored in the source images. From caf20219d6a110dbd13da3b81929b988ae08703b Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 16 Jun 2021 16:05:42 -0400 Subject: [PATCH 52/95] Update GE ASL details --- BIDS/README.md | 2 +- GE/README.md | 13 +++++++++++++ console/nii_dicom.cpp | 10 +++++----- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index 2801f90e..6beb059e 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -169,7 +169,7 @@ Fields specific to MRI scans. | SpoilingState | | 0018,9016 | B | | SpoilingType | | 0018,9016 | B | -### Modality Positron Emission Tomography Isotope Module Attributes +### Modality Positron Emission Tomography PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.9.2.html). See BIDS [BEP009 Positron Emission Tomography](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). Be aware that many of the fields required by BEP009 are not stored in DICOM or ECAT files. Since dcm2niix is limited to the information provided by the input files, it will generate JSON fields that reflect the meta data stored in the source images. diff --git a/GE/README.md b/GE/README.md index 6f6b8226..cd4ee16e 100644 --- a/GE/README.md +++ b/GE/README.md @@ -2,6 +2,19 @@ dcm2niix attempts to convert GE DICOM format images to NIfTI. The current generation DICOM files generated by GE equipment is quite impoverished relative to other vendors. Therefore, the amount of information dcm2niix is able to extract is relatively limited. Hopefully, in the future GE will provide more details that are critical for brain scientists. +## Arterial Spin Labeling + +As noted by David Shin (GE), the GE product ASL sequence sequence produces two 3D volumes. The Perfusion Weighted (PW) first pass acquires ASL tag/control spirals in interleaved fashion over many volumes (TRs), and does the subtraction and averaging in k-space. Therefore, the result is a single 3D volume. The second pass acquires a Proton Density (PD) reference volume. The PW and PD images can be combined offline to generate quantified CBF map. [Stanford](https://cni.stanford.edu/wiki/Data_Processing) includes useful notes on this sequence. The sequence specific details are listed in the table. + +| DICOM Tag | Pass 1 (PW) | Pass 2 (PD) | +|-----------|------------------------------------|------------------------------------| +| 0043,10A3 | PSEUDOCONTINUOUS |CONTINUOUS | +| 0043,10A4 | 3D pulsed continuous ASL technique |3D continuous ASL technique | +| 0043,10A5 | Label Duration (ms) |Label Duration (ms) | +| 0018,0082 |Post Label Delay (ms) | NA | + +The GE product ASL sequence is optimized for clinical diagnosis with emphasis on 3D acquisition and multi-shot interleaving for high spatial resolution. For 4D type acquisition (time resolved single-shot acquisition), GE researchers can leverage the [multi-band ASL/BOLD sequence](https://journals.plos.org/plosone/article/authors?id=10.1371/journal.pone.0190427). + ## Diffusion Tensor Notes The [NA-MIC Wiki](https://www.na-mic.org/wiki/NAMIC_Wiki:DTI:DICOM_for_DWI_and_DTI#Private_vendor:_GE) provides a nice description of the GE diffusion tags. In brief, the B-value is stored as the first element in the array of 0043,1039. The DICOM elements 0019,10bb, 0019,10bc and 0019,10bd provide the gradient direction relative to the frequency, phase and slice. As noted by Jaemin Shin (GE), the GE convention for reported diffusion gradient direction has always been in “MR physics” logical coordinate, i.e Freq (X), Phase (Y), Slice (Z). Note that this is neither “with reference to the scanner bore” (like Siemens or Philips) nor “with reference to the imaging plane” (as expected by FSL tools). This is the main source of confusion. This explains why the dcm2niix function geCorrectBvecs() checks whether the DICOM tag In-plane Phase Encoding Direction (0018,1312) is 'ROW' or 'COL'. In addition, it will generate the warning 'reorienting for ROW phase-encoding untested' if you acquire DTI data with the phase encoding in the ROW direction. If you do test this feature, please report your findings as a Github issue. Assuming you have COL phase encoding, dcm2niix should provide [FSL format](http://justinblaber.org/brief-introduction-to-dwmri/) [bvec files](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FDT/FAQ#What_conventions_do_the_bvecs_use.3F). diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 00e69c72..f59e607b 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -6632,11 +6632,11 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); char st[kDICOMStr]; //aslFlagsGE dcmStr(lLength, &buffer[lPos], st); - if (strstr(st, "CONTINUOUS") != NULL) - d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_CONTINUOUS); - else if (strstr(st, "PSEUDOCONTINUOUS") != NULL) - d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_PSEUDOCONTINUOUS); - break; + if (strstr(st, "PSEUDOCONTINUOUS") != NULL) + d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_PSEUDOCONTINUOUS); + else if (strstr(st, "CONTINUOUS") != NULL) + d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_CONTINUOUS); + break; } case kASLLabelingTechniqueGE: { //LO issue427GE if (d.manufacturer != kMANUFACTURER_GE) break; From 2538b0848e3ef557dea9434f2035137171afa74e Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Wed, 16 Jun 2021 16:34:17 -0400 Subject: [PATCH 53/95] Detect MTState from Siemens CSA header (https://github.com/bids-standard/bids-specification/pull/681#issuecomment-862574797) --- console/nii_dicom_batch.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index a2db7b83..20bbcdd7 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -570,7 +570,7 @@ int phoenixOffsetCSASeriesHeader(unsigned char *buff, int lLength) { typedef struct { float TE0, TE1, delayTimeInTR, phaseOversampling, phaseResolution, txRefAmp; int phaseEncodingLines, existUcImageNumb, ucMode, baseResolution, interp, partialFourier,echoSpacing, - difBipolar, parallelReductionFactorInPlane, refLinesPE, combineMode, patMode; + difBipolar, parallelReductionFactorInPlane, refLinesPE, combineMode, patMode, ucMTC; float alFree[kMaxWipFree] ; float adFree[kMaxWipFree]; float alTI[kMaxWipFree]; @@ -597,7 +597,8 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, csaAscii->parallelReductionFactorInPlane = 0; csaAscii->refLinesPE = 0; csaAscii->combineMode = 0; - csaAscii->patMode = 0; + csaAscii->patMode = 0; + csaAscii->ucMTC = 0; for (int i = 0; i < 8; i++) shimSetting[i] = 0.0; strcpy(coilID, ""); @@ -677,6 +678,8 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, //printf("CoilCombineMode %d\n", csaAscii->combineMode); char keyStrPATMode[] = "sPat.ucPATMode"; //n.b. field set even if PAT not enabled, e.g. will list SENSE for a R-factor of 1 csaAscii->patMode = readKeyN1(keyStrPATMode, keyPos, csaLengthTrim); + char keyStrucMTC[] = "sPrepPulses.ucMTC"; //n.b. field set even if PAT not enabled, e.g. will list SENSE for a R-factor of 1 + csaAscii->ucMTC = readKeyN1(keyStrucMTC, keyPos, csaLengthTrim); //printf("PATMODE %d\n", csaAscii->patMode); //char keyStrETD[] = "sFastImaging.lEchoTrainDuration"; //*echoTrainDuration = readKey(keyStrETD, keyPos, csaLengthTrim); @@ -1518,6 +1521,8 @@ tse3d: T2*/ fprintf(fp, "\t\"CoilCombinationMethod\": \"Sum of Squares\",\n" ); if (csaAscii.combineMode == 2) fprintf(fp, "\t\"CoilCombinationMethod\": \"Adaptive Combine\",\n" ); + if ((csaAscii.ucMTC == 1) && (d.mtState < 0)) //precedence for 0018,9020 over CSA + json_Bool(fp, "\t\"MTState\": %s,\n", 1); json_Str(fp, "\t\"ConsistencyInfo\": \"%s\",\n", consistencyInfo); if (csaAscii.parallelReductionFactorInPlane > 0) {//AccelFactorPE -> phase encoding if (csaAscii.patMode == 1) From d4737e2639484c4ef3f4283a406a68216c017a31 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Thu, 17 Jun 2021 14:29:21 -0400 Subject: [PATCH 54/95] JSON ImageOrientationText (https://github.com/rordenlab/dcm2niix/issues/522) --- BIDS/README.md | 4 ++++ GE/README.md | 7 +++++-- console/nii_dicom.cpp | 29 ++++++++++++++++++++++++++++- console/nii_dicom.h | 4 ++-- console/nii_dicom_batch.cpp | 4 ++++ 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index 6beb059e..df3f63d3 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -216,6 +216,9 @@ Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Deter | ASLLabelingTechnique | | DICOM tag 0043,10A4 | D | | LabelingDuration | s | DICOM tag 0043,10A5 | B | | PostLabelingDelay | s | DICOM tag 0018,0082 (TI) | B | +| NumberOfPointsPerArm | | DICOM tag 0027,1060 | D | +| NumberOfArms | | DICOM tag 0027,1061 | D | +| NumberOfExcitations | | DICOM tag 0027,1062 | D | ### Manufacturer Philips @@ -300,6 +303,7 @@ Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio | MatrixCoilMode | | Detects `SENSE` and `GRAPPA` | B | | DwellTime | | DICOM tag 0019,1018 | B | | BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0019,1028 | D | +| ImageOrientationText | Hz | DICOM tag 0051,100E | D | ### Manufacturer Siemens Magnetic Resonance Imaging (XA) diff --git a/GE/README.md b/GE/README.md index cd4ee16e..8b81e221 100644 --- a/GE/README.md +++ b/GE/README.md @@ -4,14 +4,17 @@ dcm2niix attempts to convert GE DICOM format images to NIfTI. The current genera ## Arterial Spin Labeling -As noted by David Shin (GE), the GE product ASL sequence sequence produces two 3D volumes. The Perfusion Weighted (PW) first pass acquires ASL tag/control spirals in interleaved fashion over many volumes (TRs), and does the subtraction and averaging in k-space. Therefore, the result is a single 3D volume. The second pass acquires a Proton Density (PD) reference volume. The PW and PD images can be combined offline to generate quantified CBF map. [Stanford](https://cni.stanford.edu/wiki/Data_Processing) includes useful notes on this sequence. The sequence specific details are listed in the table. +As noted by David Shin (GE), the GE product ASL sequence sequence produces two 3D volumes. The Perfusion Weighted (PW) first pass acquires ASL tag/control spirals in interleaved fashion over many volumes (TRs), and does the subtraction and averaging in k-space. Therefore, the result is a single 3D volume. The second pass acquires a Proton Density (PD) reference volume. The PW and PD images can be combined offline to generate quantified Cerebral Blood Flow(CBF) map. [Stanford](https://cni.stanford.edu/wiki/Data_Processing) includes useful notes on this sequence. Note that Number of Excitations (NEX) is needed for CBF quantification. The sequence specific details are listed in the table. | DICOM Tag | Pass 1 (PW) | Pass 2 (PD) | |-----------|------------------------------------|------------------------------------| | 0043,10A3 | PSEUDOCONTINUOUS |CONTINUOUS | | 0043,10A4 | 3D pulsed continuous ASL technique |3D continuous ASL technique | | 0043,10A5 | Label Duration (ms) |Label Duration (ms) | -| 0018,0082 |Post Label Delay (ms) | NA | +| 0018,0082 | Post Label Delay (ms) | NA | +| 0027,1060 | Number of Points per Arm | NA | +| 0027,1061 | Number of Arms | NA | +| 0027,1062 | Number of Excitations | NA | The GE product ASL sequence is optimized for clinical diagnosis with emphasis on 3D acquisition and multi-shot interleaving for high spatial resolution. For 4D type acquisition (time resolved single-shot acquisition), GE researchers can leverage the [multi-band ASL/BOLD sequence](https://journals.plos.org/plosone/article/authors?id=10.1371/journal.pone.0190427). diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index f59e607b..2e8dd33c 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -741,7 +741,8 @@ struct TDICOMdata clear_dicom_data() { strcpy(d.radiopharmaceutical, ""); strcpy(d.convolutionKernel, ""); strcpy(d.parallelAcquisitionTechnique, ""); - strcpy(d.unitsPT, ""); + strcpy(d.imageOrientationText, ""); + strcpy(d.unitsPT, ""); strcpy(d.decayCorrection, ""); strcpy(d.attenuationCorrectionMethod, ""); strcpy(d.reconstructionMethod, ""); @@ -854,6 +855,9 @@ struct TDICOMdata clear_dicom_data() { d.aslFlagsGE = 0; d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_UNKNOWN; d.mtState = -1; + d.numberOfExcitations = -1; + d.numberOfArms = -1; + d.numberOfPointsPerArm = -1; d.spoiling = kSPOILING_UNKOWN; d.interp3D = -1; for (int i = 0; i < kMaxOverlay; i++) @@ -4344,6 +4348,9 @@ const uint32_t kEffectiveTE = 0x0018+ (0x9082 << 16); #define kLocationsInAcquisitionGE 0x0021+(0x104F<< 16 )//SS 'LocationsInAcquisitionGE' #define kRTIA_timer 0x0021+(0x105E<< 16 )//DS #define kProtocolDataBlockGE 0x0025+(0x101B<< 16 )//OB +#define kNumberOfPointsPerArm 0x0027+(0x1060<< 16 )//FL +#define kNumberOfArms 0x0027+(0x1061<< 16 )//FL +#define kNumberOfExcitations 0x0027+(0x1062<< 16 )//FL #define kSamplesPerPixel 0x0028+(0x0002 << 16 ) #define kPhotometricInterpretation 0x0028+(0x0004 << 16 ) #define kPlanarRGB 0x0028+(0x0006 << 16 ) @@ -4379,6 +4386,7 @@ const uint32_t kEffectiveTE = 0x0018+ (0x9082 << 16); #define kDurationLabelPulseGE 0x0043+(0x10A5 << 16 ) //IS #define kMultiBandGE 0x0043+(0x10B6 << 16 ) //LO #define kAcquisitionMatrixText 0x0051+(0x100B << 16 ) //LO +#define kImageOrientationText 0x0051+(0x100E << 16 ) // #define kCoilSiemens 0x0051+(0x100F << 16 ) #define kImaPATModeText 0x0051+(0x1011 << 16 ) #define kLocationsInAcquisition 0x0054+(0x0081 << 16 ) @@ -5816,6 +5824,21 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); d.protocolBlockStartGE = (int)lPos+(int)lFileOffset+4; //printError("ProtocolDataBlockGE %d @ %d\n", d.protocolBlockLengthGE, d.protocolBlockStartGE); break; + case kNumberOfExcitations : //FL + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.numberOfExcitations = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); + break; + case kNumberOfArms : //FL + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.numberOfArms = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); + break; + case kNumberOfPointsPerArm : //FL + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.numberOfPointsPerArm = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); + break; case kDoseCalibrationFactor : d.doseCalibrationFactor = dcmStrFloat(lLength, &buffer[lPos]); break; @@ -6034,6 +6057,10 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); isInterpolated = true; } break; } + case kImageOrientationText: //issue522 + if (d.manufacturer != kMANUFACTURER_SIEMENS) break; + dcmStr(lLength, &buffer[lPos], d.imageOrientationText); + break; case kCoilSiemens : { if (d.manufacturer == kMANUFACTURER_SIEMENS) { //see if image from single coil "H12" or an array "HEA;HEP" diff --git a/console/nii_dicom.h b/console/nii_dicom.h index dfc6a05b..8cea04cb 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -202,14 +202,14 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; uint32_t coilCrc, seriesUidCrc, instanceUidCrc; int overlayStart[kMaxOverlay]; int spoiling, mtState, partialFourierDirection, interp3D, aslFlagsGE, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; - float groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; + float numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) float frameDuration, ecat_isotope_halflife, ecat_dosage; float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. double acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr]; - char coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], scanOptions[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[kDICOMStr], imageType[kDICOMStr], institutionalDepartmentName[kDICOMStr], manufacturersModelName[kDICOMStr], patientID[kDICOMStr], patientOrient[kDICOMStr], patientName[kDICOMStr], accessionNumber[kDICOMStr], seriesDescription[kDICOMStr], studyID[kDICOMStr], sequenceName[kDICOMStr], protocolName[kDICOMStr],sequenceVariant[kDICOMStr],scanningSequence[kDICOMStr], patientBirthDate[kDICOMStr], patientAge[kDICOMStr], studyDate[kDICOMStr],studyTime[kDICOMStr]; + char imageOrientationText[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], scanOptions[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[kDICOMStr], imageType[kDICOMStr], institutionalDepartmentName[kDICOMStr], manufacturersModelName[kDICOMStr], patientID[kDICOMStr], patientOrient[kDICOMStr], patientName[kDICOMStr], accessionNumber[kDICOMStr], seriesDescription[kDICOMStr], studyID[kDICOMStr], sequenceName[kDICOMStr], protocolName[kDICOMStr],sequenceVariant[kDICOMStr],scanningSequence[kDICOMStr], patientBirthDate[kDICOMStr], patientAge[kDICOMStr], studyDate[kDICOMStr],studyTime[kDICOMStr]; char institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge]; uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; struct TCSAdata CSA; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 20bbcdd7..38bf59e8 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1566,6 +1566,9 @@ tse3d: T2*/ json_Float(fp, "\t\"LabelingDuration\": %g,\n", d.durationLabelPulseGE / 1000.0); json_Float(fp, "\t\"PostLabelingDelay\": %g,\n", d.TI / 1000.0); //For GE ASL: InversionTime -> Post-label delay } + json_Float(fp, "\t\"NumberOfPointsPerArm\": %g,\n", d.numberOfPointsPerArm); + json_Float(fp, "\t\"NumberOfArms\": %g,\n", d.numberOfArms); + json_Float(fp, "\t\"NumberOfExcitations\": %g,\n", d.numberOfExcitations); if ((d.CSA.multiBandFactor > 1) && (d.modality == kMODALITY_MR)) //AccelFactorSlice fprintf(fp, "\t\"MultibandAccelerationFactor\": %d,\n", d.CSA.multiBandFactor); json_Float(fp, "\t\"PercentPhaseFOV\": %g,\n", d.phaseFieldofView); @@ -1741,6 +1744,7 @@ tse3d: T2*/ fprintf(fp, "\t\t%g", d.orient[i]); } fprintf(fp, "\t],\n"); + json_Str(fp, "\t\"ImageOrientationText\": \"%s\",\n", d.imageOrientationText); 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 From 1de5e5be9f81e563c7996efcf305ba624ef681ca Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 22 Jun 2021 10:56:01 -0400 Subject: [PATCH 55/95] clang-format cleanup --- BIDS/README.md | 18 +- CONTRIBUTE.md | 14 +- GE/README.md | 12 +- console/jpg_0XC3.cpp | 986 +-- console/main_console.cpp | 974 +-- console/nii_dicom.cpp | 12351 +++++++++++++++++----------------- console/nii_dicom_batch.cpp | 9567 +++++++++++++------------- console/nii_foreign.cpp | 613 +- console/nii_ortho.cpp | 619 +- 9 files changed, 12621 insertions(+), 12533 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index df3f63d3..ed15853e 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -39,13 +39,13 @@ The Unit column uses: - f : fraction - kg : Kilogram - list : list of text strings -- MBq : MegaBecquerels -- MHz : Megahertz -- mA : milliAmperes +- MBq : megabecquerel +- MHz : megahertz +- mA : milliampere - mm : millimeter -- s : seconds -- T : Tesla -- V : Volts +- s : second +- T : tesla +- V : volt ## Global Fields @@ -166,7 +166,7 @@ Fields specific to MRI scans. | InPlanePhaseEncodingDirectionDICOM | | DICOM tag 0018,1312 | D | | ReceiveCoilName | | DICOM tag 0018,1250 | B | | MTState | | 0018,9020 | B | -| SpoilingState | | 0018,9016 | B | +| SpoilingState | | 0018,9016 or 0018,0021 | B | | SpoilingType | | 0018,9016 | B | ### Modality Positron Emission Tomography @@ -296,6 +296,8 @@ Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio | PulseSequenceDetails | | pulse sequence label, e.g. "%SiemensSeq%\\\\tgse\_pasl" | B | | FmriExternalInfo | | | D | | WipMemBlock | | | D | +| AveragesDouble | | CSA `dAveragesDouble`, fractions possible, independent of DICOM `NumberOfAverages` (0018,0083) | D | +| AccelFact3D | | 3D Acquisitions (Parallel Reduction Factor Across Slices) | D | | ProtocolName | | Check SeriesDescription - they might be switched around | D | | RefLinesPE | | # of reference lines in the phase encoding direction for acceleration (GRAPPA) | D | | ConsistencyInfo | | The more complete software version, e.g. VE11C or VE11E instead of just VE11. | D | @@ -303,7 +305,7 @@ Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio | MatrixCoilMode | | Detects `SENSE` and `GRAPPA` | B | | DwellTime | | DICOM tag 0019,1018 | B | | BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0019,1028 | D | -| ImageOrientationText | Hz | DICOM tag 0051,100E | D | +| ImageOrientationText | | DICOM tag 0051,100E | D | ### Manufacturer Siemens Magnetic Resonance Imaging (XA) diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md index 14d21671..2a1b59ef 100644 --- a/CONTRIBUTE.md +++ b/CONTRIBUTE.md @@ -1,4 +1,6 @@ -#### dcm2niix is a community effort +#### Introduction + +dcm2niix is a community effort Like the [Brain Imaging Data Structure](https://bids.neuroimaging.io/get_involved.html), which it supports, dcm2niix is developed by the community for the community and everybody can become a part of the community. @@ -16,4 +18,12 @@ The INCF suggests indicating who is responsible for maintaining software for [st - Michael Harms (@mharms): Advanced modalities - Roger D Newman-Norlund (@rogiedodgie): User support - Rob Reid (@captainnova): Clinical modalities - - Chris Rorden (@neurolabusc): General development, user support \ No newline at end of file + - Chris Rorden (@neurolabusc): General development, user support + +#### Style Guide + +dcm2niix is written in C. Different programmers prefer different styles of indentation. Feel free to contribute code without being concerned about matching the style of the rest of the code. Once in a while, the code base will be automatically reformatted to make it appear more consistent for all users. This is done automatically with clang-format: + +``` +clang-format -i -style="{BasedOnStyle: LLVM, IndentWidth: 4, IndentCaseLabels: false, TabWidth: 4, UseTab: Always, ColumnLimit: 0}" *.cpp *.h +``` \ No newline at end of file diff --git a/GE/README.md b/GE/README.md index 8b81e221..35c5bdef 100644 --- a/GE/README.md +++ b/GE/README.md @@ -8,13 +8,13 @@ As noted by David Shin (GE), the GE product ASL sequence sequence produces two 3 | DICOM Tag | Pass 1 (PW) | Pass 2 (PD) | |-----------|------------------------------------|------------------------------------| -| 0043,10A3 | PSEUDOCONTINUOUS |CONTINUOUS | -| 0043,10A4 | 3D pulsed continuous ASL technique |3D continuous ASL technique | -| 0043,10A5 | Label Duration (ms) |Label Duration (ms) | +| 0043,10A3 | PSEUDOCONTINUOUS | CONTINUOUS | +| 0043,10A4 | 3D pulsed continuous ASL technique | 3D continuous ASL technique | +| 0043,10A5 | Label Duration (ms) | Label Duration (ms) | | 0018,0082 | Post Label Delay (ms) | NA | -| 0027,1060 | Number of Points per Arm | NA | -| 0027,1061 | Number of Arms | NA | -| 0027,1062 | Number of Excitations | NA | +| 0027,1060 | Number of Points per Arm | Number of Points per Arm | +| 0027,1061 | Number of Arms | Number of Arms | +| 0027,1062 | Number of Excitations | Number of Excitations | The GE product ASL sequence is optimized for clinical diagnosis with emphasis on 3D acquisition and multi-shot interleaving for high spatial resolution. For 4D type acquisition (time resolved single-shot acquisition), GE researchers can leverage the [multi-band ASL/BOLD sequence](https://journals.plos.org/plosone/article/authors?id=10.1371/journal.pone.0190427). diff --git a/console/jpg_0XC3.cpp b/console/jpg_0XC3.cpp index 53ffdcfb..7b5d689f 100644 --- a/console/jpg_0XC3.cpp +++ b/console/jpg_0XC3.cpp @@ -1,507 +1,513 @@ +#include "jpg_0XC3.h" +#include "print.h" #include //requires VS 2015 or later -#include +#include #include #include -#include -#include "jpg_0XC3.h" -#include "print.h" +#include -unsigned char readByte(unsigned char *lRawRA, long *lRawPos, long lRawSz) { - unsigned char ret = 0x00; - if (*lRawPos < lRawSz) - ret = lRawRA[*lRawPos]; - (*lRawPos)++; - return ret; -}// readByte() +unsigned char readByte(unsigned char *lRawRA, long *lRawPos, long lRawSz) { + unsigned char ret = 0x00; + if (*lRawPos < lRawSz) + ret = lRawRA[*lRawPos]; + (*lRawPos)++; + return ret; +} // readByte() -uint16_t readWord(unsigned char *lRawRA, long *lRawPos, long lRawSz) { - return ( (readByte(lRawRA, lRawPos, lRawSz) << 8) + readByte(lRawRA, lRawPos, lRawSz)); -}// readWord() +uint16_t readWord(unsigned char *lRawRA, long *lRawPos, long lRawSz) { + return ((readByte(lRawRA, lRawPos, lRawSz) << 8) + readByte(lRawRA, lRawPos, lRawSz)); +} // readWord() -int readBit(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos) {//Read the next single bit - int result = (lRawRA[*lRawPos] >> (7 - *lCurrentBitPos)) & 1; - (*lCurrentBitPos)++; - if (*lCurrentBitPos == 8) { - (*lRawPos)++; - *lCurrentBitPos = 0; - } - return result; -}// readBit() +int readBit(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos) { //Read the next single bit + int result = (lRawRA[*lRawPos] >> (7 - *lCurrentBitPos)) & 1; + (*lCurrentBitPos)++; + if (*lCurrentBitPos == 8) { + (*lRawPos)++; + *lCurrentBitPos = 0; + } + return result; +} // readBit() int bitMask(int bits) { - return ( (2 << (bits - 1)) -1); -}// bitMask() + return ((2 << (bits - 1)) - 1); +} // bitMask() -int readBits (unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos, int lNum) { //lNum: bits to read, not to exceed 16 - int result = lRawRA[*lRawPos]; - result = (result << 8) + lRawRA[(*lRawPos)+1]; - result = (result << 8) + lRawRA[(*lRawPos)+2]; - result = (result >> (24 - * lCurrentBitPos -lNum)) & bitMask(lNum); //lCurrentBitPos is incremented from 1, so -1 - *lCurrentBitPos = *lCurrentBitPos + lNum; - if (*lCurrentBitPos > 7) { - *lRawPos = *lRawPos + (*lCurrentBitPos >> 3); // div 8 - *lCurrentBitPos = *lCurrentBitPos & 7; //mod 8 - } - return result; -}// readBits() +int readBits(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos, int lNum) { //lNum: bits to read, not to exceed 16 + int result = lRawRA[*lRawPos]; + result = (result << 8) + lRawRA[(*lRawPos) + 1]; + result = (result << 8) + lRawRA[(*lRawPos) + 2]; + result = (result >> (24 - *lCurrentBitPos - lNum)) & bitMask(lNum); //lCurrentBitPos is incremented from 1, so -1 + *lCurrentBitPos = *lCurrentBitPos + lNum; + if (*lCurrentBitPos > 7) { + *lRawPos = *lRawPos + (*lCurrentBitPos >> 3); // div 8 + *lCurrentBitPos = *lCurrentBitPos & 7; //mod 8 + } + return result; +} // readBits() struct HufTables { - uint8_t SSSSszRA[18]; - uint8_t LookUpRA[256]; - int DHTliRA[32]; - int DHTstartRA[32]; - int HufSz[32]; - int HufCode[32]; - int HufVal[32]; - int MaxHufSi; - int MaxHufVal; -};// HufTables() + uint8_t SSSSszRA[18]; + uint8_t LookUpRA[256]; + int DHTliRA[32]; + int DHTstartRA[32]; + int HufSz[32]; + int HufCode[32]; + int HufVal[32]; + int MaxHufSi; + int MaxHufVal; +}; // HufTables() int decodePixelDifference(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos, struct HufTables l) { - int lByte = (lRawRA[*lRawPos] << *lCurrentBitPos) + (lRawRA[*lRawPos+1] >> (8- *lCurrentBitPos)); - lByte = lByte & 255; - int lHufValSSSS = l.LookUpRA[lByte]; - if (lHufValSSSS < 255) { - *lCurrentBitPos = l.SSSSszRA[lHufValSSSS] + *lCurrentBitPos; - *lRawPos = *lRawPos + (*lCurrentBitPos >> 3); - *lCurrentBitPos = *lCurrentBitPos & 7; - } else { //full SSSS is not in the first 8-bits - int lInput = lByte; - int lInputBits = 8; - (*lRawPos)++; // forward 8 bits = precisely 1 byte - do { - lInputBits++; - lInput = (lInput << 1) + readBit(lRawRA, lRawPos, lCurrentBitPos); - if (l.DHTliRA[lInputBits] != 0) { //if any entires with this length - for (int lI = l.DHTstartRA[lInputBits]; lI <= (l.DHTstartRA[lInputBits]+l.DHTliRA[lInputBits]-1); lI++) { - if (lInput == l.HufCode[lI]) - lHufValSSSS = l.HufVal[lI]; - } //check each code - } //if any entries with this length - if ((lInputBits >= l.MaxHufSi) && (lHufValSSSS > 254)) {//exhausted options CR: added rev13 - lHufValSSSS = l.MaxHufVal; - } - } while (!(lHufValSSSS < 255)); // found; - } //answer in first 8 bits - //The HufVal is referred to as the SSSS in the Codec, so it is called 'lHufValSSSS' - if (lHufValSSSS == 0) //NO CHANGE - return 0; - if (lHufValSSSS == 1) { - if (readBit(lRawRA, lRawPos, lCurrentBitPos) == 0) - return -1; - else - return 1; - } - if (lHufValSSSS == 16) { //ALL CHANGE 16 bit difference: Codec H.1.2.2 "No extra bits are appended after SSSS = 16 is encoded." Osiris fails here - return 32768; - } - //to get here - there is a 2..15 bit difference - int lDiff = readBits(lRawRA, lRawPos, lCurrentBitPos, lHufValSSSS); - if (lDiff <= bitMask(lHufValSSSS-1)) //add - lDiff = lDiff - bitMask(lHufValSSSS); - return lDiff; -}// decodePixelDifference() - -unsigned char * decode_JPEG_SOF_0XC3 (const char *fn, int skipBytes, bool verbose, int *dimX, int *dimY, int *bits, int *frames, int diskBytes) { - //decompress JPEG image named "fn" where image data is located skipBytes into file. diskBytes is compressed size of image (set to 0 if unknown) - //next line breaks MSVC - // #define abortGoto(...) ({printError(__VA_ARGS__); free(lRawRA); return NULL;}) - #define abortGoto(...) do {printError(__VA_ARGS__); free(lRawRA); return NULL;} while(0) - unsigned char *lImgRA8 = NULL; - FILE *reader = fopen(fn, "rb"); - int lSuccess = fseek(reader, 0, SEEK_END); - long lRawSz = ftell(reader)- skipBytes; - if ((diskBytes > 0) && (diskBytes < lRawSz)) //only if diskBytes is known and does not exceed length of file - lRawSz = diskBytes; - if ((lSuccess != 0) || (lRawSz <= 8)) { - printError("Unable to load 0XC3 JPEG %s\n", fn); - return NULL; //read failure - } - lSuccess = fseek(reader, skipBytes, SEEK_SET); //If successful, the function returns zero - if (lSuccess != 0) { - printError("Unable to open 0XC3 JPEG %s\n", fn); - return NULL; //read failure - } - unsigned char *lRawRA = (unsigned char*) malloc(lRawSz); - size_t lSz = fread(lRawRA, 1, lRawSz, reader); - fclose(reader); - if ((lSz < (size_t)lRawSz) || (lRawRA[0] != 0xFF) || (lRawRA[1] != 0xD8) || (lRawRA[2] != 0xFF)) { - abortGoto("JPEG signature 0xFFD8FF not found at offset %d of %s\n", skipBytes, fn);//signature failure http://en.wikipedia.org/wiki/List_of_file_signatures - } - if (verbose) - printMessage("JPEG signature 0xFFD8FF found at offset %d of %s\n", skipBytes, fn); - //next: read header - long lRawPos = 2; //Skip initial 0xFFD8, begin with third byte - //long lRawPos = 0; //Skip initial 0xFFD8, begin with third byte - unsigned char btS1, btS2, SOSse, SOSahal, btMarkerType, SOSns = 0x00; //tag - unsigned char SOSpttrans = 0; - unsigned char SOSss = 0; - uint8_t SOFnf = 0; - uint8_t SOFprecision = 0; - uint16_t SOFydim = 0; - uint16_t SOFxdim = 0; - // long SOSarrayPos; //SOFarrayPos - int lnHufTables = 0; - int lFrameCount = 1; - const int kmaxFrames = 4; - struct HufTables l[kmaxFrames+1]; - do { //read each marker in the header - do { - btS1 = readByte(lRawRA, &lRawPos, lRawSz); - if (btS1 != 0xFF) { - abortGoto("JPEG header tag must begin with 0xFF\n"); - } - btMarkerType = readByte(lRawRA, &lRawPos, lRawSz); - if ((btMarkerType == 0x01) || (btMarkerType == 0xFF) || ((btMarkerType >= 0xD0) && (btMarkerType <= 0xD7) ) ) - btMarkerType = 0;//only process segments with length fields + int lByte = (lRawRA[*lRawPos] << *lCurrentBitPos) + (lRawRA[*lRawPos + 1] >> (8 - *lCurrentBitPos)); + lByte = lByte & 255; + int lHufValSSSS = l.LookUpRA[lByte]; + if (lHufValSSSS < 255) { + *lCurrentBitPos = l.SSSSszRA[lHufValSSSS] + *lCurrentBitPos; + *lRawPos = *lRawPos + (*lCurrentBitPos >> 3); + *lCurrentBitPos = *lCurrentBitPos & 7; + } else { //full SSSS is not in the first 8-bits + int lInput = lByte; + int lInputBits = 8; + (*lRawPos)++; // forward 8 bits = precisely 1 byte + do { + lInputBits++; + lInput = (lInput << 1) + readBit(lRawRA, lRawPos, lCurrentBitPos); + if (l.DHTliRA[lInputBits] != 0) { //if any entires with this length + for (int lI = l.DHTstartRA[lInputBits]; lI <= (l.DHTstartRA[lInputBits] + l.DHTliRA[lInputBits] - 1); lI++) { + if (lInput == l.HufCode[lI]) + lHufValSSSS = l.HufVal[lI]; + } //check each code + } //if any entries with this length + if ((lInputBits >= l.MaxHufSi) && (lHufValSSSS > 254)) { //exhausted options CR: added rev13 + lHufValSSSS = l.MaxHufVal; + } + } while (!(lHufValSSSS < 255)); // found; + } //answer in first 8 bits + //The HufVal is referred to as the SSSS in the Codec, so it is called 'lHufValSSSS' + if (lHufValSSSS == 0) //NO CHANGE + return 0; + if (lHufValSSSS == 1) { + if (readBit(lRawRA, lRawPos, lCurrentBitPos) == 0) + return -1; + else + return 1; + } + if (lHufValSSSS == 16) { //ALL CHANGE 16 bit difference: Codec H.1.2.2 "No extra bits are appended after SSSS = 16 is encoded." Osiris fails here + return 32768; + } + //to get here - there is a 2..15 bit difference + int lDiff = readBits(lRawRA, lRawPos, lCurrentBitPos, lHufValSSSS); + if (lDiff <= bitMask(lHufValSSSS - 1)) //add + lDiff = lDiff - bitMask(lHufValSSSS); + return lDiff; +} // decodePixelDifference() - } while ((lRawPos < lRawSz) && (btMarkerType == 0)); - uint16_t lSegmentLength = readWord (lRawRA, &lRawPos, lRawSz); //read marker length - long lSegmentEnd = lRawPos+(lSegmentLength - 2); - if (lSegmentEnd > lRawSz) { - abortGoto("Segment larger than image\n"); - } - if (verbose) - printMessage("btMarkerType %#02X length %d@%ld\n", btMarkerType, lSegmentLength, lRawPos); - if ( ((btMarkerType >= 0xC0) && (btMarkerType <= 0xC3)) || ((btMarkerType >= 0xC5) && (btMarkerType <= 0xCB)) || ((btMarkerType >= 0xCD) && (btMarkerType <= 0xCF)) ) { - //if Start-Of-Frame (SOF) marker - SOFprecision = readByte(lRawRA, &lRawPos, lRawSz); - SOFydim = readWord(lRawRA, &lRawPos, lRawSz); - SOFxdim = readWord(lRawRA, &lRawPos, lRawSz); - SOFnf = readByte(lRawRA, &lRawPos, lRawSz); - //SOFarrayPos = lRawPos; - lRawPos = (lSegmentEnd); - if (verbose) printMessage(" [Precision %d X*Y %d*%d Frames %d]\n", SOFprecision, SOFxdim, SOFydim, SOFnf); - if (btMarkerType != 0xC3) { //lImgTypeC3 = true; - abortGoto("This JPEG decoder can only decompress lossless JPEG ITU-T81 images (SoF must be 0XC3, not %#02X)\n",btMarkerType ); - } - if ( (SOFprecision < 1) || (SOFprecision > 16) || (SOFnf < 1) || (SOFnf == 2) || (SOFnf > 3) - || ((SOFnf == 3) && (SOFprecision > 8)) ) { - abortGoto("Scalar data must be 1..16 bit, RGB data must be 8-bit (%d-bit, %d frames)\n", SOFprecision, SOFnf); - } - } else if (btMarkerType == 0xC4) {//if SOF marker else if define-Huffman-tables marker (DHT) - if (verbose) printMessage(" [Huffman Length %d]\n", lSegmentLength); - do { - uint8_t DHTnLi = readByte(lRawRA, &lRawPos, lRawSz ); //we read but ignore DHTtcth. - #pragma unused(DHTnLi) //we need to increment the input file position, but we do not care what the value is - DHTnLi = 0; - for (int lInc = 1; lInc <= 16; lInc++) { - l[lFrameCount].DHTliRA[lInc] = readByte(lRawRA, &lRawPos, lRawSz); - DHTnLi = DHTnLi + l[lFrameCount].DHTliRA[lInc]; - if (l[lFrameCount].DHTliRA[lInc] != 0) l[lFrameCount].MaxHufSi = lInc; - if (verbose) printMessage("DHT has %d combinations with %d bits\n", l[lFrameCount].DHTliRA[lInc], lInc); +unsigned char *decode_JPEG_SOF_0XC3(const char *fn, int skipBytes, bool verbose, int *dimX, int *dimY, int *bits, int *frames, int diskBytes) { +//decompress JPEG image named "fn" where image data is located skipBytes into file. diskBytes is compressed size of image (set to 0 if unknown) +//next line breaks MSVC +#define abortGoto(...) do {printError(__VA_ARGS__); free(lRawRA); return NULL;} while(0) + unsigned char *lImgRA8 = NULL; + FILE *reader = fopen(fn, "rb"); + int lSuccess = fseek(reader, 0, SEEK_END); + long lRawSz = ftell(reader) - skipBytes; + if ((diskBytes > 0) && (diskBytes < lRawSz)) //only if diskBytes is known and does not exceed length of file + lRawSz = diskBytes; + if ((lSuccess != 0) || (lRawSz <= 8)) { + printError("Unable to load 0XC3 JPEG %s\n", fn); + return NULL; //read failure + } + lSuccess = fseek(reader, skipBytes, SEEK_SET); //If successful, the function returns zero + if (lSuccess != 0) { + printError("Unable to open 0XC3 JPEG %s\n", fn); + return NULL; //read failure + } + unsigned char *lRawRA = (unsigned char *)malloc(lRawSz); + size_t lSz = fread(lRawRA, 1, lRawSz, reader); + fclose(reader); + if ((lSz < (size_t)lRawSz) || (lRawRA[0] != 0xFF) || (lRawRA[1] != 0xD8) || (lRawRA[2] != 0xFF)) { + abortGoto("JPEG signature 0xFFD8FF not found at offset %d of %s\n", skipBytes, fn); //signature failure http://en.wikipedia.org/wiki/List_of_file_signatures + } + if (verbose) + printMessage("JPEG signature 0xFFD8FF found at offset %d of %s\n", skipBytes, fn); + //next: read header + long lRawPos = 2; //Skip initial 0xFFD8, begin with third byte + //long lRawPos = 0; //Skip initial 0xFFD8, begin with third byte + unsigned char btS1, btS2, SOSse, SOSahal, btMarkerType, SOSns = 0x00; //tag + unsigned char SOSpttrans = 0; + unsigned char SOSss = 0; + uint8_t SOFnf = 0; + uint8_t SOFprecision = 0; + uint16_t SOFydim = 0; + uint16_t SOFxdim = 0; + // long SOSarrayPos; //SOFarrayPos + int lnHufTables = 0; + int lFrameCount = 1; + const int kmaxFrames = 4; + struct HufTables l[kmaxFrames + 1]; + do { //read each marker in the header + do { + btS1 = readByte(lRawRA, &lRawPos, lRawSz); + if (btS1 != 0xFF) { + abortGoto("JPEG header tag must begin with 0xFF\n"); + } + btMarkerType = readByte(lRawRA, &lRawPos, lRawSz); + if ((btMarkerType == 0x01) || (btMarkerType == 0xFF) || ((btMarkerType >= 0xD0) && (btMarkerType <= 0xD7))) + btMarkerType = 0; //only process segments with length fields - } - if (DHTnLi > 17) { - abortGoto("Huffman table corrupted.\n"); - } - int lIncY = 0; //frequency - for (int lInc = 0; lInc <= 31; lInc++) {//lInc := 0 to 31 do begin - l[lFrameCount].HufVal[lInc] = -1; - l[lFrameCount].HufSz[lInc] = -1; - l[lFrameCount].HufCode[lInc] = -1; - } - for (int lInc = 1; lInc <= 16; lInc++) {//set the huffman size values - if (l[lFrameCount].DHTliRA[lInc] > 0) { - l[lFrameCount].DHTstartRA[lInc] = lIncY+1; - for (int lIncX = 1; lIncX <= l[lFrameCount].DHTliRA[lInc]; lIncX++) { - lIncY++; - btS1 = readByte(lRawRA, &lRawPos, lRawSz); - l[lFrameCount].HufVal[lIncY] = btS1; - l[lFrameCount].MaxHufVal = btS1; - if (verbose) printMessage("DHT combination %d has a value of %d\n", lIncY, btS1); - if (btS1 <= 16) //unsigned ints ALWAYS >0, so no need for(btS1 >= 0) - l[lFrameCount].HufSz[lIncY] = lInc; - else { - abortGoto("Huffman size array corrupted.\n"); - } - } - } - } //set huffman size values - int K = 1; - int Code = 0; - int Si = l[lFrameCount].HufSz[K]; - do { - while (Si == l[lFrameCount].HufSz[K]) { - l[lFrameCount].HufCode[K] = Code; - Code = Code + 1; - K++; - } - if (K <= DHTnLi) { - while (l[lFrameCount].HufSz[K] > Si) { - Code = Code << 1; //Shl!!! - Si = Si + 1; - }//while Si - }//K <= 17 + } while ((lRawPos < lRawSz) && (btMarkerType == 0)); + uint16_t lSegmentLength = readWord(lRawRA, &lRawPos, lRawSz); //read marker length + long lSegmentEnd = lRawPos + (lSegmentLength - 2); + if (lSegmentEnd > lRawSz) { + abortGoto("Segment larger than image\n"); + } + if (verbose) + printMessage("btMarkerType %#02X length %d@%ld\n", btMarkerType, lSegmentLength, lRawPos); + if (((btMarkerType >= 0xC0) && (btMarkerType <= 0xC3)) || ((btMarkerType >= 0xC5) && (btMarkerType <= 0xCB)) || ((btMarkerType >= 0xCD) && (btMarkerType <= 0xCF))) { + //if Start-Of-Frame (SOF) marker + SOFprecision = readByte(lRawRA, &lRawPos, lRawSz); + SOFydim = readWord(lRawRA, &lRawPos, lRawSz); + SOFxdim = readWord(lRawRA, &lRawPos, lRawSz); + SOFnf = readByte(lRawRA, &lRawPos, lRawSz); + //SOFarrayPos = lRawPos; + lRawPos = (lSegmentEnd); + if (verbose) + printMessage(" [Precision %d X*Y %d*%d Frames %d]\n", SOFprecision, SOFxdim, SOFydim, SOFnf); + if (btMarkerType != 0xC3) { //lImgTypeC3 = true; + abortGoto("This JPEG decoder can only decompress lossless JPEG ITU-T81 images (SoF must be 0XC3, not %#02X)\n", btMarkerType); + } + if ((SOFprecision < 1) || (SOFprecision > 16) || (SOFnf < 1) || (SOFnf == 2) || (SOFnf > 3) || ((SOFnf == 3) && (SOFprecision > 8))) { + abortGoto("Scalar data must be 1..16 bit, RGB data must be 8-bit (%d-bit, %d frames)\n", SOFprecision, SOFnf); + } + } else if (btMarkerType == 0xC4) { //if SOF marker else if define-Huffman-tables marker (DHT) + if (verbose) + printMessage(" [Huffman Length %d]\n", lSegmentLength); + do { + uint8_t DHTnLi = readByte(lRawRA, &lRawPos, lRawSz); //we read but ignore DHTtcth. +#pragma unused(DHTnLi) //we need to increment the input file position, but we do not care what the value is + DHTnLi = 0; + for (int lInc = 1; lInc <= 16; lInc++) { + l[lFrameCount].DHTliRA[lInc] = readByte(lRawRA, &lRawPos, lRawSz); + DHTnLi = DHTnLi + l[lFrameCount].DHTliRA[lInc]; + if (l[lFrameCount].DHTliRA[lInc] != 0) + l[lFrameCount].MaxHufSi = lInc; + if (verbose) + printMessage("DHT has %d combinations with %d bits\n", l[lFrameCount].DHTliRA[lInc], lInc); + } + if (DHTnLi > 17) { + abortGoto("Huffman table corrupted.\n"); + } + int lIncY = 0; //frequency + for (int lInc = 0; lInc <= 31; lInc++) { //lInc := 0 to 31 do begin + l[lFrameCount].HufVal[lInc] = -1; + l[lFrameCount].HufSz[lInc] = -1; + l[lFrameCount].HufCode[lInc] = -1; + } + for (int lInc = 1; lInc <= 16; lInc++) { //set the huffman size values + if (l[lFrameCount].DHTliRA[lInc] > 0) { + l[lFrameCount].DHTstartRA[lInc] = lIncY + 1; + for (int lIncX = 1; lIncX <= l[lFrameCount].DHTliRA[lInc]; lIncX++) { + lIncY++; + btS1 = readByte(lRawRA, &lRawPos, lRawSz); + l[lFrameCount].HufVal[lIncY] = btS1; + l[lFrameCount].MaxHufVal = btS1; + if (verbose) + printMessage("DHT combination %d has a value of %d\n", lIncY, btS1); + if (btS1 <= 16) //unsigned ints ALWAYS >0, so no need for(btS1 >= 0) + l[lFrameCount].HufSz[lIncY] = lInc; + else { + abortGoto("Huffman size array corrupted.\n"); + } + } + } + } //set huffman size values + int K = 1; + int Code = 0; + int Si = l[lFrameCount].HufSz[K]; + do { + while (Si == l[lFrameCount].HufSz[K]) { + l[lFrameCount].HufCode[K] = Code; + Code = Code + 1; + K++; + } + if (K <= DHTnLi) { + while (l[lFrameCount].HufSz[K] > Si) { + Code = Code << 1; //Shl!!! + Si = Si + 1; + } //while Si + } //K <= 17 - } while (K <= DHTnLi); - //if (verbose) - // for (int j = 1; j <= DHTnLi; j++) - // printMessage(" [%d Sz %d Code %d Value %d]\n", j, l[lFrameCount].HufSz[j], l[lFrameCount].HufCode[j], l[lFrameCount].HufVal[j]); - lFrameCount++; - } while ((lSegmentEnd-lRawPos) >= 18); - lnHufTables = lFrameCount - 1; - lRawPos = (lSegmentEnd); - if (verbose) printMessage(" [FrameCount %d]\n", lnHufTables); - } else if (btMarkerType == 0xDD) { //if DHT marker else if Define restart interval (DRI) marker - abortGoto("btMarkerType == 0xDD: unsupported Restart Segments\n"); - //lRestartSegmentSz = ReadWord(lRawRA, &lRawPos, lRawSz); - //lRawPos = lSegmentEnd; - } else if (btMarkerType == 0xDA) { //if DRI marker else if read Start of Scan (SOS) marker - SOSns = readByte(lRawRA, &lRawPos, lRawSz); - //if Ns = 1 then NOT interleaved, else interleaved: see B.2.3 - // SOSarrayPos = lRawPos; //not required... - if (SOSns > 0) { - for (int lInc = 1; lInc <= SOSns; lInc++) { - btS1 = readByte(lRawRA, &lRawPos, lRawSz); //component identifier 1=Y,2=Cb,3=Cr,4=I,5=Q - #pragma unused(btS1) //dummy value used to increment file position - btS2 = readByte(lRawRA, &lRawPos, lRawSz); //horizontal and vertical sampling factors - #pragma unused(btS2) //dummy value used to increment file position - } - } - SOSss = readByte(lRawRA, &lRawPos, lRawSz); //predictor selection B.3 - SOSse = readByte(lRawRA, &lRawPos, lRawSz); - #pragma unused(SOSse) //dummy value used to increment file position - SOSahal = readByte(lRawRA, &lRawPos, lRawSz); //lower 4bits= pointtransform - SOSpttrans = SOSahal & 16; - if (verbose) - printMessage(" [Predictor: %d Transform %d]\n", SOSss, SOSahal); - lRawPos = (lSegmentEnd); - } else //if SOS marker else skip marker - lRawPos = (lSegmentEnd); - } while ((lRawPos < lRawSz) && (btMarkerType != 0xDA)); //0xDA=Start of scan: loop for reading header - //NEXT: Huffman decoding - if (lnHufTables < 1) { - abortGoto("Decoding error: no Huffman tables.\n"); - } - //NEXT: unpad data - delete byte that follows $FF - //int lIsRestartSegments = 0; - long lIncI = lRawPos; //input position - long lIncO = lRawPos; //output position - do { - lRawRA[lIncO] = lRawRA[lIncI]; - if (lRawRA[lIncI] == 255) { - if (lRawRA[lIncI+1] == 0) - lIncI = lIncI+1; - else if (lRawRA[lIncI+1] == 0xD9) - lIncO = -666; //end of padding - //else - // lIsRestartSegments = lRawRA[lIncI+1]; - } - lIncI++; - lIncO++; - } while (lIncO > 0); - //if (lIsRestartSegments != 0) //detects both restart and corruption https://groups.google.com/forum/#!topic/comp.protocols.dicom/JUuz0B_aE5o - // printWarning("Detected restart segments, decompress with dcmdjpeg or gdcmconv 0xFF%02X.\n", lIsRestartSegments); - //NEXT: some RGB images use only a single Huffman table for all 3 colour planes. In this case, replicate the correct values - //NEXT: prepare lookup table - for (int lFrameCount = 1; lFrameCount <= lnHufTables; lFrameCount ++) { - for (int lInc = 0; lInc <= 17; lInc ++) - l[lFrameCount].SSSSszRA[lInc] = 123; //Impossible value for SSSS, suggests 8-bits can not describe answer - for (int lInc = 0; lInc <= 255; lInc ++) - l[lFrameCount].LookUpRA[lInc] = 255; //Impossible value for SSSS, suggests 8-bits can not describe answer - } - //NEXT: fill lookuptable - for (int lFrameCount = 1; lFrameCount <= lnHufTables; lFrameCount ++) { - int lIncY = 0; - for (int lSz = 1; lSz <= 8; lSz ++) { //set the huffman lookup table for keys with lengths <=8 - if (l[lFrameCount].DHTliRA[lSz]> 0) { - for (int lIncX = 1; lIncX <= l[lFrameCount].DHTliRA[lSz]; lIncX ++) { - lIncY++; - int lHufVal = l[lFrameCount].HufVal[lIncY]; //SSSS - l[lFrameCount].SSSSszRA[lHufVal] = lSz; - int k = (l[lFrameCount].HufCode[lIncY] << (8-lSz )) & 255; //K= most sig bits for hufman table - if (lSz < 8) { //fill in all possible bits that exceed the huffman table - int lInc = bitMask(8-lSz); - for (int lCurrentBitPos = 0; lCurrentBitPos <= lInc; lCurrentBitPos++) { - l[lFrameCount].LookUpRA[k+lCurrentBitPos] = lHufVal; - } - } else - l[lFrameCount].LookUpRA[k] = lHufVal; //SSSS - //printMessage("Frame %d SSSS %d Size %d Code %d SHL %d EmptyBits %ld\n", lFrameCount, lHufRA[lFrameCount][lIncY].HufVal, lHufRA[lFrameCount][lIncY].HufSz,lHufRA[lFrameCount][lIncY].HufCode, k, lInc); - } //Set SSSS - } //Length of size lInc > 0 - } //for lInc := 1 to 8 - } //For each frame, e.g. once each for Red/Green/Blue - //NEXT: some RGB images use only a single Huffman table for all 3 colour planes. In this case, replicate the correct values - if (lnHufTables < SOFnf) { //use single Hufman table for each frame - for (int lFrameCount = lnHufTables+1; lFrameCount <= SOFnf; lFrameCount++) { - l[lFrameCount] = l[lnHufTables]; + } while (K <= DHTnLi); + //if (verbose) + // for (int j = 1; j <= DHTnLi; j++) + // printMessage(" [%d Sz %d Code %d Value %d]\n", j, l[lFrameCount].HufSz[j], l[lFrameCount].HufCode[j], l[lFrameCount].HufVal[j]); + lFrameCount++; + } while ((lSegmentEnd - lRawPos) >= 18); + lnHufTables = lFrameCount - 1; + lRawPos = (lSegmentEnd); + if (verbose) + printMessage(" [FrameCount %d]\n", lnHufTables); + } else if (btMarkerType == 0xDD) { //if DHT marker else if Define restart interval (DRI) marker + abortGoto("btMarkerType == 0xDD: unsupported Restart Segments\n"); + //lRestartSegmentSz = ReadWord(lRawRA, &lRawPos, lRawSz); + //lRawPos = lSegmentEnd; + } else if (btMarkerType == 0xDA) { //if DRI marker else if read Start of Scan (SOS) marker + SOSns = readByte(lRawRA, &lRawPos, lRawSz); + //if Ns = 1 then NOT interleaved, else interleaved: see B.2.3 + // SOSarrayPos = lRawPos; //not required... + if (SOSns > 0) { + for (int lInc = 1; lInc <= SOSns; lInc++) { + btS1 = readByte(lRawRA, &lRawPos, lRawSz); //component identifier 1=Y,2=Cb,3=Cr,4=I,5=Q +#pragma unused(btS1) //dummy value used to increment file position + btS2 = readByte(lRawRA, &lRawPos, lRawSz); //horizontal and vertical sampling factors +#pragma unused(btS2) //dummy value used to increment file position + } + } + SOSss = readByte(lRawRA, &lRawPos, lRawSz); //predictor selection B.3 + SOSse = readByte(lRawRA, &lRawPos, lRawSz); +#pragma unused(SOSse) //dummy value used to increment file position + SOSahal = readByte(lRawRA, &lRawPos, lRawSz); //lower 4bits= pointtransform + SOSpttrans = SOSahal & 16; + if (verbose) + printMessage(" [Predictor: %d Transform %d]\n", SOSss, SOSahal); + lRawPos = (lSegmentEnd); + } else //if SOS marker else skip marker + lRawPos = (lSegmentEnd); + } while ((lRawPos < lRawSz) && (btMarkerType != 0xDA)); //0xDA=Start of scan: loop for reading header + //NEXT: Huffman decoding + if (lnHufTables < 1) { + abortGoto("Decoding error: no Huffman tables.\n"); + } + //NEXT: unpad data - delete byte that follows $FF + //int lIsRestartSegments = 0; + long lIncI = lRawPos; //input position + long lIncO = lRawPos; //output position + do { + lRawRA[lIncO] = lRawRA[lIncI]; + if (lRawRA[lIncI] == 255) { + if (lRawRA[lIncI + 1] == 0) + lIncI = lIncI + 1; + else if (lRawRA[lIncI + 1] == 0xD9) + lIncO = -666; //end of padding + //else + // lIsRestartSegments = lRawRA[lIncI+1]; + } + lIncI++; + lIncO++; + } while (lIncO > 0); + //if (lIsRestartSegments != 0) //detects both restart and corruption https://groups.google.com/forum/#!topic/comp.protocols.dicom/JUuz0B_aE5o + // printWarning("Detected restart segments, decompress with dcmdjpeg or gdcmconv 0xFF%02X.\n", lIsRestartSegments); + //NEXT: some RGB images use only a single Huffman table for all 3 colour planes. In this case, replicate the correct values + //NEXT: prepare lookup table + for (int lFrameCount = 1; lFrameCount <= lnHufTables; lFrameCount++) { + for (int lInc = 0; lInc <= 17; lInc++) + l[lFrameCount].SSSSszRA[lInc] = 123; //Impossible value for SSSS, suggests 8-bits can not describe answer + for (int lInc = 0; lInc <= 255; lInc++) + l[lFrameCount].LookUpRA[lInc] = 255; //Impossible value for SSSS, suggests 8-bits can not describe answer + } + //NEXT: fill lookuptable + for (int lFrameCount = 1; lFrameCount <= lnHufTables; lFrameCount++) { + int lIncY = 0; + for (int lSz = 1; lSz <= 8; lSz++) { //set the huffman lookup table for keys with lengths <=8 + if (l[lFrameCount].DHTliRA[lSz] > 0) { + for (int lIncX = 1; lIncX <= l[lFrameCount].DHTliRA[lSz]; lIncX++) { + lIncY++; + int lHufVal = l[lFrameCount].HufVal[lIncY]; //SSSS + l[lFrameCount].SSSSszRA[lHufVal] = lSz; + int k = (l[lFrameCount].HufCode[lIncY] << (8 - lSz)) & 255; //K= most sig bits for hufman table + if (lSz < 8) { //fill in all possible bits that exceed the huffman table + int lInc = bitMask(8 - lSz); + for (int lCurrentBitPos = 0; lCurrentBitPos <= lInc; lCurrentBitPos++) { + l[lFrameCount].LookUpRA[k + lCurrentBitPos] = lHufVal; + } + } else + l[lFrameCount].LookUpRA[k] = lHufVal; //SSSS + //printMessage("Frame %d SSSS %d Size %d Code %d SHL %d EmptyBits %ld\n", lFrameCount, lHufRA[lFrameCount][lIncY].HufVal, lHufRA[lFrameCount][lIncY].HufSz,lHufRA[lFrameCount][lIncY].HufCode, k, lInc); + } //Set SSSS + } //Length of size lInc > 0 + } //for lInc := 1 to 8 + } //For each frame, e.g. once each for Red/Green/Blue + //NEXT: some RGB images use only a single Huffman table for all 3 colour planes. In this case, replicate the correct values + if (lnHufTables < SOFnf) { //use single Hufman table for each frame + for (int lFrameCount = lnHufTables + 1; lFrameCount <= SOFnf; lFrameCount++) { + l[lFrameCount] = l[lnHufTables]; - } //for each frame - } // if lnHufTables < SOFnf - //NEXT: uncompress data: different loops for different predictors - int lItems = SOFxdim*SOFydim*SOFnf; - // lRawPos++;// <- only for Pascal where array is indexed from 1 not 0 first byte of data - int lCurrentBitPos = 0; //read in a new byte - //depending on SOSss, we see Table H.1 - int lPredA = 0; - int lPredB = 0; - int lPredC = 0; - if (SOSss == 2) //predictor selection 2: above - lPredA = SOFxdim-1; - else if (SOSss == 3) //predictor selection 3: above+left - lPredA = SOFxdim; - else if ((SOSss == 4) || (SOSss == 5)) { //these use left, above and above+left WEIGHT LEFT - lPredA = 0; //Ra left - lPredB = SOFxdim-1; //Rb directly above - lPredC = SOFxdim; //Rc UpperLeft:above and to the left - } else if (SOSss == 6) { //also use left, above and above+left, WEIGHT ABOVE - lPredB = 0; - lPredA = SOFxdim-1; //Rb directly above - lPredC = SOFxdim; //Rc UpperLeft:above and to the left - } else - lPredA = 0; //Ra: directly to left) - if (SOFprecision > 8) { //start - 16 bit data - *bits = 16; - int lPx = -1; //pixel position - int lPredicted = 1 << (SOFprecision-1-SOSpttrans); - lImgRA8 = (unsigned char*) malloc(lItems * 2); - uint16_t *lImgRA16 = (uint16_t*) lImgRA8; - for (int i = 0; i < lItems; i++) - lImgRA16[i] = 0; //zero array - int frame = 1; - for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor - lPx++; //writenext voxel - if (lIncX > 1) lPredicted = lImgRA16[lPx-1]; - lImgRA16[lPx] = lPredicted+ decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); - } - for (int lIncY = 2; lIncY <= SOFydim; lIncY++) {//for all subsequent rows - lPx++; //write next voxel - lPredicted = lImgRA16[lPx-SOFxdim]; //use ABOVE - lImgRA16[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); - if (SOSss == 4) { - for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { - lPredicted = lImgRA16[lPx-lPredA]+lImgRA16[lPx-lPredB]-lImgRA16[lPx-lPredC]; - lPx++; //writenext voxel - lImgRA16[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); - } //for lIncX - } else if ((SOSss == 5) || (SOSss == 6)) { - for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { - lPredicted = lImgRA16[lPx-lPredA]+ ((lImgRA16[lPx-lPredB]-lImgRA16[lPx-lPredC]) >> 1); - lPx++; //writenext voxel - lImgRA16[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); - } //for lIncX - } else if (SOSss == 7) { - for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { - lPx++; //writenext voxel - lPredicted = (lImgRA16[lPx-1]+lImgRA16[lPx-SOFxdim]) >> 1; - lImgRA16[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); - } //for lIncX - } else { //SOSss 1,2,3 read single values - for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { - lPredicted = lImgRA16[lPx-lPredA]; - lPx++; //writenext voxel - lImgRA16[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); - } //for lIncX - } // if..else possible predictors - }//for lIncY - } else if (SOFnf == 3) { //if 16-bit data; else 8-bit 3 frames - *bits = 8; - lImgRA8 = (unsigned char*) malloc(lItems ); - int lPx[kmaxFrames+1], lPredicted[kmaxFrames+1]; //pixel position - for (int f = 1; f <= SOFnf; f++) { - lPx[f] = ((f-1) * (SOFxdim * SOFydim) ) -1; - lPredicted[f] = 1 << (SOFprecision-1-SOSpttrans); - } - for (int i = 0; i < lItems; i++) - lImgRA8[i] = 255; //zero array - for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor - for (int f = 1; f <= SOFnf; f++) { - lPx[f]++; //writenext voxel - if (lIncX > 1) lPredicted[f] = lImgRA8[lPx[f]-1]; - lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); - } - } //first row always predicted by LEFT - for (int lIncY = 2; lIncY <= SOFydim; lIncY++) {//for all subsequent rows - for (int f = 1; f <= SOFnf; f++) { - lPx[f]++; //write next voxel - lPredicted[f] = lImgRA8[lPx[f]-SOFxdim]; //use ABOVE - lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); - }//first column of row always predicted by ABOVE - if (SOSss == 4) { - for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { - for (int f = 1; f <= SOFnf; f++) { - lPredicted[f] = lImgRA8[lPx[f]-lPredA]+lImgRA8[lPx[f]-lPredB]-lImgRA8[lPx[f]-lPredC]; - lPx[f]++; //writenext voxel - lImgRA8[lPx[f]] = lPredicted[f]+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); - } - } //for lIncX - } else if ((SOSss == 5) || (SOSss == 6)) { - for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { - for (int f = 1; f <= SOFnf; f++) { - lPredicted[f] = lImgRA8[lPx[f]-lPredA]+ ((lImgRA8[lPx[f]-lPredB]-lImgRA8[lPx[f]-lPredC]) >> 1); - lPx[f]++; //writenext voxel - lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); - } - } //for lIncX - } else if (SOSss == 7) { - for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { - for (int f = 1; f <= SOFnf; f++) { - lPx[f]++; //writenext voxel - lPredicted[f] = (lImgRA8[lPx[f]-1]+lImgRA8[lPx[f]-SOFxdim]) >> 1; - lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); - } - } //for lIncX - } else { //SOSss 1,2,3 read single values - for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { - for (int f = 1; f <= SOFnf; f++) { - lPredicted[f] = lImgRA8[lPx[f]-lPredA]; - lPx[f]++; //writenext voxel - lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); - } - } //for lIncX - } // if..else possible predictors - }//for lIncY - } else { //if 8-bit data 3frames; else 8-bit 1 frames - *bits = 8; - lImgRA8 = (unsigned char*) malloc(lItems ); - int lPx = -1; //pixel position - int lPredicted = 1 << (SOFprecision-1-SOSpttrans); - for (int i = 0; i < lItems; i++) - lImgRA8[i] = 0; //zero array - for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor - lPx++; //writenext voxel - if (lIncX > 1) lPredicted = lImgRA8[lPx-1]; - int dx = decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); - lImgRA8[lPx] = lPredicted+dx; - } - for (int lIncY = 2; lIncY <= SOFydim; lIncY++) {//for all subsequent rows - lPx++; //write next voxel - lPredicted = lImgRA8[lPx-SOFxdim]; //use ABOVE - lImgRA8[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); - if (SOSss == 4) { - for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { - lPredicted = lImgRA8[lPx-lPredA]+lImgRA8[lPx-lPredB]-lImgRA8[lPx-lPredC]; - lPx++; //writenext voxel - lImgRA8[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); - } //for lIncX - } else if ((SOSss == 5) || (SOSss == 6)) { - for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { - lPredicted = lImgRA8[lPx-lPredA]+ ((lImgRA8[lPx-lPredB]-lImgRA8[lPx-lPredC]) >> 1); - lPx++; //writenext voxel - lImgRA8[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); - } //for lIncX - } else if (SOSss == 7) { - for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { - lPx++; //writenext voxel - lPredicted = (lImgRA8[lPx-1]+lImgRA8[lPx-SOFxdim]) >> 1; - lImgRA8[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); - } //for lIncX - } else { //SOSss 1,2,3 read single values - for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { - lPredicted = lImgRA8[lPx-lPredA]; - lPx++; //writenext voxel - lImgRA8[lPx] = lPredicted+decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); - } //for lIncX - } // if..else possible predictors - }//for lIncY - } //if 16bit else 8bit - free(lRawRA); - *dimX = SOFxdim; - *dimY = SOFydim; - *frames = SOFnf; - if (verbose) - printMessage("JPEG ends %ld@%ld\n", lRawPos, lRawPos+skipBytes); - return lImgRA8; -}// decode_JPEG_SOF_0XC3() + } //for each frame + } // if lnHufTables < SOFnf + //NEXT: uncompress data: different loops for different predictors + int lItems = SOFxdim * SOFydim * SOFnf; + // lRawPos++;// <- only for Pascal where array is indexed from 1 not 0 first byte of data + int lCurrentBitPos = 0; //read in a new byte + //depending on SOSss, we see Table H.1 + int lPredA = 0; + int lPredB = 0; + int lPredC = 0; + if (SOSss == 2) //predictor selection 2: above + lPredA = SOFxdim - 1; + else if (SOSss == 3) //predictor selection 3: above+left + lPredA = SOFxdim; + else if ((SOSss == 4) || (SOSss == 5)) { //these use left, above and above+left WEIGHT LEFT + lPredA = 0; //Ra left + lPredB = SOFxdim - 1; //Rb directly above + lPredC = SOFxdim; //Rc UpperLeft:above and to the left + } else if (SOSss == 6) { //also use left, above and above+left, WEIGHT ABOVE + lPredB = 0; + lPredA = SOFxdim - 1; //Rb directly above + lPredC = SOFxdim; //Rc UpperLeft:above and to the left + } else + lPredA = 0; //Ra: directly to left) + if (SOFprecision > 8) { //start - 16 bit data + *bits = 16; + int lPx = -1; //pixel position + int lPredicted = 1 << (SOFprecision - 1 - SOSpttrans); + lImgRA8 = (unsigned char *)malloc(lItems * 2); + uint16_t *lImgRA16 = (uint16_t *)lImgRA8; + for (int i = 0; i < lItems; i++) + lImgRA16[i] = 0; //zero array + int frame = 1; + for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor + lPx++; //writenext voxel + if (lIncX > 1) + lPredicted = lImgRA16[lPx - 1]; + lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); + } + for (int lIncY = 2; lIncY <= SOFydim; lIncY++) { //for all subsequent rows + lPx++; //write next voxel + lPredicted = lImgRA16[lPx - SOFxdim]; //use ABOVE + lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); + if (SOSss == 4) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPredicted = lImgRA16[lPx - lPredA] + lImgRA16[lPx - lPredB] - lImgRA16[lPx - lPredC]; + lPx++; //writenext voxel + lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); + } //for lIncX + } else if ((SOSss == 5) || (SOSss == 6)) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPredicted = lImgRA16[lPx - lPredA] + ((lImgRA16[lPx - lPredB] - lImgRA16[lPx - lPredC]) >> 1); + lPx++; //writenext voxel + lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); + } //for lIncX + } else if (SOSss == 7) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPx++; //writenext voxel + lPredicted = (lImgRA16[lPx - 1] + lImgRA16[lPx - SOFxdim]) >> 1; + lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); + } //for lIncX + } else { //SOSss 1,2,3 read single values + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPredicted = lImgRA16[lPx - lPredA]; + lPx++; //writenext voxel + lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); + } //for lIncX + } // if..else possible predictors + } //for lIncY + } else if (SOFnf == 3) { //if 16-bit data; else 8-bit 3 frames + *bits = 8; + lImgRA8 = (unsigned char *)malloc(lItems); + int lPx[kmaxFrames + 1], lPredicted[kmaxFrames + 1]; //pixel position + for (int f = 1; f <= SOFnf; f++) { + lPx[f] = ((f - 1) * (SOFxdim * SOFydim)) - 1; + lPredicted[f] = 1 << (SOFprecision - 1 - SOSpttrans); + } + for (int i = 0; i < lItems; i++) + lImgRA8[i] = 255; //zero array + for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor + for (int f = 1; f <= SOFnf; f++) { + lPx[f]++; //writenext voxel + if (lIncX > 1) + lPredicted[f] = lImgRA8[lPx[f] - 1]; + lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); + } + } //first row always predicted by LEFT + for (int lIncY = 2; lIncY <= SOFydim; lIncY++) { //for all subsequent rows + for (int f = 1; f <= SOFnf; f++) { + lPx[f]++; //write next voxel + lPredicted[f] = lImgRA8[lPx[f] - SOFxdim]; //use ABOVE + lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); + } //first column of row always predicted by ABOVE + if (SOSss == 4) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + for (int f = 1; f <= SOFnf; f++) { + lPredicted[f] = lImgRA8[lPx[f] - lPredA] + lImgRA8[lPx[f] - lPredB] - lImgRA8[lPx[f] - lPredC]; + lPx[f]++; //writenext voxel + lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); + } + } //for lIncX + } else if ((SOSss == 5) || (SOSss == 6)) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + for (int f = 1; f <= SOFnf; f++) { + lPredicted[f] = lImgRA8[lPx[f] - lPredA] + ((lImgRA8[lPx[f] - lPredB] - lImgRA8[lPx[f] - lPredC]) >> 1); + lPx[f]++; //writenext voxel + lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); + } + } //for lIncX + } else if (SOSss == 7) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + for (int f = 1; f <= SOFnf; f++) { + lPx[f]++; //writenext voxel + lPredicted[f] = (lImgRA8[lPx[f] - 1] + lImgRA8[lPx[f] - SOFxdim]) >> 1; + lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); + } + } //for lIncX + } else { //SOSss 1,2,3 read single values + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + for (int f = 1; f <= SOFnf; f++) { + lPredicted[f] = lImgRA8[lPx[f] - lPredA]; + lPx[f]++; //writenext voxel + lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); + } + } //for lIncX + } // if..else possible predictors + } //for lIncY + } else { //if 8-bit data 3frames; else 8-bit 1 frames + *bits = 8; + lImgRA8 = (unsigned char *)malloc(lItems); + int lPx = -1; //pixel position + int lPredicted = 1 << (SOFprecision - 1 - SOSpttrans); + for (int i = 0; i < lItems; i++) + lImgRA8[i] = 0; //zero array + for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor + lPx++; //writenext voxel + if (lIncX > 1) + lPredicted = lImgRA8[lPx - 1]; + int dx = decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); + lImgRA8[lPx] = lPredicted + dx; + } + for (int lIncY = 2; lIncY <= SOFydim; lIncY++) { //for all subsequent rows + lPx++; //write next voxel + lPredicted = lImgRA8[lPx - SOFxdim]; //use ABOVE + lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); + if (SOSss == 4) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPredicted = lImgRA8[lPx - lPredA] + lImgRA8[lPx - lPredB] - lImgRA8[lPx - lPredC]; + lPx++; //writenext voxel + lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); + } //for lIncX + } else if ((SOSss == 5) || (SOSss == 6)) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPredicted = lImgRA8[lPx - lPredA] + ((lImgRA8[lPx - lPredB] - lImgRA8[lPx - lPredC]) >> 1); + lPx++; //writenext voxel + lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); + } //for lIncX + } else if (SOSss == 7) { + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPx++; //writenext voxel + lPredicted = (lImgRA8[lPx - 1] + lImgRA8[lPx - SOFxdim]) >> 1; + lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); + } //for lIncX + } else { //SOSss 1,2,3 read single values + for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { + lPredicted = lImgRA8[lPx - lPredA]; + lPx++; //writenext voxel + lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); + } //for lIncX + } // if..else possible predictors + } //for lIncY + } //if 16bit else 8bit + free(lRawRA); + *dimX = SOFxdim; + *dimY = SOFydim; + *frames = SOFnf; + if (verbose) + printMessage("JPEG ends %ld@%ld\n", lRawPos, lRawPos + skipBytes); + return lImgRA8; +} // decode_JPEG_SOF_0XC3() diff --git a/console/main_console.cpp b/console/main_console.cpp index 8e91a324..68d44f7d 100644 --- a/console/main_console.cpp +++ b/console/main_console.cpp @@ -1,6 +1,6 @@ -// main.m dcm2niix +//main_console.cpp dcm2niix // by Chris Rorden on 3/22/14, see license.txt -// Copyright (c) 2014 Chris Rorden. All rights reserved. +// Copyright (c) 2014-2021 Chris Rorden. All rights reserved. //g++ -O3 main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -s -o dcm2niix -lz @@ -12,7 +12,6 @@ // sudo ./configure; // sudo make - //to generate combined 32-bit and 64-bit builds for OSX : // g++ -O3 -x c++ main_console.c nii_dicom.c nifti1_io_core.c nii_ortho.c nii_dicom_batch.c -s -arch x86_64 -o dcm2niix64 -lz // g++ -O3 -x c++ main_console.c nii_dicom.c nifti1_io_core.c nii_ortho.c nii_dicom_batch.c -s -arch i386 -o dcm2niix32 -lz @@ -25,203 +24,200 @@ // vcvarsall amd64 // cl /EHsc 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 -DmyDisableOpenJPEG /o dcm2niix - //#define mydebugtest //automatically process directory specified in main, ignore input arguments +#include +#include #include //requires VS 2015 or later +#include #include -#include -#include #include -#include -#include +#include //#include -#include // clock_t, clock, CLOCKS_PER_SEC -#include -#include "nii_dicom_batch.h" -#include "nii_dicom.h" #include "nifti1_io_core.h" +#include "nii_dicom.h" +#include "nii_dicom_batch.h" #include +#include +#include // clock_t, clock, CLOCKS_PER_SEC #if !defined(_WIN64) && !defined(_WIN32) -#include #include -double get_wall_time(){ - struct timeval time; - if (gettimeofday(&time,NULL)){ - // Handle error - return 0; - } - return (double)time.tv_sec + (double)time.tv_usec * .000001; +#include +double get_wall_time() { + struct timeval time; + if (gettimeofday(&time, NULL)) { + // Handle error + return 0; + } + return (double)time.tv_sec + (double)time.tv_usec * .000001; } #endif - -const char* removePath(const char* path) { // "/usr/path/filename.exe" -> "filename.exe" - const char* pDelimeter = strrchr (path, '\\'); - if (pDelimeter) - path = pDelimeter+1; - pDelimeter = strrchr (path, '/'); - if (pDelimeter) - path = pDelimeter+1; - return path; +const char *removePath(const char *path) { // "/usr/path/filename.exe" -> "filename.exe" + const char *pDelimeter = strrchr(path, '\\'); + if (pDelimeter) + path = pDelimeter + 1; + pDelimeter = strrchr(path, '/'); + if (pDelimeter) + path = pDelimeter + 1; + return path; } //removePath() char bool2Char(bool b) { - if (b) return('y'); - return('n'); + if (b) + return ('y'); + return ('n'); } -void showHelp(const char * argv[], struct TDCMopts opts) { - const char *cstr = removePath(argv[0]); - printf("usage: %s [options] \n", cstr); - printf(" Options :\n"); - printf(" -1..-9 : gz compression level (1=fastest..9=smallest, default %d)\n", opts.gzLevel); - printf(" -a : adjacent DICOMs (images from same series always in same folder) for faster conversion (n/y, default n)\n"); - printf(" -b : BIDS sidecar (y/n/o [o=only: no NIfTI], default %c)\n", bool2Char(opts.isCreateBIDS)); - printf(" -ba : anonymize BIDS (y/n, default %c)\n", bool2Char(opts.isAnonymizeBIDS)); - printf(" -c : comment stored in NIfTI aux_file (provide up to 24 characters e.g. '-c first_visit')\n"); - printf(" -d : directory search depth. Convert DICOMs in sub-folders of in_folder? (0..9, default %d)\n", opts.dirSearchDepth); - printf(" -e : export as NRRD instead of NIfTI (y/n, default n)\n"); - #ifdef mySegmentByAcq - #define kQstr " %%q=sequence number," - #else - #define kQstr "" - #endif - printf(" -f : filename (%%a=antenna (coil) name, %%b=basename, %%c=comments, %%d=description, %%e=echo number, %%f=folder name, %%g=accession number, %%i=ID of patient, %%j=seriesInstanceUID, %%k=studyInstanceUID, %%m=manufacturer, %%n=name of patient, %%o=mediaObjectInstanceUID, %%p=protocol,%s %%r=instance number, %%s=series number, %%t=time, %%u=acquisition number, %%v=vendor, %%x=study ID; %%z=sequence name; default '%s')\n", kQstr, opts.filename); - printf(" -g : generate defaults file (y/n/o/i [o=only: reset and write defaults; i=ignore: reset defaults], default n)\n"); - printf(" -h : show help\n"); - printf(" -i : ignore derived, localizer and 2D images (y/n, default n)\n"); +void showHelp(const char *argv[], struct TDCMopts opts) { + const char *cstr = removePath(argv[0]); + printf("usage: %s [options] \n", cstr); + printf(" Options :\n"); + printf(" -1..-9 : gz compression level (1=fastest..9=smallest, default %d)\n", opts.gzLevel); + printf(" -a : adjacent DICOMs (images from same series always in same folder) for faster conversion (n/y, default n)\n"); + printf(" -b : BIDS sidecar (y/n/o [o=only: no NIfTI], default %c)\n", bool2Char(opts.isCreateBIDS)); + printf(" -ba : anonymize BIDS (y/n, default %c)\n", bool2Char(opts.isAnonymizeBIDS)); + printf(" -c : comment stored in NIfTI aux_file (provide up to 24 characters e.g. '-c first_visit')\n"); + printf(" -d : directory search depth. Convert DICOMs in sub-folders of in_folder? (0..9, default %d)\n", opts.dirSearchDepth); + printf(" -e : export as NRRD instead of NIfTI (y/n, default n)\n"); +#ifdef mySegmentByAcq +#define kQstr " %%q=sequence number," +#else +#define kQstr "" +#endif + printf(" -f : filename (%%a=antenna (coil) name, %%b=basename, %%c=comments, %%d=description, %%e=echo number, %%f=folder name, %%g=accession number, %%i=ID of patient, %%j=seriesInstanceUID, %%k=studyInstanceUID, %%m=manufacturer, %%n=name of patient, %%o=mediaObjectInstanceUID, %%p=protocol,%s %%r=instance number, %%s=series number, %%t=time, %%u=acquisition number, %%v=vendor, %%x=study ID; %%z=sequence name; default '%s')\n", kQstr, opts.filename); + printf(" -g : generate defaults file (y/n/o/i [o=only: reset and write defaults; i=ignore: reset defaults], default n)\n"); + printf(" -h : show help\n"); + printf(" -i : ignore derived, localizer and 2D images (y/n, default n)\n"); char max16Ch = 'n'; - if (opts.isMaximize16BitRange == kMaximize16BitRange_True) max16Ch = 'y'; - if (opts.isMaximize16BitRange == kMaximize16BitRange_Raw) max16Ch = 'o'; - printf(" -l : losslessly scale 16-bit integers to use dynamic range (y/n/o [yes=scale, no=no, but uint16->int16, o=original], default %c)\n", max16Ch); - printf(" -m : merge 2D slices from same series regardless of echo, exposure, etc. (n/y or 0/1/2, default 2) [no, yes, auto]\n"); - printf(" -n : only convert this series CRC number - can be used up to %i times (default convert all)\n", MAX_NUM_SERIES); - printf(" -o : output directory (omit to save to input folder)\n"); - printf(" -p : Philips precise float (not display) scaling (y/n, default y)\n"); - printf(" -r : rename instead of convert DICOMs (y/n, default n)\n"); - printf(" -s : single file mode, do not convert other images in folder (y/n, default n)\n"); - //text notes replaced with BIDS: this function is deprecated - //printf(" -t : text notes includes private patient details (y/n, default n)\n"); - #if !defined(_WIN64) && !defined(_WIN32) //shell script for Unix only + if (opts.isMaximize16BitRange == kMaximize16BitRange_True) + max16Ch = 'y'; + if (opts.isMaximize16BitRange == kMaximize16BitRange_Raw) + max16Ch = 'o'; + printf(" -l : losslessly scale 16-bit integers to use dynamic range (y/n/o [yes=scale, no=no, but uint16->int16, o=original], default %c)\n", max16Ch); + printf(" -m : merge 2D slices from same series regardless of echo, exposure, etc. (n/y or 0/1/2, default 2) [no, yes, auto]\n"); + printf(" -n : only convert this series CRC number - can be used up to %i times (default convert all)\n", MAX_NUM_SERIES); + printf(" -o : output directory (omit to save to input folder)\n"); + printf(" -p : Philips precise float (not display) scaling (y/n, default y)\n"); + printf(" -r : rename instead of convert DICOMs (y/n, default n)\n"); + printf(" -s : single file mode, do not convert other images in folder (y/n, default n)\n"); +//text notes replaced with BIDS: this function is deprecated +//printf(" -t : text notes includes private patient details (y/n, default n)\n"); +#if !defined(_WIN64) && !defined(_WIN32) //shell script for Unix only printf(" -u : up-to-date check\n"); - #endif +#endif printf(" -v : verbose (n/y or 0/1/2, default 0) [no, yes, logorrheic]\n"); -//#define kNAME_CONFLICT_SKIP 0 //0 = write nothing for a file that exists with desired name -//#define kNAME_CONFLICT_OVERWRITE 1 //1 = overwrite existing file with same name -//#define kNAME_CONFLICT_ADD_SUFFIX 2 //default 2 = write with new suffix as a new file - printf(" -w : write behavior for name conflicts (0,1,2, default 2: 0=skip duplicates, 1=overwrite, 2=add suffix)\n"); - printf(" -x : crop 3D acquisitions (y/n/i, default n, use 'i'gnore to neither crop nor rotate 3D acquistions)\n"); - char gzCh = 'n'; - if (opts.isGz) gzCh = 'y'; + //#define kNAME_CONFLICT_SKIP 0 //0 = write nothing for a file that exists with desired name + //#define kNAME_CONFLICT_OVERWRITE 1 //1 = overwrite existing file with same name + //#define kNAME_CONFLICT_ADD_SUFFIX 2 //default 2 = write with new suffix as a new file + printf(" -w : write behavior for name conflicts (0,1,2, default 2: 0=skip duplicates, 1=overwrite, 2=add suffix)\n"); + printf(" -x : crop 3D acquisitions (y/n/i, default n, use 'i'gnore to neither crop nor rotate 3D acquistions)\n"); + char gzCh = 'n'; + if (opts.isGz) + gzCh = 'y'; #if defined(_WIN64) || defined(_WIN32) - //n.b. the optimal use of pigz requires pipes that are not provided for Windows - #ifdef myDisableZLib - if (strlen(opts.pigzname) > 0) - printf(" -z : gz compress images (y/n/3, default %c) [y=pigz, n=no, 3=no,3D]\n", gzCh); - else - printf(" -z : gz compress images (y/n/3, default %c) [y=pigz(MISSING!), n=no, 3=no,3D]\n", gzCh); - #else - #ifdef myDisableMiniZ - printf(" -z : gz compress images (y/i/n/3, default %c) [y=pigz, i=internal:zlib, n=no, 3=no,3D]\n", gzCh); - #else - printf(" -z : gz compress images (y/i/n/3, default %c) [y=pigz, i=internal:miniz, n=no, 3=no,3D]\n", gzCh); - #endif - #endif - printf(" --big-endian : byte order (y/n/o, default o) [y=big-end, n=little-end, o=optimal/native]\n"); - printf(" --progress : report progress (y/n, default n)\n"); - printf(" --ignore_trigger_times : disregard values in 0018,1060 and 0020,9153\n"); - printf(" --terse : omit filename post-fixes (can cause overwrites)\n"); - printf(" --version : report version\n"); - printf(" --xml : Slicer format features\n"); - printf(" Defaults stored in Windows registry\n"); - printf(" Examples :\n"); - printf(" %s c:\\DICOM\\dir\n", cstr); - printf(" %s -c \"my comment\" c:\\DICOM\\dir\n", cstr); - printf(" %s -o c:\\out\\dir c:\\DICOM\\dir\n", cstr); - printf(" %s -f mystudy%%s c:\\DICOM\\dir\n", cstr); - printf(" %s -o \"c:\\dir with spaces\\dir\" c:\\dicomdir\n", cstr); +//n.b. the optimal use of pigz requires pipes that are not provided for Windows +#ifdef myDisableZLib + if (strlen(opts.pigzname) > 0) + printf(" -z : gz compress images (y/n/3, default %c) [y=pigz, n=no, 3=no,3D]\n", gzCh); + else + printf(" -z : gz compress images (y/n/3, default %c) [y=pigz(MISSING!), n=no, 3=no,3D]\n", gzCh); +#else +#ifdef myDisableMiniZ + printf(" -z : gz compress images (y/i/n/3, default %c) [y=pigz, i=internal:zlib, n=no, 3=no,3D]\n", gzCh); +#else + printf(" -z : gz compress images (y/i/n/3, default %c) [y=pigz, i=internal:miniz, n=no, 3=no,3D]\n", gzCh); +#endif +#endif + printf(" --big-endian : byte order (y/n/o, default o) [y=big-end, n=little-end, o=optimal/native]\n"); + printf(" --progress : report progress (y/n, default n)\n"); + printf(" --ignore_trigger_times : disregard values in 0018,1060 and 0020,9153\n"); + printf(" --terse : omit filename post-fixes (can cause overwrites)\n"); + printf(" --version : report version\n"); + printf(" --xml : Slicer format features\n"); + printf(" Defaults stored in Windows registry\n"); + printf(" Examples :\n"); + printf(" %s c:\\DICOM\\dir\n", cstr); + printf(" %s -c \"my comment\" c:\\DICOM\\dir\n", cstr); + printf(" %s -o c:\\out\\dir c:\\DICOM\\dir\n", cstr); + printf(" %s -f mystudy%%s c:\\DICOM\\dir\n", cstr); + printf(" %s -o \"c:\\dir with spaces\\dir\" c:\\dicomdir\n", cstr); +#else +#ifdef myDisableZLib + if (strlen(opts.pigzname) > 0) + printf(" -z : gz compress images (y/o/n/3, default %c) [y=pigz, o=optimal pigz, n=no, 3=no,3D]\n", gzCh); + else + printf(" -z : gz compress images (y/o/n/3, default %c) [y=pigz(MISSING!), o=optimal(requires pigz), n=no, 3=no,3D]\n", gzCh); +#else +#ifdef myDisableMiniZ + printf(" -z : gz compress images (y/o/i/n/3, default %c) [y=pigz, o=optimal pigz, i=internal:zlib, n=no, 3=no,3D]\n", gzCh); #else - #ifdef myDisableZLib - if (strlen(opts.pigzname) > 0) - printf(" -z : gz compress images (y/o/n/3, default %c) [y=pigz, o=optimal pigz, n=no, 3=no,3D]\n", gzCh); - else - printf(" -z : gz compress images (y/o/n/3, default %c) [y=pigz(MISSING!), o=optimal(requires pigz), n=no, 3=no,3D]\n", gzCh); - #else - #ifdef myDisableMiniZ - printf(" -z : gz compress images (y/o/i/n/3, default %c) [y=pigz, o=optimal pigz, i=internal:zlib, n=no, 3=no,3D]\n", gzCh); - #else - printf(" -z : gz compress images (y/o/i/n/3, default %c) [y=pigz, o=optimal pigz, i=internal:miniz, n=no, 3=no,3D]\n", gzCh); - #endif - #endif - printf(" --big-endian : byte order (y/n/o, default o) [y=big-end, n=little-end, o=optimal/native]\n"); - printf(" --progress : Slicer format progress information (y/n, default n)\n"); - printf(" --terse : omit filename post-fixes (can cause overwrites)\n"); - printf(" --version : report version\n"); - printf(" --xml : Slicer format features\n"); - printf(" Defaults file : %s\n", opts.optsname); - printf(" Examples :\n"); - printf(" %s /Users/chris/dir\n", cstr); - printf(" %s -c \"my comment\" /Users/chris/dir\n", cstr); - printf(" %s -o /users/cr/outdir/ -z y ~/dicomdir\n", cstr); - printf(" %s -f %%p_%%s -b y -ba n ~/dicomdir\n", cstr); - printf(" %s -f mystudy%%s ~/dicomdir\n", cstr); - printf(" %s -o \"~/dir with spaces/dir\" ~/dicomdir\n", cstr); + printf(" -z : gz compress images (y/o/i/n/3, default %c) [y=pigz, o=optimal pigz, i=internal:miniz, n=no, 3=no,3D]\n", gzCh); +#endif +#endif + printf(" --big-endian : byte order (y/n/o, default o) [y=big-end, n=little-end, o=optimal/native]\n"); + printf(" --progress : Slicer format progress information (y/n, default n)\n"); + printf(" --terse : omit filename post-fixes (can cause overwrites)\n"); + printf(" --version : report version\n"); + printf(" --xml : Slicer format features\n"); + printf(" Defaults file : %s\n", opts.optsname); + printf(" Examples :\n"); + printf(" %s /Users/chris/dir\n", cstr); + printf(" %s -c \"my comment\" /Users/chris/dir\n", cstr); + printf(" %s -o /users/cr/outdir/ -z y ~/dicomdir\n", cstr); + printf(" %s -f %%p_%%s -b y -ba n ~/dicomdir\n", cstr); + printf(" %s -f mystudy%%s ~/dicomdir\n", cstr); + printf(" %s -o \"~/dir with spaces/dir\" ~/dicomdir\n", cstr); #endif } //showHelp() -int invalidParam(int i, const char * argv[]) { - if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') - || (argv[i][0] == 'n') || (argv[i][0] == 'N') - || (argv[i][0] == 'o') || (argv[i][0] == 'O') - || (argv[i][0] == 'h') || (argv[i][0] == 'H') - || (argv[i][0] == 'i') || (argv[i][0] == 'I') - || (argv[i][0] == '0') || (argv[i][0] == '1') - || (argv[i][0] == '2') || (argv[i][0] == '3') ) +int invalidParam(int i, const char *argv[]) { + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == 'o') || (argv[i][0] == 'O') || (argv[i][0] == 'h') || (argv[i][0] == 'H') || (argv[i][0] == 'i') || (argv[i][0] == 'I') || (argv[i][0] == '0') || (argv[i][0] == '1') || (argv[i][0] == '2') || (argv[i][0] == '3')) return 0; //if (argv[i][0] != '-') return 0; - printf(" Error: invalid option '%s %s'\n", argv[i-1], argv[i]); + printf(" Error: invalid option '%s %s'\n", argv[i - 1], argv[i]); return 1; } #if !defined(_WIN64) && !defined(_WIN32) //shell script for Unix only int checkUpToDate() { - #define URL "/rordenlab/dcm2niix/releases/" - #define APIURL "\"https://api.github.com/repos" URL "latest\"" - #define HTMURL "https://github.com" URL - #define SHELLSCRIPT "#!/usr/bin/env bash\n curl --silent " APIURL " | grep '\"tag_name\":' | sed -E 's/.*\"([^\"]+)\".*/\\1/'" - //check first 13 characters, e.g. "v1.0.20171204" - #define versionChars 13 - FILE *pipe = popen(SHELLSCRIPT, "r"); - char ch, gitvers[versionChars+1]; - int n = 0; - int nMatch = 0; - while ((ch = fgetc(pipe)) != EOF) { - if (n < versionChars) { - gitvers[n] = ch; - if (gitvers[n] == kDCMvers[n]) - nMatch ++; - n ++; - } - } - pclose(pipe); +#define URL "/rordenlab/dcm2niix/releases/" +#define APIURL "\"https://api.github.com/repos" URL "latest\"" +#define HTMURL "https://github.com" URL +#define SHELLSCRIPT "#!/usr/bin/env bash\n curl --silent " APIURL " | grep '\"tag_name\":' | sed -E 's/.*\"([^\"]+)\".*/\\1/'" +//check first 13 characters, e.g. "v1.0.20171204" +#define versionChars 13 + FILE *pipe = popen(SHELLSCRIPT, "r"); + char ch, gitvers[versionChars + 1]; + int n = 0; + int nMatch = 0; + while ((ch = fgetc(pipe)) != EOF) { + if (n < versionChars) { + gitvers[n] = ch; + if (gitvers[n] == kDCMvers[n]) + nMatch++; + n++; + } + } + pclose(pipe); gitvers[n] = 0; //null terminate - if (n < 1) { //script reported nothing - printf("Error: unable to check version with script:\n %s\n", SHELLSCRIPT); - return 3; //different from EXIT_SUCCESS (0) and EXIT_FAILURE (1) - } - if (nMatch == versionChars) { //versions match - printf("Good news: Your version is up to date: %s\n", gitvers); - return EXIT_SUCCESS; - } + if (n < 1) { //script reported nothing + printf("Error: unable to check version with script:\n %s\n", SHELLSCRIPT); + return 3; //different from EXIT_SUCCESS (0) and EXIT_FAILURE (1) + } + if (nMatch == versionChars) { //versions match + printf("Good news: Your version is up to date: %s\n", gitvers); + return EXIT_SUCCESS; + } //report error - char myvers[versionChars+1]; - for (int i = 0; i < versionChars; i++) myvers[i] = kDCMvers[i]; - myvers[versionChars] = 0; //null terminate - int myv = atoi(myvers + 5); //skip "v1.0." + char myvers[versionChars + 1]; + for (int i = 0; i < versionChars; i++) + myvers[i] = kDCMvers[i]; + myvers[versionChars] = 0; //null terminate + int myv = atoi(myvers + 5); //skip "v1.0." int gitv = atoi(gitvers + 5); //skip "v1.0." if (myv > gitv) { printf("Warning: your version ('%s') more recent than stable release ('%s')\n %s\n", myvers, gitvers, HTMURL); @@ -234,350 +230,370 @@ int checkUpToDate() { #endif //shell script for UNIX only void showXML() { -//https://www.slicer.org/wiki/Documentation/Nightly/Developers/SlicerExecutionModel#XML_Schema - printf("\n"); - printf("\n"); - printf("dcm2niix\n"); - printf("DICOM importer\n"); - printf(" \n"); - printf(" At least one parameter\n"); - printf(" \n"); - printf("\n"); + //https://www.slicer.org/wiki/Documentation/Nightly/Developers/SlicerExecutionModel#XML_Schema + printf("\n"); + printf("\n"); + printf("dcm2niix\n"); + printf("DICOM importer\n"); + printf(" \n"); + printf(" At least one parameter\n"); + printf(" \n"); + printf("\n"); } //#define mydebugtest -int main(int argc, const char * argv[]) -{ - struct TDCMopts opts; - bool isSaveIni = false; - bool isOutNameSpecified = false; - bool isResetDefaults = false; - readIniFile(&opts, argv); //set default preferences +int main(int argc, const char *argv[]) { + struct TDCMopts opts; + bool isSaveIni = false; + bool isOutNameSpecified = false; + bool isResetDefaults = false; + readIniFile(&opts, argv); //set default preferences #ifdef mydebugtest - //strcpy(opts.indir, "/Users/rorden/desktop/sliceOrder/dicom2/Philips_PARREC_Rotation/NoRotation/DBIEX_4_1.PAR"); - //strcpy(opts.indir, "/Users/rorden/desktop/sliceOrder/dicom2/test"); - strcpy(opts.indir, "e:\\t1s"); + //strcpy(opts.indir, "/Users/rorden/desktop/sliceOrder/dicom2/Philips_PARREC_Rotation/NoRotation/DBIEX_4_1.PAR"); + //strcpy(opts.indir, "/Users/rorden/desktop/sliceOrder/dicom2/test"); + strcpy(opts.indir, "e:\\t1s"); #else - #if defined(__APPLE__) - #define kOS "MacOS" - #elif (defined(__linux) || defined(__linux__)) - #define kOS "Linux" - #else - #define kOS "Windows" - #endif - printf("Chris Rorden's dcm2niiX version %s (%llu-bit %s)\n",kDCMvers, (unsigned long long) sizeof(size_t)*8, kOS); - if (argc < 2) { - showHelp(argv, opts); - return 0; - } - //for (int i = 1; i < argc; i++) { printf(" argument %d= '%s'\n", i, argv[i]);} - int i = 1; - int lastCommandArg = 0; - while (i < (argc)) { //-1 as final parameter is DICOM directory - if ((strlen(argv[i]) > 1) && (argv[i][0] == '-')) { //command - if (argv[i][1] == 'h') { - showHelp(argv, opts); - } else if (( ! strcmp(argv[i], "--big-endian")) && ((i+1) < argc)) { +#if defined(__APPLE__) +#define kOS "MacOS" +#elif (defined(__linux) || defined(__linux__)) +#define kOS "Linux" +#else +#define kOS "Windows" +#endif + printf("Chris Rorden's dcm2niiX version %s (%llu-bit %s)\n", kDCMvers, (unsigned long long)sizeof(size_t) * 8, kOS); + if (argc < 2) { + showHelp(argv, opts); + return 0; + } + //for (int i = 1; i < argc; i++) { printf(" argument %d= '%s'\n", i, argv[i]);} + int i = 1; + int lastCommandArg = 0; + while (i < (argc)) { //-1 as final parameter is DICOM directory + if ((strlen(argv[i]) > 1) && (argv[i][0] == '-')) { //command + if (argv[i][1] == 'h') { + showHelp(argv, opts); + } else if ((!strcmp(argv[i], "--big-endian")) && ((i + 1) < argc)) { i++; - if ((littleEndianPlatform()) && ((argv[i][0] == 'y') || (argv[i][0] == 'Y'))) { - opts.isSaveNativeEndian = false; - printf("NIfTI data will be big-endian (byte-swapped)\n"); - } - if ((!littleEndianPlatform()) && ((argv[i][0] == 'n') || (argv[i][0] == 'N'))) { - opts.isSaveNativeEndian = false; - printf("NIfTI data will be little-endian\n"); - } - } else if ( ! strcmp(argv[i], "--ignore_trigger_times")) { + if ((littleEndianPlatform()) && ((argv[i][0] == 'y') || (argv[i][0] == 'Y'))) { + opts.isSaveNativeEndian = false; + printf("NIfTI data will be big-endian (byte-swapped)\n"); + } + if ((!littleEndianPlatform()) && ((argv[i][0] == 'n') || (argv[i][0] == 'N'))) { + opts.isSaveNativeEndian = false; + printf("NIfTI data will be little-endian\n"); + } + } else if (!strcmp(argv[i], "--ignore_trigger_times")) { opts.isIgnoreTriggerTimes = true; printf("ignore_trigger_times may have unintended consequences (issue 499)\n"); - } else if ( ! strcmp(argv[i], "--terse")) { + } else if (!strcmp(argv[i], "--terse")) { opts.isAddNamePostFixes = false; - } else if ( ! strcmp(argv[i], "--version")) { + } else if (!strcmp(argv[i], "--version")) { printf("%s\n", kDCMdate); - return kEXIT_REPORT_VERSION; - } else if (( ! strcmp(argv[i], "--progress")) && ((i+1) < argc)) { + return kEXIT_REPORT_VERSION; + } else if ((!strcmp(argv[i], "--progress")) && ((i + 1) < argc)) { i++; - if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) - opts.isProgress = 0; - else - opts.isProgress = 1; - if (argv[i][0] == '2') opts.isProgress = 2; //logorrheic - } else if ( ! strcmp(argv[i], "--xml")) { + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isProgress = 0; + else + opts.isProgress = 1; + if (argv[i][0] == '2') + opts.isProgress = 2; //logorrheic + } else if (!strcmp(argv[i], "--xml")) { showXML(); return EXIT_SUCCESS; - } else if ((argv[i][1] == 'a') && ((i+1) < argc)) { //adjacent DICOMs + } else if ((argv[i][1] == 'a') && ((i + 1) < argc)) { //adjacent DICOMs + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isOneDirAtATime = false; + else + opts.isOneDirAtATime = true; + } else if ((argv[i][1] >= '1') && (argv[i][1] <= '9')) { + opts.gzLevel = abs((int)strtol(argv[i], NULL, 10)); + if (opts.gzLevel > 11) + opts.gzLevel = 11; + } else if ((argv[i][1] == 'b') && ((i + 1) < argc)) { + if (strlen(argv[i]) < 3) { //"-b y" + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isCreateBIDS = false; + else if ((argv[i][0] == 'i') || (argv[i][0] == 'I')) { + //input only mode (for development): does not create NIfTI or BIDS outputs! + opts.isCreateBIDS = false; + opts.isOnlyBIDS = true; + } else { + opts.isCreateBIDS = true; + if ((argv[i][0] == 'o') || (argv[i][0] == 'O')) + opts.isOnlyBIDS = true; + } + } else if (argv[i][2] == 'a') { //"-ba y" + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isAnonymizeBIDS = false; + else + opts.isAnonymizeBIDS = true; + } else + printf("Error: Unknown command line argument: '%s'\n", argv[i]); + } else if ((argv[i][1] == 'c') && ((i + 1) < argc)) { + i++; + snprintf(opts.imageComments, 24, "%s", argv[i]); + } else if ((argv[i][1] == 'd') && ((i + 1) < argc)) { + i++; + if ((argv[i][0] >= '0') && (argv[i][0] <= '9')) + opts.dirSearchDepth = abs((int)strtol(argv[i], NULL, 10)); + } else if ((argv[i][1] == 'e') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == '1')) + opts.isSaveNRRD = true; + } else if ((argv[i][1] == 'g') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y')) + isSaveIni = true; + if (((argv[i][0] == 'i') || (argv[i][0] == 'I')) && (!isResetDefaults)) { + isResetDefaults = true; + printf("Defaults ignored\n"); + setDefaultOpts(&opts, argv); + i = 0; //re-read all settings for this pass, e.g. "dcm2niix -f %p_%s -d o" should save filename as "%p_%s" + } + if (((argv[i][0] == 'o') || (argv[i][0] == 'O')) && (!isResetDefaults)) { + //reset defaults - do not read, but do write defaults + isSaveIni = true; + isResetDefaults = true; + printf("Defaults reset\n"); + setDefaultOpts(&opts, argv); + //this next line is optional, otherwise "dcm2niix -f %p_%s -d o" and "dcm2niix -d o -f %p_%s" will create different results + i = 0; //re-read all settings for this pass, e.g. "dcm2niix -f %p_%s -d o" should save filename as "%p_%s" + } + } else if ((argv[i][1] == 'i') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isIgnoreDerivedAnd2D = false; + else + opts.isIgnoreDerivedAnd2D = true; + } else if ((argv[i][1] == 'j') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == '1')) { + opts.isTestx0021x105E = true; + printf("undocumented '-j y' compares GE slice timing from 0021,105E\n"); + } + } else if ((argv[i][1] == 'l') && ((i + 1) < argc)) { i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) - opts.isOneDirAtATime = false; - else - opts.isOneDirAtATime = true; - } else if ((argv[i][1] >= '1') && (argv[i][1] <= '9')) { - opts.gzLevel = abs((int)strtol(argv[i], NULL, 10)); - if (opts.gzLevel > 11) - opts.gzLevel = 11; - } else if ((argv[i][1] == 'b') && ((i+1) < argc)) { - if (strlen(argv[i]) < 3) { //"-b y" - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) - opts.isCreateBIDS = false; - else if ((argv[i][0] == 'i') || (argv[i][0] == 'I')) { - //input only mode (for development): does not create NIfTI or BIDS outputs! - opts.isCreateBIDS = false; - opts.isOnlyBIDS = true; - } else { - opts.isCreateBIDS = true; - if ((argv[i][0] == 'o') || (argv[i][0] == 'O')) - opts.isOnlyBIDS = true; - } - } else if (argv[i][2] == 'a') {//"-ba y" - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) - opts.isAnonymizeBIDS = false; - else - opts.isAnonymizeBIDS = true; - } else - printf("Error: Unknown command line argument: '%s'\n", argv[i]); - } else if ((argv[i][1] == 'c') && ((i+1) < argc)) { - i++; - snprintf(opts.imageComments,24,"%s",argv[i]); - } else if ((argv[i][1] == 'd') && ((i+1) < argc)) { - i++; - if ((argv[i][0] >= '0') && (argv[i][0] <= '9')) - opts.dirSearchDepth = abs((int)strtol(argv[i], NULL, 10)); - } else if ((argv[i][1] == 'e') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == '1')) - opts.isSaveNRRD = true; - } else if ((argv[i][1] == 'g') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'y') || (argv[i][0] == 'Y')) - isSaveIni = true; - if (((argv[i][0] == 'i') || (argv[i][0] == 'I')) && (!isResetDefaults)) { - isResetDefaults = true; - printf("Defaults ignored\n"); - setDefaultOpts(&opts, argv); - i = 0; //re-read all settings for this pass, e.g. "dcm2niix -f %p_%s -d o" should save filename as "%p_%s" - } - if (((argv[i][0] == 'o') || (argv[i][0] == 'O')) && (!isResetDefaults)) { - //reset defaults - do not read, but do write defaults - isSaveIni = true; - isResetDefaults = true; - printf("Defaults reset\n"); - setDefaultOpts(&opts, argv); - //this next line is optional, otherwise "dcm2niix -f %p_%s -d o" and "dcm2niix -d o -f %p_%s" will create different results - i = 0; //re-read all settings for this pass, e.g. "dcm2niix -f %p_%s -d o" should save filename as "%p_%s" - } - } else if ((argv[i][1] == 'i') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) - opts.isIgnoreDerivedAnd2D = false; - else - opts.isIgnoreDerivedAnd2D = true; - } else if ((argv[i][1] == 'j') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == '1')) { - opts.isTestx0021x105E = true; - printf("undocumented '-j y' compares GE slice timing from 0021,105E\n"); - } - } else if ((argv[i][1] == 'l') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'o') || (argv[i][0] == 'O')) - opts.isMaximize16BitRange = kMaximize16BitRange_Raw; - else if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) - opts.isMaximize16BitRange = kMaximize16BitRange_False; - else - opts.isMaximize16BitRange = kMaximize16BitRange_True; - } else if ((argv[i][1] == 'm') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) - opts.isForceStackSameSeries = 0; - if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == '1')) - opts.isForceStackSameSeries = 1; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'o') || (argv[i][0] == 'O')) + opts.isMaximize16BitRange = kMaximize16BitRange_Raw; + else if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isMaximize16BitRange = kMaximize16BitRange_False; + else + opts.isMaximize16BitRange = kMaximize16BitRange_True; + } else if ((argv[i][1] == 'm') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isForceStackSameSeries = 0; + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == '1')) + opts.isForceStackSameSeries = 1; if ((argv[i][0] == '2')) - opts.isForceStackSameSeries = 2; - if ((argv[i][0] == 'o') || (argv[i][0] == 'O')) { - opts.isForceStackDCE = false; + opts.isForceStackSameSeries = 2; + if ((argv[i][0] == 'o') || (argv[i][0] == 'O')) { + opts.isForceStackDCE = false; //printf("Advanced feature: '-m o' merges images despite varying series number\n"); } - if ((argv[i][0] == '2')) { - opts.isIgnoreSeriesInstanceUID = true; + if ((argv[i][0] == '2')) { + opts.isIgnoreSeriesInstanceUID = true; printf("Advanced feature: '-m 2' ignores Series Instance UID.\n"); } - } else if ((argv[i][1] == 'p') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) - opts.isPhilipsFloatNotDisplayScaling = false; - else - opts.isPhilipsFloatNotDisplayScaling = true; - } else if ((argv[i][1] == 'r') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'y') || (argv[i][0] == 'Y')) - opts.isRenameNotConvert = true; - } else if ((argv[i][1] == 's') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) - opts.isOnlySingleFile = false; - else - opts.isOnlySingleFile = true; - } else if ((argv[i][1] == 't') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) - opts.isCreateText = false; - else - opts.isCreateText = true; - #if !defined(_WIN64) && !defined(_WIN32) //shell script for Unix only - } else if (argv[i][1] == 'u') { + } else if ((argv[i][1] == 'p') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isPhilipsFloatNotDisplayScaling = false; + else + opts.isPhilipsFloatNotDisplayScaling = true; + } else if ((argv[i][1] == 'r') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y')) + opts.isRenameNotConvert = true; + } else if ((argv[i][1] == 's') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isOnlySingleFile = false; + else + opts.isOnlySingleFile = true; + } else if ((argv[i][1] == 't') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isCreateText = false; + else + opts.isCreateText = true; +#if !defined(_WIN64) && !defined(_WIN32) //shell script for Unix only + } else if (argv[i][1] == 'u') { return checkUpToDate(); - #endif - } else if ((argv[i][1] == 'v') && ((i+1) >= argc)) { +#endif + } else if ((argv[i][1] == 'v') && ((i + 1) >= argc)) { printf("%s\n", kDCMdate); - return kEXIT_REPORT_VERSION; - } else if ((argv[i][1] == 'v') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) //0: verbose OFF - opts.isVerbose = 0; - else if ((argv[i][0] == 'h') || (argv[i][0] == 'H') || (argv[i][0] == '2')) //2: verbose HYPER - opts.isVerbose = 2; - else - opts.isVerbose = 1; //1: verbose ON - } else if ((argv[i][1] == 'w') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if (argv[i][0] == '0') opts.nameConflictBehavior = 0; - if (argv[i][0] == '1') opts.nameConflictBehavior = 1; - if (argv[i][0] == '2') opts.nameConflictBehavior = 2; - - //if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) - // opts.isOneDirAtATime = false; - //else - // opts.isOneDirAtATime = true; - } else if ((argv[i][1] == 'x') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) - opts.isCrop = false; - else if ((argv[i][0] == 'i') || (argv[i][0] == 'I')) { - opts.isRotate3DAcq = false; - opts.isCrop = false; - } else - opts.isCrop = true; - } else if ((argv[i][1] == 'y') && ((i+1) < argc)) { - i++; - bool isFlipY = opts.isFlipY; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') ) { - opts.isFlipY = true; //force use of internal compression instead of pigz - strcpy(opts.pigzname,""); - } else if ((argv[i][0] == 'n') || (argv[i][0] == 'N')) - opts.isFlipY = false; - if (isFlipY != opts.isFlipY) - printf("Advanced feature: You are flipping the default order of rows in your image.\n"); - } else if ((argv[i][1] == 'z') && ((i+1) < argc)) { - i++; - if (invalidParam(i, argv)) return 0; - if ((argv[i][0] == '3') ) { - opts.isGz = false; //uncompressed 3D - opts.isSave3D = true; - } else if ((argv[i][0] == 'i') || (argv[i][0] == 'I') ) { - opts.isGz = true; - #ifndef myDisableZLib - strcpy(opts.pigzname,""); //force use of internal compression instead of pigz - #endif - } else if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) - opts.isGz = false; - else - opts.isGz = true; - if (argv[i][0] == 'o') - opts.isPipedGz = true; //pipe to pigz without saving uncompressed to disk - } else if ((argv[i][1] == 'f') && ((i+1) < argc)) { - i++; - strcpy(opts.filename,argv[i]); - isOutNameSpecified = true; - } else if ((argv[i][1] == 'o') && ((i+1) < argc)) { - i++; - strcpy(opts.outdir,argv[i]); - } else if ((argv[i][1] == 'n') && ((i+1) < argc)) { - i++; - double seriesNumber = atof(argv[i]); - if (seriesNumber < 0) - opts.numSeries = -1.0; //report series: convert none - else if ((opts.numSeries >= 0) && (opts.numSeries < MAX_NUM_SERIES)) { - opts.seriesNumber[opts.numSeries] = seriesNumber; - opts.numSeries += 1; - } - else { - printf("Warning: too many series specified, ignoring -n %s\n", argv[i]); - } - } else - printf(" Error: invalid option '%s %s'\n", argv[i], argv[i+1]);; - lastCommandArg = i; - } //if parameter is a command - i ++; //read next parameter - } //while parameters to read - #ifndef myDisableZLib - if ((opts.isGz) && (opts.dirSearchDepth < 1) && (strlen(opts.pigzname)>0)) { - strcpy(opts.pigzname,""); - printf("n.b. Setting directory search depth of zero invokes internal gz (network mode)\n"); + return kEXIT_REPORT_VERSION; + } else if ((argv[i][1] == 'v') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) //0: verbose OFF + opts.isVerbose = 0; + else if ((argv[i][0] == 'h') || (argv[i][0] == 'H') || (argv[i][0] == '2')) //2: verbose HYPER + opts.isVerbose = 2; + else + opts.isVerbose = 1; //1: verbose ON + } else if ((argv[i][1] == 'w') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if (argv[i][0] == '0') + opts.nameConflictBehavior = 0; + if (argv[i][0] == '1') + opts.nameConflictBehavior = 1; + if (argv[i][0] == '2') + opts.nameConflictBehavior = 2; + } else if ((argv[i][1] == 'x') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isCrop = false; + else if ((argv[i][0] == 'i') || (argv[i][0] == 'I')) { + opts.isRotate3DAcq = false; + opts.isCrop = false; + } else + opts.isCrop = true; + } else if ((argv[i][1] == 'y') && ((i + 1) < argc)) { + i++; + bool isFlipY = opts.isFlipY; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == 'y') || (argv[i][0] == 'Y')) { + opts.isFlipY = true; //force use of internal compression instead of pigz + strcpy(opts.pigzname, ""); + } else if ((argv[i][0] == 'n') || (argv[i][0] == 'N')) + opts.isFlipY = false; + if (isFlipY != opts.isFlipY) + printf("Advanced feature: You are flipping the default order of rows in your image.\n"); + } else if ((argv[i][1] == 'z') && ((i + 1) < argc)) { + i++; + if (invalidParam(i, argv)) + return 0; + if ((argv[i][0] == '3')) { + opts.isGz = false; //uncompressed 3D + opts.isSave3D = true; + } else if ((argv[i][0] == 'i') || (argv[i][0] == 'I')) { + opts.isGz = true; +#ifndef myDisableZLib + strcpy(opts.pigzname, ""); //force use of internal compression instead of pigz +#endif + } else if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) + opts.isGz = false; + else + opts.isGz = true; + if (argv[i][0] == 'o') + opts.isPipedGz = true; //pipe to pigz without saving uncompressed to disk + } else if ((argv[i][1] == 'f') && ((i + 1) < argc)) { + i++; + strcpy(opts.filename, argv[i]); + isOutNameSpecified = true; + } else if ((argv[i][1] == 'o') && ((i + 1) < argc)) { + i++; + strcpy(opts.outdir, argv[i]); + } else if ((argv[i][1] == 'n') && ((i + 1) < argc)) { + i++; + double seriesNumber = atof(argv[i]); + if (seriesNumber < 0) + opts.numSeries = -1.0; //report series: convert none + else if ((opts.numSeries >= 0) && (opts.numSeries < MAX_NUM_SERIES)) { + opts.seriesNumber[opts.numSeries] = seriesNumber; + opts.numSeries += 1; + } else { + printf("Warning: too many series specified, ignoring -n %s\n", argv[i]); + } + } else + printf(" Error: invalid option '%s %s'\n", argv[i], argv[i + 1]); + ; + lastCommandArg = i; + } //if parameter is a command + i++; //read next parameter + } //while parameters to read +#ifndef myDisableZLib + if ((opts.isGz) && (opts.dirSearchDepth < 1) && (strlen(opts.pigzname) > 0)) { + strcpy(opts.pigzname, ""); + printf("n.b. Setting directory search depth of zero invokes internal gz (network mode)\n"); } - #endif +#endif if ((opts.isRenameNotConvert) && (!isOutNameSpecified)) { //sensible naming scheme for renaming option - //strcpy(opts.filename,argv[i]); - //2019 - now include "%o" to append media SOP UID, as instance number is not required to be unique - #if defined(_WIN64) || defined(_WIN32) - strcpy(opts.filename,"%t\\%s_%p\\%4r_%o.dcm"); //nrrd or nhdr (windows folders) - #else - strcpy(opts.filename,"%t/%s_%p/%4r_%o.dcm"); //nrrd or nhdr (unix folders) - #endif +//strcpy(opts.filename,argv[i]); +//2019 - now include "%o" to append media SOP UID, as instance number is not required to be unique +#if defined(_WIN64) || defined(_WIN32) + strcpy(opts.filename, "%t\\%s_%p\\%4r_%o.dcm"); //nrrd or nhdr (windows folders) +#else + strcpy(opts.filename, "%t/%s_%p/%4r_%o.dcm"); //nrrd or nhdr (unix folders) +#endif printf("renaming without output filename, assuming '-f %s'\n", opts.filename); } - if (isSaveIni) - saveIniFile(opts); - //printf("%d %d",argc,lastCommandArg); - if (argc == (lastCommandArg+1)) { //+1 as array indexed from 0 - //the user did not provide an input filename, report filename structure - char niiFilename[1024]; - strcpy(opts.outdir,"");//no input supplied - nii_createDummyFilename(niiFilename, opts); - printf("%s\n",niiFilename); - return EXIT_SUCCESS; - } + if (isSaveIni) + saveIniFile(opts); + //printf("%d %d",argc,lastCommandArg); + if (argc == (lastCommandArg + 1)) { //+1 as array indexed from 0 + //the user did not provide an input filename, report filename structure + char niiFilename[1024]; + strcpy(opts.outdir, ""); //no input supplied + nii_createDummyFilename(niiFilename, opts); + printf("%s\n", niiFilename); + return EXIT_SUCCESS; + } #endif - #ifndef myEnableMultipleInputs - if ((argc-lastCommandArg-1) > 1) { - printf("Warning: only processing last of %d input files (recompile with 'myEnableMultipleInputs' to recursively process multiple files)\n", argc-lastCommandArg-1); +#ifndef myEnableMultipleInputs + if ((argc - lastCommandArg - 1) > 1) { + printf("Warning: only processing last of %d input files (recompile with 'myEnableMultipleInputs' to recursively process multiple files)\n", argc - lastCommandArg - 1); lastCommandArg = argc - 2; } - #endif - #if !defined(_WIN64) && !defined(_WIN32) +#endif +#if !defined(_WIN64) && !defined(_WIN32) double startWall = get_wall_time(); - #endif - clock_t start = clock(); - for (i = (lastCommandArg+1); i < argc; i++) { - strcpy(opts.indir,argv[i]); // [argc-1] - int ret = nii_loadDir(&opts); - if (ret != EXIT_SUCCESS) - return ret; - } - #if !defined(_WIN64) && !defined(_WIN32) - printf ("Conversion required %f seconds (%f for core code).\n",get_wall_time() - startWall, ((float)(clock()-start))/CLOCKS_PER_SEC); - #else - printf ("Conversion required %f seconds.\n",((float)(clock()-start))/CLOCKS_PER_SEC); - #endif - //if (isSaveIni) //we now save defaults earlier, in case of early termination. - // saveIniFile(opts); - return EXIT_SUCCESS; +#endif + clock_t start = clock(); + for (i = (lastCommandArg + 1); i < argc; i++) { + strcpy(opts.indir, argv[i]); // [argc-1] + int ret = nii_loadDir(&opts); + if (ret != EXIT_SUCCESS) + return ret; + } +#if !defined(_WIN64) && !defined(_WIN32) + printf("Conversion required %f seconds (%f for core code).\n", get_wall_time() - startWall, ((float)(clock() - start)) / CLOCKS_PER_SEC); +#else + printf("Conversion required %f seconds.\n", ((float)(clock() - start)) / CLOCKS_PER_SEC); +#endif + //if (isSaveIni) //we now save defaults earlier, in case of early termination. + // saveIniFile(opts); + return EXIT_SUCCESS; } diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 2e8dd33c..0a4ff521 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -1,415 +1,424 @@ //#define MY_DEBUG #if defined(_WIN64) || defined(_WIN32) - #include //write to registry +#include //write to registry #endif #ifdef _MSC_VER - #include - #define getcwd _getcwd - #define chdir _chrdir - #include "io.h" - #include - //#define snprintf _snprintf - //#define vsnprintf _vsnprintf - #define strcasecmp _stricmp - #define strncasecmp _strnicmp - #ifdef _WIN32 - #pragma comment(lib, "advapi32") - #endif +#include +#define getcwd _getcwd +#define chdir _chrdir +#include "io.h" +#include +//#define snprintf _snprintf +//#define vsnprintf _vsnprintf +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#ifdef _WIN32 +#pragma comment(lib, "advapi32") +#endif #else - #include +#include #endif //#include //clock() #ifndef USING_R #include "nifti1.h" #endif -#include "print.h" +#include "jpg_0XC3.h" +#include "nifti1_io_core.h" #include "nii_dicom.h" -#include -#include // discriminate files from folders -#include -#include +#include "print.h" #include //toupper +#include #include -#include #include -#include "jpg_0XC3.h" -#include #include -#include "nifti1_io_core.h" +#include +#include +#include +#include // discriminate files from folders +#include #ifdef USING_R - #undef isnan - #define isnan ISNAN +#undef isnan +#define isnan ISNAN #endif #ifndef myDisableClassicJPEG - #ifdef myTurboJPEG - #include - #else - #include "ujpeg.h" - #endif +#ifdef myTurboJPEG +#include +#else +#include "ujpeg.h" +#endif #endif #ifdef myEnableJasper - #include +#include #endif #ifndef myDisableOpenJPEG - #include "openjpeg.h" +#include "openjpeg.h" #ifdef myEnableJasper - ERROR: YOU CAN NOT COMPILE WITH myEnableJasper AND NOT myDisableOpenJPEG OPTIONS SET SIMULTANEOUSLY +ERROR : YOU CAN NOT COMPILE WITH myEnableJasper AND NOT myDisableOpenJPEG OPTIONS SET SIMULTANEOUSLY #endif -unsigned char * imagetoimg(opj_image_t * image) -{ - int numcmpts = image->numcomps; - int sgnd = image->comps[0].sgnd ; - int width = image->comps[0].w; - int height = image->comps[0].h; - int bpp = (image->comps[0].prec + 7) >> 3; //e.g. 12 bits requires 2 bytes - int imgbytes = bpp * width * height * numcmpts; - bool isOK = true; - if (numcmpts > 1) { - for (int comp = 1; comp < numcmpts; comp++) { //check RGB data - if (image->comps[0].w != image->comps[comp].w) isOK = false; - if (image->comps[0].h != image->comps[comp].h) isOK = false; - if (image->comps[0].dx != image->comps[comp].dx) isOK = false; - if (image->comps[0].dy != image->comps[comp].dy) isOK = false; - if (image->comps[0].prec != image->comps[comp].prec) isOK = false; - if (image->comps[0].sgnd != image->comps[comp].sgnd) isOK = false; - } - if (numcmpts != 3) isOK = false; //we only handle Gray and RedGreenBlue, not GrayAlpha or RedGreenBlueAlpha - if (image->comps[0].prec != 8) isOK = false; //only 8-bit for RGB data - } - if ((image->comps[0].prec < 1) || (image->comps[0].prec > 16)) isOK = false; //currently we only handle 1 and 2 byte data - if (!isOK) { - printMessage("jpeg decode failure w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version()); - return NULL; - } - #ifdef MY_DEBUG - printMessage("w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version()); - #endif - //extract the data - if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) { - printError("Catastrophic decompression error\n"); - return NULL; - } - unsigned char *img = (unsigned char *)malloc(imgbytes); - uint16_t * img16ui = (uint16_t*) img; //unsigned 16-bit - int16_t * img16i = (int16_t*) img; //signed 16-bit - if (sgnd) bpp = -bpp; - if (bpp == -1) { - free(img); - printError("Signed 8-bit DICOM?\n"); - return NULL; - } - //n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB - int pix = 0; //ouput pixel - for (int cmptno = 0; cmptno < numcmpts; ++cmptno) { - int cpix = 0; //component pixel - int* v = image->comps[cmptno].data; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - switch (bpp) { - case 1: - img[pix] = (unsigned char) v[cpix]; - break; - case 2: - img16ui[pix] = (uint16_t) v[cpix]; - break; - case -2: - img16i[pix] = (int16_t) v[cpix]; - break; - } - pix ++; - cpix ++; - }//for x - } //for y - } //for each component - return img; -}// imagetoimg() +unsigned char * imagetoimg(opj_image_t *image) { + int numcmpts = image->numcomps; + int sgnd = image->comps[0].sgnd; + int width = image->comps[0].w; + int height = image->comps[0].h; + int bpp = (image->comps[0].prec + 7) >> 3; //e.g. 12 bits requires 2 bytes + int imgbytes = bpp * width * height * numcmpts; + bool isOK = true; + if (numcmpts > 1) { + for (int comp = 1; comp < numcmpts; comp++) { //check RGB data + if (image->comps[0].w != image->comps[comp].w) + isOK = false; + if (image->comps[0].h != image->comps[comp].h) + isOK = false; + if (image->comps[0].dx != image->comps[comp].dx) + isOK = false; + if (image->comps[0].dy != image->comps[comp].dy) + isOK = false; + if (image->comps[0].prec != image->comps[comp].prec) + isOK = false; + if (image->comps[0].sgnd != image->comps[comp].sgnd) + isOK = false; + } + if (numcmpts != 3) + isOK = false; //we only handle Gray and RedGreenBlue, not GrayAlpha or RedGreenBlueAlpha + if (image->comps[0].prec != 8) + isOK = false; //only 8-bit for RGB data + } + if ((image->comps[0].prec < 1) || (image->comps[0].prec > 16)) + isOK = false; //currently we only handle 1 and 2 byte data + if (!isOK) { + printMessage("jpeg decode failure w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version()); + return NULL; + } +#ifdef MY_DEBUG + printMessage("w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version()); +#endif + //extract the data + if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) { + printError("Catastrophic decompression error\n"); + return NULL; + } + unsigned char *img = (unsigned char *)malloc(imgbytes); + uint16_t *img16ui = (uint16_t *)img; //unsigned 16-bit + int16_t *img16i = (int16_t *)img; //signed 16-bit + if (sgnd) + bpp = -bpp; + if (bpp == -1) { + free(img); + printError("Signed 8-bit DICOM?\n"); + return NULL; + } + //n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB + int pix = 0; //ouput pixel + for (int cmptno = 0; cmptno < numcmpts; ++cmptno) { + int cpix = 0; //component pixel + int *v = image->comps[cmptno].data; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + switch (bpp) { + case 1: + img[pix] = (unsigned char)v[cpix]; + break; + case 2: + img16ui[pix] = (uint16_t)v[cpix]; + break; + case -2: + img16i[pix] = (int16_t)v[cpix]; + break; + } + pix++; + cpix++; + } //for x + } //for y + } //for each component + return img; +} // imagetoimg() typedef struct bufinfo { - unsigned char *buf; - unsigned char *cur; - size_t len; + unsigned char *buf; + unsigned char *cur; + size_t len; } BufInfo; -static void my_stream_free (void * p_user_data) { //do nothing - //BufInfo d = (BufInfo) p_user_data; - //free(d.buf); +static void my_stream_free(void *p_user_data) { //do nothing + //BufInfo d = (BufInfo) p_user_data; + //free(d.buf); } // my_stream_free() -static OPJ_UINT32 opj_read_from_buffer(void * p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo* p_file) { - OPJ_UINT32 l_nb_read; - if(p_file->cur + p_nb_bytes < p_file->buf + p_file->len ) - { - l_nb_read = p_nb_bytes; - } - else - { - l_nb_read = (OPJ_UINT32)(p_file->buf + p_file->len - p_file->cur); - } - memcpy(p_buffer, p_file->cur, l_nb_read); - p_file->cur += l_nb_read; - - return l_nb_read ? l_nb_read : ((OPJ_UINT32)-1); +static OPJ_UINT32 opj_read_from_buffer(void *p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo *p_file) { + OPJ_UINT32 l_nb_read; + if (p_file->cur + p_nb_bytes < p_file->buf + p_file->len) { + l_nb_read = p_nb_bytes; + } else { + l_nb_read = (OPJ_UINT32)(p_file->buf + p_file->len - p_file->cur); + } + memcpy(p_buffer, p_file->cur, l_nb_read); + p_file->cur += l_nb_read; + + return l_nb_read ? l_nb_read : ((OPJ_UINT32)-1); } //opj_read_from_buffer() -static OPJ_UINT32 opj_write_from_buffer(void * p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo* p_file) { - memcpy(p_file->cur,p_buffer, p_nb_bytes); - p_file->cur += p_nb_bytes; - p_file->len += p_nb_bytes; - return p_nb_bytes; +static OPJ_UINT32 opj_write_from_buffer(void *p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo *p_file) { + memcpy(p_file->cur, p_buffer, p_nb_bytes); + p_file->cur += p_nb_bytes; + p_file->len += p_nb_bytes; + return p_nb_bytes; } // opj_write_from_buffer() -static OPJ_SIZE_T opj_skip_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo * p_file) { - if(p_file->cur + p_nb_bytes < p_file->buf + p_file->len ) - { - p_file->cur += p_nb_bytes; - return p_nb_bytes; - } - p_file->cur = p_file->buf + p_file->len; - return (OPJ_SIZE_T)-1; +static OPJ_SIZE_T opj_skip_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo *p_file) { + if (p_file->cur + p_nb_bytes < p_file->buf + p_file->len) { + p_file->cur += p_nb_bytes; + return p_nb_bytes; + } + p_file->cur = p_file->buf + p_file->len; + return (OPJ_SIZE_T)-1; } //opj_skip_from_buffer() //fix for https://github.com/neurolabusc/dcm_qa/issues/5 -static OPJ_BOOL opj_seek_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo * p_file) { - //printf("opj_seek_from_buffer %d + %d -> %d + %d\n", p_file->cur , p_nb_bytes, p_file->buf, p_file->len); - if (p_nb_bytes < p_file->len ) { - p_file->cur = p_file->buf + p_nb_bytes; - return OPJ_TRUE; - } - p_file->cur = p_file->buf + p_file->len; - return OPJ_FALSE; +static OPJ_BOOL opj_seek_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo *p_file) { +//printf("opj_seek_from_buffer %d + %d -> %d + %d\n", p_file->cur , p_nb_bytes, p_file->buf, p_file->len); + if (p_nb_bytes < p_file->len) { + p_file->cur = p_file->buf + p_nb_bytes; + return OPJ_TRUE; + } + p_file->cur = p_file->buf + p_file->len; + return OPJ_FALSE; } //opj_seek_from_buffer() -/*static OPJ_BOOL opj_seek_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo * p_file) { - if((p_file->cur + p_nb_bytes) < (p_file->buf + p_file->len) ) { - p_file->cur += p_nb_bytes; - return OPJ_TRUE; - } - p_file->cur = p_file->buf + p_file->len; - return OPJ_FALSE; -} //opj_seek_from_buffer()*/ - -opj_stream_t* opj_stream_create_buffer_stream(BufInfo* p_file, OPJ_UINT32 p_size, OPJ_BOOL p_is_read_stream) { - opj_stream_t* l_stream; - if(! p_file) return NULL; - l_stream = opj_stream_create(p_size, p_is_read_stream); - if(! l_stream) return NULL; - opj_stream_set_user_data(l_stream, p_file , my_stream_free); - opj_stream_set_user_data_length(l_stream, p_file->len); - opj_stream_set_read_function(l_stream, (opj_stream_read_fn) opj_read_from_buffer); - opj_stream_set_write_function(l_stream, (opj_stream_write_fn) opj_write_from_buffer); - opj_stream_set_skip_function(l_stream, (opj_stream_skip_fn) opj_skip_from_buffer); - opj_stream_set_seek_function(l_stream, (opj_stream_seek_fn) opj_seek_from_buffer); - return l_stream; +opj_stream_t *opj_stream_create_buffer_stream(BufInfo *p_file, OPJ_UINT32 p_size, OPJ_BOOL p_is_read_stream) { + opj_stream_t *l_stream; + if (!p_file) + return NULL; + l_stream = opj_stream_create(p_size, p_is_read_stream); + if (!l_stream) + return NULL; + opj_stream_set_user_data(l_stream, p_file, my_stream_free); + opj_stream_set_user_data_length(l_stream, p_file->len); + opj_stream_set_read_function(l_stream, (opj_stream_read_fn)opj_read_from_buffer); + opj_stream_set_write_function(l_stream, (opj_stream_write_fn)opj_write_from_buffer); + opj_stream_set_skip_function(l_stream, (opj_stream_skip_fn)opj_skip_from_buffer); + opj_stream_set_seek_function(l_stream, (opj_stream_seek_fn)opj_seek_from_buffer); + return l_stream; } //opj_stream_create_buffer_stream() -unsigned char * nii_loadImgCoreOpenJPEG(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) { - //OpenJPEG library is not well documented and has changed between versions - //Since the JPEG is embedded in a DICOM we need to skip bytes at the start of the file - // In theory we might also want to strip data that exists AFTER the image, see gdcmJPEG2000Codec.c - unsigned char * ret = NULL; - opj_dparameters_t params; - opj_codec_t *codec; - opj_image_t *jpx; - opj_stream_t *stream; - FILE *reader = fopen(imgname, "rb"); - fseek(reader, 0, SEEK_END); - long size = ftell(reader)- dcm.imageStart; - if (size <= 8) return NULL; - fseek(reader, dcm.imageStart, SEEK_SET); - unsigned char *data = (unsigned char*) malloc(size); - size_t sz = fread(data, 1, size, reader); - fclose(reader); - if (sz < size) return NULL; - OPJ_CODEC_FORMAT format = OPJ_CODEC_JP2; - //DICOM JPEG2k is SUPPOSED to start with codestream, but some vendors include a header - if (data[0] == 0xFF && data[1] == 0x4F && data[2] == 0xFF && data[3] == 0x51) format = OPJ_CODEC_J2K; - opj_set_default_decoder_parameters(¶ms); - BufInfo dx; - dx.buf = data; - dx.cur = data; - dx.len = size; - stream = opj_stream_create_buffer_stream(&dx, (OPJ_UINT32)size, true); - if (stream == NULL) return NULL; - codec = opj_create_decompress(format); - // setup the decoder decoding parameters using user parameters - if ( !opj_setup_decoder(codec, ¶ms) ) goto cleanup2; - // Read the main header of the codestream and if necessary the JP2 boxes - if(! opj_read_header( stream, codec, &jpx)){ - printError( "OpenJPEG failed to read the header %s (offset %d)\n", imgname, dcm.imageStart); - //comment these next lines to abort: include these to create zero-padded slice - #ifdef MY_ZEROFILLBROKENJPGS - //fix broken slices https://github.com/scitran-apps/dcm2niix/issues/4 - printError( "Zero-filled slice created\n"); - int imgbytes = (hdr.bitpix/8)*hdr.dim[1]*hdr.dim[2]; - ret = (unsigned char*) calloc(imgbytes,1); - #endif - goto cleanup2; - } - // Get the decoded image - if ( !( opj_decode(codec, stream, jpx) && opj_end_decompress(codec,stream) ) ) { - printError( "OpenJPEG j2k_to_image failed to decode %s\n",imgname); - goto cleanup1; - } - ret = imagetoimg(jpx); +unsigned char *nii_loadImgCoreOpenJPEG(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) { + //OpenJPEG library is not well documented and has changed between versions + //Since the JPEG is embedded in a DICOM we need to skip bytes at the start of the file + // In theory we might also want to strip data that exists AFTER the image, see gdcmJPEG2000Codec.c + unsigned char *ret = NULL; + opj_dparameters_t params; + opj_codec_t *codec; + opj_image_t *jpx; + opj_stream_t *stream; + FILE *reader = fopen(imgname, "rb"); + fseek(reader, 0, SEEK_END); + long size = ftell(reader) - dcm.imageStart; + if (size <= 8) + return NULL; + fseek(reader, dcm.imageStart, SEEK_SET); + unsigned char *data = (unsigned char *)malloc(size); + size_t sz = fread(data, 1, size, reader); + fclose(reader); + if (sz < size) + return NULL; + OPJ_CODEC_FORMAT format = OPJ_CODEC_JP2; + //DICOM JPEG2k is SUPPOSED to start with codestream, but some vendors include a header + if (data[0] == 0xFF && data[1] == 0x4F && data[2] == 0xFF && data[3] == 0x51) + format = OPJ_CODEC_J2K; + opj_set_default_decoder_parameters(¶ms); + BufInfo dx; + dx.buf = data; + dx.cur = data; + dx.len = size; + stream = opj_stream_create_buffer_stream(&dx, (OPJ_UINT32)size, true); + if (stream == NULL) + return NULL; + codec = opj_create_decompress(format); + // setup the decoder decoding parameters using user parameters + if (!opj_setup_decoder(codec, ¶ms)) + goto cleanup2; + // Read the main header of the codestream and if necessary the JP2 boxes + if (!opj_read_header(stream, codec, &jpx)) { + printError("OpenJPEG failed to read the header %s (offset %d)\n", imgname, dcm.imageStart); +//comment these next lines to abort: include these to create zero-padded slice +#ifdef MY_ZEROFILLBROKENJPGS + //fix broken slices https://github.com/scitran-apps/dcm2niix/issues/4 + printError("Zero-filled slice created\n"); + int imgbytes = (hdr.bitpix / 8) * hdr.dim[1] * hdr.dim[2]; + ret = (unsigned char *)calloc(imgbytes, 1); +#endif + goto cleanup2; + } + // Get the decoded image + if (!(opj_decode(codec, stream, jpx) && opj_end_decompress(codec, stream))) { + printError("OpenJPEG j2k_to_image failed to decode %s\n", imgname); + goto cleanup1; + } + ret = imagetoimg(jpx); cleanup1: - opj_image_destroy(jpx); + opj_image_destroy(jpx); cleanup2: - free(dx.buf); - opj_stream_destroy(stream); - opj_destroy_codec(codec); - return ret; + free(dx.buf); + opj_stream_destroy(stream); + opj_destroy_codec(codec); + return ret; } #endif //myDisableOpenJPEG #ifndef M_PI -#define M_PI 3.14159265358979323846 +#define M_PI 3.14159265358979323846 #endif float deFuzz(float v) { - if (fabs(v) < 0.00001) - return 0; - else - return v; - + if (fabs(v) < 0.00001) + return 0; + else + return v; } #ifdef MY_DEBUG void reportMat33(char *str, mat33 A) { - printMessage("%s = [%g %g %g ; %g %g %g; %g %g %g ]\n",str, - deFuzz(A.m[0][0]),deFuzz(A.m[0][1]),deFuzz(A.m[0][2]), - deFuzz(A.m[1][0]),deFuzz(A.m[1][1]),deFuzz(A.m[1][2]), - deFuzz(A.m[2][0]),deFuzz(A.m[2][1]),deFuzz(A.m[2][2])); + printMessage("%s = [%g %g %g ; %g %g %g; %g %g %g ]\n", str, + deFuzz(A.m[0][0]), deFuzz(A.m[0][1]), deFuzz(A.m[0][2]), + deFuzz(A.m[1][0]), deFuzz(A.m[1][1]), deFuzz(A.m[1][2]), + deFuzz(A.m[2][0]), deFuzz(A.m[2][1]), deFuzz(A.m[2][2])); } void reportMat44(char *str, mat44 A) { //example: reportMat44((char*)"out",*R); - printMessage("%s = [%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1]\n",str, - deFuzz(A.m[0][0]),deFuzz(A.m[0][1]),deFuzz(A.m[0][2]),deFuzz(A.m[0][3]), - deFuzz(A.m[1][0]),deFuzz(A.m[1][1]),deFuzz(A.m[1][2]),deFuzz(A.m[1][3]), - deFuzz(A.m[2][0]),deFuzz(A.m[2][1]),deFuzz(A.m[2][2]),deFuzz(A.m[2][3])); + printMessage("%s = [%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1]\n", str, + deFuzz(A.m[0][0]), deFuzz(A.m[0][1]), deFuzz(A.m[0][2]), deFuzz(A.m[0][3]), + deFuzz(A.m[1][0]), deFuzz(A.m[1][1]), deFuzz(A.m[1][2]), deFuzz(A.m[1][3]), + deFuzz(A.m[2][0]), deFuzz(A.m[2][1]), deFuzz(A.m[2][2]), deFuzz(A.m[2][3])); } #endif -int verify_slice_dir (struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, mat44 *R, int isVerbose){ - //returns slice direction: 1=sag,2=coronal,3=axial, -= flipped - if (h->dim[3] < 2) return 0; //don't care direction for single slice - int iSL = 1; //find Z-slice direction: row with highest magnitude of 3rd column - if ( (fabs(R->m[1][2]) >= fabs(R->m[0][2])) - && (fabs(R->m[1][2]) >= fabs(R->m[2][2]))) iSL = 2; // - if ( (fabs(R->m[2][2]) >= fabs(R->m[0][2])) - && (fabs(R->m[2][2]) >= fabs(R->m[1][2]))) iSL = 3; //axial acquisition - float pos = NAN; - if ( !isnan(d2.patientPosition[iSL]) ) { //patient position fields exist - pos = d2.patientPosition[iSL]; - if (isSameFloat(pos, d.patientPosition[iSL])) pos = NAN; +int verify_slice_dir(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, mat44 *R, int isVerbose) { +//returns slice direction: 1=sag,2=coronal,3=axial, -= flipped + if (h->dim[3] < 2) + return 0; //don't care direction for single slice + int iSL = 1; //find Z-slice direction: row with highest magnitude of 3rd column + if ((fabs(R->m[1][2]) >= fabs(R->m[0][2])) && (fabs(R->m[1][2]) >= fabs(R->m[2][2]))) + iSL = 2; // + if ((fabs(R->m[2][2]) >= fabs(R->m[0][2])) && (fabs(R->m[2][2]) >= fabs(R->m[1][2]))) + iSL = 3; //axial acquisition + float pos = NAN; + if (!isnan(d2.patientPosition[iSL])) { //patient position fields exist + pos = d2.patientPosition[iSL]; + if (isSameFloat(pos, d.patientPosition[iSL])) + pos = NAN; #ifdef MY_DEBUG - if (!isnan(pos)) printMessage("position determined using lastFile %f\n",pos); + if (!isnan(pos)) + printMessage("position determined using lastFile %f\n", pos); #endif - } - if (isnan(pos) &&( !isnan(d.patientPositionLast[iSL]) ) ) { //patient position fields exist - pos = d.patientPositionLast[iSL]; - if (isSameFloat(pos, d.patientPosition[iSL])) pos = NAN; + } + if (isnan(pos) && (!isnan(d.patientPositionLast[iSL]))) { //patient position fields exist + pos = d.patientPositionLast[iSL]; + if (isSameFloat(pos, d.patientPosition[iSL])) + pos = NAN; #ifdef MY_DEBUG - if (!isnan(pos)) printMessage("position determined using last (4d) %f\n",pos); + if (!isnan(pos)) + printMessage("position determined using last (4d) %f\n", pos); #endif - } - if (isnan(pos) && ( !isnan(d.stackOffcentre[iSL])) ) - pos = d.stackOffcentre[iSL]; - if (isnan(pos) && ( !isnan(d.lastScanLoc)) ) - pos = d.lastScanLoc; - //if (isnan(pos)) - vec4 x; - x.v[0] = 0.0; x.v[1] = 0.0; x.v[2]=(float)(h->dim[3]-1.0); x.v[3] = 1.0; - vec4 pos1v = nifti_vect44mat44_mul(x, *R); - float pos1 = pos1v.v[iSL-1];//-1 as C indexed from 0 - bool flip = false; - if (!isnan(pos)) // we have real SliceLocation for last slice or volume center - flip = (pos > R->m[iSL-1][3]) != (pos1 > R->m[iSL-1][3]); // same direction?, note C indices from 0 - else {// we do some guess work and warn user - vec3 readV = setVec3(d.orient[1],d.orient[2],d.orient[3]); - vec3 phaseV = setVec3(d.orient[4],d.orient[5],d.orient[6]); + } + if (isnan(pos) && (!isnan(d.stackOffcentre[iSL]))) + pos = d.stackOffcentre[iSL]; + if (isnan(pos) && (!isnan(d.lastScanLoc))) + pos = d.lastScanLoc; + vec4 x; + x.v[0] = 0.0; + x.v[1] = 0.0; + x.v[2] = (float)(h->dim[3] - 1.0); + x.v[3] = 1.0; + vec4 pos1v = nifti_vect44mat44_mul(x, *R); + float pos1 = pos1v.v[iSL - 1]; //-1 as C indexed from 0 + bool flip = false; + if (!isnan(pos)) // we have real SliceLocation for last slice or volume center + flip = (pos > R->m[iSL - 1][3]) != (pos1 > R->m[iSL - 1][3]); // same direction?, note C indices from 0 + else { // we do some guess work and warn user + vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]); + vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]); //printMessage("rd %g %g %g\n",readV.v[0],readV.v[1],readV.v[2]); //printMessage("ph %g %g %g\n",phaseV.v[0],phaseV.v[1],phaseV.v[2]); - vec3 sliceV = crossProduct(readV, phaseV); //order important: this is our hail mary - flip = ((sliceV.v[0]+sliceV.v[1]+sliceV.v[2]) < 0); - //printMessage("verify slice dir %g %g %g\n",sliceV.v[0],sliceV.v[1],sliceV.v[2]); - if (isVerbose) { //1st pass only - if (!d.isDerived) {//do not warn user if image is derived + vec3 sliceV = crossProduct(readV, phaseV); //order important: this is our hail mary + flip = ((sliceV.v[0] + sliceV.v[1] + sliceV.v[2]) < 0); + //printMessage("verify slice dir %g %g %g\n",sliceV.v[0],sliceV.v[1],sliceV.v[2]); + if (isVerbose) { //1st pass only + if (!d.isDerived) { //do not warn user if image is derived printWarning("Unable to determine slice direction: please check whether slices are flipped\n"); } else { printWarning("Unable to determine slice direction: please check whether slices are flipped (derived image)\n"); - } - } - } - if (flip) { - for (int i = 0; i < 4; i++) - R->m[i][2] = -R->m[i][2]; - } - if (flip) - iSL = -iSL; - #ifdef MY_DEBUG - printMessage("verify slice dir %d %d %d\n",h->dim[1],h->dim[2],h->dim[3]); - //reportMat44((char*)"Rout",*R); - printMessage("flip = %d\n",flip); - printMessage("sliceDir = %d\n",iSL); - printMessage(" pos1 = %f\n",pos1); - #endif + } + } + } + if (flip) { + for (int i = 0; i < 4; i++) + R->m[i][2] = -R->m[i][2]; + } + if (flip) + iSL = -iSL; +#ifdef MY_DEBUG + printMessage("verify slice dir %d %d %d\n", h->dim[1], h->dim[2], h->dim[3]); + //reportMat44((char*)"Rout",*R); + printMessage("flip = %d\n", flip); + printMessage("sliceDir = %d\n", iSL); + printMessage(" pos1 = %f\n", pos1); +#endif return iSL; } //verify_slice_dir() -mat44 noNaN(mat44 Q44, bool isVerbose, bool * isBogus) //simplify any headers that have NaN values +mat44 noNaN(mat44 Q44, bool isVerbose, bool *isBogus) //simplify any headers that have NaN values { - mat44 ret = Q44; - bool isNaN44 = false; - for (int i = 0; i < 4; i++) - for (int j = 0; j < 4; j++) - if (isnan(ret.m[i][j])) - isNaN44 = true; - if (isNaN44) { - *isBogus = true; - if (isVerbose) - printWarning("Bogus spatial matrix (perhaps non-spatial image): inspect spatial orientation\n"); - for (int i = 0; i < 4; i++) - for (int j = 0; j < 4; j++) - if (i == j) - ret.m[i][j] = 1; - else - ret.m[i][j] = 0; - ret.m[1][1] = -1; - } //if isNaN detected - return ret; + mat44 ret = Q44; + bool isNaN44 = false; + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + if (isnan(ret.m[i][j])) + isNaN44 = true; + if (isNaN44) { + *isBogus = true; + if (isVerbose) + printWarning("Bogus spatial matrix (perhaps non-spatial image): inspect spatial orientation\n"); + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + if (i == j) + ret.m[i][j] = 1; + else + ret.m[i][j] = 0; + ret.m[1][1] = -1; + } //if isNaN detected + return ret; } #define kSessionOK 0 #define kSessionBadMatrix 1 void setQSForm(struct nifti_1_header *h, mat44 Q44i, bool isVerbose) { - bool isBogus = false; - mat44 Q44 = noNaN(Q44i, isVerbose, & isBogus); - if ((h->session_error == kSessionBadMatrix) || (isBogus)) { - h->session_error = kSessionBadMatrix; - h->sform_code = NIFTI_XFORM_UNKNOWN; - } else - h->sform_code = NIFTI_XFORM_SCANNER_ANAT; - h->srow_x[0] = Q44.m[0][0]; - h->srow_x[1] = Q44.m[0][1]; - h->srow_x[2] = Q44.m[0][2]; - h->srow_x[3] = Q44.m[0][3]; - h->srow_y[0] = Q44.m[1][0]; - h->srow_y[1] = Q44.m[1][1]; - h->srow_y[2] = Q44.m[1][2]; - h->srow_y[3] = Q44.m[1][3]; - h->srow_z[0] = Q44.m[2][0]; - h->srow_z[1] = Q44.m[2][1]; - h->srow_z[2] = Q44.m[2][2]; - h->srow_z[3] = Q44.m[2][3]; - float dumdx, dumdy, dumdz; - nifti_mat44_to_quatern( Q44 , &h->quatern_b, &h->quatern_c, &h->quatern_d,&h->qoffset_x, &h->qoffset_y, &h->qoffset_z, &dumdx, &dumdy, &dumdz,&h->pixdim[0]) ; - h->qform_code = h->sform_code; + bool isBogus = false; + mat44 Q44 = noNaN(Q44i, isVerbose, &isBogus); + if ((h->session_error == kSessionBadMatrix) || (isBogus)) { + h->session_error = kSessionBadMatrix; + h->sform_code = NIFTI_XFORM_UNKNOWN; + } else + h->sform_code = NIFTI_XFORM_SCANNER_ANAT; + h->srow_x[0] = Q44.m[0][0]; + h->srow_x[1] = Q44.m[0][1]; + h->srow_x[2] = Q44.m[0][2]; + h->srow_x[3] = Q44.m[0][3]; + h->srow_y[0] = Q44.m[1][0]; + h->srow_y[1] = Q44.m[1][1]; + h->srow_y[2] = Q44.m[1][2]; + h->srow_y[3] = Q44.m[1][3]; + h->srow_z[0] = Q44.m[2][0]; + h->srow_z[1] = Q44.m[2][1]; + h->srow_z[2] = Q44.m[2][2]; + h->srow_z[3] = Q44.m[2][3]; + float dumdx, dumdy, dumdz; + nifti_mat44_to_quatern(Q44, &h->quatern_b, &h->quatern_c, &h->quatern_d, &h->qoffset_x, &h->qoffset_y, &h->qoffset_z, &dumdx, &dumdy, &dumdz, &h->pixdim[0]); + h->qform_code = h->sform_code; } //setQSForm() #ifdef my_unused @@ -417,11 +426,10 @@ void setQSForm(struct nifti_1_header *h, mat44 Q44i, bool isVerbose) { ivec3 maxCol(mat33 R) { //return index of maximum column in 3x3 matrix, e.g. [1 0 0; 0 1 0; 0 0 1] -> 1,2,3 ivec3 ixyz; - //foo is abs(R) - mat33 foo; - for (int i=0 ; i < 3 ; i++ ) - for (int j=0 ; j < 3 ; j++ ) - foo.m[i][j] = fabs(R.m[i][j]); + mat33 foo; + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + foo.m[i][j] = fabs(R.m[i][j]); //ixyz.v[0] : row with largest value in column 1 ixyz.v[0] = 1; if ((foo.m[1][0] > foo.m[0][0]) && (foo.m[1][0] >= foo.m[2][0])) @@ -443,7 +451,7 @@ ivec3 maxCol(mat33 R) { ixyz.v[1] = 2; } //ixyz.v[2] : 3rd row, constrained by previous rows - ixyz.v[2] = 6 - ixyz.v[1] - ixyz.v[0];//sum of 1+2+3 + ixyz.v[2] = 6 - ixyz.v[1] - ixyz.v[0]; //sum of 1+2+3 return ixyz; } @@ -457,28 +465,28 @@ int sign(float x) { } // Subfunction: get dicom xform matrix and related info -// This is a direct port of Xiangrui Li's dicm2nii function +// This is a direct port of Xiangrui Li's dicm2nii function mat44 xform_mat(struct TDICOMdata d) { - vec3 readV = setVec3(d.orient[1],d.orient[2],d.orient[3]); - vec3 phaseV = setVec3(d.orient[4],d.orient[5],d.orient[6]); - vec3 sliceV = crossProduct(readV ,phaseV); - mat33 R; - LOAD_MAT33(R, readV.v[0], readV.v[1], readV.v[2], - phaseV.v[0], phaseV.v[1], phaseV.v[2], - sliceV.v[0], sliceV.v[1], sliceV.v[2]); - R = nifti_mat33_transpose(R); + vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]); + vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]); + vec3 sliceV = crossProduct(readV, phaseV); + mat33 R; + LOAD_MAT33(R, readV.v[0], readV.v[1], readV.v[2], + phaseV.v[0], phaseV.v[1], phaseV.v[2], + sliceV.v[0], sliceV.v[1], sliceV.v[2]); + R = nifti_mat33_transpose(R); //reportMat33((char*)"R",R); ivec3 ixyz = maxCol(R); //printMessage("%d %d %d\n", ixyz.v[0], ixyz.v[1], ixyz.v[2]); int iSL = ixyz.v[2]; // 1/2/3 for Sag/Cor/Tra slice - float cosSL = R.m[iSL-1][2]; + float cosSL = R.m[iSL - 1][2]; //printMessage("cosSL\t%g\n", cosSL); //vec3 pixdim = setVec3(d.xyzMM[1], d.xyzMM[2], d.xyzMM[3]); //printMessage("%g %g %g\n", pixdim.v[0], pixdim.v[1], pixdim.v[2]); mat33 pixdim; - LOAD_MAT33(pixdim, d.xyzMM[1], 0.0, 0.0, - 0.0, d.xyzMM[2], 0.0, - 0.0, 0.0, d.xyzMM[3]); + LOAD_MAT33(pixdim, d.xyzMM[1], 0.0, 0.0, + 0.0, d.xyzMM[2], 0.0, + 0.0, 0.0, d.xyzMM[3]); R = nifti_mat33_mul(R, pixdim); //reportMat33((char*)"R",R); mat44 R44; @@ -487,36 +495,34 @@ mat44 xform_mat(struct TDICOMdata d) { R.m[2][0], R.m[2][1], R.m[2][2], d.patientPosition[3]); //reportMat44((char*)"R",R44); //rest are former: R = verify_slice_dir(R, s, dim, iSL) - - - if ((d.xyzDim[3]<2) && (d.CSA.mosaicSlices < 2)) + if ((d.xyzDim[3] < 2) && (d.CSA.mosaicSlices < 2)) return R44; //don't care direction for single slice vec3 dim = setVec3(d.xyzDim[1], d.xyzDim[2], d.xyzDim[3]); if (d.CSA.mosaicSlices > 1) { //Siemens mosaic: use dim(1) since no transpose to img - float nRowCol = ceil(sqrt((double) d.CSA.mosaicSlices)); - dim.v[0] = dim.v[0] / nRowCol; - dim.v[1] = dim.v[1] / nRowCol; - dim.v[2] = d.CSA.mosaicSlices; - vec4 dim4 = setVec4((nRowCol-1)*dim.v[0]/2.0f, (nRowCol-1)*dim.v[1]/2.0f, 0); - vec4 offset = nifti_vect44mat44_mul(dim4, R44 ); - //printMessage("%g %g %g\n", dim.v[0], dim.v[1], dim.v[2]); - //printMessage("%g %g %g\n", dim4.v[0], dim4.v[1], dim4.v[2]); - //printMessage("%g %g %g %g\n", offset.v[0], offset.v[1], offset.v[2], offset.v[3]); + float nRowCol = ceil(sqrt((double)d.CSA.mosaicSlices)); + dim.v[0] = dim.v[0] / nRowCol; + dim.v[1] = dim.v[1] / nRowCol; + dim.v[2] = d.CSA.mosaicSlices; + vec4 dim4 = setVec4((nRowCol - 1) * dim.v[0] / 2.0f, (nRowCol - 1) * dim.v[1] / 2.0f, 0); + vec4 offset = nifti_vect44mat44_mul(dim4, R44); + //printMessage("%g %g %g\n", dim.v[0], dim.v[1], dim.v[2]); + //printMessage("%g %g %g\n", dim4.v[0], dim4.v[1], dim4.v[2]); + //printMessage("%g %g %g %g\n", offset.v[0], offset.v[1], offset.v[2], offset.v[3]); //printMessage("nRowCol\t%g\n", nRowCol); R44.m[0][3] = offset.v[0]; R44.m[1][3] = offset.v[1]; R44.m[2][3] = offset.v[2]; //R44.m[3][3] = offset.v[3]; - if (sign(d.CSA.sliceNormV[iSL]) != sign(cosSL)) { + if (sign(d.CSA.sliceNormV[iSL]) != sign(cosSL)) { R44.m[0][2] = -R44.m[0][2]; R44.m[1][2] = -R44.m[1][2]; R44.m[2][2] = -R44.m[2][2]; R44.m[3][2] = -R44.m[3][2]; } - //reportMat44((char*)"iR44",R44); + //reportMat44((char*)"iR44",R44); return R44; } else if (true) { -//SliceNormalVector TO DO + //SliceNormalVector TO DO printMessage("Not completed"); #ifndef USING_R exit(2); @@ -527,720 +533,718 @@ mat44 xform_mat(struct TDICOMdata d) { #ifndef USING_R exit(1); #else - return R44; + return R44; #endif } mat44 set_nii_header(struct TDICOMdata d) { mat44 R = xform_mat(d); //R(1:2,:) = -R(1:2,:); % dicom LPS to nifti RAS, xform matrix before reorient - for (int i=0; i<2; i++) - for(int j=0; j<4; j++) - R.m[i][j] = -R.m[i][j]; - #ifdef MY_DEBUG - reportMat44((char*)"R44",R); - #endif + for (int i = 0; i < 2; i++) + for (int j = 0; j < 4; j++) + R.m[i][j] = -R.m[i][j]; +#ifdef MY_DEBUG + reportMat44((char *)"R44", R); +#endif } #endif -/*mat44 doQuadruped(mat44 m) { - mat44 m_in = m; - mat44 rot; - LOAD_MAT44(rot, 1.0,0.0,0.0,0.0, - 0.0,0.0,-1.0,0.0, - 0.0,-1.0,0.0,0.0); - return nifti_mat44_mul( rot, m_in ); -}*/ - -// This code predates Xiangrui Li's set_nii_header function -mat44 set_nii_header_x(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int* sliceDir, int isVerbose) { - *sliceDir = 0; - mat44 Q44 = nifti_dicom2mat(d.orient, d.patientPosition, d.xyzMM); - //Q44 = doQuadruped(Q44); +// This code predates Xiangrui Li's set_nii_header function +mat44 set_nii_header_x(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int *sliceDir, int isVerbose) { + *sliceDir = 0; + mat44 Q44 = nifti_dicom2mat(d.orient, d.patientPosition, d.xyzMM); + //Q44 = doQuadruped(Q44); if (d.isSegamiOasis == true) { //Segami reconstructions appear to disregard DICOM spatial parameters: assume center of volume is isocenter and no table tilt // Consider sample image with d.orient (0020,0037) = -1 0 0; 0 1 0: this suggests image RAI (L->R, P->A, S->I) but the vendors viewing software suggests LPS //Perhaps we should ignore 0020,0037 and 0020,0032 as they are hidden in sequence 0054,0022, but in this case no positioning is provided // http://www.cs.ucl.ac.uk/fileadmin/cmic/Documents/DavidAtkinson/DICOM.pdf // https://www.slicer.org/wiki/Coordinate_systems - LOAD_MAT44(Q44, -h->pixdim[1],0,0,0, 0,-h->pixdim[2],0,0, 0,0,h->pixdim[3],0); //X and Y dimensions flipped in NIfTI (RAS) vs DICOM (LPS) - vec4 originVx = setVec4( (h->dim[1]+1.0f)/2.0f, (h->dim[2]+1.0f)/2.0f, (h->dim[3]+1.0f)/2.0f); - vec4 originMm = nifti_vect44mat44_mul(originVx, Q44); - for (int i = 0; i < 3; i++) - Q44.m[i][3] = -originMm.v[i]; //set origin to center voxel - if (isVerbose) { - //printMessage("origin (vx) %g %g %g\n",originVx.v[0],originVx.v[1],originVx.v[2]); - //printMessage("origin (mm) %g %g %g\n",originMm.v[0],originMm.v[1],originMm.v[2]); - printWarning("Segami coordinates defy DICOM convention, please check orientation\n"); - } - return Q44; - } - //next line only for Siemens mosaic: ignore for UIH grid + LOAD_MAT44(Q44, -h->pixdim[1], 0, 0, 0, 0, -h->pixdim[2], 0, 0, 0, 0, h->pixdim[3], 0); //X and Y dimensions flipped in NIfTI (RAS) vs DICOM (LPS) + vec4 originVx = setVec4((h->dim[1] + 1.0f) / 2.0f, (h->dim[2] + 1.0f) / 2.0f, (h->dim[3] + 1.0f) / 2.0f); + vec4 originMm = nifti_vect44mat44_mul(originVx, Q44); + for (int i = 0; i < 3; i++) + Q44.m[i][3] = -originMm.v[i]; //set origin to center voxel + if (isVerbose) { + //printMessage("origin (vx) %g %g %g\n",originVx.v[0],originVx.v[1],originVx.v[2]); + //printMessage("origin (mm) %g %g %g\n",originMm.v[0],originMm.v[1],originMm.v[2]); + printWarning("Segami coordinates defy DICOM convention, please check orientation\n"); + } + return Q44; + } + //next line only for Siemens mosaic: ignore for UIH grid // https://github.com/xiangruili/dicm2nii/commit/47ad9e6d9bc8a999344cbd487d602d420fb1509f - if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.mosaicSlices > 1)) { - double nRowCol = ceil(sqrt((double) d.CSA.mosaicSlices)); - double lFactorX = (d.xyzDim[1] -(d.xyzDim[1]/nRowCol) )/2.0; - double lFactorY = (d.xyzDim[2] -(d.xyzDim[2]/nRowCol) )/2.0; - Q44.m[0][3] =(float)((Q44.m[0][0]*lFactorX)+(Q44.m[0][1]*lFactorY)+Q44.m[0][3]); + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.mosaicSlices > 1)) { + double nRowCol = ceil(sqrt((double)d.CSA.mosaicSlices)); + double lFactorX = (d.xyzDim[1] - (d.xyzDim[1] / nRowCol)) / 2.0; + double lFactorY = (d.xyzDim[2] - (d.xyzDim[2] / nRowCol)) / 2.0; + Q44.m[0][3] = (float)((Q44.m[0][0] * lFactorX) + (Q44.m[0][1] * lFactorY) + Q44.m[0][3]); Q44.m[1][3] = (float)((Q44.m[1][0] * lFactorX) + (Q44.m[1][1] * lFactorY) + Q44.m[1][3]); Q44.m[2][3] = (float)((Q44.m[2][0] * lFactorX) + (Q44.m[2][1] * lFactorY) + Q44.m[2][3]); - for (int c=0; c<2; c++) - for (int r=0; r<4; r++) - Q44.m[c][r] = -Q44.m[c][r]; - mat33 Q; - - LOAD_MAT33(Q, d.orient[1], d.orient[4],d.CSA.sliceNormV[1], - d.orient[2],d.orient[5],d.CSA.sliceNormV[2], - d.orient[3],d.orient[6],d.CSA.sliceNormV[3]); - if (nifti_mat33_determ(Q) < 0) { //Siemens sagittal are R>>L, whereas NIfTI is L>>R, we retain Siemens order on disk so ascending is still ascending, but we need to have the spatial transform reflect this. - mat44 det; - *sliceDir = kSliceOrientMosaicNegativeDeterminant; //we need to handle DTI vectors accordingly - LOAD_MAT44(det, 1.0l,0.0l,0.0l,0.0l, 0.0l,1.0l,0.0l,0.0l, 0.0l,0.0l,-1.0l,0.0l); - //patient_to_tal.m[2][3] = 1-d.CSA.MosaicSlices; - Q44 = nifti_mat44_mul(Q44,det); - } - } else { //not a mosaic - *sliceDir = verify_slice_dir(d, d2, h, &Q44, isVerbose); - for (int c=0; c<4; c++)// LPS to nifti RAS, xform matrix before reorient - for (int r=0; r<2; r++) //swap rows 1 & 2 - Q44.m[r][c] = - Q44.m[r][c]; - } - #ifdef MY_DEBUG - reportMat44((char*)"Q44",Q44); - #endif - return Q44; + for (int c = 0; c < 2; c++) + for (int r = 0; r < 4; r++) + Q44.m[c][r] = -Q44.m[c][r]; + mat33 Q; + LOAD_MAT33(Q, d.orient[1], d.orient[4], d.CSA.sliceNormV[1], + d.orient[2], d.orient[5], d.CSA.sliceNormV[2], + d.orient[3], d.orient[6], d.CSA.sliceNormV[3]); + if (nifti_mat33_determ(Q) < 0) { //Siemens sagittal are R>>L, whereas NIfTI is L>>R, we retain Siemens order on disk so ascending is still ascending, but we need to have the spatial transform reflect this. + mat44 det; + *sliceDir = kSliceOrientMosaicNegativeDeterminant; //we need to handle DTI vectors accordingly + LOAD_MAT44(det, 1.0l, 0.0l, 0.0l, 0.0l, 0.0l, 1.0l, 0.0l, 0.0l, 0.0l, 0.0l, -1.0l, 0.0l); + //patient_to_tal.m[2][3] = 1-d.CSA.MosaicSlices; + Q44 = nifti_mat44_mul(Q44, det); + } + } else { //not a mosaic + *sliceDir = verify_slice_dir(d, d2, h, &Q44, isVerbose); + for (int c = 0; c < 4; c++) // LPS to nifti RAS, xform matrix before reorient + for (int r = 0; r < 2; r++) //swap rows 1 & 2 + Q44.m[r][c] = -Q44.m[r][c]; + } +#ifdef MY_DEBUG + reportMat44((char *)"Q44", Q44); +#endif + return Q44; } -int headerDcm2NiiSForm(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose) { //fill header s and q form - //see http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c - //returns sliceDir: 0=unknown,1=sag,2=coro,3=axial,-=reversed slices - int sliceDir = 0; - if (h->dim[3] < 2) { - mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose); - setQSForm(h,Q44, isVerbose); - return sliceDir; //don't care direction for single slice - } - h->sform_code = NIFTI_XFORM_UNKNOWN; - h->qform_code = NIFTI_XFORM_UNKNOWN; - bool isOK = false; - for (int i = 1; i <= 6; i++) - if (d.orient[i] != 0.0) isOK = true; - if (!isOK) { - //we will have to guess, assume axial acquisition saved in standard Siemens style? - d.orient[1] = 1.0f; d.orient[2] = 0.0f; d.orient[3] = 0.0f; - d.orient[1] = 0.0f; d.orient[2] = 1.0f; d.orient[3] = 0.0f; - if ((d.isDerived) || ((d.bitsAllocated == 8) && (d.samplesPerPixel == 3) && (d.manufacturer == kMANUFACTURER_SIEMENS))) { - printMessage("Unable to determine spatial orientation: 0020,0037 missing (probably not a problem: derived image)\n"); - } else { - printMessage("Unable to determine spatial orientation: 0020,0037 missing (Type 1 attribute: not a valid DICOM) Series %ld\n", d.seriesNum); - } - } - mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose); - setQSForm(h,Q44, isVerbose); - return sliceDir; +int headerDcm2NiiSForm(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose) { //fill header s and q form + //see http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c + //returns sliceDir: 0=unknown,1=sag,2=coro,3=axial,-=reversed slices + int sliceDir = 0; + if (h->dim[3] < 2) { + mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose); + setQSForm(h, Q44, isVerbose); + return sliceDir; //don't care direction for single slice + } + h->sform_code = NIFTI_XFORM_UNKNOWN; + h->qform_code = NIFTI_XFORM_UNKNOWN; + bool isOK = false; + for (int i = 1; i <= 6; i++) + if (d.orient[i] != 0.0) + isOK = true; + if (!isOK) { + //we will have to guess, assume axial acquisition saved in standard Siemens style? + d.orient[1] = 1.0f; + d.orient[2] = 0.0f; + d.orient[3] = 0.0f; + d.orient[1] = 0.0f; + d.orient[2] = 1.0f; + d.orient[3] = 0.0f; + if ((d.isDerived) || ((d.bitsAllocated == 8) && (d.samplesPerPixel == 3) && (d.manufacturer == kMANUFACTURER_SIEMENS))) { + printMessage("Unable to determine spatial orientation: 0020,0037 missing (probably not a problem: derived image)\n"); + } else { + printMessage("Unable to determine spatial orientation: 0020,0037 missing (Type 1 attribute: not a valid DICOM) Series %ld\n", d.seriesNum); + } + } + mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose); + setQSForm(h, Q44, isVerbose); + return sliceDir; } //headerDcm2NiiSForm() int headerDcm2Nii2(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose) { //final pass after de-mosaic - char txt[1024] = {""}; - if (h->slice_code == NIFTI_SLICE_UNKNOWN) h->slice_code = d.CSA.sliceOrder; - if (h->slice_code == NIFTI_SLICE_UNKNOWN) h->slice_code = d2.CSA.sliceOrder; //sometimes the first slice order is screwed up https://github.com/eauerbach/CMRR-MB/issues/29 - if (d.modality == kMODALITY_MR) - sprintf(txt, "TE=%.2g;Time=%.3f", d.TE,d.acquisitionTime); - else - sprintf(txt, "Time=%.3f", d.acquisitionTime); - if (d.CSA.phaseEncodingDirectionPositive >= 0) { - char dtxt[1024] = {""}; - sprintf(dtxt, ";phase=%d", d.CSA.phaseEncodingDirectionPositive); - strcat(txt,dtxt); - } - //from dicm2nii 20151117 InPlanePhaseEncodingDirection - if (d.phaseEncodingRC =='R') - 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); - } - // GCC 8 warns about truncation using snprintf - // snprintf(h->descrip,80, "%s",txt); - memcpy(h->descrip, txt, 79); - h->descrip[79] = '\0'; - - if (strlen(d.imageComments) > 0) - snprintf(h->aux_file,24,"%.23s",d.imageComments); - return headerDcm2NiiSForm(d,d2, h, isVerbose); + char txt[1024] = {""}; + if (h->slice_code == NIFTI_SLICE_UNKNOWN) + h->slice_code = d.CSA.sliceOrder; + if (h->slice_code == NIFTI_SLICE_UNKNOWN) + h->slice_code = d2.CSA.sliceOrder; //sometimes the first slice order is screwed up https://github.com/eauerbach/CMRR-MB/issues/29 + if (d.modality == kMODALITY_MR) + sprintf(txt, "TE=%.2g;Time=%.3f", d.TE, d.acquisitionTime); + else + sprintf(txt, "Time=%.3f", d.acquisitionTime); + if (d.CSA.phaseEncodingDirectionPositive >= 0) { + char dtxt[1024] = {""}; + sprintf(dtxt, ";phase=%d", d.CSA.phaseEncodingDirectionPositive); + strcat(txt, dtxt); + } + //from dicm2nii 20151117 InPlanePhaseEncodingDirection + if (d.phaseEncodingRC == 'R') + 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); + } + // GCC 8 warns about truncation using snprintf + // snprintf(h->descrip,80, "%s",txt); + memcpy(h->descrip, txt, 79); + h->descrip[79] = '\0'; + if (strlen(d.imageComments) > 0) + snprintf(h->aux_file, 24, "%.23s", d.imageComments); + return headerDcm2NiiSForm(d, d2, h, isVerbose); } //headerDcm2Nii2() -int dcmStrLen (int len, int kMaxLen) { - if (len < kMaxLen) - return len+1; - else - return kMaxLen; +int dcmStrLen(int len, int kMaxLen) { + if (len < kMaxLen) + return len + 1; + else + return kMaxLen; } //dcmStrLen() struct TDICOMdata clear_dicom_data() { - struct TDICOMdata d; - //d.dti4D = NULL; - d.locationsInAcquisition = 0; - d.locationsInAcquisitionConflict = 0; //for GE discrepancy between tags 0020,1002; 0021,104F; 0054,0081 - d.modality = kMODALITY_UNKNOWN; - d.effectiveEchoSpacingGE = 0; - for (int i=0; i < 4; i++) { - d.CSA.dtiV[i] = 0; - d.patientPosition[i] = NAN; - //d.patientPosition2nd[i] = NAN; //used to distinguish XYZT vs XYTZ for Philips 4D - d.patientPositionLast[i] = NAN; //used to compute slice direction for Philips 4D - d.stackOffcentre[i] = NAN; - d.angulation[i] = 0.0f; - d.xyzMM[i] = 1; - } - for (int i=0; i < MAX_NUMBER_OF_DIMENSIONS; ++i) - d.dimensionIndexValues[i] = 0; - //d.CSA.sliceTiming[0] = -1.0f; //impossible value denotes not known - for (int z = 0; z < kMaxEPI3D; z++) - d.CSA.sliceTiming[z] = -1.0; - d.CSA.numDti = 0; - for (int i=0; i < 5; i++) - d.xyzDim[i] = 1; - for (int i = 0; i < 7; i++) - d.orient[i] = 0.0f; - strcpy(d.patientName, ""); - strcpy(d.patientID, ""); - strcpy(d.accessionNumber, ""); - strcpy(d.imageType,""); - strcpy(d.imageComments, ""); - strcpy(d.imageBaseName, ""); - strcpy(d.phaseEncodingDirectionDisplayedUIH, ""); - strcpy(d.studyDate, ""); - strcpy(d.studyTime, ""); - strcpy(d.protocolName, ""); - strcpy(d.seriesDescription, ""); - strcpy(d.sequenceName, ""); - strcpy(d.scanningSequence, ""); - strcpy(d.sequenceVariant, ""); - strcpy(d.manufacturersModelName, ""); - strcpy(d.institutionalDepartmentName, ""); - strcpy(d.procedureStepDescription, ""); - strcpy(d.institutionName, ""); - strcpy(d.referringPhysicianName, ""); - strcpy(d.institutionAddress, ""); - strcpy(d.deviceSerialNumber, ""); - strcpy(d.softwareVersions, ""); - strcpy(d.stationName, ""); - strcpy(d.scanOptions, ""); - //strcpy(d.mrAcquisitionType, ""); - strcpy(d.seriesInstanceUID, ""); - strcpy(d.instanceUID, ""); - strcpy(d.studyID, ""); - strcpy(d.studyInstanceUID, ""); - strcpy(d.bodyPartExamined,""); - strcpy(d.coilName, ""); - strcpy(d.coilElements, ""); - strcpy(d.radiopharmaceutical, ""); - strcpy(d.convolutionKernel, ""); + struct TDICOMdata d; + //d.dti4D = NULL; + d.locationsInAcquisition = 0; + d.locationsInAcquisitionConflict = 0; //for GE discrepancy between tags 0020,1002; 0021,104F; 0054,0081 + d.modality = kMODALITY_UNKNOWN; + d.effectiveEchoSpacingGE = 0; + for (int i = 0; i < 4; i++) { + d.CSA.dtiV[i] = 0; + d.patientPosition[i] = NAN; + //d.patientPosition2nd[i] = NAN; //used to distinguish XYZT vs XYTZ for Philips 4D + d.patientPositionLast[i] = NAN; //used to compute slice direction for Philips 4D + d.stackOffcentre[i] = NAN; + d.angulation[i] = 0.0f; + d.xyzMM[i] = 1; + } + for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; ++i) + d.dimensionIndexValues[i] = 0; + //d.CSA.sliceTiming[0] = -1.0f; //impossible value denotes not known + for (int z = 0; z < kMaxEPI3D; z++) + d.CSA.sliceTiming[z] = -1.0; + d.CSA.numDti = 0; + for (int i = 0; i < 5; i++) + d.xyzDim[i] = 1; + for (int i = 0; i < 7; i++) + d.orient[i] = 0.0f; + strcpy(d.patientName, ""); + strcpy(d.patientID, ""); + strcpy(d.accessionNumber, ""); + strcpy(d.imageType, ""); + strcpy(d.imageComments, ""); + strcpy(d.imageBaseName, ""); + strcpy(d.phaseEncodingDirectionDisplayedUIH, ""); + strcpy(d.studyDate, ""); + strcpy(d.studyTime, ""); + strcpy(d.protocolName, ""); + strcpy(d.seriesDescription, ""); + strcpy(d.sequenceName, ""); + strcpy(d.scanningSequence, ""); + strcpy(d.sequenceVariant, ""); + strcpy(d.manufacturersModelName, ""); + strcpy(d.institutionalDepartmentName, ""); + strcpy(d.procedureStepDescription, ""); + strcpy(d.institutionName, ""); + strcpy(d.referringPhysicianName, ""); + strcpy(d.institutionAddress, ""); + strcpy(d.deviceSerialNumber, ""); + strcpy(d.softwareVersions, ""); + strcpy(d.stationName, ""); + strcpy(d.scanOptions, ""); + //strcpy(d.mrAcquisitionType, ""); + strcpy(d.seriesInstanceUID, ""); + strcpy(d.instanceUID, ""); + strcpy(d.studyID, ""); + strcpy(d.studyInstanceUID, ""); + strcpy(d.bodyPartExamined, ""); + strcpy(d.coilName, ""); + strcpy(d.coilElements, ""); + strcpy(d.radiopharmaceutical, ""); + strcpy(d.convolutionKernel, ""); strcpy(d.parallelAcquisitionTechnique, ""); strcpy(d.imageOrientationText, ""); strcpy(d.unitsPT, ""); - strcpy(d.decayCorrection, ""); - strcpy(d.attenuationCorrectionMethod, ""); - strcpy(d.reconstructionMethod, ""); - d.phaseEncodingLines = 0; - //~ d.patientPositionSequentialRepeats = 0; - //~ d.patientPositionRepeats = 0; - d.isHasPhase = false; - d.isHasReal = false; - d.isHasImaginary = false; - d.isHasMagnitude = false; - //d.maxGradDynVol = -1; //PAR/REC only - d.sliceOrient = kSliceOrientUnknown; - d.dateTime = (double)19770703150928.0; - d.acquisitionTime = 0.0f; - d.acquisitionDate = 0.0f; - d.manufacturer = kMANUFACTURER_UNKNOWN; - d.isPlanarRGB = false; - d.lastScanLoc = NAN; - d.TR = 0.0; - d.TE = 0.0; - d.TI = 0.0; - d.flipAngle = 0.0; - d.bandwidthPerPixelPhaseEncode = 0.0; - d.acquisitionDuration = 0.0; - d.imagingFrequency = 0.0; - d.numberOfAverages = 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; - d.waterFatShift = 0.0; - d.groupDelay = 0.0; - d.decayFactor = 0.0; - d.percentSampling = 0.0; - d.phaseFieldofView = 0.0; - d.dwellTime = 0; - d.protocolBlockStartGE = 0; - d.protocolBlockLengthGE = 0; - d.phaseEncodingSteps = 0; - d.coilCrc = 0; - d.seriesUidCrc = 0; - d.instanceUidCrc = 0; - d.accelFactPE = 0.0; - d.accelFactOOP = 0.0; - //d.patientPositionNumPhilips = 0; - d.imageBytes = 0; - d.intenScale = 1; - d.intenScalePhilips = 0; - d.intenIntercept = 0; - d.gantryTilt = 0.0; - d.radionuclidePositronFraction = 0.0; - d.radionuclideHalfLife = 0.0; - d.doseCalibrationFactor = 0.0; - d.ecat_isotope_halflife = 0.0; - d.frameDuration = -1.0; - d.ecat_dosage = 0.0; - d.radionuclideTotalDose = 0.0; - d.seriesNum = 1; - d.acquNum = 0; - d.imageNum = 1; - d.imageStart = 0; - d.is3DAcq = false; //e.g. MP-RAGE, SPACE, TFE - d.is2DAcq = false; // - d.isDerived = false; //0008,0008 = DERIVED,CSAPARALLEL,POSDISP - d.isSegamiOasis = false; //these images do not store spatial coordinates + strcpy(d.decayCorrection, ""); + strcpy(d.attenuationCorrectionMethod, ""); + strcpy(d.reconstructionMethod, ""); + d.phaseEncodingLines = 0; + //~ d.patientPositionSequentialRepeats = 0; + //~ d.patientPositionRepeats = 0; + d.isHasPhase = false; + d.isHasReal = false; + d.isHasImaginary = false; + d.isHasMagnitude = false; + //d.maxGradDynVol = -1; //PAR/REC only + d.sliceOrient = kSliceOrientUnknown; + d.dateTime = (double)19770703150928.0; + d.acquisitionTime = 0.0f; + d.acquisitionDate = 0.0f; + d.manufacturer = kMANUFACTURER_UNKNOWN; + d.isPlanarRGB = false; + d.lastScanLoc = NAN; + d.TR = 0.0; + d.TE = 0.0; + d.TI = 0.0; + d.flipAngle = 0.0; + d.bandwidthPerPixelPhaseEncode = 0.0; + d.acquisitionDuration = 0.0; + d.imagingFrequency = 0.0; + d.numberOfAverages = 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; + d.waterFatShift = 0.0; + d.groupDelay = 0.0; + d.decayFactor = 0.0; + d.percentSampling = 0.0; + d.phaseFieldofView = 0.0; + d.dwellTime = 0; + d.protocolBlockStartGE = 0; + d.protocolBlockLengthGE = 0; + d.phaseEncodingSteps = 0; + d.coilCrc = 0; + d.seriesUidCrc = 0; + d.instanceUidCrc = 0; + d.accelFactPE = 0.0; + d.accelFactOOP = 0.0; + //d.patientPositionNumPhilips = 0; + d.imageBytes = 0; + d.intenScale = 1; + d.intenScalePhilips = 0; + d.intenIntercept = 0; + d.gantryTilt = 0.0; + d.radionuclidePositronFraction = 0.0; + d.radionuclideHalfLife = 0.0; + d.doseCalibrationFactor = 0.0; + d.ecat_isotope_halflife = 0.0; + d.frameDuration = -1.0; + d.ecat_dosage = 0.0; + d.radionuclideTotalDose = 0.0; + d.seriesNum = 1; + d.acquNum = 0; + d.imageNum = 1; + d.imageStart = 0; + d.is3DAcq = false; //e.g. MP-RAGE, SPACE, TFE + d.is2DAcq = false; // + d.isDerived = false; //0008,0008 = DERIVED,CSAPARALLEL,POSDISP + d.isSegamiOasis = false; //these images do not store spatial coordinates d.isBVecWorldCoordinates = false; //bvecs can be in image space (GE) or world coordinates (Siemens) - d.isGrayscaleSoftcopyPresentationState = false; - d.isRawDataStorage = false; - d.isPartialFourier = false; - d.isIR = false; - d.isEPI = false; - d.isDiffusion = false; - d.isVectorFromBMatrix = false; - d.isStackableSeries = false; //combine DCE series https://github.com/rordenlab/dcm2niix/issues/252 - d.isXA10A = false; //https://github.com/rordenlab/dcm2niix/issues/236 - d.triggerDelayTime = 0.0; - d.RWVScale = 0.0; - d.RWVIntercept = 0.0; - d.isScaleOrTEVaries = false; - d.isScaleVariesEnh = false; //issue363 - d.bitsAllocated = 16;//bits - d.bitsStored = 0; - d.samplesPerPixel = 1; - d.pixelPaddingValue = NAN; - d.isValid = false; - d.isXRay = false; - d.isMultiEcho = false; - d.isSigned = false; //default is unsigned! - d.isFloat = false; //default is for integers, not single or double precision - d.isResampled = false; //assume data not resliced to remove gantry tilt problems - d.isLocalizer = false; - d.isNonParallelSlices = false; - d.isCoilVaries = false; - d.compressionScheme = 0; //none - d.isExplicitVR = true; - d.isLittleEndian = true; //DICOM initially always little endian - d.converted2NII = 0; - d.numberOfDiffusionDirectionGE = -1; - d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNKNOWN; - d.rtia_timerGE = -1.0; - d.rawDataRunNumber = -1; - d.maxEchoNumGE = -1; - d.epiVersionGE = -1; - d.internalepiVersionGE = -1; + d.isGrayscaleSoftcopyPresentationState = false; + d.isRawDataStorage = false; + d.isPartialFourier = false; + d.isIR = false; + d.isEPI = false; + d.isDiffusion = false; + d.isVectorFromBMatrix = false; + d.isStackableSeries = false; //combine DCE series https://github.com/rordenlab/dcm2niix/issues/252 + d.isXA10A = false; //https://github.com/rordenlab/dcm2niix/issues/236 + d.triggerDelayTime = 0.0; + d.RWVScale = 0.0; + d.RWVIntercept = 0.0; + d.isScaleOrTEVaries = false; + d.isScaleVariesEnh = false; //issue363 + d.bitsAllocated = 16; //bits + d.bitsStored = 0; + d.samplesPerPixel = 1; + d.pixelPaddingValue = NAN; + d.isValid = false; + d.isXRay = false; + d.isMultiEcho = false; + d.isSigned = false; //default is unsigned! + d.isFloat = false; //default is for integers, not single or double precision + d.isResampled = false; //assume data not resliced to remove gantry tilt problems + d.isLocalizer = false; + d.isNonParallelSlices = false; + d.isCoilVaries = false; + d.compressionScheme = 0; //none + d.isExplicitVR = true; + d.isLittleEndian = true; //DICOM initially always little endian + d.converted2NII = 0; + d.numberOfDiffusionDirectionGE = -1; + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNKNOWN; + d.rtia_timerGE = -1.0; + d.rawDataRunNumber = -1; + d.maxEchoNumGE = -1; + d.epiVersionGE = -1; + d.internalepiVersionGE = -1; d.durationLabelPulseGE = -1; d.aslFlagsGE = 0; - d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_UNKNOWN; + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_UNKNOWN; d.mtState = -1; - d.numberOfExcitations = -1; - d.numberOfArms = -1; - d.numberOfPointsPerArm = -1; + d.numberOfExcitations = -1; + d.numberOfArms = -1; + d.numberOfPointsPerArm = -1; d.spoiling = kSPOILING_UNKOWN; - d.interp3D = -1; - for (int i = 0; i < kMaxOverlay; i++) - d.overlayStart[i] = 0; - d.isHasOverlay = false; - d.isPrivateCreatorRemap = false; + d.interp3D = -1; + for (int i = 0; i < kMaxOverlay; i++) + d.overlayStart[i] = 0; + d.isHasOverlay = false; + d.isPrivateCreatorRemap = false; d.isRealIsPhaseMapHz = false; - d.numberOfImagesInGridUIH = 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] = 0.0; - d.CSA.sliceNormV[2] = 0.0; - d.CSA.sliceNormV[3] = 1.0; //default Siemens Image Numbering is F>>H https://www.mccauslandcenter.sc.edu/crnl/tools/stc - d.CSA.sliceOrder = NIFTI_SLICE_UNKNOWN; - d.CSA.slice_start = 0; - d.CSA.slice_end = 0; - d.CSA.protocolSliceNumber1 = 0; - d.CSA.phaseEncodingDirectionPositive = -1; //unknown - d.CSA.isPhaseMap = false; - d.CSA.multiBandFactor = 1; - d.CSA.SeriesHeader_offset = 0; - d.CSA.SeriesHeader_length = 0; - return d; + d.numberOfImagesInGridUIH = 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] = 0.0; + d.CSA.sliceNormV[2] = 0.0; + d.CSA.sliceNormV[3] = 1.0; //default Siemens Image Numbering is F>>H https://www.mccauslandcenter.sc.edu/crnl/tools/stc + d.CSA.sliceOrder = NIFTI_SLICE_UNKNOWN; + d.CSA.slice_start = 0; + d.CSA.slice_end = 0; + d.CSA.protocolSliceNumber1 = 0; + d.CSA.phaseEncodingDirectionPositive = -1; //unknown + d.CSA.isPhaseMap = false; + d.CSA.multiBandFactor = 1; + d.CSA.SeriesHeader_offset = 0; + d.CSA.SeriesHeader_length = 0; + return d; } //clear_dicom_data() int isdigitdot(int c) { //returns true if digit or '.' - if (c == '.') return 1; + if (c == '.') + return 1; return isdigit(c); } -void dcmStrDigitsDotOnlyKey(char key, char* lStr) { - //e.g. string "F:2.50" returns 2.50 if key==":" - size_t len = strlen(lStr); - if (len < 1) return; - bool isKey = false; - for (int i = 0; i < (int) len; i++) { - if (!isdigitdot(lStr[i]) ) { - isKey = (lStr[i] == key); - lStr[i] = ' '; - - } else if (!isKey) - lStr[i] = ' '; - } +void dcmStrDigitsDotOnlyKey(char key, char *lStr) { +//e.g. string "F:2.50" returns 2.50 if key==":" + size_t len = strlen(lStr); + if (len < 1) + return; + bool isKey = false; + for (int i = 0; i < (int)len; i++) { + if (!isdigitdot(lStr[i])) { + isKey = (lStr[i] == key); + lStr[i] = ' '; + } else if (!isKey) + lStr[i] = ' '; + } } //dcmStrDigitsOnlyKey() -void dcmStrDigitsOnlyKey(char key, char* lStr) { - //e.g. string "p2s3" returns 2 if key=="p" and 3 if key=="s" - size_t len = strlen(lStr); - if (len < 1) return; - bool isKey = false; - for (int i = 0; i < (int) len; i++) { - if (!isdigit(lStr[i]) ) { - isKey = (lStr[i] == key); - lStr[i] = ' '; - - } else if (!isKey) - lStr[i] = ' '; - } +void dcmStrDigitsOnlyKey(char key, char *lStr) { +//e.g. string "p2s3" returns 2 if key=="p" and 3 if key=="s" + size_t len = strlen(lStr); + if (len < 1) + return; + bool isKey = false; + for (int i = 0; i < (int)len; i++) { + if (!isdigit(lStr[i])) { + isKey = (lStr[i] == key); + lStr[i] = ' '; + } else if (!isKey) + lStr[i] = ' '; + } } //dcmStrDigitsOnlyKey() -void dcmStrDigitsOnly(char* lStr) { - //e.g. change "H11" to " 11" - size_t len = strlen(lStr); - if (len < 1) return; - for (int i = 0; i < (int) len; i++) - if (!isdigit(lStr[i]) ) - lStr[i] = ' '; +void dcmStrDigitsOnly(char *lStr) { +//e.g. change "H11" to " 11" + size_t len = strlen(lStr); + if (len < 1) + return; + for (int i = 0; i < (int)len; i++) + if (!isdigit(lStr[i])) + lStr[i] = ' '; } // Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ -uint32_t mz_crc32X(unsigned char *ptr, size_t buf_len) -{ - static const uint32_t s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, - 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; - uint32_t crcu32 = 0; - if (!ptr) return crcu32; - crcu32 = ~crcu32; while (buf_len--) { uint8_t b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } - return ~crcu32; +uint32_t mz_crc32X(unsigned char *ptr, size_t buf_len) { + static const uint32_t s_crc32[16] = {0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,0xedb88320, 0xf00f9344, + 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c}; + uint32_t crcu32 = 0; + if (!ptr) + return crcu32; + crcu32 = ~crcu32; + while (buf_len--) { + uint8_t b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; } -void dcmStr(int lLength, unsigned char lBuffer[], char* lOut, bool isStrLarge = false) { - if (lLength < 1) return; - char * cString = (char *)malloc(sizeof(char) * (lLength + 1)); - cString[lLength] =0; - memcpy(cString, (char*)&lBuffer[0], lLength); - //memcpy(cString, test, lLength); - //printMessage("X%dX\n", (unsigned char)d.patientName[1]); - #ifdef ISO8859 - for (int i = 0; i < lLength; i++) - //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1 - if (cString[i]< 1) { - unsigned char c = (unsigned char)cString[i]; - if ((c >= 192) && (c <= 198)) cString[i] = 'A'; - if (c == 199) cString[i] = 'C'; - if ((c >= 200) && (c <= 203)) cString[i] = 'E'; - if ((c >= 204) && (c <= 207)) cString[i] = 'I'; - if (c == 208) cString[i] = 'D'; - if (c == 209) cString[i] = 'N'; - if ((c >= 210) && (c <= 214)) cString[i] = 'O'; - if (c == 215) cString[i] = 'x'; - if (c == 216) cString[i] = 'O'; - if ((c >= 217) && (c <= 220)) cString[i] = 'O'; - if (c == 221) cString[i] = 'Y'; - if ((c >= 224) && (c <= 230)) cString[i] = 'a'; - if (c == 231) cString[i] = 'c'; - if ((c >= 232) && (c <= 235)) cString[i] = 'e'; - if ((c >= 236) && (c <= 239)) cString[i] = 'i'; - if (c == 240) cString[i] = 'o'; - if (c == 241) cString[i] = 'n'; - if ((c >= 242) && (c <= 246)) cString[i] = 'o'; - if (c == 248) cString[i] = 'o'; - if ((c >= 249) && (c <= 252)) cString[i] = 'u'; - if (c == 253) cString[i] = 'y'; - if (c == 255) cString[i] = 'y'; - } - #endif - //we no longer sanitize strings, see issue 425 - int len = lLength; - if (cString[len-1] == ' ') len--; - //while ((len > 0) && (cString[len]=='_')) len--; //remove trailing '_' - cString[len] = 0; //null-terminate, strlcpy does this anyway - int maxLen = kDICOMStr; - if (isStrLarge) maxLen = kDICOMStrLarge; - len = dcmStrLen(len, maxLen); - if (len == maxLen) { //we need space for null-termination - if (cString[len-2] == '_') len = len -2; - } - memcpy(lOut,cString,len-1); - lOut[len-1] = 0; +void dcmStr(int lLength, unsigned char lBuffer[], char *lOut, bool isStrLarge = false) { + if (lLength < 1) + return; + char *cString = (char *)malloc(sizeof(char) * (lLength + 1)); + cString[lLength] = 0; + memcpy(cString, (char *)&lBuffer[0], lLength); +//memcpy(cString, test, lLength); +//printMessage("X%dX\n", (unsigned char)d.patientName[1]); +#ifdef ISO8859 + for (int i = 0; i < lLength; i++) + //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1 + if (cString[i] < 1) { + unsigned char c = (unsigned char)cString[i]; + if ((c >= 192) && (c <= 198)) + cString[i] = 'A'; + if (c == 199) + cString[i] = 'C'; + if ((c >= 200) && (c <= 203)) + cString[i] = 'E'; + if ((c >= 204) && (c <= 207)) + cString[i] = 'I'; + if (c == 208) + cString[i] = 'D'; + if (c == 209) + cString[i] = 'N'; + if ((c >= 210) && (c <= 214)) + cString[i] = 'O'; + if (c == 215) + cString[i] = 'x'; + if (c == 216) + cString[i] = 'O'; + if ((c >= 217) && (c <= 220)) + cString[i] = 'O'; + if (c == 221) + cString[i] = 'Y'; + if ((c >= 224) && (c <= 230)) + cString[i] = 'a'; + if (c == 231) + cString[i] = 'c'; + if ((c >= 232) && (c <= 235)) + cString[i] = 'e'; + if ((c >= 236) && (c <= 239)) + cString[i] = 'i'; + if (c == 240) + cString[i] = 'o'; + if (c == 241) + cString[i] = 'n'; + if ((c >= 242) && (c <= 246)) + cString[i] = 'o'; + if (c == 248) + cString[i] = 'o'; + if ((c >= 249) && (c <= 252)) + cString[i] = 'u'; + if (c == 253) + cString[i] = 'y'; + if (c == 255) + cString[i] = 'y'; + } +#endif + //we no longer sanitize strings, see issue 425 + int len = lLength; + if (cString[len - 1] == ' ') + len--; + //while ((len > 0) && (cString[len]=='_')) len--; //remove trailing '_' + cString[len] = 0; //null-terminate, strlcpy does this anyway + int maxLen = kDICOMStr; + if (isStrLarge) + maxLen = kDICOMStrLarge; + len = dcmStrLen(len, maxLen); + if (len == maxLen) { //we need space for null-termination + if (cString[len - 2] == '_') + len = len - 2; + } + memcpy(lOut, cString, len - 1); + lOut[len - 1] = 0; free(cString); } //dcmStr() -#ifdef MY_OLD //this code works on Intel but not some older systems https://github.com/rordenlab/dcm2niix/issues/327 -float dcmFloat(int lByteLength, unsigned char lBuffer[], bool littleEndian) {//read binary 32-bit float - //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian - bool swap = (littleEndian != littleEndianPlatform()); - float retVal = 0; - if (lByteLength < 4) return retVal; - memcpy(&retVal, (char*)&lBuffer[0], 4); - if (!swap) return retVal; - float swapVal; - char *inFloat = ( char* ) & retVal; - char *outFloat = ( char* ) & swapVal; - outFloat[0] = inFloat[3]; - outFloat[1] = inFloat[2]; - outFloat[2] = inFloat[1]; - outFloat[3] = inFloat[0]; - //printMessage("swapped val = %f\n",swapVal); - return swapVal; +#ifdef MY_OLD +//this code works on Intel but not some older systems https://github.com/rordenlab/dcm2niix/issues/327 +float dcmFloat(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 32-bit float +//http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian + bool swap = (littleEndian != littleEndianPlatform()); + float retVal = 0; + if (lByteLength < 4) + return retVal; + memcpy(&retVal, (char *)&lBuffer[0], 4); + if (!swap) + return retVal; + float swapVal; + char *inFloat = (char *)&retVal; + char *outFloat = (char *)&swapVal; + outFloat[0] = inFloat[3]; + outFloat[1] = inFloat[2]; + outFloat[2] = inFloat[1]; + outFloat[3] = inFloat[0]; + //printMessage("swapped val = %f\n",swapVal); + return swapVal; } //dcmFloat() -double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], - const bool littleEndian) {//read binary 64-bit float - //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian - bool swap = (littleEndian != littleEndianPlatform()); - double retVal = 0.0f; - if (lByteLength < 8) return retVal; - memcpy(&retVal, (char*)&lBuffer[0], 8); - if (!swap) return retVal; - char *floatToConvert = ( char* ) & lBuffer; - char *returnFloat = ( char* ) & retVal; - //swap the bytes into a temporary buffer - returnFloat[0] = floatToConvert[7]; - returnFloat[1] = floatToConvert[6]; - returnFloat[2] = floatToConvert[5]; - returnFloat[3] = floatToConvert[4]; - returnFloat[4] = floatToConvert[3]; - returnFloat[5] = floatToConvert[2]; - returnFloat[6] = floatToConvert[1]; - returnFloat[7] = floatToConvert[0]; - //printMessage("swapped val = %f\n",retVal); - return retVal; +double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], const bool littleEndian) { //read binary 64-bit float +//http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian + bool swap = (littleEndian != littleEndianPlatform()); + double retVal = 0.0f; + if (lByteLength < 8) + return retVal; + memcpy(&retVal, (char *)&lBuffer[0], 8); + if (!swap) + return retVal; + char *floatToConvert = (char *)&lBuffer; + char *returnFloat = (char *)&retVal; + //swap the bytes into a temporary buffer + returnFloat[0] = floatToConvert[7]; + returnFloat[1] = floatToConvert[6]; + returnFloat[2] = floatToConvert[5]; + returnFloat[3] = floatToConvert[4]; + returnFloat[4] = floatToConvert[3]; + returnFloat[5] = floatToConvert[2]; + returnFloat[6] = floatToConvert[1]; + returnFloat[7] = floatToConvert[0]; + //printMessage("swapped val = %f\n",retVal); + return retVal; } //dcmFloatDouble() #else -float dcmFloat(int lByteLength, unsigned char lBuffer[], bool littleEndian) {//read binary 32-bit float - //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian - if (lByteLength < 4) return 0.0; - bool swap = (littleEndian != littleEndianPlatform()); - union { - uint32_t i; - float f; - uint8_t c[4]; - } i,o; - memcpy(&i.i, (char*)&lBuffer[0], 4); - //printf("%02x%02x%02x%02x\n",i.c[0], i.c[1], i.c[2], i.c[3]); - if (!swap) return i.f; - o.c[0] = i.c[3]; - o.c[1] = i.c[2]; - o.c[2] = i.c[1]; - o.c[3] = i.c[0]; - //printf("swp %02x%02x%02x%02x\n",o.c[0], o.c[1], o.c[2], o.c[3]); - return o.f; +float dcmFloat(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 32-bit float + //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian + if (lByteLength < 4) + return 0.0; + bool swap = (littleEndian != littleEndianPlatform()); + union { + uint32_t i; + float f; + uint8_t c[4]; + } i, o; + memcpy(&i.i, (char *)&lBuffer[0], 4); + //printf("%02x%02x%02x%02x\n",i.c[0], i.c[1], i.c[2], i.c[3]); + if (!swap) + return i.f; + o.c[0] = i.c[3]; + o.c[1] = i.c[2]; + o.c[2] = i.c[1]; + o.c[3] = i.c[0]; + //printf("swp %02x%02x%02x%02x\n",o.c[0], o.c[1], o.c[2], o.c[3]); + return o.f; } //dcmFloat() -double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], - const bool littleEndian) {//read binary 64-bit float - //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian - if (lByteLength < 8) return 0.0; - bool swap = (littleEndian != littleEndianPlatform()); - union { - uint32_t i; - double d; - uint8_t c[8]; - } i,o; - memcpy(&i.i, (char*)&lBuffer[0], 8); - if (!swap) return i.d; - o.c[0] = i.c[7]; - o.c[1] = i.c[6]; - o.c[2] = i.c[5]; - o.c[3] = i.c[4]; - o.c[4] = i.c[3]; - o.c[5] = i.c[2]; - o.c[6] = i.c[1]; - o.c[7] = i.c[0]; - return o.d; +double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], const bool littleEndian) { //read binary 64-bit float + //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian + if (lByteLength < 8) + return 0.0; + bool swap = (littleEndian != littleEndianPlatform()); + union { + uint32_t i; + double d; + uint8_t c[8]; + } i, o; + memcpy(&i.i, (char *)&lBuffer[0], 8); + if (!swap) + return i.d; + o.c[0] = i.c[7]; + o.c[1] = i.c[6]; + o.c[2] = i.c[5]; + o.c[3] = i.c[4]; + o.c[4] = i.c[3]; + o.c[5] = i.c[2]; + o.c[6] = i.c[1]; + o.c[7] = i.c[0]; + return o.d; } //dcmFloatDouble() #endif -int dcmInt (int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 16 or 32 bit integer - if (littleEndian) { - if (lByteLength <= 3) - return lBuffer[0] | (lBuffer[1]<<8); //shortint vs word? - return lBuffer[0]+(lBuffer[1]<<8)+(lBuffer[2]<<16)+(lBuffer[3]<<24); //shortint vs word? - } - if (lByteLength <= 3) - return lBuffer[1] | (lBuffer[0]<<8); //shortint vs word? - return lBuffer[3]+(lBuffer[2]<<8)+(lBuffer[1]<<16)+(lBuffer[0]<<24); //shortint vs word? +int dcmInt(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 16 or 32 bit integer + if (littleEndian) { + if (lByteLength <= 3) + return lBuffer[0] | (lBuffer[1] << 8); //shortint vs word? + return lBuffer[0] + (lBuffer[1] << 8) + (lBuffer[2] << 16) + (lBuffer[3] << 24); //shortint vs word? + } + if (lByteLength <= 3) + return lBuffer[1] | (lBuffer[0] << 8); //shortint vs word? + return lBuffer[3] + (lBuffer[2] << 8) + (lBuffer[1] << 16) + (lBuffer[0] << 24); //shortint vs word? } //dcmInt() - -uint32_t dcmAttributeTag (unsigned char lBuffer[], bool littleEndian) { - // read Attribute Tag (AT) value - // return in Group + (Element << 16) format - if (littleEndian) - return lBuffer[0]+(lBuffer[1]<<8)+(lBuffer[2]<<16)+(lBuffer[3]<<24); - return lBuffer[1]+(lBuffer[0]<<8)+(lBuffer[3]<<16)+(lBuffer[2]<<24); +uint32_t dcmAttributeTag(unsigned char lBuffer[], bool littleEndian) { + // read Attribute Tag (AT) value + // return in Group + (Element << 16) format + if (littleEndian) + return lBuffer[0] + (lBuffer[1] << 8) + (lBuffer[2] << 16) + (lBuffer[3] << 24); + return lBuffer[1] + (lBuffer[0] << 8) + (lBuffer[3] << 16) + (lBuffer[2] << 24); } //dcmInt() -/* -//the code below trims strings after integer -// does not appear required not http://en.cppreference.com/w/cpp/string/byte/atoi -// "(atoi) Discards any whitespace characters until the first non-whitespace character is found, then takes as many characters as possible to form a valid integer" -int dcmStrInt (const int lByteLength, const unsigned char lBuffer[]) {//read int stored as a string -//returns first integer e.g. if 0043,1039 is "1000\8\0\0" the result will be 1000 - if (lByteLength < 1) return 0; //error - bool isOK = false; - int i = 0; - for (i = 0; i <= lByteLength; i++) { - if ((lBuffer[i] >= '0') && (lBuffer[i] <= '9')) - isOK = true; - else if (isOK) - break; - } - if (!isOK) return 0; //error - char * cString = (char *)malloc(sizeof(char) * (i + 1)); - cString[i] =0; - memcpy(cString, (const unsigned char*)(&lBuffer[0]), i); - int ret = atoi(cString); - free(cString); - return ret; -} //dcmStrInt() -*/ - -int dcmStrInt (const int lByteLength, const unsigned char lBuffer[]) {//read int stored as a string -//#ifdef _MSC_VER - char * cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); -//#else -// char cString[lByteLength + 1]; -//#endif - cString[lByteLength] =0; - memcpy(cString, (const unsigned char*)(&lBuffer[0]), lByteLength); - //printMessage(" --> *%s* %s%s\n",cString, &lBuffer[0],&lBuffer[1]); - int ret = atoi(cString); -//#ifdef _MSC_VER + +int dcmStrInt(const int lByteLength, const unsigned char lBuffer[]) { //read int stored as a string + char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); + cString[lByteLength] = 0; + memcpy(cString, (const unsigned char *)(&lBuffer[0]), lByteLength); + int ret = atoi(cString); free(cString); -//#endif return ret; } //dcmStrInt() -int dcmStrManufacturer (const int lByteLength, unsigned char lBuffer[]) {//read float stored as a string - if (lByteLength < 2) return kMANUFACTURER_UNKNOWN; -//#ifdef _MSC_VER - char * cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); -//#else -// char cString[lByteLength + 1]; -//#endif +int dcmStrManufacturer(const int lByteLength, unsigned char lBuffer[]) { //read float stored as a string + if (lByteLength < 2) + return kMANUFACTURER_UNKNOWN; + //#ifdef _MSC_VER + char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); + //#else + // char cString[lByteLength + 1]; + //#endif int ret = kMANUFACTURER_UNKNOWN; - cString[lByteLength] = 0; - memcpy(cString, (char*)&lBuffer[0], lByteLength); - if ((toupper(cString[0])== 'S') && (toupper(cString[1])== 'I')) - ret = kMANUFACTURER_SIEMENS; - if ((toupper(cString[0])== 'G') && (toupper(cString[1])== 'E')) - ret = kMANUFACTURER_GE; - if ((toupper(cString[0])== 'H') && (toupper(cString[1])== 'I')) - ret = kMANUFACTURER_HITACHI; - if ((toupper(cString[0])== 'P') && (toupper(cString[1])== 'H')) - ret = kMANUFACTURER_PHILIPS; - if ((toupper(cString[0])== 'T') && (toupper(cString[1])== 'O')) - ret = kMANUFACTURER_TOSHIBA; - //CANON_MEC - if ((toupper(cString[0])== 'C') && (toupper(cString[1])== 'A')) - ret = kMANUFACTURER_CANON; - if ((toupper(cString[0])== 'U') && (toupper(cString[1])== 'I')) - ret = kMANUFACTURER_UIH; - if ((toupper(cString[0])== 'B') && (toupper(cString[1])== 'R')) - ret = kMANUFACTURER_BRUKER; + cString[lByteLength] = 0; + memcpy(cString, (char *)&lBuffer[0], lByteLength); + if ((toupper(cString[0]) == 'S') && (toupper(cString[1]) == 'I')) + ret = kMANUFACTURER_SIEMENS; + if ((toupper(cString[0]) == 'G') && (toupper(cString[1]) == 'E')) + ret = kMANUFACTURER_GE; + if ((toupper(cString[0]) == 'H') && (toupper(cString[1]) == 'I')) + ret = kMANUFACTURER_HITACHI; + if ((toupper(cString[0]) == 'P') && (toupper(cString[1]) == 'H')) + ret = kMANUFACTURER_PHILIPS; + if ((toupper(cString[0]) == 'T') && (toupper(cString[1]) == 'O')) + ret = kMANUFACTURER_TOSHIBA; + //CANON_MEC + if ((toupper(cString[0]) == 'C') && (toupper(cString[1]) == 'A')) + ret = kMANUFACTURER_CANON; + if ((toupper(cString[0]) == 'U') && (toupper(cString[1]) == 'I')) + ret = kMANUFACTURER_UIH; + if ((toupper(cString[0]) == 'B') && (toupper(cString[1]) == 'R')) + ret = kMANUFACTURER_BRUKER; if (ret == kMANUFACTURER_UNKNOWN) - printWarning("Unknown manufacturer %s\n",cString); -//#ifdef _MSC_VER + printWarning("Unknown manufacturer %s\n", cString); + //#ifdef _MSC_VER free(cString); -//#endif + //#endif return ret; } //dcmStrManufacturer -float csaMultiFloat (unsigned char buff[], int nItems, float Floats[], int *ItemsOK) { - //warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats] - //if lnItems == 1, returns first item, if lnItems > 1 returns index of final successful conversion - TCSAitem itemCSA; - *ItemsOK = 0; - if (nItems < 1) return 0.0f; - Floats[1] = 0; - int lPos = 0; - for (int lI = 1; lI <= nItems; lI++) { - memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); - lPos +=sizeof(itemCSA); - - // Storage order is always little-endian, so byte-swap required values if necessary - if (!littleEndianPlatform()) - nifti_swap_4bytes(1, &itemCSA.xx2_Len); - - if (itemCSA.xx2_Len > 0) { - char * cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len)); - memcpy(cString, &buff[lPos], itemCSA.xx2_Len); //TPX memcpy(&cString, &buff[lPos], sizeof(cString)); - lPos += ((itemCSA.xx2_Len +3)/4)*4; - //printMessage(" %d item length %d = %s\n",lI, itemCSA.xx2_Len, cString); - Floats[lI] = (float) atof(cString); - *ItemsOK = lI; //some sequences have store empty items - free(cString); - } - } //for each item - return Floats[1]; +float csaMultiFloat(unsigned char buff[], int nItems, float Floats[], int *ItemsOK) { + //warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats] + //if lnItems == 1, returns first item, if lnItems > 1 returns index of final successful conversion + TCSAitem itemCSA; + *ItemsOK = 0; + if (nItems < 1) + return 0.0f; + Floats[1] = 0; + int lPos = 0; + for (int lI = 1; lI <= nItems; lI++) { + memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); + lPos += sizeof(itemCSA); + // Storage order is always little-endian, so byte-swap required values if necessary + if (!littleEndianPlatform()) + nifti_swap_4bytes(1, &itemCSA.xx2_Len); + if (itemCSA.xx2_Len > 0) { + char *cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len)); + memcpy(cString, &buff[lPos], itemCSA.xx2_Len); //TPX memcpy(&cString, &buff[lPos], sizeof(cString)); + lPos += ((itemCSA.xx2_Len + 3) / 4) * 4; + //printMessage(" %d item length %d = %s\n",lI, itemCSA.xx2_Len, cString); + Floats[lI] = (float)atof(cString); + *ItemsOK = lI; //some sequences have store empty items + free(cString); + } + } //for each item + return Floats[1]; } //csaMultiFloat() -bool csaIsPhaseMap (unsigned char buff[], int nItems) { - //returns true if the tag "ImageHistory" has an item named "CC:ComplexAdd" - TCSAitem itemCSA; - if (nItems < 1) return false; - int lPos = 0; - for (int lI = 1; lI <= nItems; lI++) { - memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); - lPos +=sizeof(itemCSA); - - // Storage order is always little-endian, so byte-swap required values if necessary - if (!littleEndianPlatform()) - nifti_swap_4bytes(1, &itemCSA.xx2_Len); - - if (itemCSA.xx2_Len > 0) { -//#ifdef _MSC_VER - char * cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len + 1)); -//#else - // char cString[itemCSA.xx2_Len]; -//#endif - memcpy(cString, &buff[lPos], sizeof(itemCSA.xx2_Len)); //TPX memcpy(&cString, &buff[lPos], sizeof(cString)); - lPos += ((itemCSA.xx2_Len +3)/4)*4; - //printMessage(" %d item length %d = %s\n",lI, itemCSA.xx2_Len, cString); - if (strcmp(cString, "CC:ComplexAdd") == 0) - return true; -//#ifdef _MSC_VER - free(cString); -//#endif - } - } //for each item - return false; +bool csaIsPhaseMap(unsigned char buff[], int nItems) { + //returns true if the tag "ImageHistory" has an item named "CC:ComplexAdd" + TCSAitem itemCSA; + if (nItems < 1) + return false; + int lPos = 0; + for (int lI = 1; lI <= nItems; lI++) { + memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); + lPos += sizeof(itemCSA); + // Storage order is always little-endian, so byte-swap required values if necessary + if (!littleEndianPlatform()) + nifti_swap_4bytes(1, &itemCSA.xx2_Len); + if (itemCSA.xx2_Len > 0) { + char *cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len + 1)); + memcpy(cString, &buff[lPos], sizeof(itemCSA.xx2_Len)); //TPX memcpy(&cString, &buff[lPos], sizeof(cString)); + lPos += ((itemCSA.xx2_Len + 3) / 4) * 4; + //printMessage(" %d item length %d = %s\n",lI, itemCSA.xx2_Len, cString); + if (strcmp(cString, "CC:ComplexAdd") == 0) + return true; + free(cString); + } + } //for each item + return false; } //csaIsPhaseMap() void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3DAcq) { if ((is3DAcq) || (itemsOK < 1)) //we expect 3D sequences to be simultaneous - return; + return; if (itemsOK > kMaxEPI3D) { printError("Please increase kMaxEPI3D and recompile\n"); return; @@ -1249,14 +1253,14 @@ void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3D minTimeValue = CSA->sliceTiming[0]; for (int z = 0; z < itemsOK; z++) if (CSA->sliceTiming[z] < minTimeValue) - minTimeValue = CSA->sliceTiming[z]; + minTimeValue = CSA->sliceTiming[z]; //CSA can report negative slice times // https://neurostars.org/t/slice-timing-illegal-values-in-fmriprep/1516/8 // Nov 1, 2018 wrote: - // If you have an interleaved dataset we can more definitively validate this formula (aka sliceTime(i) - min(sliceTimes())). + // 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"); //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 + 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; } @@ -1264,7 +1268,7 @@ void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3D timeValue1 = CSA->sliceTiming[0]; int nTimeZero = 0; if (CSA->sliceTiming[0] == 0) - nTimeZero++; + nTimeZero++; int minTimeIndex = 0; int maxTimeIndex = minTimeIndex; minTimeValue = CSA->sliceTiming[0]; @@ -1273,18 +1277,19 @@ void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3D printMessage(" sliceTimes %g\t", CSA->sliceTiming[0]); for (int z = 1; z < itemsOK; z++) { //find index and value of fastest time if (isVerbose > 1) - printMessage("%g\t", CSA->sliceTiming[z]); + printMessage("%g\t", CSA->sliceTiming[z]); if (CSA->sliceTiming[z] == 0) nTimeZero++; if (CSA->sliceTiming[z] < minTimeValue) { minTimeValue = CSA->sliceTiming[z]; - minTimeIndex = (float) z; + minTimeIndex = (float)z; } if (CSA->sliceTiming[z] > maxTimeValue) { maxTimeValue = CSA->sliceTiming[z]; - maxTimeIndex = (float) z; + maxTimeIndex = (float)z; } - if (CSA->sliceTiming[z] == timeValue1) CSA->multiBandFactor++; + if (CSA->sliceTiming[z] == timeValue1) + CSA->multiBandFactor++; } if (isVerbose > 1) printMessage("\n"); @@ -1296,17 +1301,17 @@ void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3D } if (nTimeZero < 2) { //not for multi-band, not 3D if (minTimeIndex == 1) - CSA->sliceOrder = NIFTI_SLICE_ALT_INC2;// e.g. 3,1,4,2 - else if (minTimeIndex == (itemsOK-2)) - CSA->sliceOrder = NIFTI_SLICE_ALT_DEC2;// e.g. 2,4,1,3 or 5,2,4,1,3 + CSA->sliceOrder = NIFTI_SLICE_ALT_INC2; // e.g. 3,1,4,2 + else if (minTimeIndex == (itemsOK - 2)) + CSA->sliceOrder = NIFTI_SLICE_ALT_DEC2; // e.g. 2,4,1,3 or 5,2,4,1,3 else if ((minTimeIndex == 0) && (CSA->sliceTiming[1] < CSA->sliceTiming[2])) CSA->sliceOrder = NIFTI_SLICE_SEQ_INC; // e.g. 1,2,3,4 else if ((minTimeIndex == 0) && (CSA->sliceTiming[1] > CSA->sliceTiming[2])) CSA->sliceOrder = NIFTI_SLICE_ALT_INC; //e.g. 1,3,2,4 - else if ((minTimeIndex == (itemsOK-1)) && (CSA->sliceTiming[itemsOK-3] > CSA->sliceTiming[itemsOK-2])) - CSA->sliceOrder = NIFTI_SLICE_SEQ_DEC; //e.g. 4,3,2,1 or 5,4,3,2,1 - else if ((minTimeIndex == (itemsOK-1)) && (CSA->sliceTiming[itemsOK-3] < CSA->sliceTiming[itemsOK-2])) - CSA->sliceOrder = NIFTI_SLICE_ALT_DEC; //e.g. 4,2,3,1 or 3,5,2,4,1 + else if ((minTimeIndex == (itemsOK - 1)) && (CSA->sliceTiming[itemsOK - 3] > CSA->sliceTiming[itemsOK - 2])) + CSA->sliceOrder = NIFTI_SLICE_SEQ_DEC; //e.g. 4,3,2,1 or 5,4,3,2,1 + else if ((minTimeIndex == (itemsOK - 1)) && (CSA->sliceTiming[itemsOK - 3] < CSA->sliceTiming[itemsOK - 2])) + CSA->sliceOrder = NIFTI_SLICE_ALT_DEC; //e.g. 4,2,3,1 or 3,5,2,4,1 else { if (!is3DAcq) //we expect 3D sequences to be simultaneous printWarning("Unable to determine slice order from CSA tag MosaicRefAcqTimes\n"); @@ -1320,414 +1325,426 @@ void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3D } //checkSliceTimes() int readCSAImageHeader(unsigned char *buff, int lLength, struct TCSAdata *CSA, int isVerbose, bool is3DAcq) { - //see also http://afni.nimh.nih.gov/pub/dist/src/siemens_dicom_csa.c - //printMessage("%c%c%c%c\n",buff[0],buff[1],buff[2],buff[3]); - if (lLength < 36) return EXIT_FAILURE; - if ((buff[0] != 'S') || (buff[1] != 'V') || (buff[2] != '1') || (buff[3] != '0') ) return EXIT_FAILURE; - int lPos = 8; //skip 8 bytes of data, 'SV10' plus 2 32-bit values unused1 and unused2 - int lnTag = buff[lPos]+(buff[lPos+1]<<8)+(buff[lPos+2]<<16)+(buff[lPos+3]<<24); - if (buff[lPos+4] != 77) return EXIT_FAILURE; - lPos += 8; //skip 8 bytes of data, 32-bit lnTag plus 77 00 00 0 - TCSAtag tagCSA; - TCSAitem itemCSA; - int itemsOK; - float lFloats[7]; - for (int lT = 1; lT <= lnTag; lT++) { - memcpy(&tagCSA, &buff[lPos], sizeof(tagCSA)); //read tag - lPos +=sizeof(tagCSA); - // Storage order is always little-endian, so byte-swap required values if necessary - if (!littleEndianPlatform()) - nifti_swap_4bytes(1, &tagCSA.nitems); - if (isVerbose > 1) //extreme verbosity: show every CSA tag - printMessage(" %d CSA of %s %d\n",lPos, tagCSA.name, tagCSA.nitems); - /*if (true) { - printMessage("%d CSA of %s %d\n",lPos, tagCSA.name, tagCSA.nitems); - float * vals = (float *)malloc(sizeof(float) * (tagCSA.nitems + 1)); - csaMultiFloat (&buff[lPos], tagCSA.nitems,vals, &itemsOK); - if (itemsOK > 0) { - for (int z = 1; z <= itemsOK; z++) //find index and value of fastest time - printMessage("%g\t", vals[z]); - printMessage("\n"); - } - }*/ - - if (tagCSA.nitems > 0) { - if (strcmp(tagCSA.name, "ImageHistory") == 0) - CSA->isPhaseMap = csaIsPhaseMap(&buff[lPos], tagCSA.nitems); - else if (strcmp(tagCSA.name, "NumberOfImagesInMosaic") == 0) - CSA->mosaicSlices = (int) round(csaMultiFloat (&buff[lPos], 1,lFloats, &itemsOK)); - else if (strcmp(tagCSA.name, "B_value") == 0) { - CSA->dtiV[0] = csaMultiFloat (&buff[lPos], 1,lFloats, &itemsOK); - if (CSA->dtiV[0] < 0.0) { - printWarning("(Corrupt) CSA reports negative b-value! %g\n",CSA->dtiV[0]); - CSA->dtiV[0] = 0.0; - } - CSA->numDti = 1; //triggered by b-value, as B0 images do not have DiffusionGradientDirection tag - } - else if ((strcmp(tagCSA.name, "DiffusionGradientDirection") == 0) && (tagCSA.nitems > 2)){ - CSA->dtiV[1] = csaMultiFloat (&buff[lPos], 3,lFloats, &itemsOK); - CSA->dtiV[2] = lFloats[2]; - CSA->dtiV[3] = lFloats[3]; - if (isVerbose) - printMessage("DiffusionGradientDirection %f %f %f\n",lFloats[1],lFloats[2],lFloats[3]); - } else if ((strcmp(tagCSA.name, "SliceNormalVector") == 0) && (tagCSA.nitems > 2)){ - CSA->sliceNormV[1] = csaMultiFloat (&buff[lPos], 3,lFloats, &itemsOK); - CSA->sliceNormV[2] = lFloats[2]; - CSA->sliceNormV[3] = lFloats[3]; - if (isVerbose > 1) - printMessage(" SliceNormalVector %f %f %f\n",CSA->sliceNormV[1],CSA->sliceNormV[2],CSA->sliceNormV[3]); - } else if (strcmp(tagCSA.name, "SliceMeasurementDuration") == 0) - CSA->sliceMeasurementDuration = csaMultiFloat (&buff[lPos], 3,lFloats, &itemsOK); - else if (strcmp(tagCSA.name, "BandwidthPerPixelPhaseEncode") == 0) - CSA->bandwidthPerPixelPhaseEncode = csaMultiFloat (&buff[lPos], 3,lFloats, &itemsOK); - else if ((strcmp(tagCSA.name, "MosaicRefAcqTimes") == 0) && (tagCSA.nitems > 3) ){ +//see also http://afni.nimh.nih.gov/pub/dist/src/siemens_dicom_csa.c +//printMessage("%c%c%c%c\n",buff[0],buff[1],buff[2],buff[3]); + if (lLength < 36) + return EXIT_FAILURE; + if ((buff[0] != 'S') || (buff[1] != 'V') || (buff[2] != '1') || (buff[3] != '0')) + return EXIT_FAILURE; + int lPos = 8; //skip 8 bytes of data, 'SV10' plus 2 32-bit values unused1 and unused2 + int lnTag = buff[lPos] + (buff[lPos + 1] << 8) + (buff[lPos + 2] << 16) + (buff[lPos + 3] << 24); + if (buff[lPos + 4] != 77) + return EXIT_FAILURE; + lPos += 8; //skip 8 bytes of data, 32-bit lnTag plus 77 00 00 0 + TCSAtag tagCSA; + TCSAitem itemCSA; + int itemsOK; + float lFloats[7]; + for (int lT = 1; lT <= lnTag; lT++) { + memcpy(&tagCSA, &buff[lPos], sizeof(tagCSA)); //read tag + lPos += sizeof(tagCSA); + // Storage order is always little-endian, so byte-swap required values if necessary + if (!littleEndianPlatform()) + nifti_swap_4bytes(1, &tagCSA.nitems); + if (isVerbose > 1) //extreme verbosity: show every CSA tag + printMessage(" %d CSA of %s %d\n", lPos, tagCSA.name, tagCSA.nitems); + if (tagCSA.nitems > 0) { + if (strcmp(tagCSA.name, "ImageHistory") == 0) + CSA->isPhaseMap = csaIsPhaseMap(&buff[lPos], tagCSA.nitems); + else if (strcmp(tagCSA.name, "NumberOfImagesInMosaic") == 0) + CSA->mosaicSlices = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK)); + else if (strcmp(tagCSA.name, "B_value") == 0) { + CSA->dtiV[0] = csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK); + if (CSA->dtiV[0] < 0.0) { + printWarning("(Corrupt) CSA reports negative b-value! %g\n", CSA->dtiV[0]); + CSA->dtiV[0] = 0.0; + } + CSA->numDti = 1; //triggered by b-value, as B0 images do not have DiffusionGradientDirection tag + } else if ((strcmp(tagCSA.name, "DiffusionGradientDirection") == 0) && (tagCSA.nitems > 2)) { + CSA->dtiV[1] = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK); + CSA->dtiV[2] = lFloats[2]; + CSA->dtiV[3] = lFloats[3]; + if (isVerbose) + printMessage("DiffusionGradientDirection %f %f %f\n", lFloats[1], lFloats[2], lFloats[3]); + } else if ((strcmp(tagCSA.name, "SliceNormalVector") == 0) && (tagCSA.nitems > 2)) { + CSA->sliceNormV[1] = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK); + CSA->sliceNormV[2] = lFloats[2]; + CSA->sliceNormV[3] = lFloats[3]; + if (isVerbose > 1) + printMessage(" SliceNormalVector %f %f %f\n", CSA->sliceNormV[1], CSA->sliceNormV[2], CSA->sliceNormV[3]); + } else if (strcmp(tagCSA.name, "SliceMeasurementDuration") == 0) + CSA->sliceMeasurementDuration = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK); + else if (strcmp(tagCSA.name, "BandwidthPerPixelPhaseEncode") == 0) + CSA->bandwidthPerPixelPhaseEncode = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK); + else if ((strcmp(tagCSA.name, "MosaicRefAcqTimes") == 0) && (tagCSA.nitems > 3)) { if (itemsOK > kMaxEPI3D) { printError("Please increase kMaxEPI3D and recompile\n"); } else { - float * sliceTimes = (float *)malloc(sizeof(float) * (tagCSA.nitems + 1)); - csaMultiFloat (&buff[lPos], tagCSA.nitems,sliceTimes, &itemsOK); - for (int z = 0; z < kMaxEPI3D; z++) - CSA->sliceTiming[z] = -1.0; - for (int z = 0; z < itemsOK; z++) - CSA->sliceTiming[z] = sliceTimes[z+1]; + float *sliceTimes = (float *)malloc(sizeof(float) * (tagCSA.nitems + 1)); + csaMultiFloat(&buff[lPos], tagCSA.nitems, sliceTimes, &itemsOK); + for (int z = 0; z < kMaxEPI3D; z++) + CSA->sliceTiming[z] = -1.0; + for (int z = 0; z < itemsOK; z++) + CSA->sliceTiming[z] = sliceTimes[z + 1]; free(sliceTimes); checkSliceTimes(CSA, itemsOK, isVerbose, is3DAcq); - } - } else if (strcmp(tagCSA.name, "ProtocolSliceNumber") == 0) - CSA->protocolSliceNumber1 = (int) round (csaMultiFloat (&buff[lPos], 1,lFloats, &itemsOK)); - else if (strcmp(tagCSA.name, "PhaseEncodingDirectionPositive") == 0) - CSA->phaseEncodingDirectionPositive = (int) round (csaMultiFloat (&buff[lPos], 1,lFloats, &itemsOK)); - for (int lI = 1; lI <= tagCSA.nitems; lI++) { - memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); - lPos +=sizeof(itemCSA); - // Storage order is always little-endian, so byte-swap required values if necessary - if (!littleEndianPlatform()) - nifti_swap_4bytes(1, &itemCSA.xx2_Len); - lPos += ((itemCSA.xx2_Len +3)/4)*4; - } - } //if at least 1 item - }// for lT 1..lnTag - if (CSA->protocolSliceNumber1 > 1) CSA->sliceOrder = NIFTI_SLICE_UNKNOWN; - return EXIT_SUCCESS; + } + } else if (strcmp(tagCSA.name, "ProtocolSliceNumber") == 0) + CSA->protocolSliceNumber1 = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK)); + else if (strcmp(tagCSA.name, "PhaseEncodingDirectionPositive") == 0) + CSA->phaseEncodingDirectionPositive = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK)); + for (int lI = 1; lI <= tagCSA.nitems; lI++) { + memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); + lPos += sizeof(itemCSA); + // Storage order is always little-endian, so byte-swap required values if necessary + if (!littleEndianPlatform()) + nifti_swap_4bytes(1, &itemCSA.xx2_Len); + lPos += ((itemCSA.xx2_Len + 3) / 4) * 4; + } + } //if at least 1 item + } // for lT 1..lnTag + if (CSA->protocolSliceNumber1 > 1) + CSA->sliceOrder = NIFTI_SLICE_UNKNOWN; + return EXIT_SUCCESS; } // readCSAImageHeader() -void dcmMultiShorts (int lByteLength, unsigned char lBuffer[], int lnShorts, uint16_t *lShorts, bool littleEndian) { +void dcmMultiShorts(int lByteLength, unsigned char lBuffer[], int lnShorts, uint16_t *lShorts, bool littleEndian) { //read array of unsigned shorts US http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html - if ((lnShorts < 1) || (lByteLength != (lnShorts * 2))) return; - memcpy(&lShorts[0], (uint16_t *)&lBuffer[0], lByteLength); - bool swap = (littleEndian != littleEndianPlatform()); - if (swap) - nifti_swap_2bytes(lnShorts, &lShorts[0]); + if ((lnShorts < 1) || (lByteLength != (lnShorts * 2))) + return; + memcpy(&lShorts[0], (uint16_t *)&lBuffer[0], lByteLength); + bool swap = (littleEndian != littleEndianPlatform()); + if (swap) + nifti_swap_2bytes(lnShorts, &lShorts[0]); } //dcmMultiShorts() -void dcmMultiLongs (int lByteLength, unsigned char lBuffer[], int lnLongs, uint32_t *lLongs, bool littleEndian) { - //read array of unsigned longs UL http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html - if((lnLongs < 1) || (lByteLength != (lnLongs * 4))) - return; - memcpy(&lLongs[0], (uint32_t *)&lBuffer[0], lByteLength); - bool swap = (littleEndian != littleEndianPlatform()); - if (swap) - nifti_swap_4bytes(lnLongs, &lLongs[0]); +void dcmMultiLongs(int lByteLength, unsigned char lBuffer[], int lnLongs, uint32_t *lLongs, bool littleEndian) { +//read array of unsigned longs UL http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html + if ((lnLongs < 1) || (lByteLength != (lnLongs * 4))) + return; + memcpy(&lLongs[0], (uint32_t *)&lBuffer[0], lByteLength); + bool swap = (littleEndian != littleEndianPlatform()); + if (swap) + nifti_swap_4bytes(lnLongs, &lLongs[0]); } //dcmMultiLongs() -void dcmMultiFloat (int lByteLength, char lBuffer[], int lnFloats, float *lFloats) { - //warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats] - if ((lnFloats < 1) || (lByteLength < 1)) return; -//#ifdef _MSC_VER - char * cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); -//#else -// char cString[lByteLength + 1]; -//#endif - memcpy(cString, (char*)&lBuffer[0], lByteLength); - cString[lByteLength] = 0; //null terminate - char *temp=( char *)malloc(lByteLength+1); - int f = 0,lStart = 0; - bool isOK = false; - for (int i = 0; i <= lByteLength; i++) { - if ((lBuffer[i] >= '0') && (lBuffer[i] <= '9')) isOK = true; - if ((isOK) && ((i == (lByteLength)) || (lBuffer[i] == '/') || (lBuffer[i] == ' ') || (lBuffer[i] == '\\') )){ - //x strlcpy(temp,&cString[lStart],i-lStart+1); - snprintf(temp,i-lStart+1,"%s",&cString[lStart]); - //printMessage("dcmMultiFloat %s\n",temp); - if (f < lnFloats) { - f ++; - lFloats[f] = (float) atof(temp); - isOK = false; - //printMessage("%d == %f\n", f, atof(temp)); - } //if f <= nFloats - lStart = i+1; - } //if isOK - } //for i to length - free(temp); -//#ifdef _MSC_VER +void dcmMultiFloat(int lByteLength, char lBuffer[], int lnFloats, float *lFloats) { +//warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats] + if ((lnFloats < 1) || (lByteLength < 1)) + return; + char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); + memcpy(cString, (char *)&lBuffer[0], lByteLength); + cString[lByteLength] = 0; //null terminate + char *temp = (char *)malloc(lByteLength + 1); + int f = 0, lStart = 0; + bool isOK = false; + for (int i = 0; i <= lByteLength; i++) { + if ((lBuffer[i] >= '0') && (lBuffer[i] <= '9')) + isOK = true; + if ((isOK) && ((i == (lByteLength)) || (lBuffer[i] == '/') || (lBuffer[i] == ' ') || (lBuffer[i] == '\\'))) { + snprintf(temp, i - lStart + 1, "%s", &cString[lStart]); + //printMessage("dcmMultiFloat %s\n",temp); + if (f < lnFloats) { + f++; + lFloats[f] = (float)atof(temp); + isOK = false; + //printMessage("%d == %f\n", f, atof(temp)); + } //if f <= nFloats + lStart = i + 1; + } //if isOK + } //for i to length + free(temp); free(cString); -//#endif } //dcmMultiFloat() -float dcmStrFloat (const int lByteLength, const unsigned char lBuffer[]) { //read float stored as a string -//#ifdef _MSC_VER - char * cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); -//#else -// char cString[lByteLength + 1]; -//#endif - memcpy(cString, (char*)&lBuffer[0], lByteLength); - cString[lByteLength] = 0; //null terminate - float ret = (float) atof(cString); -//#ifdef _MSC_VER +float dcmStrFloat(const int lByteLength, const unsigned char lBuffer[]) { //read float stored as a string + char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); + memcpy(cString, (char *)&lBuffer[0], lByteLength); + cString[lByteLength] = 0; //null terminate + float ret = (float)atof(cString); free(cString); -//#endif return ret; } //dcmStrFloat() int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeSForm) { - //printMessage("bytes %dx%dx%d %d, %d\n",d.XYZdim[1],d.XYZdim[2],d.XYZdim[3], d.Allocbits_per_pixel, d.samplesPerPixel); memset(h, 0, sizeof(nifti_1_header)); //zero-fill structure so unused items are consistent - for (int i = 0; i < 80; i++) h->descrip[i] = 0; - for (int i = 0; i < 24; i++) h->aux_file[i] = 0; - for (int i = 0; i < 18; i++) h->db_name[i] = 0; - for (int i = 0; i < 10; i++) h->data_type[i] = 0; - for (int i = 0; i < 16; i++) h->intent_name[i] = 0; - if ((d.bitsAllocated == 8) && (d.samplesPerPixel == 3)) { - h->intent_code = NIFTI_INTENT_ESTIMATE; //make sure we treat this as RGBRGB...RGB - h->datatype = DT_RGB24; - } else if ((d.bitsAllocated == 8) && (d.samplesPerPixel == 1)) - h->datatype = DT_UINT8; - else if ((d.bitsAllocated == 12) && (d.samplesPerPixel == 1)) - h->datatype = DT_INT16; - else if ((d.bitsAllocated == 16) && (d.samplesPerPixel == 1) && (d.isSigned)) - h->datatype = DT_INT16; - else if ((d.bitsAllocated == 16) && (d.samplesPerPixel == 1) && (!d.isSigned)) - h->datatype = DT_UINT16; - else if ((d.bitsAllocated == 32) && (d.isFloat)) - h->datatype = DT_FLOAT32; - else if (d.bitsAllocated == 32) - h->datatype = DT_INT32; - else if ((d.bitsAllocated == 64) && (d.isFloat)) - h->datatype = DT_FLOAT64; - else { - printMessage("Unsupported DICOM bit-depth %d with %d samples per pixel\n",d.bitsAllocated,d.samplesPerPixel); - return EXIT_FAILURE; - } - if ((h->datatype == DT_UINT16) && (d.bitsStored > 0) &&(d.bitsStored < 16)) - h->datatype = DT_INT16; // DT_INT16 is more widely supported, same represenation for values 0..32767 - for (int i = 0; i < 8; i++) { - h->pixdim[i] = 0.0f; - h->dim[i] = 0; - } - //next items listed as unused in NIfTI format, but zeroed for consistency across runs + for (int i = 0; i < 80; i++) + h->descrip[i] = 0; + for (int i = 0; i < 24; i++) + h->aux_file[i] = 0; + for (int i = 0; i < 18; i++) + h->db_name[i] = 0; + for (int i = 0; i < 10; i++) + h->data_type[i] = 0; + for (int i = 0; i < 16; i++) + h->intent_name[i] = 0; + if ((d.bitsAllocated == 8) && (d.samplesPerPixel == 3)) { + h->intent_code = NIFTI_INTENT_ESTIMATE; //make sure we treat this as RGBRGB...RGB + h->datatype = DT_RGB24; + } else if ((d.bitsAllocated == 8) && (d.samplesPerPixel == 1)) + h->datatype = DT_UINT8; + else if ((d.bitsAllocated == 12) && (d.samplesPerPixel == 1)) + h->datatype = DT_INT16; + else if ((d.bitsAllocated == 16) && (d.samplesPerPixel == 1) && (d.isSigned)) + h->datatype = DT_INT16; + else if ((d.bitsAllocated == 16) && (d.samplesPerPixel == 1) && (!d.isSigned)) + h->datatype = DT_UINT16; + else if ((d.bitsAllocated == 32) && (d.isFloat)) + h->datatype = DT_FLOAT32; + else if (d.bitsAllocated == 32) + h->datatype = DT_INT32; + else if ((d.bitsAllocated == 64) && (d.isFloat)) + h->datatype = DT_FLOAT64; + else { + printMessage("Unsupported DICOM bit-depth %d with %d samples per pixel\n", d.bitsAllocated, d.samplesPerPixel); + return EXIT_FAILURE; + } + if ((h->datatype == DT_UINT16) && (d.bitsStored > 0) && (d.bitsStored < 16)) + h->datatype = DT_INT16; // DT_INT16 is more widely supported, same represenation for values 0..32767 + for (int i = 0; i < 8; i++) { + h->pixdim[i] = 0.0f; + h->dim[i] = 0; + } + //next items listed as unused in NIfTI format, but zeroed for consistency across runs h->extents = 0; - h->session_error = kSessionOK; - h->glmin = 0; //unused, but make consistent - h->glmax = 0; //unused, but make consistent - h->regular = 114; //in legacy Analyze this was always 114 - //these are important - h->scl_inter = d.intenIntercept; - h->scl_slope = d.intenScale; - h->cal_max = 0; - h->cal_min = 0; - h->magic[0]='n'; - h->magic[1]='+'; - h->magic[2]='1'; - h->magic[3]='\0'; - h->vox_offset = (float) d.imageStart; - if (d.bitsAllocated == 12) - h->bitpix = 16 * d.samplesPerPixel; - else - h->bitpix = d.bitsAllocated * d.samplesPerPixel; - h->pixdim[1] = d.xyzMM[1]; - h->pixdim[2] = d.xyzMM[2]; - h->pixdim[3] = d.xyzMM[3]; - h->pixdim[4] = d.TR / 1000.0; //TR reported in msec, time is in sec - h->dim[1] = d.xyzDim[1]; - h->dim[2] = d.xyzDim[2]; - h->dim[3] = d.xyzDim[3]; - h->dim[4] = d.xyzDim[4]; - h->dim[5] = 1; - h->dim[6] = 1; - h->dim[7] = 1; - if (h->dim[4] < 2) - h->dim[0] = 3; - else - h->dim[0] = 4; - for (int i = 0; i <= 3; i++) { - h->srow_x[i] = 0.0f; - h->srow_y[i] = 0.0f; - h->srow_z[i] = 0.0f; - } - h->slice_start = 0; - h->slice_end = 0; - h->srow_x[0] = -1; - h->srow_y[2] = 1; - h->srow_z[1] = -1; - h->srow_x[3] = ((float) h->dim[1] / 2); + h->session_error = kSessionOK; + h->glmin = 0; //unused, but make consistent + h->glmax = 0; //unused, but make consistent + h->regular = 114; //in legacy Analyze this was always 114 + //these are important + h->scl_inter = d.intenIntercept; + h->scl_slope = d.intenScale; + h->cal_max = 0; + h->cal_min = 0; + h->magic[0] = 'n'; + h->magic[1] = '+'; + h->magic[2] = '1'; + h->magic[3] = '\0'; + h->vox_offset = (float)d.imageStart; + if (d.bitsAllocated == 12) + h->bitpix = 16 * d.samplesPerPixel; + else + h->bitpix = d.bitsAllocated * d.samplesPerPixel; + h->pixdim[1] = d.xyzMM[1]; + h->pixdim[2] = d.xyzMM[2]; + h->pixdim[3] = d.xyzMM[3]; + h->pixdim[4] = d.TR / 1000.0; //TR reported in msec, time is in sec + h->dim[1] = d.xyzDim[1]; + h->dim[2] = d.xyzDim[2]; + h->dim[3] = d.xyzDim[3]; + h->dim[4] = d.xyzDim[4]; + h->dim[5] = 1; + h->dim[6] = 1; + h->dim[7] = 1; + if (h->dim[4] < 2) + h->dim[0] = 3; + else + h->dim[0] = 4; + for (int i = 0; i <= 3; i++) { + h->srow_x[i] = 0.0f; + h->srow_y[i] = 0.0f; + h->srow_z[i] = 0.0f; + } + h->slice_start = 0; + h->slice_end = 0; + h->srow_x[0] = -1; + h->srow_y[2] = 1; + h->srow_z[1] = -1; + h->srow_x[3] = ((float)h->dim[1] / 2); h->srow_y[3] = -((float)h->dim[3] / 2); h->srow_z[3] = ((float)h->dim[2] / 2); - h->qform_code = NIFTI_XFORM_UNKNOWN; - h->sform_code = NIFTI_XFORM_UNKNOWN; - h->toffset = 0; - h->intent_code = NIFTI_INTENT_NONE; - h->dim_info = 0; //Freq, Phase and Slice all unknown - h->xyzt_units = NIFTI_UNITS_MM + NIFTI_UNITS_SEC; - h->slice_duration = 0; //avoid +inf/-inf, NaN - h->intent_p1 = 0; //avoid +inf/-inf, NaN - h->intent_p2 = 0; //avoid +inf/-inf, NaN - h->intent_p3 = 0; //avoid +inf/-inf, NaN - h->pixdim[0] = 1; //QFactor should be 1 or -1 - h->sizeof_hdr = 348; //used to signify header does not need to be byte-swapped - h->slice_code = d.CSA.sliceOrder; - if (isComputeSForm) - headerDcm2Nii2(d, d, h, false); - return EXIT_SUCCESS; + h->qform_code = NIFTI_XFORM_UNKNOWN; + h->sform_code = NIFTI_XFORM_UNKNOWN; + h->toffset = 0; + h->intent_code = NIFTI_INTENT_NONE; + h->dim_info = 0; //Freq, Phase and Slice all unknown + h->xyzt_units = NIFTI_UNITS_MM + NIFTI_UNITS_SEC; + h->slice_duration = 0; //avoid +inf/-inf, NaN + h->intent_p1 = 0; //avoid +inf/-inf, NaN + h->intent_p2 = 0; //avoid +inf/-inf, NaN + h->intent_p3 = 0; //avoid +inf/-inf, NaN + h->pixdim[0] = 1; //QFactor should be 1 or -1 + h->sizeof_hdr = 348; //used to signify header does not need to be byte-swapped + h->slice_code = d.CSA.sliceOrder; + if (isComputeSForm) + headerDcm2Nii2(d, d, h, false); + return EXIT_SUCCESS; } // headerDcm2Nii() -bool isFloatDiff (float a, float b) { - return (fabs (a - b) > FLT_EPSILON); +bool isFloatDiff(float a, float b) { + return (fabs(a - b) > FLT_EPSILON); } //isFloatDiff() -mat33 nifti_mat33_reorder_cols( mat33 m, ivec3 v ) { - // matlab equivalent ret = m(:, v); where v is 1,2,3 [INDEXED FROM ONE!!!!] - mat33 ret; - for (int r=0; r<3; r++) { - for(int c=0; c<3; c++) - ret.m[r][c] = m.m[r][v.v[c]-1]; - } - return ret; +mat33 nifti_mat33_reorder_cols(mat33 m, ivec3 v) { +// matlab equivalent ret = m(:, v); where v is 1,2,3 [INDEXED FROM ONE!!!!] + mat33 ret; + for (int r = 0; r < 3; r++) { + for (int c = 0; c < 3; c++) + ret.m[r][c] = m.m[r][v.v[c] - 1]; + } + return ret; } //nifti_mat33_reorder_cols() -void changeExt (char *file_name, const char* ext) { - char *p_extension; - p_extension = strrchr(file_name, '.'); - //if ((p_extension > file_name) && (strlen(ext) < 1)) - // p_extension--; - if (p_extension) - strcpy(++p_extension, ext); +void changeExt(char *file_name, const char *ext) { + char *p_extension; + p_extension = strrchr(file_name, '.'); + if (p_extension) + strcpy(++p_extension, ext); } //changeExt() -void cleanStr(char* lOut) { +void cleanStr(char *lOut) { //e.g. strings such as image comments with special characters (e.g. "G/6/2009") can disrupt file saves size_t lLength = strlen(lOut); - if (lLength < 1) return; - char * cString = (char *)malloc(sizeof(char) * (lLength + 1)); - cString[lLength] =0; - memcpy(cString, (char*)&lOut[0], lLength); - for (int i = 0; i < lLength; i++) - //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1 - if (cString[i]< 1) { - unsigned char c = (unsigned char)cString[i]; - if ((c >= 192) && (c <= 198)) cString[i] = 'A'; - if (c == 199) cString[i] = 'C'; - if ((c >= 200) && (c <= 203)) cString[i] = 'E'; - if ((c >= 204) && (c <= 207)) cString[i] = 'I'; - if (c == 208) cString[i] = 'D'; - if (c == 209) cString[i] = 'N'; - if ((c >= 210) && (c <= 214)) cString[i] = 'O'; - if (c == 215) cString[i] = 'x'; - if (c == 216) cString[i] = 'O'; - if ((c >= 217) && (c <= 220)) cString[i] = 'O'; - if (c == 221) cString[i] = 'Y'; - if ((c >= 224) && (c <= 230)) cString[i] = 'a'; - if (c == 231) cString[i] = 'c'; - if ((c >= 232) && (c <= 235)) cString[i] = 'e'; - if ((c >= 236) && (c <= 239)) cString[i] = 'i'; - if (c == 240) cString[i] = 'o'; - if (c == 241) cString[i] = 'n'; - if ((c >= 242) && (c <= 246)) cString[i] = 'o'; - if (c == 248) cString[i] = 'o'; - if ((c >= 249) && (c <= 252)) cString[i] = 'u'; - if (c == 253) cString[i] = 'y'; - if (c == 255) cString[i] = 'y'; - } - for (int i = 0; i < lLength; i++) - if ((cString[i]<1) || (cString[i]==' ') || (cString[i]==',') || (cString[i]=='/') || (cString[i]=='\\') || (cString[i]=='%') || (cString[i]=='*') || (cString[i] == 9) || (cString[i] == 10) || (cString[i] == 11) || (cString[i] == 13)) cString[i] = '_'; //issue398 - //if ((cString[i]<1) || (cString[i]==' ') || (cString[i]==',') || (cString[i]=='^') || (cString[i]=='/') || (cString[i]=='\\') || (cString[i]=='%') || (cString[i]=='*') || (cString[i] == 9) || (cString[i] == 10) || (cString[i] == 11) || (cString[i] == 13)) cString[i] = '_'; - int len = 1; - for (int i = 1; i < lLength; i++) { //remove repeated "_" - if ((cString[i-1]!='_') || (cString[i]!='_')) { - cString[len] =cString[i]; - len++; - } - } //for each item - if (cString[len-1] == '_') len--; - cString[len] = 0; //null-terminate, strlcpy does this anyway - int maxLen = kDICOMStr; - len = dcmStrLen(len, maxLen); - if (len == maxLen) { //we need space for null-termination - if (cString[len-2] == '_') len = len -2; - } - memcpy(lOut,cString,len-1); - lOut[len-1] = 0; + if (lLength < 1) + return; + char *cString = (char *)malloc(sizeof(char) * (lLength + 1)); + cString[lLength] = 0; + memcpy(cString, (char *)&lOut[0], lLength); + for (int i = 0; i < lLength; i++) + //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1 + if (cString[i] < 1) { + unsigned char c = (unsigned char)cString[i]; + if ((c >= 192) && (c <= 198)) + cString[i] = 'A'; + if (c == 199) + cString[i] = 'C'; + if ((c >= 200) && (c <= 203)) + cString[i] = 'E'; + if ((c >= 204) && (c <= 207)) + cString[i] = 'I'; + if (c == 208) + cString[i] = 'D'; + if (c == 209) + cString[i] = 'N'; + if ((c >= 210) && (c <= 214)) + cString[i] = 'O'; + if (c == 215) + cString[i] = 'x'; + if (c == 216) + cString[i] = 'O'; + if ((c >= 217) && (c <= 220)) + cString[i] = 'O'; + if (c == 221) + cString[i] = 'Y'; + if ((c >= 224) && (c <= 230)) + cString[i] = 'a'; + if (c == 231) + cString[i] = 'c'; + if ((c >= 232) && (c <= 235)) + cString[i] = 'e'; + if ((c >= 236) && (c <= 239)) + cString[i] = 'i'; + if (c == 240) + cString[i] = 'o'; + if (c == 241) + cString[i] = 'n'; + if ((c >= 242) && (c <= 246)) + cString[i] = 'o'; + if (c == 248) + cString[i] = 'o'; + if ((c >= 249) && (c <= 252)) + cString[i] = 'u'; + if (c == 253) + cString[i] = 'y'; + if (c == 255) + cString[i] = 'y'; + } + for (int i = 0; i < lLength; i++) + if ((cString[i] < 1) || (cString[i] == ' ') || (cString[i] == ',') || (cString[i] == '/') || (cString[i] == '\\') || (cString[i] == '%') || (cString[i] == '*') || (cString[i] == 9) || (cString[i] == 10) || (cString[i] == 11) || (cString[i] == 13)) + cString[i] = '_'; //issue398 + //if ((cString[i]<1) || (cString[i]==' ') || (cString[i]==',') || (cString[i]=='^') || (cString[i]=='/') || (cString[i]=='\\') || (cString[i]=='%') || (cString[i]=='*') || (cString[i] == 9) || (cString[i] == 10) || (cString[i] == 11) || (cString[i] == 13)) cString[i] = '_'; + int len = 1; + for (int i = 1; i < lLength; i++) { //remove repeated "_" + if ((cString[i - 1] != '_') || (cString[i] != '_')) { + cString[len] = cString[i]; + len++; + } + } //for each item + if (cString[len - 1] == '_') + len--; + cString[len] = 0; //null-terminate, strlcpy does this anyway + int maxLen = kDICOMStr; + len = dcmStrLen(len, maxLen); + if (len == maxLen) { //we need space for null-termination + if (cString[len - 2] == '_') + len = len - 2; + } + memcpy(lOut, cString, len - 1); + lOut[len - 1] = 0; free(cString); } //cleanStr() -int isSameFloatGE (float a, float b) { -//Kludge for bug in 0002,0016="DIGITAL_JACKET", 0008,0070="GE MEDICAL SYSTEMS" DICOM data: Orient field (0020:0037) can vary 0.00604261 == 0.00604273 !!! - //return (a == b); //niave approach does not have any tolerance for rounding errors - return (fabs (a - b) <= 0.0001); +int isSameFloatGE(float a, float b) { + //Kludge for bug in 0002,0016="DIGITAL_JACKET", 0008,0070="GE MEDICAL SYSTEMS" DICOM data: Orient field (0020:0037) can vary 0.00604261 == 0.00604273 !!! + //return (a == b); //niave approach does not have any tolerance for rounding errors + return (fabs(a - b) <= 0.0001); } -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; -dti4D->decayFactor[0] = -1; -dti4D->frameDuration[0] = -1; -dti4D->intenScale[0] = 0.0; -strcpy(d.protocolName, ""); //erase dummy with empty -strcpy(d.seriesDescription, ""); //erase dummy with empty -strcpy(d.sequenceName, ""); //erase dummy with empty -strcpy(d.scanningSequence, ""); -FILE *fp = fopen(parname, "r"); -if (fp == NULL) return d; +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; + dti4D->decayFactor[0] = -1; + dti4D->frameDuration[0] = -1; + dti4D->intenScale[0] = 0.0; + strcpy(d.protocolName, ""); //erase dummy with empty + strcpy(d.seriesDescription, ""); //erase dummy with empty + strcpy(d.sequenceName, ""); //erase dummy with empty + strcpy(d.scanningSequence, ""); + FILE *fp = fopen(parname, "r"); + if (fp == NULL) + return d; #define LINESZ 2048 -#define kSlice 0 -#define kEcho 1 -#define kDyn 2 -#define kCardiac 3 -#define kImageType 4 -#define kSequence 5 -#define kIndex 6 +#define kSlice 0 +#define kEcho 1 +#define kDyn 2 +#define kCardiac 3 +#define kImageType 4 +#define kSequence 5 +#define kIndex 6 //V3 only identical for columns 1..6 -#define kBitsPerVoxel 7 //V3: not per slice: "Image pixel size [8 or 16 bits]" -#define kXdim 9 //V3: not per slice: "Recon resolution (x, y)" -#define kYdim 10 //V3: not per slice: "Recon resolution (x, y)" -int kRI = 11; //V3: 7 -int kRS = 12; //V3: 8 -int kSS = 13; //V3: 9 -int kAngulationAPs = 16; //V3: 12 -int kAngulationFHs = 17; //V3: 13 -int kAngulationRLs = 18; //V3: 14 -int kPositionAP = 19; //V3: 15 -int kPositionFH = 20; //V3: 16 -int kPositionRL = 21; //V3: 17 -#define kThickmm 22 //V3: not per slice: "Slice thickness [mm]" -#define kGapmm 23 //V3: not per slice: "Slice gap [mm]" -int kSliceOrients = 25; //V3: 19 -int kXmm = 28; //V3: 22 -int kYmm = 29; //V3: 23 -int kTEcho = 30; //V3: 24 -int kDynTime = 31; //V3: 25 -int kTriggerTime = 32; //V3: 26 -int kbval = 33; //V3: 27 +#define kBitsPerVoxel 7 //V3: not per slice: "Image pixel size [8 or 16 bits]" +#define kXdim 9 //V3: not per slice: "Recon resolution (x, y)" +#define kYdim 10 //V3: not per slice: "Recon resolution (x, y)" + int kRI = 11; //V3: 7 + int kRS = 12; //V3: 8 + int kSS = 13; //V3: 9 + int kAngulationAPs = 16; //V3: 12 + int kAngulationFHs = 17; //V3: 13 + int kAngulationRLs = 18; //V3: 14 + int kPositionAP = 19; //V3: 15 + int kPositionFH = 20; //V3: 16 + int kPositionRL = 21; //V3: 17 +#define kThickmm 22 //V3: not per slice: "Slice thickness [mm]" +#define kGapmm 23 //V3: not per slice: "Slice gap [mm]" + int kSliceOrients = 25; //V3: 19 + int kXmm = 28; //V3: 22 + int kYmm = 29; //V3: 23 + int kTEcho = 30; //V3: 24 + int kDynTime = 31; //V3: 25 + int kTriggerTime = 32; //V3: 26 + int kbval = 33;//V3: 27 //the following do not exist in V3 -#define kInversionDelayMs 40 -#define kbvalNumber 41 -#define kGradientNumber 42 +#define kInversionDelayMs 40 +#define kbvalNumber 41 +#define kGradientNumber 42 //the following do not exist in V40 or earlier -#define kv1 47 -#define kv2 45 -#define kv3 46 +#define kv1 47 +#define kv2 45 +#define kv3 46 //the following do not exist in V41 or earlier -#define kASL 48 +#define kASL 48 #define kMaxImageType 4 //4 observed image types: real, imag, mag, phase (in theory also subsequent calculation such as B1) - printWarning("dcm2niix PAR is not actively supported (hint: use dicm2nii)\n"); - if (isReadPhase) printWarning(" Reading phase images from PAR/REC\n"); - char buff[LINESZ]; + printWarning("dcm2niix PAR is not actively supported (hint: use dicm2nii)\n"); + if (isReadPhase) + printWarning(" Reading phase images from PAR/REC\n"); + char buff[LINESZ]; //next values: PAR V3 only int v3BitsPerVoxel = 16; //V3: not per slice: "Image pixel size [8 or 16 bits]" int v3Xdim = 128; //not per slice: "Recon resolution (x, y)" - int v3Ydim = 128; //V3: not per slice: "Recon resolution (x, y)" - float v3Thickmm = 2.0; //V3: not per slice: "Slice thickness [mm]" - float v3Gapmm = 0.0; //V3: not per slice: "Slice gap [mm]" + int v3Ydim = 128; //V3: not per slice: "Recon resolution (x, y)" + float v3Thickmm = 2.0; //V3: not per slice: "Slice thickness [mm]" + float v3Gapmm = 0.0; //V3: not per slice: "Slice gap [mm]" //from top of header int maxNumberOfDiffusionValues = 1; int maxNumberOfGradientOrients = 1; @@ -1735,83 +1752,83 @@ int kbval = 33; //V3: 27 int maxNumberOfEchoes = 1; int maxNumberOfDynamics = 1; int maxNumberOfMixes = 1; - int maxNumberOfLabels = 1;//Number of label types <0=no ASL> - float maxBValue = 0.0f; - float maxDynTime = 0.0f; - float minDynTime = 999999.0f; - float TE = 0.0; - int minDyn = 32767; - int maxDyn = 0; - int minSlice = 32767; - int maxSlice = 0; - bool ADCwarning = false; - bool isTypeWarning = false; - bool isType4Warning = false; - bool isSequenceWarning = false; - int numSlice2D = 0; - int prevDyn = -1; - bool dynNotAscending = false; - int parVers = 0; - int maxSeq = -1; //maximum value of Seq column - int seq1 = -1; //value of Seq volume for first slice - int maxEcho = 1; - int maxCardiac = 1; - int nCols = 26; - //int diskSlice = 0; - int num3DExpected = 0; //number of 3D volumes in the top part of the header - int num2DExpected = 0; //number of 2D slices described in the top part of the header - int maxVol = -1; - int patientPositionNumPhilips = 0; - d.isValid = false; - const int kMaxCols = 49; - float *cols = (float *)malloc(sizeof(float) * (kMaxCols+1)); - for (int i = 0; i < kMaxCols; i++) - cols[i] = 0.0; //old versions of PAR do not fill all columns - beware of buffer overflow - char *p = fgets (buff, LINESZ, fp); - bool isIntenScaleVaries = false; - for (int i = 0; i < kMaxDTI4D; i++) { - dti4D->S[i].V[0] = -1.0; - dti4D->TE[i] = -1.0; - } - for (int i = 0; i < kMaxSlice2D; i++) - dti4D->sliceOrder[i] = -1; - while (p) { - if (strlen(buff) < 1) - continue; - if (buff[0] == '#') { //comment - char Comment[7][50]; - sscanf(buff, "# %s %s %s %s %s %s V%s\n", Comment[0], Comment[1], Comment[2], Comment[3],Comment[4], Comment[5],Comment[6]); - if ((strcmp(Comment[0], "sl") == 0) && (strcmp(Comment[1], "ec") == 0) ) { - num3DExpected = maxNumberOfGradientOrients * maxNumberOfDiffusionValues * maxNumberOfLabels - * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes; - num2DExpected = d.xyzDim[3] * num3DExpected; - if ((num2DExpected ) >= kMaxSlice2D) { + int maxNumberOfLabels = 1; //Number of label types <0=no ASL> + float maxBValue = 0.0f; + float maxDynTime = 0.0f; + float minDynTime = 999999.0f; + float TE = 0.0; + int minDyn = 32767; + int maxDyn = 0; + int minSlice = 32767; + int maxSlice = 0; + bool ADCwarning = false; + bool isTypeWarning = false; + bool isType4Warning = false; + bool isSequenceWarning = false; + int numSlice2D = 0; + int prevDyn = -1; + bool dynNotAscending = false; + int parVers = 0; + int maxSeq = -1; //maximum value of Seq column + int seq1 = -1; //value of Seq volume for first slice + int maxEcho = 1; + int maxCardiac = 1; + int nCols = 26; + //int diskSlice = 0; + int num3DExpected = 0; //number of 3D volumes in the top part of the header + int num2DExpected = 0; //number of 2D slices described in the top part of the header + int maxVol = -1; + int patientPositionNumPhilips = 0; + d.isValid = false; + const int kMaxCols = 49; + float *cols = (float *)malloc(sizeof(float) * (kMaxCols + 1)); + for (int i = 0; i < kMaxCols; i++) + cols[i] = 0.0; //old versions of PAR do not fill all columns - beware of buffer overflow + char *p = fgets(buff, LINESZ, fp); + bool isIntenScaleVaries = false; + for (int i = 0; i < kMaxDTI4D; i++) { + dti4D->S[i].V[0] = -1.0; + dti4D->TE[i] = -1.0; + } + for (int i = 0; i < kMaxSlice2D; i++) + dti4D->sliceOrder[i] = -1; + while (p) { + if (strlen(buff) < 1) + continue; + if (buff[0] == '#') { //comment + char Comment[7][50]; + sscanf(buff, "# %s %s %s %s %s %s V%s\n", Comment[0], Comment[1], Comment[2], Comment[3], Comment[4], Comment[5], Comment[6]); + if ((strcmp(Comment[0], "sl") == 0) && (strcmp(Comment[1], "ec") == 0)) { + num3DExpected = maxNumberOfGradientOrients * maxNumberOfDiffusionValues * maxNumberOfLabels * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes; + num2DExpected = d.xyzDim[3] * num3DExpected; + if ((num2DExpected) >= kMaxSlice2D) { 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); - free (cols); + d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, + maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); + free(cols); return d; - } + } } - if (strcmp(Comment[1], "TRYOUT") == 0) { - //sscanf(buff, "# %s %s %s %s %s %s V%s\n", Comment[0], Comment[1], Comment[2], Comment[3],Comment[4], Comment[5],Comment[6]); - parVers = (int)round(atof(Comment[6])*10); //4.2 = 42 etc - if (parVers <= 29) { - printMessage("Unsupported old PAR version %0.2f (use dicm2nii)\n", parVers/10.0); - return d; - //nCols = 26; //e.g. PAR 3.0 has 26 relevant columns - } if (parVers < 40) { - nCols = 29; // PAR 3.0? - kRI = 7; - kRS = 8; - kSS = 9; + if (strcmp(Comment[1], "TRYOUT") == 0) { + //sscanf(buff, "# %s %s %s %s %s %s V%s\n", Comment[0], Comment[1], Comment[2], Comment[3],Comment[4], Comment[5],Comment[6]); + parVers = (int)round(atof(Comment[6]) * 10); //4.2 = 42 etc + if (parVers <= 29) { + printMessage("Unsupported old PAR version %0.2f (use dicm2nii)\n", parVers / 10.0); + return d; + //nCols = 26; //e.g. PAR 3.0 has 26 relevant columns + } + if (parVers < 40) { + nCols = 29; // PAR 3.0? + kRI = 7; + kRS = 8; + kSS = 9; kAngulationAPs = 12; kAngulationFHs = 13; kAngulationRLs = 14; - kPositionAP = 15; - kPositionFH = 16; - kPositionRL = 17; + kPositionAP = 15; + kPositionFH = 16; + kPositionRL = 17; kSliceOrients = 19; kXmm = 22; kYmm = 23; @@ -1819,351 +1836,374 @@ int kbval = 33; //V3: 27 kDynTime = 25; kTriggerTime = 26; kbval = 27; - } else if (parVers < 41) - nCols = kv1; //e.g PAR 4.0 - else if (parVers < 42) - nCols = kASL; //e.g. PAR 4.1 - last column is final diffusion b-value - else - nCols = kMaxCols; //e.g. PAR 4.2 - } - //the following do not exist in V3 - p = fgets (buff, LINESZ, fp);//get next line - continue; - } //process '#' comment - if (buff[0] == '.') { //tag - char Comment[9][50]; - for (int i = 0; i < 9; i++) - strcpy(Comment[i], ""); - sscanf(buff, ". %s %s %s %s %s %s %s %s %s\n", Comment[0], Comment[1],Comment[2], Comment[3], Comment[4], Comment[5], Comment[6], Comment[7], Comment[8]); - if ((strcmp(Comment[0], "Acquisition") == 0) && (strcmp(Comment[1], "nr") == 0)) { - d.acquNum = atoi( Comment[3]); - d.seriesNum = d.acquNum; - } - if ((strcmp(Comment[0], "Recon") == 0) && (strcmp(Comment[1], "resolution") == 0)) { - v3Xdim = (int) atoi(Comment[5]); - v3Ydim = (int) atoi(Comment[6]); - //printMessage("recon %d,%d\n", v3Xdim,v3Ydim); - } - if ((strcmp(Comment[1], "pixel") == 0) && (strcmp(Comment[2], "size") == 0)) { - v3BitsPerVoxel = (int) atoi(Comment[8]); - //printMessage("bits %d\n", v3BitsPerVoxel); - } - if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "gap") == 0)) { - v3Gapmm = (float) atof(Comment[4]); - //printMessage("gap %g\n", v3Gapmm); - } - if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "thickness") == 0)) { - v3Thickmm = (float) atof(Comment[4]); - //printMessage("thick %g\n", v3Thickmm); - } - if ((strcmp(Comment[0], "Repetition") == 0) && (strcmp(Comment[1], "time") == 0)) - d.TR = (float) atof(Comment[4]); - if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "name") == 0)) { - strcpy(d.patientName, Comment[3]); - strcat(d.patientName, Comment[4]); - strcat(d.patientName, Comment[5]); - strcat(d.patientName, Comment[6]); - strcat(d.patientName, Comment[7]); - cleanStr(d.patientName); - //printMessage("%s\n",d.patientName); - } - if ((strcmp(Comment[0], "Technique") == 0) && (strcmp(Comment[1], ":") == 0)) { - strcpy(d.patientID, Comment[2]); - strcat(d.patientID, Comment[3]); - strcat(d.patientID, Comment[4]); - strcat(d.patientID, Comment[5]); - strcat(d.patientID, Comment[6]); - strcat(d.patientID, Comment[7]); - cleanStr(d.patientID); - } - if ((strcmp(Comment[0], "Protocol") == 0) && (strcmp(Comment[1], "name") == 0)) { - strcpy(d.protocolName, Comment[3]); - strcat(d.protocolName, Comment[4]); - strcat(d.protocolName, Comment[5]); - strcat(d.protocolName, Comment[6]); - strcat(d.protocolName, Comment[7]); - cleanStr(d.protocolName); - } - if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "name") == 0)) { - strcpy(d.imageComments, Comment[3]); - strcat(d.imageComments, Comment[4]); - strcat(d.imageComments, Comment[5]); - strcat(d.imageComments, Comment[6]); - strcat(d.imageComments, Comment[7]); - cleanStr(d.imageComments); - } - if ((strcmp(Comment[0], "Series") == 0) && (strcmp(Comment[1], "Type") == 0)) { - strcpy(d.seriesDescription, Comment[3]); - strcat(d.seriesDescription, Comment[4]); - strcat(d.seriesDescription, Comment[5]); - strcat(d.seriesDescription, Comment[6]); - strcat(d.seriesDescription, Comment[7]); - cleanStr(d.seriesDescription); - } - if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "date/time") == 0)) { - if ((strlen(Comment[3]) >= 10) && (strlen(Comment[5]) >= 8)) { - //DICOM date format is YYYYMMDD, but PAR stores YYYY.MM.DD 2016.03.25 - d.studyDate[0] = Comment[3][0]; - d.studyDate[1] = Comment[3][1]; - d.studyDate[2] = Comment[3][2]; - d.studyDate[3] = Comment[3][3]; - d.studyDate[4] = Comment[3][5]; - d.studyDate[5] = Comment[3][6]; - d.studyDate[6] = Comment[3][8]; - d.studyDate[7] = Comment[3][9]; - d.studyDate[8] = '\0'; - //DICOM time format is HHMMSS.FFFFFF, but PAR stores HH:MM:SS, e.g. 18:00:42 or 09:34:16 - d.studyTime[0] = Comment[5][0]; - d.studyTime[1] = Comment[5][1]; - d.studyTime[2] = Comment[5][3]; - d.studyTime[3] = Comment[5][4]; - d.studyTime[4] = Comment[5][6]; - d.studyTime[5] = Comment[5][7]; - d.studyTime[6] = '\0'; - d.dateTime = (atof(d.studyDate)* 1000000) + atof(d.studyTime); - } - } - if ((strcmp(Comment[0], "Off") == 0) && (strcmp(Comment[1], "Centre") == 0)) { - //Off Centre midslice(ap,fh,rl) [mm] - d.stackOffcentre[2] = (float) atof(Comment[5]); - d.stackOffcentre[3] = (float) atof(Comment[6]); - d.stackOffcentre[1] = (float) atof(Comment[7]); - } - if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "position") == 0)) { - //Off Centre midslice(ap,fh,rl) [mm] - d.patientOrient[0] = toupper(Comment[3][0]); - d.patientOrient[1] = toupper(Comment[4][0]); - d.patientOrient[2] = toupper(Comment[5][0]); - d.patientOrient[3] = 0; - } - if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "slices/locations") == 0)) { - d.xyzDim[3] = atoi(Comment[5]); - } - if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "diffusion") == 0)) { - maxNumberOfDiffusionValues = atoi(Comment[6]); - //if (maxNumberOfDiffusionValues > 1) maxNumberOfDiffusionValues -= 1; //if two listed, one is B=0 - } - if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "gradient") == 0)) { - maxNumberOfGradientOrients = atoi(Comment[6]); - //Warning ISOTROPIC scans may be stored that are not reported here! 32 directions plus isotropic = 33 volumes - } - if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "cardiac") == 0)) { - maxNumberOfCardiacPhases = atoi(Comment[6]); - } - if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "echoes") == 0)) { - maxNumberOfEchoes = atoi(Comment[5]); - if (maxNumberOfEchoes > 1) d.isMultiEcho = true; - } - if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "dynamics") == 0)) { - maxNumberOfDynamics = atoi(Comment[5]); - } - if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "mixes") == 0)) { - maxNumberOfMixes = atoi(Comment[5]); - if (maxNumberOfMixes > 1) - printError("maxNumberOfMixes > 1. Please update this software to support these images\n"); - } - if ((strcmp(Comment[0], "Number") == 0) && (strcmp(Comment[2], "label") == 0)) { - maxNumberOfLabels = atoi(Comment[7]); - if (maxNumberOfLabels < 1) maxNumberOfLabels = 1; - } - p = fgets (buff, LINESZ, fp);//get next line - continue; - } //process '.' tag - if (strlen(buff) < 24) { //empty line - p = fgets (buff, LINESZ, fp);//get next line - continue; - } - if (parVers < 20) { - printError("PAR files should have 'CLINICAL TRYOUT' line with a version from 2.0-4.2: %s\n", parname); - free (cols); - return d; - } - for (int i = 0; i <= nCols; i++) - cols[i] = strtof(p, &p); // p+1 skip comma, read a float - //printMessage("xDim %dv%d yDim %dv%d bits %dv%d\n", d.xyzDim[1],(int)cols[kXdim], d.xyzDim[2], (int)cols[kYdim], d.bitsAllocated, (int)cols[kBitsPerVoxel]); + } else if (parVers < 41) + nCols = kv1; //e.g PAR 4.0 + else if (parVers < 42) + nCols = kASL; //e.g. PAR 4.1 - last column is final diffusion b-value + else + nCols = kMaxCols; //e.g. PAR 4.2 + } + //the following do not exist in V3 + p = fgets(buff, LINESZ, fp); //get next line + continue; + } //process '#' comment + if (buff[0] == '.') { //tag + char Comment[9][50]; + for (int i = 0; i < 9; i++) + strcpy(Comment[i], ""); + sscanf(buff, ". %s %s %s %s %s %s %s %s %s\n", Comment[0], Comment[1], Comment[2], Comment[3], Comment[4], Comment[5], Comment[6], Comment[7], Comment[8]); + if ((strcmp(Comment[0], "Acquisition") == 0) && (strcmp(Comment[1], "nr") == 0)) { + d.acquNum = atoi(Comment[3]); + d.seriesNum = d.acquNum; + } + if ((strcmp(Comment[0], "Recon") == 0) && (strcmp(Comment[1], "resolution") == 0)) { + v3Xdim = (int)atoi(Comment[5]); + v3Ydim = (int)atoi(Comment[6]); + //printMessage("recon %d,%d\n", v3Xdim,v3Ydim); + } + if ((strcmp(Comment[1], "pixel") == 0) && (strcmp(Comment[2], "size") == 0)) { + v3BitsPerVoxel = (int)atoi(Comment[8]); + //printMessage("bits %d\n", v3BitsPerVoxel); + } + if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "gap") == 0)) { + v3Gapmm = (float)atof(Comment[4]); + //printMessage("gap %g\n", v3Gapmm); + } + if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "thickness") == 0)) { + v3Thickmm = (float)atof(Comment[4]); + //printMessage("thick %g\n", v3Thickmm); + } + if ((strcmp(Comment[0], "Repetition") == 0) && (strcmp(Comment[1], "time") == 0)) + d.TR = (float)atof(Comment[4]); + if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "name") == 0)) { + strcpy(d.patientName, Comment[3]); + strcat(d.patientName, Comment[4]); + strcat(d.patientName, Comment[5]); + strcat(d.patientName, Comment[6]); + strcat(d.patientName, Comment[7]); + cleanStr(d.patientName); + //printMessage("%s\n",d.patientName); + } + if ((strcmp(Comment[0], "Technique") == 0) && (strcmp(Comment[1], ":") == 0)) { + strcpy(d.patientID, Comment[2]); + strcat(d.patientID, Comment[3]); + strcat(d.patientID, Comment[4]); + strcat(d.patientID, Comment[5]); + strcat(d.patientID, Comment[6]); + strcat(d.patientID, Comment[7]); + cleanStr(d.patientID); + } + if ((strcmp(Comment[0], "Protocol") == 0) && (strcmp(Comment[1], "name") == 0)) { + strcpy(d.protocolName, Comment[3]); + strcat(d.protocolName, Comment[4]); + strcat(d.protocolName, Comment[5]); + strcat(d.protocolName, Comment[6]); + strcat(d.protocolName, Comment[7]); + cleanStr(d.protocolName); + } + if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "name") == 0)) { + strcpy(d.imageComments, Comment[3]); + strcat(d.imageComments, Comment[4]); + strcat(d.imageComments, Comment[5]); + strcat(d.imageComments, Comment[6]); + strcat(d.imageComments, Comment[7]); + cleanStr(d.imageComments); + } + if ((strcmp(Comment[0], "Series") == 0) && (strcmp(Comment[1], "Type") == 0)) { + strcpy(d.seriesDescription, Comment[3]); + strcat(d.seriesDescription, Comment[4]); + strcat(d.seriesDescription, Comment[5]); + strcat(d.seriesDescription, Comment[6]); + strcat(d.seriesDescription, Comment[7]); + cleanStr(d.seriesDescription); + } + if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "date/time") == 0)) { + if ((strlen(Comment[3]) >= 10) && (strlen(Comment[5]) >= 8)) { + //DICOM date format is YYYYMMDD, but PAR stores YYYY.MM.DD 2016.03.25 + d.studyDate[0] = Comment[3][0]; + d.studyDate[1] = Comment[3][1]; + d.studyDate[2] = Comment[3][2]; + d.studyDate[3] = Comment[3][3]; + d.studyDate[4] = Comment[3][5]; + d.studyDate[5] = Comment[3][6]; + d.studyDate[6] = Comment[3][8]; + d.studyDate[7] = Comment[3][9]; + d.studyDate[8] = '\0'; + //DICOM time format is HHMMSS.FFFFFF, but PAR stores HH:MM:SS, e.g. 18:00:42 or 09:34:16 + d.studyTime[0] = Comment[5][0]; + d.studyTime[1] = Comment[5][1]; + d.studyTime[2] = Comment[5][3]; + d.studyTime[3] = Comment[5][4]; + d.studyTime[4] = Comment[5][6]; + d.studyTime[5] = Comment[5][7]; + d.studyTime[6] = '\0'; + d.dateTime = (atof(d.studyDate) * 1000000) + atof(d.studyTime); + } + } + if ((strcmp(Comment[0], "Off") == 0) && (strcmp(Comment[1], "Centre") == 0)) { + //Off Centre midslice(ap,fh,rl) [mm] + d.stackOffcentre[2] = (float)atof(Comment[5]); + d.stackOffcentre[3] = (float)atof(Comment[6]); + d.stackOffcentre[1] = (float)atof(Comment[7]); + } + if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "position") == 0)) { + //Off Centre midslice(ap,fh,rl) [mm] + d.patientOrient[0] = toupper(Comment[3][0]); + d.patientOrient[1] = toupper(Comment[4][0]); + d.patientOrient[2] = toupper(Comment[5][0]); + d.patientOrient[3] = 0; + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "slices/locations") == 0)) { + d.xyzDim[3] = atoi(Comment[5]); + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "diffusion") == 0)) { + maxNumberOfDiffusionValues = atoi(Comment[6]); + //if (maxNumberOfDiffusionValues > 1) maxNumberOfDiffusionValues -= 1; //if two listed, one is B=0 + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "gradient") == 0)) { + maxNumberOfGradientOrients = atoi(Comment[6]); + //Warning ISOTROPIC scans may be stored that are not reported here! 32 directions plus isotropic = 33 volumes + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "cardiac") == 0)) { + maxNumberOfCardiacPhases = atoi(Comment[6]); + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "echoes") == 0)) { + maxNumberOfEchoes = atoi(Comment[5]); + if (maxNumberOfEchoes > 1) + d.isMultiEcho = true; + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "dynamics") == 0)) { + maxNumberOfDynamics = atoi(Comment[5]); + } + if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "mixes") == 0)) { + maxNumberOfMixes = atoi(Comment[5]); + if (maxNumberOfMixes > 1) + printError("maxNumberOfMixes > 1. Please update this software to support these images\n"); + } + if ((strcmp(Comment[0], "Number") == 0) && (strcmp(Comment[2], "label") == 0)) { + maxNumberOfLabels = atoi(Comment[7]); + if (maxNumberOfLabels < 1) + maxNumberOfLabels = 1; + } + p = fgets(buff, LINESZ, fp); //get next line + continue; + } //process '.' tag + if (strlen(buff) < 24) { //empty line + p = fgets(buff, LINESZ, fp); //get next line + continue; + } + if (parVers < 20) { + printError("PAR files should have 'CLINICAL TRYOUT' line with a version from 2.0-4.2: %s\n", parname); + free(cols); + return d; + } + for (int i = 0; i <= nCols; i++) + cols[i] = strtof(p, &p); // p+1 skip comma, read a float + //printMessage("xDim %dv%d yDim %dv%d bits %dv%d\n", d.xyzDim[1],(int)cols[kXdim], d.xyzDim[2], (int)cols[kYdim], d.bitsAllocated, (int)cols[kBitsPerVoxel]); if ((int)cols[kSlice] == 0) { //line does not contain attributes - p = fgets (buff, LINESZ, fp);//get next line + p = fgets(buff, LINESZ, fp); //get next line continue; } //diskSlice ++; - bool isADC = false; - 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; - } - if (numSlice2D < 1) { - d.xyzMM[1] = cols[kXmm]; - d.xyzMM[2] = cols[kYmm]; - if (parVers < 40) { //v3 does things differently - //cccc - d.xyzDim[1] = v3Xdim; + bool isADC = false; + 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; + } + if (numSlice2D < 1) { + d.xyzMM[1] = cols[kXmm]; + d.xyzMM[2] = cols[kYmm]; + if (parVers < 40) { //v3 does things differently + //cccc + d.xyzDim[1] = v3Xdim; d.xyzDim[2] = v3Ydim; - d.xyzMM[3] = v3Thickmm + v3Gapmm; - d.bitsAllocated = v3BitsPerVoxel; - d.bitsStored = v3BitsPerVoxel; - } else { - d.xyzDim[1] = (int) cols[kXdim]; - d.xyzDim[2] = (int) cols[kYdim]; - d.xyzMM[3] = cols[kThickmm] + cols[kGapmm]; - d.bitsAllocated = (int) cols[kBitsPerVoxel]; - d.bitsStored = (int) cols[kBitsPerVoxel]; - - } - d.patientPosition[1] = cols[kPositionRL]; - d.patientPosition[2] = cols[kPositionAP]; - d.patientPosition[3] = cols[kPositionFH]; - d.angulation[1] = cols[kAngulationRLs]; - d.angulation[2] = cols[kAngulationAPs]; - d.angulation[3] = cols[kAngulationFHs]; - d.sliceOrient = (int) cols[kSliceOrients]; + d.xyzMM[3] = v3Thickmm + v3Gapmm; + d.bitsAllocated = v3BitsPerVoxel; + d.bitsStored = v3BitsPerVoxel; + } else { + d.xyzDim[1] = (int)cols[kXdim]; + d.xyzDim[2] = (int)cols[kYdim]; + d.xyzMM[3] = cols[kThickmm] + cols[kGapmm]; + d.bitsAllocated = (int)cols[kBitsPerVoxel]; + d.bitsStored = (int)cols[kBitsPerVoxel]; + } + d.patientPosition[1] = cols[kPositionRL]; + d.patientPosition[2] = cols[kPositionAP]; + d.patientPosition[3] = cols[kPositionFH]; + d.angulation[1] = cols[kAngulationRLs]; + d.angulation[2] = cols[kAngulationAPs]; + d.angulation[3] = cols[kAngulationFHs]; + d.sliceOrient = (int)cols[kSliceOrients]; d.TE = cols[kTEcho]; - d.echoNum = cols[kEcho]; - d.TI = cols[kInversionDelayMs]; + d.echoNum = cols[kEcho]; + d.TI = cols[kInversionDelayMs]; d.intenIntercept = cols[kRI]; - d.intenScale = cols[kRS]; - d.intenScalePhilips = cols[kSS]; - } else { - if (parVers >= 40) { - if ((d.xyzDim[1] != cols[kXdim]) || (d.xyzDim[2] != cols[kYdim]) || (d.bitsAllocated != cols[kBitsPerVoxel]) ) { + d.intenScale = cols[kRS]; + d.intenScalePhilips = cols[kSS]; + } else { + if (parVers >= 40) { + if ((d.xyzDim[1] != cols[kXdim]) || (d.xyzDim[2] != cols[kYdim]) || (d.bitsAllocated != cols[kBitsPerVoxel])) { printError("Slice dimensions or bit depth varies %s\n", parname); - printError("xDim %dv%d yDim %dv%d bits %dv%d\n", d.xyzDim[1],(int)cols[kXdim], d.xyzDim[2], (int)cols[kYdim], d.bitsAllocated, (int)cols[kBitsPerVoxel]); + printError("xDim %dv%d yDim %dv%d bits %dv%d\n", d.xyzDim[1], (int)cols[kXdim], d.xyzDim[2], (int)cols[kYdim], d.bitsAllocated, (int)cols[kBitsPerVoxel]); return d; } - } - if ((d.intenScale != cols[kRS]) || (d.intenIntercept != cols[kRI])) - isIntenScaleVaries = true; - } - if (cols[kImageType] == 0) d.isHasMagnitude = true; - if (cols[kImageType] != 0) d.isHasPhase = true; - if (isSameFloat(cols[kImageType],18)) { - //printWarning("Field map in Hz will be saved as the 'real' image.\n"); - //isTypeWarning = true; + } + if ((d.intenScale != cols[kRS]) || (d.intenIntercept != cols[kRI])) + isIntenScaleVaries = true; + } + if (cols[kImageType] == 0) + d.isHasMagnitude = true; + if (cols[kImageType] != 0) + d.isHasPhase = 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; - } - if (cols[kDyn] > maxDyn) maxDyn = (int) cols[kDyn]; - if (cols[kDyn] < minDyn) minDyn = (int) cols[kDyn]; - if (cols[kDyn] < prevDyn) dynNotAscending = true; - prevDyn = cols[kDyn]; - if (cols[kDynTime] > maxDynTime) maxDynTime = cols[kDynTime]; - if (cols[kDynTime] < minDynTime) minDynTime = cols[kDynTime]; - if (cols[kEcho] > maxEcho) maxEcho = cols[kEcho]; - if (cols[kCardiac] > maxCardiac) maxCardiac = cols[kCardiac]; - if ((cols[kEcho] == 1) && (cols[kDyn] == 1) && (cols[kCardiac] == 1) && (cols[kGradientNumber] == 1)) { + } 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; + } + if (cols[kDyn] > maxDyn) + maxDyn = (int)cols[kDyn]; + if (cols[kDyn] < minDyn) + minDyn = (int)cols[kDyn]; + if (cols[kDyn] < prevDyn) + dynNotAscending = true; + prevDyn = cols[kDyn]; + if (cols[kDynTime] > maxDynTime) + maxDynTime = cols[kDynTime]; + if (cols[kDynTime] < minDynTime) + minDynTime = cols[kDynTime]; + if (cols[kEcho] > maxEcho) + maxEcho = cols[kEcho]; + if (cols[kCardiac] > maxCardiac) + maxCardiac = cols[kCardiac]; + if ((cols[kEcho] == 1) && (cols[kDyn] == 1) && (cols[kCardiac] == 1) && (cols[kGradientNumber] == 1)) { if (cols[kSlice] == 1) { d.patientPosition[1] = cols[kPositionRL]; - d.patientPosition[2] = cols[kPositionAP]; - d.patientPosition[3] = cols[kPositionFH]; + d.patientPosition[2] = cols[kPositionAP]; + d.patientPosition[3] = cols[kPositionFH]; } patientPositionNumPhilips++; } if (true) { //for every slice int slice = (int)cols[kSlice]; - if (slice < minSlice) minSlice = slice; + if (slice < minSlice) + minSlice = slice; if (slice > maxSlice) { maxSlice = slice; d.patientPositionLast[1] = cols[kPositionRL]; - d.patientPositionLast[2] = cols[kPositionAP]; - d.patientPositionLast[3] = cols[kPositionFH]; + d.patientPositionLast[2] = cols[kPositionAP]; + d.patientPositionLast[3] = cols[kPositionFH]; } - int volStep = maxNumberOfDynamics; + int volStep = maxNumberOfDynamics; int vol = ((int)cols[kDyn] - 1); - #ifdef old - int gradDynVol = (int)cols[kGradientNumber] - 1; - if (gradDynVol < 0) gradDynVol = 0; //old PAREC without cols[kGradientNumber] - vol = vol + (volStep * (gradDynVol)); - if (vol < 0) vol = 0; - volStep = volStep * maxNumberOfGradientOrients; - int bval = (int)cols[kbvalNumber]; - if (bval > 2) //b=0 is 0, b=1000 is 1, b=2000 is 2 - b=0 does not have multiple directions - bval = bval - 1; - else - bval = 1; - //if (slice == 1) printMessage("bVal %d bVec %d isADC %d nbVal %d nGrad %d\n",(int) cols[kbvalNumber], (int)cols[kGradientNumber], isADC, maxNumberOfDiffusionValues, maxNumberOfGradientOrients); - vol = vol + (volStep * (bval- 1)); - volStep = volStep * (maxNumberOfDiffusionValues-1); +#ifdef old + int gradDynVol = (int)cols[kGradientNumber] - 1; + if (gradDynVol < 0) + gradDynVol = 0; //old PAREC without cols[kGradientNumber] + vol = vol + (volStep * (gradDynVol)); + if (vol < 0) + vol = 0; + volStep = volStep * maxNumberOfGradientOrients; + int bval = (int)cols[kbvalNumber]; + if (bval > 2) //b=0 is 0, b=1000 is 1, b=2000 is 2 - b=0 does not have multiple directions + bval = bval - 1; + else + bval = 1; + //if (slice == 1) printMessage("bVal %d bVec %d isADC %d nbVal %d nGrad %d\n",(int) cols[kbvalNumber], (int)cols[kGradientNumber], isADC, maxNumberOfDiffusionValues, maxNumberOfGradientOrients); + vol = vol + (volStep * (bval - 1)); + volStep = volStep * (maxNumberOfDiffusionValues - 1); + if (isADC) + vol = volStep + (bval - 1); +#else + if (maxNumberOfDiffusionValues > 1) { + int grad = (int)cols[kGradientNumber] - 1; + if (grad < 0) + grad = 0; //old v4 does not have this tag + int bval = (int)cols[kbvalNumber] - 1; + if (bval < 0) + bval = 0; //old v4 does not have this tag if (isADC) - vol = volStep + (bval-1); - #else - if (maxNumberOfDiffusionValues > 1) { - int grad = (int)cols[kGradientNumber] - 1; - if (grad < 0) grad = 0; //old v4 does not have this tag - int bval = (int)cols[kbvalNumber] - 1; - if (bval < 0) bval = 0; //old v4 does not have this tag - if (isADC) - vol = vol + (volStep * maxNumberOfDiffusionValues * maxNumberOfGradientOrients) +bval; - else - vol = vol + (volStep * grad) + (bval * maxNumberOfGradientOrients); + vol = vol + (volStep * maxNumberOfDiffusionValues * maxNumberOfGradientOrients) + bval; + else + vol = vol + (volStep * grad) + (bval * maxNumberOfGradientOrients); - volStep = volStep * (maxNumberOfDiffusionValues+1) * maxNumberOfGradientOrients; - //if (slice == 1) printMessage("vol %d step %d bVal %d bVec %d isADC %d nbVal %d nGrad %d\n", vol, volStep, (int) cols[kbvalNumber], (int)cols[kGradientNumber], isADC, maxNumberOfDiffusionValues, maxNumberOfGradientOrients); - } - #endif - vol = vol + (volStep * ((int)cols[kEcho] - 1)); + volStep = volStep * (maxNumberOfDiffusionValues + 1) * maxNumberOfGradientOrients; + //if (slice == 1) printMessage("vol %d step %d bVal %d bVec %d isADC %d nbVal %d nGrad %d\n", vol, volStep, (int) cols[kbvalNumber], (int)cols[kGradientNumber], isADC, maxNumberOfDiffusionValues, maxNumberOfGradientOrients); + } +#endif + vol = vol + (volStep * ((int)cols[kEcho] - 1)); volStep = volStep * maxNumberOfEchoes; - vol = vol + (volStep * ((int)cols[kCardiac] - 1)); + vol = vol + (volStep * ((int)cols[kCardiac] - 1)); volStep = volStep * maxNumberOfCardiacPhases; int ASL = (int)cols[kASL]; - if (ASL < 1) ASL = 1; - vol = vol + (volStep * (ASL - 1)); + if (ASL < 1) + ASL = 1; + vol = vol + (volStep * (ASL - 1)); volStep = volStep * maxNumberOfLabels; //if ((int)cols[kSequence] > 0) int seq = (int)cols[kSequence]; - if (seq1 < 0) seq1 = seq; - if (seq > maxSeq) maxSeq = seq; - if (seq != seq1) {//sequence varies within this PAR file + if (seq1 < 0) + seq1 = seq; + if (seq > maxSeq) + maxSeq = seq; + if (seq != seq1) { //sequence varies within this PAR file if (!isSequenceWarning) { - isSequenceWarning =true; + isSequenceWarning = true; printWarning("'scanning sequence' column varies within a single file. This behavior is not described at the top of the header.\n"); } - vol = vol + (volStep * 1); + vol = vol + (volStep * 1); volStep = volStep * 2; } //if (slice == 1) printMessage("%d\t%d\t%d\t%d\t%d\n", isADC,(int)cols[kbvalNumber], (int)cols[kGradientNumber], bval, vol); - if (vol > maxVol) maxVol = vol; + if (vol > maxVol) + maxVol = vol; 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"); - isType4Warning = true; - } + printWarning("Unknown image type (4). Be aware the 'phase' image is of an unknown type.\n"); + isType4Warning = true; + } isPhase = true; //2019 } 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; - } + 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); + if (isReal) + vol += num3DExpected; + if (isImaginary) + vol += (2 * num3DExpected); + if (isPhase) + vol += (3 * num3DExpected); if (vol >= kMaxDTI4D) { - printError("Use dicm2nii or increase kMaxDTI4D (currently %d)to be more than %d\n", kMaxDTI4D, kMaxImageType*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); - free (cols); - return d; - } + printError("Use dicm2nii or increase kMaxDTI4D (currently %d)to be more than %d\n", kMaxDTI4D, kMaxImageType * 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); + free(cols); + return d; + } // dti4D->S[vol].V[0] = cols[kbval]; //dti4D->gradDynVol[vol] = gradDynVol; dti4D->TE[vol] = cols[kTEcho]; if (isSameFloatGE(cols[kTEcho], 0)) - dti4D->TE[vol] = TE;//kludge for cols[kImageType]==18 where TE set as 0 + dti4D->TE[vol] = TE; //kludge for cols[kImageType]==18 where TE set as 0 else TE = cols[kTEcho]; - dti4D->triggerDelayTime[vol] = cols[kTriggerTime]; - if (dti4D->TE[vol] < 0) dti4D->TE[vol] = 0; //used to detect sparse volumes + dti4D->triggerDelayTime[vol] = cols[kTriggerTime]; + if (dti4D->TE[vol] < 0) + dti4D->TE[vol] = 0; //used to detect sparse volumes //dti4D->intenIntercept[vol] = cols[kRI]; //dti4D->intenScale[vol] = cols[kRS]; //dti4D->intenScalePhilips[vol] = cols[kSS]; @@ -2172,15 +2212,14 @@ int kbval = 33; //V3: 27 dti4D->isPhase[vol] = isPhase; if ((maxNumberOfGradientOrients > 1) && (parVers > 40)) { dti4D->S[vol].V[0] = cols[kbval]; - dti4D->S[vol].V[1] = cols[kv1]; - dti4D->S[vol].V[2] = cols[kv2]; - dti4D->S[vol].V[3] = cols[kv3]; - if ((vol+1) > d.CSA.numDti) - d.CSA.numDti = vol+1; + dti4D->S[vol].V[1] = cols[kv1]; + dti4D->S[vol].V[2] = cols[kv2]; + dti4D->S[vol].V[3] = cols[kv3]; + if ((vol + 1) > d.CSA.numDti) + d.CSA.numDti = vol + 1; } - 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]; + if (numSlice2D < kMaxDTI4D) { //issue 363: intensity can vary with each 2D slice of 4D volume + dti4D->intenIntercept[numSlice2D] = cols[kRI]; dti4D->intenScale[numSlice2D] = cols[kRS]; dti4D->intenScalePhilips[numSlice2D] = cols[kSS]; } @@ -2190,36 +2229,36 @@ int kbval = 33; //V3: 27 //if (cols[kImageType] != 0) //yikes - phase maps! // slice = slice + numExpected; //printWarning("%d\t%d\n", slice -1, numSlice2D); - if ((slice >= 0) && (slice < kMaxSlice2D) && (numSlice2D < kMaxSlice2D) && (numSlice2D >= 0)) { - dti4D->sliceOrder[slice -1] = numSlice2D; + if ((slice >= 0) && (slice < kMaxSlice2D) && (numSlice2D < kMaxSlice2D) && (numSlice2D >= 0)) { + dti4D->sliceOrder[slice - 1] = numSlice2D; //printMessage("%d\t%d\t%d\n", numSlice2D, slice, (int)cols[kSlice],(int)vol); } numSlice2D++; - } - //printMessage("%f %f %lu\n",cols[9],cols[kGradientNumber], strlen(buff)) - p = fgets (buff, LINESZ, fp);//get next line - } - free (cols); - fclose (fp); - if ((parVers <= 0) || (numSlice2D < 1)) { + } + //printMessage("%f %f %lu\n",cols[9],cols[kGradientNumber], strlen(buff)) + p = fgets(buff, LINESZ, fp); //get next line + } + free(cols); + fclose(fp); + if ((parVers <= 0) || (numSlice2D < 1)) { 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; - } + 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) { //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; - } - d.manufacturer = kMANUFACTURER_PHILIPS; - d.isValid = true; - d.isSigned = true; - //remove unused volumes - this will happen if unless we have all 4 image types: real, imag, mag, phase - maxVol = 0; - for (int i = 0; i < kMaxDTI4D; i++) { - if (dti4D->TE[i] > -1.0) { + 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; + //remove unused volumes - this will happen if unless we have all 4 image types: real, imag, mag, phase + maxVol = 0; + for (int i = 0; i < kMaxDTI4D; i++) { + if (dti4D->TE[i] > -1.0) { dti4D->TE[maxVol] = dti4D->TE[i]; dti4D->triggerDelayTime[maxVol] = dti4D->triggerDelayTime[i]; //dti4D->intenIntercept[maxVol] = dti4D->intenIntercept[i]; @@ -2232,35 +2271,39 @@ int kbval = 33; //V3: 27 dti4D->S[maxVol].V[1] = dti4D->S[i].V[1]; dti4D->S[maxVol].V[2] = dti4D->S[i].V[2]; dti4D->S[maxVol].V[3] = dti4D->S[i].V[3]; - maxVol = maxVol + 1; - } - } - 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 - int slice = 0; - for (int i = 0; i < kMaxSlice2D; i++) { - if (dti4D->sliceOrder[i] > -1) { //this slice was populated - dti4D->sliceOrder[slice] = dti4D->sliceOrder[i]; - slice = slice + 1; - } - } - if (slice != numSlice2D) { - printError("Catastrophic error: found %d but expected %d slices. %s\n", slice, numSlice2D, parname); - printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d\n", - d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, - maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes,maxNumberOfLabels); - d.isValid = false; - } - for (int i = 0; i < numSlice2D; i++) { //issue363 - if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) d.isScaleVariesEnh = true; - if (dti4D->intenScale[i] != dti4D->intenScale[0]) d.isScaleVariesEnh = true; - if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) d.isScaleVariesEnh = true; + maxVol = maxVol + 1; + } + } + 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 + int slice = 0; + for (int i = 0; i < kMaxSlice2D; i++) { + if (dti4D->sliceOrder[i] > -1) { //this slice was populated + dti4D->sliceOrder[slice] = dti4D->sliceOrder[i]; + slice = slice + 1; + } + } + if (slice != numSlice2D) { + printError("Catastrophic error: found %d but expected %d slices. %s\n", slice, numSlice2D, parname); + printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d\n", + d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, + maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); + d.isValid = false; + } + for (int i = 0; i < numSlice2D; i++) { //issue363 + if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) + d.isScaleVariesEnh = true; + if (dti4D->intenScale[i] != dti4D->intenScale[0]) + d.isScaleVariesEnh = true; + if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) + d.isScaleVariesEnh = true; //printf("%g --> %g\n", dti4D->intenIntercept[i], dti4D->intenScale[i]); - } - if (d.isScaleVariesEnh) { //juggle to sorted order, required for subsequent rescaling - printWarning("PAR/REC intensity scaling varies between slices (please validate output).\n"); - TDTI4D tmp; - for (int i = 0; i < numSlice2D; i++) { //issue363 + } + if (d.isScaleVariesEnh) { //juggle to sorted order, required for subsequent rescaling + printWarning("PAR/REC intensity scaling varies between slices (please validate output).\n"); + TDTI4D tmp; + for (int i = 0; i < numSlice2D; i++) { //issue363 tmp.intenIntercept[i] = dti4D->intenIntercept[i]; tmp.intenScale[i] = dti4D->intenScale[i]; tmp.intenScalePhilips[i] = dti4D->intenScalePhilips[i]; @@ -2269,158 +2312,169 @@ int kbval = 33; //V3: 27 int j = dti4D->sliceOrder[i]; dti4D->intenIntercept[i] = tmp.intenIntercept[j]; dti4D->intenScale[i] = tmp.intenScale[j]; - dti4D->intenScalePhilips[i] = tmp.intenScalePhilips[j]; - } - } + dti4D->intenScalePhilips[i] = tmp.intenScalePhilips[j]; + } + } 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; dti4D->intenScale[0] = 0.0; } - if ((maxSlice-minSlice+1) != d.xyzDim[3]) { - int numSlice = (maxSlice - minSlice)+1; + if ((maxSlice - minSlice + 1) != d.xyzDim[3]) { + int numSlice = (maxSlice - minSlice) + 1; printWarning("Expected %d slices, but found %d (%d..%d). %s\n", d.xyzDim[3], numSlice, minSlice, maxSlice, parname); - if (numSlice <= 0) d.isValid = false; + if (numSlice <= 0) + d.isValid = false; d.xyzDim[3] = numSlice; num2DExpected = d.xyzDim[3] * num3DExpected; } - if ((maxBValue <= 0.0f) && (maxDyn > minDyn) && (maxDynTime > minDynTime)) { //use max vs min Dyn instead of && (d.CSA.numDti > 1) - int numDyn = (maxDyn - minDyn)+1; - if (numDyn != maxNumberOfDynamics) { - printWarning("Expected %d dynamics, but found %d (%d..%d).\n", maxNumberOfDynamics, numDyn, minDyn, maxDyn); + if ((maxBValue <= 0.0f) && (maxDyn > minDyn) && (maxDynTime > minDynTime)) { //use max vs min Dyn instead of && (d.CSA.numDti > 1) + int numDyn = (maxDyn - minDyn) + 1; + if (numDyn != maxNumberOfDynamics) { + printWarning("Expected %d dynamics, but found %d (%d..%d).\n", maxNumberOfDynamics, numDyn, minDyn, maxDyn); maxNumberOfDynamics = numDyn; - num3DExpected = maxNumberOfGradientOrients * maxNumberOfDiffusionValues * maxNumberOfLabels - * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes; - num2DExpected = d.xyzDim[3] * num3DExpected; - } - float TRms = 1000.0f * (maxDynTime - minDynTime) / (float)(numDyn-1); //-1 for fence post - if (fabs(TRms - d.TR) > 0.005f) - 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) ) { - 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"); - } + num3DExpected = maxNumberOfGradientOrients * maxNumberOfDiffusionValues * maxNumberOfLabels * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes; + num2DExpected = d.xyzDim[3] * num3DExpected; + } + float TRms = 1000.0f * (maxDynTime - minDynTime) / (float)(numDyn - 1); //-1 for fence post + if (fabs(TRms - d.TR) > 0.005f) + 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)) { + 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) { - 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); - d.isValid = false; - } - if (dynNotAscending) { - printWarning("PAR file volumes not saved in ascending temporal order (please check re-ordering)\n"); - } - if ((slice % d.xyzDim[3]) != 0) { - printError("Total number of slices (%d) not divisible by slices per 3D volume (%d) [acquisition aborted]. Try dicm2nii or R2AGUI: %s\n", slice, d.xyzDim[3], parname); - d.isValid = false; - return d; - } - d.xyzDim[4] = slice/d.xyzDim[3]; - d.locationsInAcquisition = d.xyzDim[3]; - if (ADCwarning) - printWarning("PAR/REC dataset includes derived (isotropic, ADC, etc) map(s) that could disrupt analysis. Please remove volume and ensure vectors are reported correctly\n"); - if (isIntenScaleVaries) - printWarning("Intensity slope/intercept varies between slices! [check resulting images]\n"); - if ((isVerbose) && (d.isValid)) { + 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); + d.isValid = false; + } + if (dynNotAscending) { + printWarning("PAR file volumes not saved in ascending temporal order (please check re-ordering)\n"); + } + if ((slice % d.xyzDim[3]) != 0) { + printError("Total number of slices (%d) not divisible by slices per 3D volume (%d) [acquisition aborted]. Try dicm2nii or R2AGUI: %s\n", slice, d.xyzDim[3], parname); + d.isValid = false; + return d; + } + d.xyzDim[4] = slice / d.xyzDim[3]; + d.locationsInAcquisition = d.xyzDim[3]; + if (ADCwarning) + printWarning("PAR/REC dataset includes derived (isotropic, ADC, etc) map(s) that could disrupt analysis. Please remove volume and ensure vectors are reported correctly\n"); + if (isIntenScaleVaries) + printWarning("Intensity slope/intercept varies between slices! [check resulting images]\n"); + if ((isVerbose) && (d.isValid)) { printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d\n", - d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, - maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes,maxNumberOfLabels); - } - if ((d.xyzDim[3] > 1) && (minSlice == 1) && (maxSlice > minSlice)) { //issue 273 + d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, + maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); + } + if ((d.xyzDim[3] > 1) && (minSlice == 1) && (maxSlice > minSlice)) { //issue 273 float dx[4]; - dx[1] = (d.patientPosition[1]-d.patientPositionLast[1]); - dx[2] = (d.patientPosition[2]-d.patientPositionLast[2]); - dx[3] = (d.patientPosition[3]-d.patientPositionLast[3]); + dx[1] = (d.patientPosition[1] - d.patientPositionLast[1]); + dx[2] = (d.patientPosition[2] - d.patientPositionLast[2]); + dx[3] = (d.patientPosition[3] - d.patientPositionLast[3]); //compute error using 3D pythagorean theorm - float sliceMM = sqrt(pow(dx[1],2)+pow(dx[2],2)+pow(dx[3],2) ); + float sliceMM = sqrt(pow(dx[1], 2) + pow(dx[2], 2) + pow(dx[3], 2)); sliceMM = sliceMM / (maxSlice - minSlice); if (!(isSameFloatGE(sliceMM, d.xyzMM[3]))) { //if (d.xyzMM[3] > 0.0) printWarning("Distance between slices reported by slice gap+thick does not match estimate from slice positions (issue 273).\n"); d.xyzMM[3] = sliceMM; } - } //issue 273 - printMessage("Done reading PAR header version %.1f, with %d slices\n", (float)parVers/10, numSlice2D); + } //issue 273 + printMessage("Done reading PAR header version %.1f, with %d slices\n", (float)parVers / 10, numSlice2D); //see Xiangrui Li 's dicm2nii (also BSD license) // http://www.mathworks.com/matlabcentral/fileexchange/42997-dicom-to-nifti-converter // Rotation order and signs are figured out by trial and error, not 100% sure - float d2r = (float) (M_PI/180.0); - vec3 ca = setVec3(cos(d.angulation[1]*d2r),cos(d.angulation[2]*d2r),cos(d.angulation[3]*d2r)); - vec3 sa = setVec3(sin(d.angulation[1]*d2r),sin(d.angulation[2]*d2r),sin(d.angulation[3]*d2r)); - mat33 rx,ry,rz; - LOAD_MAT33(rx,1.0f, 0.0f, 0.0f, 0.0f, ca.v[0], -sa.v[0], 0.0f, sa.v[0], ca.v[0]); - LOAD_MAT33(ry, ca.v[1], 0.0f, sa.v[1], 0.0f, 1.0f, 0.0f, -sa.v[1], 0.0f, ca.v[1]); - LOAD_MAT33(rz, ca.v[2], -sa.v[2], 0.0f, sa.v[2], ca.v[2], 0.0f, 0.0f, 0.0f, 1.0f); - mat33 R = nifti_mat33_mul( rx,ry ); - R = nifti_mat33_mul( R,rz); - ivec3 ixyz = setiVec3(1,2,3); - if (d.sliceOrient == kSliceOrientSag) { - ixyz = setiVec3(2,3,1); - for (int r = 0; r < 3; r++) - for (int c = 0; c < 3; c++) - if (c != 1) R.m[r][c] = -R.m[r][c]; //invert first and final columns - }else if (d.sliceOrient == kSliceOrientCor) { - ixyz = setiVec3(1,3,2); - for (int r = 0; r < 3; r++) - R.m[r][2] = -R.m[r][2]; //invert rows of final column - } - R = nifti_mat33_reorder_cols(R,ixyz); //dicom rotation matrix - d.orient[1] = R.m[0][0]; d.orient[2] = R.m[1][0]; d.orient[3] = R.m[2][0]; - d.orient[4] = R.m[0][1]; d.orient[5] = R.m[1][1]; d.orient[6] = R.m[2][1]; - mat33 diag; - LOAD_MAT33(diag, d.xyzMM[1],0.0f,0.0f, 0.0f,d.xyzMM[2],0.0f, 0.0f,0.0f, d.xyzMM[3]); - R= nifti_mat33_mul( R, diag ); - mat44 R44; - LOAD_MAT44(R44, R.m[0][0],R.m[0][1],R.m[0][2],d.stackOffcentre[1], - R.m[1][0],R.m[1][1],R.m[1][2],d.stackOffcentre[2], - R.m[2][0],R.m[2][1],R.m[2][2],d.stackOffcentre[3]); - vec3 x; - if (parVers > 40) //guess - x = setVec3(((float)d.xyzDim[1]-1)/2,((float)d.xyzDim[2]-1)/2,((float)d.xyzDim[3]-1)/2); - else - x = setVec3((float)d.xyzDim[1]/2,(float)d.xyzDim[2]/2,((float)d.xyzDim[3]-1)/2); - mat44 eye; - LOAD_MAT44(eye, 1.0f,0.0f,0.0f,x.v[0], - 0.0f,1.0f,0.0f,x.v[1], - 0.0f,0.0f,1.0f,x.v[2]); - eye= nifti_mat44_inverse( eye ); //we wish to compute R/eye, so compute invEye and calculate R*invEye - R44= nifti_mat44_mul( R44 , eye ); - vec4 y; - y.v[0]=0.0f; y.v[1]=0.0f; y.v[2]=(float) d.xyzDim[3]-1.0f; y.v[3]=1.0f; - y= nifti_vect44mat44_mul(y, R44 ); - int iOri = 2; //for axial, slices are 3rd dimenson (indexed from 0) (k) - if (d.sliceOrient == kSliceOrientSag) iOri = 0; //for sagittal, slices are 1st dimension (i) - if (d.sliceOrient == kSliceOrientCor) iOri = 1; //for coronal, slices are 2nd dimension (j) - if (d.xyzDim[3] > 1) { //detect and fix Philips Bug + float d2r = (float)(M_PI / 180.0); + vec3 ca = setVec3(cos(d.angulation[1] * d2r), cos(d.angulation[2] * d2r), cos(d.angulation[3] * d2r)); + vec3 sa = setVec3(sin(d.angulation[1] * d2r), sin(d.angulation[2] * d2r), sin(d.angulation[3] * d2r)); + mat33 rx, ry, rz; + LOAD_MAT33(rx, 1.0f, 0.0f, 0.0f, 0.0f, ca.v[0], -sa.v[0], 0.0f, sa.v[0], ca.v[0]); + LOAD_MAT33(ry, ca.v[1], 0.0f, sa.v[1], 0.0f, 1.0f, 0.0f, -sa.v[1], 0.0f, ca.v[1]); + LOAD_MAT33(rz, ca.v[2], -sa.v[2], 0.0f, sa.v[2], ca.v[2], 0.0f, 0.0f, 0.0f, 1.0f); + mat33 R = nifti_mat33_mul(rx, ry); + R = nifti_mat33_mul(R, rz); + ivec3 ixyz = setiVec3(1, 2, 3); + if (d.sliceOrient == kSliceOrientSag) { + ixyz = setiVec3(2, 3, 1); + for (int r = 0; r < 3; r++) + for (int c = 0; c < 3; c++) + if (c != 1) + R.m[r][c] = -R.m[r][c]; //invert first and final columns + } else if (d.sliceOrient == kSliceOrientCor) { + ixyz = setiVec3(1, 3, 2); + for (int r = 0; r < 3; r++) + R.m[r][2] = -R.m[r][2]; //invert rows of final column + } + R = nifti_mat33_reorder_cols(R, ixyz); //dicom rotation matrix + d.orient[1] = R.m[0][0]; + d.orient[2] = R.m[1][0]; + d.orient[3] = R.m[2][0]; + d.orient[4] = R.m[0][1]; + d.orient[5] = R.m[1][1]; + d.orient[6] = R.m[2][1]; + mat33 diag; + LOAD_MAT33(diag, d.xyzMM[1], 0.0f, 0.0f, 0.0f, d.xyzMM[2], 0.0f, 0.0f, 0.0f, d.xyzMM[3]); + R = nifti_mat33_mul(R, diag); + mat44 R44; + LOAD_MAT44(R44, R.m[0][0], R.m[0][1], R.m[0][2], d.stackOffcentre[1], + R.m[1][0], R.m[1][1], R.m[1][2], d.stackOffcentre[2], + R.m[2][0], R.m[2][1], R.m[2][2], d.stackOffcentre[3]); + vec3 x; + if (parVers > 40) //guess + x = setVec3(((float)d.xyzDim[1] - 1) / 2, ((float)d.xyzDim[2] - 1) / 2, ((float)d.xyzDim[3] - 1) / 2); + else + x = setVec3((float)d.xyzDim[1] / 2, (float)d.xyzDim[2] / 2, ((float)d.xyzDim[3] - 1) / 2); + mat44 eye; + LOAD_MAT44(eye, 1.0f, 0.0f, 0.0f, x.v[0], + 0.0f, 1.0f, 0.0f, x.v[1], + 0.0f, 0.0f, 1.0f, x.v[2]); + eye = nifti_mat44_inverse(eye); //we wish to compute R/eye, so compute invEye and calculate R*invEye + R44 = nifti_mat44_mul(R44, eye); + vec4 y; + y.v[0] = 0.0f; + y.v[1] = 0.0f; + y.v[2] = (float)d.xyzDim[3] - 1.0f; + y.v[3] = 1.0f; + y = nifti_vect44mat44_mul(y, R44); + int iOri = 2; //for axial, slices are 3rd dimenson (indexed from 0) (k) + if (d.sliceOrient == kSliceOrientSag) + iOri = 0; //for sagittal, slices are 1st dimension (i) + if (d.sliceOrient == kSliceOrientCor) + iOri = 1; //for coronal, slices are 2nd dimension (j) + if (d.xyzDim[3] > 1) { //detect and fix Philips Bug //Est: assuming "image offcentre (ap,fh,rl in mm )" is correct float stackOffcentreEst[4]; - stackOffcentreEst[1] = (d.patientPosition[1]+d.patientPositionLast[1]) * 0.5; - stackOffcentreEst[2] = (d.patientPosition[2]+d.patientPositionLast[2]) * 0.5; - stackOffcentreEst[3] = (d.patientPosition[3]+d.patientPositionLast[3]) * 0.5; + stackOffcentreEst[1] = (d.patientPosition[1] + d.patientPositionLast[1]) * 0.5; + stackOffcentreEst[2] = (d.patientPosition[2] + d.patientPositionLast[2]) * 0.5; + stackOffcentreEst[3] = (d.patientPosition[3] + d.patientPositionLast[3]) * 0.5; //compute error using 3D pythagorean theorm - stackOffcentreEst[0] = sqrt(pow(stackOffcentreEst[1]-d.stackOffcentre[1],2)+pow(stackOffcentreEst[2]-d.stackOffcentre[2],2)+pow(stackOffcentreEst[3]-d.stackOffcentre[3],2) ); + stackOffcentreEst[0] = sqrt(pow(stackOffcentreEst[1] - d.stackOffcentre[1], 2) + pow(stackOffcentreEst[2] - d.stackOffcentre[2], 2) + pow(stackOffcentreEst[3] - d.stackOffcentre[3], 2)); //Est: assuming "image offcentre (ap,fh,rl in mm )" is stored in order rl,ap,fh float stackOffcentreRev[4]; - stackOffcentreRev[1] = (d.patientPosition[2]+d.patientPositionLast[2]) * 0.5; - stackOffcentreRev[2] = (d.patientPosition[3]+d.patientPositionLast[3]) * 0.5; - stackOffcentreRev[3] = (d.patientPosition[1]+d.patientPositionLast[1]) * 0.5; + stackOffcentreRev[1] = (d.patientPosition[2] + d.patientPositionLast[2]) * 0.5; + stackOffcentreRev[2] = (d.patientPosition[3] + d.patientPositionLast[3]) * 0.5; + stackOffcentreRev[3] = (d.patientPosition[1] + d.patientPositionLast[1]) * 0.5; //compute error using 3D pythagorean theorm - stackOffcentreRev[0] = sqrt(pow(stackOffcentreRev[1]-d.stackOffcentre[1],2)+pow(stackOffcentreRev[2]-d.stackOffcentre[2],2)+pow(stackOffcentreRev[3]-d.stackOffcentre[3],2) ); + stackOffcentreRev[0] = sqrt(pow(stackOffcentreRev[1] - d.stackOffcentre[1], 2) + pow(stackOffcentreRev[2] - d.stackOffcentre[2], 2) + pow(stackOffcentreRev[3] - d.stackOffcentre[3], 2)); //detect, report and fix error if ((stackOffcentreEst[0] > 1.0) && (stackOffcentreRev[0] < stackOffcentreEst[0])) { //error detected: the ">1.0" handles the low precision of the "Off Centre" values printMessage("Order of 'image offcentre (ap,fh,rl in mm )' appears incorrect (assuming rl,ap,fh)\n"); - printMessage(" err[ap,fh,rl]= %g (%g %g %g) \n",stackOffcentreEst[0],stackOffcentreEst[1],stackOffcentreEst[2],stackOffcentreEst[3]); - printMessage(" err[rl,ap,fh]= %g (%g %g %g) \n",stackOffcentreRev[0],stackOffcentreRev[1],stackOffcentreRev[2],stackOffcentreRev[3]); - printMessage(" orient\t%d\tOffCentre 1st->mid->nth\t%g\t%g\t%g\t->\t%g\t%g\t%g\t->\t%g\t%g\t%g\t=\t%g\t%s\n",iOri, - d.patientPosition[1],d.patientPosition[2],d.patientPosition[3], + printMessage(" err[ap,fh,rl]= %g (%g %g %g) \n", stackOffcentreEst[0], stackOffcentreEst[1], stackOffcentreEst[2], stackOffcentreEst[3]); + printMessage(" err[rl,ap,fh]= %g (%g %g %g) \n", stackOffcentreRev[0], stackOffcentreRev[1], stackOffcentreRev[2], stackOffcentreRev[3]); + printMessage(" orient\t%d\tOffCentre 1st->mid->nth\t%g\t%g\t%g\t->\t%g\t%g\t%g\t->\t%g\t%g\t%g\t=\t%g\t%s\n", iOri, + d.patientPosition[1], d.patientPosition[2], d.patientPosition[3], d.stackOffcentre[1], d.stackOffcentre[2], d.stackOffcentre[3], - d.patientPositionLast[1],d.patientPositionLast[2],d.patientPositionLast[3],(d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]), parname); + d.patientPositionLast[1], d.patientPositionLast[2], d.patientPositionLast[3], (d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]), parname); //correct patientPosition for (int i = 1; i < 4; i++) stackOffcentreRev[i] = d.patientPosition[i]; @@ -2437,12 +2491,15 @@ int kbval = 33; //V3: 27 } //if 3D data bool flip = false; //assume head first supine - if ((iOri == 0) && (((d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]) > 0))) flip = true; //6/2018 : TODO, not sure if this is >= or > - if ((iOri == 1) && (((d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]) <= 0))) flip = true; //<= not <, leslie_dti_6_1.PAR - if ((iOri == 2) && (((d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]) <= 0))) flip = true; //<= not <, see leslie_dti_3_1.PAR + if ((iOri == 0) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) > 0))) + flip = true; //6/2018 : TODO, not sure if this is >= or > + if ((iOri == 1) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) <= 0))) + flip = true; //<= not <, leslie_dti_6_1.PAR + if ((iOri == 2) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) <= 0))) + flip = true; //<= not <, see leslie_dti_3_1.PAR if (flip) { - //if ((d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]) < 0) { - //if (( (y.v[iOri]-R44.m[iOri][3])>0 ) == ( (y.v[iOri]-d.stackOffcentre[iOri+1])>0 ) ) { + //if ((d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]) < 0) { + //if (( (y.v[iOri]-R44.m[iOri][3])>0 ) == ( (y.v[iOri]-d.stackOffcentre[iOri+1])>0 ) ) { d.patientPosition[1] = R44.m[0][3]; d.patientPosition[2] = R44.m[1][3]; d.patientPosition[3] = R44.m[2][3]; @@ -2450,7 +2507,7 @@ int kbval = 33; //V3: 27 d.patientPositionLast[2] = y.v[1]; d.patientPositionLast[3] = y.v[2]; //printWarning(" Flipping slice order: please verify %s\n", parname); - }else { + } else { //printWarning(" NOT Flipping slice order: please verify %s\n", parname); d.patientPosition[1] = y.v[0]; d.patientPosition[2] = y.v[1]; @@ -2459,363 +2516,312 @@ int kbval = 33; //V3: 27 d.patientPositionLast[2] = R44.m[1][3]; d.patientPositionLast[3] = R44.m[2][3]; } - //finish up - changeExt (parname, "REC"); - #ifndef _MSC_VER //Linux is case sensitive, #include - if( access( parname, F_OK ) != 0 ) changeExt (parname, "rec"); - #endif - d.locationsInAcquisition = d.xyzDim[3]; - 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 - if (maxVol > 0) { //maxVol indexed from 0 + //finish up + changeExt(parname, "REC"); +#ifndef _MSC_VER //Linux is case sensitive, #include + if (access(parname, F_OK) != 0) + changeExt(parname, "rec"); +#endif + d.locationsInAcquisition = d.xyzDim[3]; + 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 + if (maxVol > 0) { //maxVol indexed from 0 for (int i = 1; i <= maxVol; i++) { //if (dti4D->gradDynVol[i] > d.maxGradDynVol) d.maxGradDynVol = dti4D->gradDynVol[i]; //issue363 slope/intercept can vary for each 2D slice, not only between 3D volumes in a 4D time series //if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) d.isScaleOrTEVaries = true; //if (dti4D->intenScale[i] != dti4D->intenScale[0]) d.isScaleOrTEVaries = true; //if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) d.isScaleOrTEVaries = true; - if (dti4D->isPhase[i] != dti4D->isPhase[0]) d.isScaleOrTEVaries = true; - if (dti4D->isReal[i] != dti4D->isReal[0]) d.isScaleOrTEVaries = true; - if (dti4D->isImaginary[i] != dti4D->isImaginary[0]) d.isScaleOrTEVaries = true; - if (dti4D->triggerDelayTime[i] != dti4D->triggerDelayTime[0]) d.isScaleOrTEVaries = true; + if (dti4D->isPhase[i] != dti4D->isPhase[0]) + d.isScaleOrTEVaries = true; + if (dti4D->isReal[i] != dti4D->isReal[0]) + d.isScaleOrTEVaries = true; + if (dti4D->isImaginary[i] != dti4D->isImaginary[0]) + d.isScaleOrTEVaries = true; + if (dti4D->triggerDelayTime[i] != dti4D->triggerDelayTime[0]) + d.isScaleOrTEVaries = true; } //if (d.isScaleOrTEVaries) // printWarning("Varying dimensions (echoes, phase maps, intensity scaling) will require volumes to be saved separately (hint: you may prefer dicm2nii output)\n"); - } - //if (d.CSA.numDti > 1) - // for (int i = 0; i < d.CSA.numDti; i++) - // printMessage("%d\tb=\t%g\tv=\t%g\t%g\t%g\n",i,dti4D->S[i].V[0],dti4D->S[i].V[1],dti4D->S[i].V[2],dti4D->S[i].V[3]); - //check DTI makes sense - if (d.CSA.numDti > 1) { - bool v1varies = false; - bool v2varies = false; - bool v3varies = false; - for (int i = 1; i < d.CSA.numDti; i++) { - if (dti4D->S[0].V[1] != dti4D->S[i].V[1]) v1varies = true; - if (dti4D->S[0].V[2] != dti4D->S[i].V[2]) v2varies = true; - if (dti4D->S[0].V[3] != dti4D->S[i].V[3]) v3varies = true; - } - if ((!v1varies) || (!v2varies) || (!v3varies)) - printError("Bizarre b-vectors %s\n", parname); - } - if ((maxEcho > 1) || (maxCardiac > 1)) printWarning("Multiple Echo (%d) or Cardiac (%d). Carefully inspect output\n", maxEcho, maxCardiac); - if ((maxEcho > 1) || (maxCardiac > 1)) d.isScaleOrTEVaries = true; - return d; + } + //if (d.CSA.numDti > 1) + // for (int i = 0; i < d.CSA.numDti; i++) + // printMessage("%d\tb=\t%g\tv=\t%g\t%g\t%g\n",i,dti4D->S[i].V[0],dti4D->S[i].V[1],dti4D->S[i].V[2],dti4D->S[i].V[3]); + //check DTI makes sense + if (d.CSA.numDti > 1) { + bool v1varies = false; + bool v2varies = false; + bool v3varies = false; + for (int i = 1; i < d.CSA.numDti; i++) { + if (dti4D->S[0].V[1] != dti4D->S[i].V[1]) + v1varies = true; + if (dti4D->S[0].V[2] != dti4D->S[i].V[2]) + v2varies = true; + if (dti4D->S[0].V[3] != dti4D->S[i].V[3]) + v3varies = true; + } + if ((!v1varies) || (!v2varies) || (!v3varies)) + printError("Bizarre b-vectors %s\n", parname); + } + if ((maxEcho > 1) || (maxCardiac > 1)) + printWarning("Multiple Echo (%d) or Cardiac (%d). Carefully inspect output\n", maxEcho, maxCardiac); + if ((maxEcho > 1) || (maxCardiac > 1)) + d.isScaleOrTEVaries = true; + return d; } //nii_readParRec() size_t nii_SliceBytes(struct nifti_1_header hdr) { - //size of 2D slice - size_t imgsz = hdr.bitpix/8; - for (int i = 1; i < 3; i++) - if (hdr.dim[i] > 1) - imgsz = imgsz * hdr.dim[i]; - return imgsz; + //size of 2D slice + size_t imgsz = hdr.bitpix / 8; + for (int i = 1; i < 3; i++) + if (hdr.dim[i] > 1) + imgsz = imgsz * hdr.dim[i]; + return imgsz; } //nii_SliceBytes() size_t nii_ImgBytes(struct nifti_1_header hdr) { - size_t imgsz = hdr.bitpix/8; - for (int i = 1; i < 8; i++) - if (hdr.dim[i] > 1) - imgsz = imgsz * hdr.dim[i]; - return imgsz; + size_t imgsz = hdr.bitpix / 8; + for (int i = 1; i < 8; i++) + if (hdr.dim[i] > 1) + imgsz = imgsz * hdr.dim[i]; + return imgsz; } //nii_ImgBytes() //unsigned char * nii_demosaic(unsigned char* inImg, struct nifti_1_header *hdr, int nMosaicSlices, int ProtocolSliceNumber1) { -unsigned char * nii_demosaic(unsigned char* inImg, struct nifti_1_header *hdr, int nMosaicSlices, bool isUIH) { - //demosaic http://nipy.org/nibabel/dicom/dicom_mosaic.html - if (nMosaicSlices < 2) return inImg; - //Byte inImg[ [img length] ]; - //[img getBytes:&inImg length:[img length]]; - int nCol = (int) ceil(sqrt((double) nMosaicSlices)); - int nRow = nCol; - //n.b. Siemens store 20 images as 5x5 grid, UIH as 5rows, 4 Col https://github.com/rordenlab/dcm2niix/issues/225 - if (isUIH) - nRow = ceil((float)nMosaicSlices/(float)nCol); - //printf("%d = %dx%d\n", nMosaicSlices, nCol, nRow); - int colBytes = hdr->dim[1]/nCol * hdr->bitpix/8; - int lineBytes = hdr->dim[1] * hdr->bitpix/8; - int rowBytes = hdr->dim[1] * hdr->dim[2]/nRow * hdr->bitpix/8; - int col = 0; - int row = 0; - int lOutPos = 0; - hdr->dim[1] = hdr->dim[1]/nCol; - hdr->dim[2] = hdr->dim[2]/nRow; - hdr->dim[3] = nMosaicSlices; - size_t imgsz = nii_ImgBytes(*hdr); - unsigned char *outImg = (unsigned char *)malloc(imgsz); - for (int m=1; m <= nMosaicSlices; m++) { - int lPos = (row * rowBytes) + (col * colBytes); - for (int y = 0; y < hdr->dim[2]; y++) { - memcpy(&outImg[lOutPos], &inImg[lPos], colBytes); // dest, src, bytes - lPos += lineBytes; - lOutPos +=colBytes; - } - col ++; - if (col >= nCol) { - row ++; - col = 0; - } //start new column - } //for m = each mosaic slice - free(inImg); - return outImg; +unsigned char *nii_demosaic(unsigned char *inImg, struct nifti_1_header *hdr, int nMosaicSlices, bool isUIH) { + //demosaic http://nipy.org/nibabel/dicom/dicom_mosaic.html + if (nMosaicSlices < 2) + return inImg; + //Byte inImg[ [img length] ]; + //[img getBytes:&inImg length:[img length]]; + int nCol = (int)ceil(sqrt((double)nMosaicSlices)); + int nRow = nCol; + //n.b. Siemens store 20 images as 5x5 grid, UIH as 5rows, 4 Col https://github.com/rordenlab/dcm2niix/issues/225 + if (isUIH) + nRow = ceil((float)nMosaicSlices / (float)nCol); + //printf("%d = %dx%d\n", nMosaicSlices, nCol, nRow); + int colBytes = hdr->dim[1] / nCol * hdr->bitpix / 8; + int lineBytes = hdr->dim[1] * hdr->bitpix / 8; + int rowBytes = hdr->dim[1] * hdr->dim[2] / nRow * hdr->bitpix / 8; + int col = 0; + int row = 0; + int lOutPos = 0; + hdr->dim[1] = hdr->dim[1] / nCol; + hdr->dim[2] = hdr->dim[2] / nRow; + hdr->dim[3] = nMosaicSlices; + size_t imgsz = nii_ImgBytes(*hdr); + unsigned char *outImg = (unsigned char *)malloc(imgsz); + for (int m = 1; m <= nMosaicSlices; m++) { + int lPos = (row * rowBytes) + (col * colBytes); + for (int y = 0; y < hdr->dim[2]; y++) { + memcpy(&outImg[lOutPos], &inImg[lPos], colBytes); // dest, src, bytes + lPos += lineBytes; + lOutPos += colBytes; + } + col++; + if (col >= nCol) { + row++; + col = 0; + } //start new column + } //for m = each mosaic slice + free(inImg); + return outImg; } // nii_demosaic() -unsigned char * nii_flipImgY(unsigned char* bImg, struct nifti_1_header *hdr){ - //DICOM row order opposite from NIfTI - int dim3to7 = 1; - for (int i = 3; i < 8; i++) - if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; - size_t lineBytes = hdr->dim[1] * hdr->bitpix/8; - if ((hdr->datatype == DT_RGB24) && (hdr->bitpix == 24) && (hdr->intent_code == NIFTI_INTENT_NONE)) { - //we use the intent code to indicate planar vs triplet... - lineBytes = hdr->dim[1]; - dim3to7 = dim3to7 * 3; - } //rgb data saved planar (RRR..RGGGG..GBBB..B -//#ifdef _MSC_VER - unsigned char * line = (unsigned char *)malloc(sizeof(unsigned char) * (lineBytes)); -//#else -// unsigned char line[lineBytes]; -//#endif - size_t sliceBytes = hdr->dim[2] * lineBytes; - int halfY = hdr->dim[2] / 2; //note truncated toward zero, so halfY=2 regardless of 4 or 5 columns - for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice - size_t slBottom = (size_t)sl*sliceBytes; - size_t slTop = (((size_t)sl+1)*sliceBytes)-lineBytes; - for (int y = 0; y < halfY; y++) { - //swap order of lines - memcpy(line, &bImg[slBottom], lineBytes);//memcpy(&line, &bImg[slBottom], lineBytes); - memcpy(&bImg[slBottom], &bImg[slTop], lineBytes); - memcpy(&bImg[slTop], line, lineBytes);//tpx memcpy(&bImg[slTop], &line, lineBytes); - slTop -= lineBytes; - slBottom += lineBytes; - } //for y - } //for each slice -//#ifdef _MSC_VER +unsigned char *nii_flipImgY(unsigned char *bImg, struct nifti_1_header *hdr) { + //DICOM row order opposite from NIfTI + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + size_t lineBytes = hdr->dim[1] * hdr->bitpix / 8; + if ((hdr->datatype == DT_RGB24) && (hdr->bitpix == 24) && (hdr->intent_code == NIFTI_INTENT_NONE)) { + //we use the intent code to indicate planar vs triplet... + lineBytes = hdr->dim[1]; + dim3to7 = dim3to7 * 3; + } //rgb data saved planar (RRR..RGGGG..GBBB..B + unsigned char *line = (unsigned char *)malloc(sizeof(unsigned char) * (lineBytes)); + size_t sliceBytes = hdr->dim[2] * lineBytes; + int halfY = hdr->dim[2] / 2; //note truncated toward zero, so halfY=2 regardless of 4 or 5 columns + for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice + size_t slBottom = (size_t)sl * sliceBytes; + size_t slTop = (((size_t)sl + 1) * sliceBytes) - lineBytes; + for (int y = 0; y < halfY; y++) { + //swap order of lines + memcpy(line, &bImg[slBottom], lineBytes); //memcpy(&line, &bImg[slBottom], lineBytes); + memcpy(&bImg[slBottom], &bImg[slTop], lineBytes); + memcpy(&bImg[slTop], line, lineBytes); //tpx memcpy(&bImg[slTop], &line, lineBytes); + slTop -= lineBytes; + slBottom += lineBytes; + } //for y + } //for each slice free(line); -//#endif - return bImg; + return bImg; } // nii_flipImgY() -unsigned char * nii_flipImgZ(unsigned char* bImg, struct nifti_1_header *hdr){ - //DICOM row order opposite from NIfTI - int halfZ = hdr->dim[3] / 2; //note truncated toward zero, so halfY=2 regardless of 4 or 5 columns - if (halfZ < 1) return bImg; - int dim4to7 = 1; - for (int i = 4; i < 8; i++) - if (hdr->dim[i] > 1) dim4to7 = dim4to7 * hdr->dim[i]; - size_t sliceBytes = hdr->dim[1] * hdr->dim[2] * hdr->bitpix/8; - size_t volBytes = sliceBytes * hdr->dim[3]; - unsigned char * slice = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes)); - for (int vol = 0; vol < dim4to7; vol++) { //for each 2D slice - size_t slBottom = vol*volBytes; - size_t slTop = ((vol+1)*volBytes)-sliceBytes; - for (int z = 0; z < halfZ; z++) { - //swap order of lines - memcpy(slice, &bImg[slBottom], sliceBytes); //TPX memcpy(&slice, &bImg[slBottom], sliceBytes); - memcpy(&bImg[slBottom], &bImg[slTop], sliceBytes); - memcpy(&bImg[slTop], slice, sliceBytes); //TPX - slTop -= sliceBytes; - slBottom += sliceBytes; - } //for Z - } //for each volume +unsigned char *nii_flipImgZ(unsigned char *bImg, struct nifti_1_header *hdr) { + //DICOM row order opposite from NIfTI + int halfZ = hdr->dim[3] / 2; //note truncated toward zero, so halfY=2 regardless of 4 or 5 columns + if (halfZ < 1) + return bImg; + int dim4to7 = 1; + for (int i = 4; i < 8; i++) + if (hdr->dim[i] > 1) + dim4to7 = dim4to7 * hdr->dim[i]; + size_t sliceBytes = hdr->dim[1] * hdr->dim[2] * hdr->bitpix / 8; + size_t volBytes = sliceBytes * hdr->dim[3]; + unsigned char *slice = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes)); + for (int vol = 0; vol < dim4to7; vol++) { //for each 2D slice + size_t slBottom = vol * volBytes; + size_t slTop = ((vol + 1) * volBytes) - sliceBytes; + for (int z = 0; z < halfZ; z++) { + //swap order of lines + memcpy(slice, &bImg[slBottom], sliceBytes); //TPX memcpy(&slice, &bImg[slBottom], sliceBytes); + memcpy(&bImg[slBottom], &bImg[slTop], sliceBytes); + memcpy(&bImg[slTop], slice, sliceBytes); //TPX + slTop -= sliceBytes; + slBottom += sliceBytes; + } //for Z + } //for each volume free(slice); - return bImg; + return bImg; } // nii_flipImgZ() -/*unsigned char * nii_reorderSlices(unsigned char* bImg, struct nifti_1_header *h, struct TDTI4D *dti4D){ - //flip slice order - Philips scanners can save data in non-contiguous order - //if ((h->dim[3] < 2) || (h->dim[4] > 1)) return bImg; - return bImg; - if (h->dim[3] < 2) return bImg; - if (h->dim[3] >= kMaxDTI4D) { - printWarning("Unable to reorder slices (%d > %d)\n", h->dim[3], kMaxDTI4D); - return bImg; - } - printError("OBSOLETE<<< Slices not spatially contiguous: please check output [new feature]\n"); return bImg; - int dim4to7 = 1; - for (int i = 4; i < 8; i++) - if (h->dim[i] > 1) dim4to7 = dim4to7 * h->dim[i]; - int sliceBytes = h->dim[1] * h->dim[2] * h->bitpix/8; - if (sliceBytes < 0) return bImg; - size_t volBytes = sliceBytes * h->dim[3]; - unsigned char *srcImg = (unsigned char *)malloc(volBytes); - //printMessage("Reordering %d volumes\n", dim4to7); - for (int v = 0; v < dim4to7; v++) { - //for (int v = 0; v < 1; v++) { - - size_t volStart = v * volBytes; - memcpy(&srcImg[0], &bImg[volStart], volBytes); //dest, src, size - for (int z = 0; z < h->dim[3]; z++) { //for each slice - int src = dti4D->S[z].sliceNumberMrPhilips - 1; //-1 as Philips indexes slices from 1 not 0 - if ((v > 0) && (dti4D->S[0].sliceNumberMrPhilipsVol2 >= 0)) - src = dti4D->S[z].sliceNumberMrPhilipsVol2 - 1; - //printMessage("Reordering volume %d slice %d\n", v, dti4D->S[z].sliceNumberMrPhilips); - if ((src < 0) || (src >= h->dim[3])) continue; - memcpy(&bImg[volStart+(src*sliceBytes)], &srcImg[z*sliceBytes], sliceBytes); //dest, src, size - } - } - free(srcImg); - return bImg; -}// nii_reorderSlices() -*/ -unsigned char * nii_flipZ(unsigned char* bImg, struct nifti_1_header *h){ - //flip slice order - if (h->dim[3] < 2) return bImg; - mat33 s; - mat44 Q44; - LOAD_MAT33(s,h->srow_x[0],h->srow_x[1],h->srow_x[2], h->srow_y[0],h->srow_y[1],h->srow_y[2], - h->srow_z[0],h->srow_z[1],h->srow_z[2]); - LOAD_MAT44(Q44,h->srow_x[0],h->srow_x[1],h->srow_x[2],h->srow_x[3], - h->srow_y[0],h->srow_y[1],h->srow_y[2],h->srow_y[3], - h->srow_z[0],h->srow_z[1],h->srow_z[2],h->srow_z[3]); - vec4 v= setVec4(0.0f,0.0f,(float) h->dim[3]-1.0f); - v = nifti_vect44mat44_mul(v, Q44); //after flip this voxel will be the origin - mat33 mFlipZ; - LOAD_MAT33(mFlipZ,1.0f, 0.0f, 0.0f, 0.0f,1.0f,0.0f, 0.0f,0.0f,-1.0f); - s= nifti_mat33_mul( s , mFlipZ ); - LOAD_MAT44(Q44, s.m[0][0],s.m[0][1],s.m[0][2],v.v[0], - s.m[1][0],s.m[1][1],s.m[1][2],v.v[1], - s.m[2][0],s.m[2][1],s.m[2][2],v.v[2]); - //printMessage(" ----------> %f %f %f\n",v.v[0],v.v[1],v.v[2]); - setQSForm(h,Q44, true); - //printMessage("nii_flipImgY dims %dx%dx%d %d \n",h->dim[1],h->dim[2], dim3to7,h->bitpix/8); - return nii_flipImgZ(bImg,h); -}// nii_flipZ() - -unsigned char * nii_flipY(unsigned char* bImg, struct nifti_1_header *h){ - mat33 s; - mat44 Q44; - LOAD_MAT33(s,h->srow_x[0],h->srow_x[1],h->srow_x[2], h->srow_y[0],h->srow_y[1],h->srow_y[2], - h->srow_z[0],h->srow_z[1],h->srow_z[2]); - LOAD_MAT44(Q44,h->srow_x[0],h->srow_x[1],h->srow_x[2],h->srow_x[3], - h->srow_y[0],h->srow_y[1],h->srow_y[2],h->srow_y[3], - h->srow_z[0],h->srow_z[1],h->srow_z[2],h->srow_z[3]); - vec4 v= setVec4(0,(float) h->dim[2]-1,0); - v = nifti_vect44mat44_mul(v, Q44); //after flip this voxel will be the origin - mat33 mFlipY; - LOAD_MAT33(mFlipY,1.0f, 0.0f, 0.0f, 0.0f,-1.0f,0.0f, 0.0f,0.0f,1.0f); - - s= nifti_mat33_mul( s , mFlipY ); - LOAD_MAT44(Q44, s.m[0][0],s.m[0][1],s.m[0][2],v.v[0], - s.m[1][0],s.m[1][1],s.m[1][2],v.v[1], - s.m[2][0],s.m[2][1],s.m[2][2],v.v[2]); - setQSForm(h,Q44, true); - //printMessage("nii_flipImgY dims %dx%d %d \n",h->dim[1],h->dim[2], h->bitpix/8); - return nii_flipImgY(bImg,h); -}// nii_flipY() - -/*void conv12bit16bit(unsigned char * img, struct nifti_1_header hdr) { -//convert 12-bit allocated data to 16-bit -// works for MR-MONO2-12-angio-an1 from http://www.barre.nom.fr/medical/samples/ -// looks wrong: this sample toggles between big and little endian stores +unsigned char *nii_flipZ(unsigned char *bImg, struct nifti_1_header *h) { + //flip slice order + if (h->dim[3] < 2) + return bImg; + mat33 s; + mat44 Q44; + LOAD_MAT33(s, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_y[0], h->srow_y[1], h->srow_y[2], + h->srow_z[0], h->srow_z[1], h->srow_z[2]); + LOAD_MAT44(Q44, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_x[3], + h->srow_y[0], h->srow_y[1], h->srow_y[2], h->srow_y[3], + h->srow_z[0], h->srow_z[1], h->srow_z[2], h->srow_z[3]); + vec4 v = setVec4(0.0f, 0.0f, (float)h->dim[3] - 1.0f); + v = nifti_vect44mat44_mul(v, Q44); //after flip this voxel will be the origin + mat33 mFlipZ; + LOAD_MAT33(mFlipZ, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f); + s = nifti_mat33_mul(s, mFlipZ); + LOAD_MAT44(Q44, s.m[0][0], s.m[0][1], s.m[0][2], v.v[0], + s.m[1][0], s.m[1][1], s.m[1][2], v.v[1], + s.m[2][0], s.m[2][1], s.m[2][2], v.v[2]); + //printMessage(" ----------> %f %f %f\n",v.v[0],v.v[1],v.v[2]); + setQSForm(h, Q44, true); + //printMessage("nii_flipImgY dims %dx%dx%d %d \n",h->dim[1],h->dim[2], dim3to7,h->bitpix/8); + return nii_flipImgZ(bImg, h); +} // nii_flipZ() + +unsigned char *nii_flipY(unsigned char *bImg, struct nifti_1_header *h) { + mat33 s; + mat44 Q44; + LOAD_MAT33(s, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_y[0], h->srow_y[1], h->srow_y[2], + h->srow_z[0], h->srow_z[1], h->srow_z[2]); + LOAD_MAT44(Q44, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_x[3], + h->srow_y[0], h->srow_y[1], h->srow_y[2], h->srow_y[3], + h->srow_z[0], h->srow_z[1], h->srow_z[2], h->srow_z[3]); + vec4 v = setVec4(0, (float)h->dim[2] - 1, 0); + v = nifti_vect44mat44_mul(v, Q44); //after flip this voxel will be the origin + mat33 mFlipY; + LOAD_MAT33(mFlipY, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f); + s = nifti_mat33_mul(s, mFlipY); + LOAD_MAT44(Q44, s.m[0][0], s.m[0][1], s.m[0][2], v.v[0], + s.m[1][0], s.m[1][1], s.m[1][2], v.v[1], + s.m[2][0], s.m[2][1], s.m[2][2], v.v[2]); + setQSForm(h, Q44, true); + //printMessage("nii_flipImgY dims %dx%d %d \n",h->dim[1],h->dim[2], h->bitpix/8); + return nii_flipImgY(bImg, h); +} // nii_flipY() + +void conv12bit16bit(unsigned char *img, struct nifti_1_header hdr) { + //convert 12-bit allocated data to 16-bit + // works for MR-MONO2-12-angio-an1 from http://www.barre.nom.fr/medical/samples/ + // looks wrong: this sample toggles between big and little endian stores printWarning("Support for images that allocate 12 bits is experimental\n"); - int nVox = nii_ImgBytes(hdr) / (hdr.bitpix/8); - for (int i=(nVox-1); i >= 0; i--) { - int i16 = i * 2; - int i12 = floor(i * 1.5); - uint16_t val; - if ((i % 2) != 1) { - val = (img[i12+0] << 4) + (img[i12+1] >> 4); - } else { - val = ((img[i12+0] & 0x0F) << 8) + img[i12+1]; - } - - //if ((i % 2) != 1) { - // val = img[i12+0] + ((img[i12+1] & 0xF0) << 4); - //} else { - // val = (img[i12+0] & 0x0F) + (img[i12+1] << 4); - //} - val = val & 0xFFF; - img[i16+0] = val & 0xFF; - img[i16+1] = (val >> 8) & 0xFF; - } -} //conv12bit16bit()*/ - -void conv12bit16bit(unsigned char * img, struct nifti_1_header hdr) { -//convert 12-bit allocated data to 16-bit -// works for MR-MONO2-12-angio-an1 from http://www.barre.nom.fr/medical/samples/ -// looks wrong: this sample toggles between big and little endian stores - printWarning("Support for images that allocate 12 bits is experimental\n"); - int nVox = (int) nii_ImgBytes(hdr) / (hdr.bitpix/8); - for (int i=(nVox-1); i >= 0; i--) { - int i16 = i * 2; - int i12 = floor(i * 1.5); - uint16_t val; - if ((i % 2) != 1) { - val = img[i12+1] + (img[i12+0] << 8); - val = val >> 4; - } else { - val = img[i12+0] + (img[i12+1] << 8); - } - img[i16+0] = val & 0xFF; - img[i16+1] = (val >> 8) & 0xFF; - } + int nVox = (int)nii_ImgBytes(hdr) / (hdr.bitpix / 8); + for (int i = (nVox - 1); i >= 0; i--) { + int i16 = i * 2; + int i12 = floor(i * 1.5); + uint16_t val; + if ((i % 2) != 1) { + val = img[i12 + 1] + (img[i12 + 0] << 8); + val = val >> 4; + } else { + val = img[i12 + 0] + (img[i12 + 1] << 8); + } + img[i16 + 0] = val & 0xFF; + img[i16 + 1] = (val >> 8) & 0xFF; + } } //conv12bit16bit() -unsigned char * nii_loadImgCore(char* imgname, struct nifti_1_header hdr, int bitsAllocated, int imageStart32) { - size_t imgsz = nii_ImgBytes(hdr); - size_t imgszRead = imgsz; +unsigned char *nii_loadImgCore(char *imgname, struct nifti_1_header hdr, int bitsAllocated, int imageStart32) { + size_t imgsz = nii_ImgBytes(hdr); + size_t imgszRead = imgsz; size_t imageStart = imageStart32; - if (bitsAllocated == 12) - imgszRead = round(imgsz * 0.75); - FILE *file = fopen(imgname , "rb"); + if (bitsAllocated == 12) + imgszRead = round(imgsz * 0.75); + FILE *file = fopen(imgname, "rb"); if (!file) { - printError("Unable to open '%s'\n", imgname); - return NULL; - } + printError("Unable to open '%s'\n", imgname); + return NULL; + } fseek(file, 0, SEEK_END); - long fileLen=ftell(file); - if (fileLen < (imgszRead + imageStart)) { - //note hdr.vox_offset is a float: issue507 - //https://www.nitrc.org/forum/message.php?msg_id=27155 - printMessage("FileSize < (ImageSize+HeaderSize): %ld < (%zu+%zu) \n", fileLen, imgszRead, imageStart); - printWarning("File not large enough to store image data: %s\n", imgname); - return NULL; - } - fseek(file, (long) imageStart, SEEK_SET); - unsigned char *bImg = (unsigned char *)malloc(imgsz); - //int i = 0; - //while (bImg[i] == 0) i++; - //printMessage("%d %d<\n",i,bImg[i]); - size_t sz = fread(bImg, 1, imgszRead, file); + long fileLen = ftell(file); + if (fileLen < (imgszRead + imageStart)) { + //note hdr.vox_offset is a float: issue507 + //https://www.nitrc.org/forum/message.php?msg_id=27155 + printMessage("FileSize < (ImageSize+HeaderSize): %ld < (%zu+%zu) \n", fileLen, imgszRead, imageStart); + printWarning("File not large enough to store image data: %s\n", imgname); + return NULL; + } + fseek(file, (long)imageStart, SEEK_SET); + unsigned char *bImg = (unsigned char *)malloc(imgsz); + //int i = 0; + //while (bImg[i] == 0) i++; + //printMessage("%d %d<\n",i,bImg[i]); + size_t sz = fread(bImg, 1, imgszRead, file); fclose(file); if (sz < imgszRead) { - printError("Only loaded %zu of %zu bytes for %s\n", sz, imgszRead, imgname); - return NULL; - } + printError("Only loaded %zu of %zu bytes for %s\n", sz, imgszRead, imgname); + return NULL; + } if (bitsAllocated == 12) - conv12bit16bit(bImg, hdr); - return bImg; + conv12bit16bit(bImg, hdr); + return bImg; } //nii_loadImgCore() -unsigned char * nii_planar2rgb(unsigned char* bImg, struct nifti_1_header *hdr, int isPlanar) { +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 - if (bImg == NULL) return NULL; - if (hdr->datatype != DT_RGB24) return bImg; - if (isPlanar == 0) return bImg; + if (bImg == NULL) + return NULL; + if (hdr->datatype != DT_RGB24) + return bImg; + if (isPlanar == 0) + return bImg; int dim3to7 = 1; for (int i = 3; i < 8; i++) - if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; - int sliceBytes8 = hdr->dim[1]*hdr->dim[2]; + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int sliceBytes8 = hdr->dim[1] * hdr->dim[2]; int sliceBytes24 = sliceBytes8 * 3; - unsigned char * slice24 = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes24)); + unsigned char *slice24 = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes24)); int sliceOffsetRGB = 0; int sliceOffsetR = 0; int sliceOffsetG = sliceOffsetR + sliceBytes8; - int sliceOffsetB = sliceOffsetR + 2*sliceBytes8; + int sliceOffsetB = sliceOffsetR + 2 * sliceBytes8; //printMessage("planar->rgb %dx%dx%d\n", hdr->dim[1],hdr->dim[2], dim3to7); - int i = 0; + int i = 0; for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice memcpy(slice24, &bImg[sliceOffsetRGB], sliceBytes24); for (int rgb = 0; rgb < sliceBytes8; rgb++) { - bImg[i++] =slice24[sliceOffsetR+rgb]; - bImg[i++] =slice24[sliceOffsetG+rgb]; - bImg[i++] =slice24[sliceOffsetB+rgb]; + bImg[i++] = slice24[sliceOffsetR + rgb]; + bImg[i++] = slice24[sliceOffsetG + rgb]; + bImg[i++] = slice24[sliceOffsetB + rgb]; } sliceOffsetRGB += sliceBytes24; } //for each slice @@ -2823,129 +2829,111 @@ unsigned char * nii_planar2rgb(unsigned char* bImg, struct nifti_1_header *hdr, return bImg; } //nii_planar2rgb() -unsigned char * nii_rgb2planar(unsigned char* bImg, struct nifti_1_header *hdr, int isPlanar) { - //DICOM data saved in triples RGBRGBRGB, Analyze RGB saved in planes RRR..RGGG..GBBBB..B - if (bImg == NULL) return NULL; - if (hdr->datatype != DT_RGB24) return bImg; - if (isPlanar == 1) return bImg;//return nii_bgr2rgb(bImg,hdr); - int dim3to7 = 1; - for (int i = 3; i < 8; i++) - if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; - int sliceBytes8 = hdr->dim[1]*hdr->dim[2]; - int sliceBytes24 = sliceBytes8 * 3; - unsigned char * slice24 = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes24)); - //printMessage("rgb->planar %dx%dx%d\n", hdr->dim[1],hdr->dim[2], dim3to7); - int sliceOffsetR = 0; - for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice - memcpy(slice24, &bImg[sliceOffsetR], sliceBytes24); //TPX memcpy(&slice24, &bImg[sliceOffsetR], sliceBytes24); - int sliceOffsetG = sliceOffsetR + sliceBytes8; - int sliceOffsetB = sliceOffsetR + 2*sliceBytes8; - int i = 0; - int j = 0; - for (int rgb = 0; rgb < sliceBytes8; rgb++) { - bImg[sliceOffsetR+j] =slice24[i++]; - bImg[sliceOffsetG+j] =slice24[i++]; - bImg[sliceOffsetB+j] =slice24[i++]; - j++; - } - sliceOffsetR += sliceBytes24; - } //for each slice - free(slice24); - return bImg; -} //nii_rgb2Planar() - -unsigned char * nii_iVaries(unsigned char *img, struct nifti_1_header *hdr, struct TDTI4D *dti4D){ - //each DICOM image can have its own intesity scaling, whereas NIfTI requires the same scaling for all images in a file - //WARNING: do this BEFORE nii_check16bitUnsigned!!!! - //if (hdr->datatype != DT_INT16) return img; - int dim3to7 = 1; - for (int i = 3; i < 8; i++) - if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; - int nVox = hdr->dim[1]*hdr->dim[2]* dim3to7; - if (nVox < 1) return img; - float * img32=(float*)malloc(nVox*sizeof(float)); - if (hdr->datatype == DT_UINT8) { - uint8_t * img8i = (uint8_t*) img; - for (int i=0; i < nVox; i++) - img32[i] = img8i[i]; - } else if (hdr->datatype == DT_UINT16) { - uint16_t * img16ui = (uint16_t*) img; - for (int i=0; i < nVox; i++) - img32[i] = img16ui[i]; - } else if (hdr->datatype == DT_INT16) { - int16_t * img16i = (int16_t*) img; - for (int i=0; i < nVox; i++) - img32[i] = img16i[i]; - } else if (hdr->datatype == DT_INT32) { - int32_t * img32i = (int32_t*) img; - for (int i=0; i < nVox; i++) - img32[i] = (float) img32i[i]; - } - free (img); //release previous image - if ((dti4D != NULL) && (dti4D->intenScale[0] != 0.0)) { //enhanced dataset, intensity varies across slices of a single file - if (dti4D->RWVScale[0] != 0.0) printWarning("Intensity scale/slope using 0028,1053 and 0028,1052"); //to do: real-world values and precise values - int dim1to2 = hdr->dim[1]*hdr->dim[2]; - int slice = -1; - //(0028,1052) SS = scale slope (2005,100E) RealWorldIntercept = (0040,9224) Real World Slope = (0040,9225) - //printf("vol\tRS(0028,1053)\tRI(0028,1052)\tSS(2005,100E)\trwS(0040,9225)\trwI(0040,9224)\n"); - for (int i=0; i < nVox; i++) { //issue 363 - if ((i % dim1to2) == 0) { - slice++; - //printf("%d\t%g\t%g\t%g\t%g\t%g\n", slice, dti4D->intenScale[slice], dti4D->intenIntercept[slice],dti4D->intenScalePhilips[slice], dti4D->RWVScale[slice], dti4D->RWVIntercept[slice]); - } - img32[i] = (img32[i]* dti4D->intenScale[slice])+dti4D->intenIntercept[slice]; - } - } else { // - for (int i=0; i < nVox; i++) - img32[i] = (img32[i]* hdr->scl_slope)+hdr->scl_inter; - } - hdr->scl_slope = 1; - hdr->scl_inter = 0; - hdr->datatype = DT_FLOAT; - hdr->bitpix = 32; - return (unsigned char*) img32; -} //nii_iVaries() - -/*unsigned char * nii_reorderSlicesX(unsigned char* bImg, struct nifti_1_header *hdr, struct TDTI4D *dti4D) { -//Philips can save slices in any random order... rearrange all of them +unsigned char *nii_rgb2planar(unsigned char *bImg, struct nifti_1_header *hdr, int isPlanar) { + //DICOM data saved in triples RGBRGBRGB, Analyze RGB saved in planes RRR..RGGG..GBBBB..B + if (bImg == NULL) + return NULL; + if (hdr->datatype != DT_RGB24) + return bImg; + if (isPlanar == 1) + return bImg; //return nii_bgr2rgb(bImg,hdr); int dim3to7 = 1; - for (int i = 3; i < 8; i++) - if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; - if (dim3to7 < 2) return bImg; - uint64_t sliceBytes = hdr->dim[1]*hdr->dim[2]*hdr->bitpix/8; - unsigned char *sliceImg = (unsigned char *)malloc( sliceBytes); - uint32_t *idx = (uint32_t *)malloc( dim3to7 * sizeof(uint32_t)); - for (int i = 0; i < dim3to7; i++) //for each volume - idx[i] = i; - for (int i = 0; i < dim3to7; i++) { //for each volume - int fromSlice = idx[dti4D->sliceOrder[i]]; - //if (i < 10) printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", i, toSlice); - if (i != fromSlice) { - uint64_t inPos = fromSlice * sliceBytes; - uint64_t outPos = i * sliceBytes; - memcpy( &sliceImg[0], &bImg[outPos], sliceBytes); //dest, src -> copy slice about to be overwritten - memcpy( &bImg[outPos], &bImg[inPos], sliceBytes); //dest, src - memcpy( &bImg[inPos], &sliceImg[0], sliceBytes); // - idx[i] = fromSlice; - } - } - free(idx); - free(sliceImg); - return bImg; -}*/ - -unsigned char * nii_reorderSlicesX(unsigned char* bImg, struct nifti_1_header *hdr, struct TDTI4D *dti4D) { -//Philips can save slices in any random order... rearrange all of them + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int sliceBytes8 = hdr->dim[1] * hdr->dim[2]; + int sliceBytes24 = sliceBytes8 * 3; + unsigned char *slice24 = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes24)); + //printMessage("rgb->planar %dx%dx%d\n", hdr->dim[1],hdr->dim[2], dim3to7); + int sliceOffsetR = 0; + for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice + memcpy(slice24, &bImg[sliceOffsetR], sliceBytes24); //TPX memcpy(&slice24, &bImg[sliceOffsetR], sliceBytes24); + int sliceOffsetG = sliceOffsetR + sliceBytes8; + int sliceOffsetB = sliceOffsetR + 2 * sliceBytes8; + int i = 0; + int j = 0; + for (int rgb = 0; rgb < sliceBytes8; rgb++) { + bImg[sliceOffsetR + j] = slice24[i++]; + bImg[sliceOffsetG + j] = slice24[i++]; + bImg[sliceOffsetB + j] = slice24[i++]; + j++; + } + sliceOffsetR += sliceBytes24; + } //for each slice + free(slice24); + return bImg; +} //nii_rgb2Planar() + +unsigned char *nii_iVaries(unsigned char *img, struct nifti_1_header *hdr, struct TDTI4D *dti4D) { + //each DICOM image can have its own intesity scaling, whereas NIfTI requires the same scaling for all images in a file + //WARNING: do this BEFORE nii_check16bitUnsigned!!!! + //if (hdr->datatype != DT_INT16) return img; + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; + if (nVox < 1) + return img; + float *img32 = (float *)malloc(nVox * sizeof(float)); + if (hdr->datatype == DT_UINT8) { + uint8_t *img8i = (uint8_t *)img; + for (int i = 0; i < nVox; i++) + img32[i] = img8i[i]; + } else if (hdr->datatype == DT_UINT16) { + uint16_t *img16ui = (uint16_t *)img; + for (int i = 0; i < nVox; i++) + img32[i] = img16ui[i]; + } else if (hdr->datatype == DT_INT16) { + int16_t *img16i = (int16_t *)img; + for (int i = 0; i < nVox; i++) + img32[i] = img16i[i]; + } else if (hdr->datatype == DT_INT32) { + int32_t *img32i = (int32_t *)img; + for (int i = 0; i < nVox; i++) + img32[i] = (float)img32i[i]; + } + free(img); //release previous image + if ((dti4D != NULL) && (dti4D->intenScale[0] != 0.0)) { //enhanced dataset, intensity varies across slices of a single file + if (dti4D->RWVScale[0] != 0.0) + printWarning("Intensity scale/slope using 0028,1053 and 0028,1052"); //to do: real-world values and precise values + int dim1to2 = hdr->dim[1] * hdr->dim[2]; + int slice = -1; + //(0028,1052) SS = scale slope (2005,100E) RealWorldIntercept = (0040,9224) Real World Slope = (0040,9225) + //printf("vol\tRS(0028,1053)\tRI(0028,1052)\tSS(2005,100E)\trwS(0040,9225)\trwI(0040,9224)\n"); + for (int i = 0; i < nVox; i++) { //issue 363 + if ((i % dim1to2) == 0) { + slice++; + //printf("%d\t%g\t%g\t%g\t%g\t%g\n", slice, dti4D->intenScale[slice], dti4D->intenIntercept[slice],dti4D->intenScalePhilips[slice], dti4D->RWVScale[slice], dti4D->RWVIntercept[slice]); + } + img32[i] = (img32[i] * dti4D->intenScale[slice]) + dti4D->intenIntercept[slice]; + } + } else { // + for (int i = 0; i < nVox; i++) + img32[i] = (img32[i] * hdr->scl_slope) + hdr->scl_inter; + } + hdr->scl_slope = 1; + hdr->scl_inter = 0; + hdr->datatype = DT_FLOAT; + hdr->bitpix = 32; + return (unsigned char *)img32; +} //nii_iVaries() + +unsigned char *nii_reorderSlicesX(unsigned char *bImg, struct nifti_1_header *hdr, struct TDTI4D *dti4D) { + //Philips can save slices in any random order... rearrange all of them int dim3to7 = 1; - for (int i = 3; i < 8; i++) - if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; - if (dim3to7 < 2) return bImg; - if (dim3to7 > kMaxSlice2D) return bImg; - uint64_t imgSz = nii_ImgBytes(*hdr); - uint64_t sliceBytes = hdr->dim[1]*hdr->dim[2]*hdr->bitpix/8; - unsigned char *outImg = (unsigned char *)malloc( imgSz); - memcpy( &outImg[0],&bImg[0], imgSz); - for (int i = 0; i < dim3to7; i++) { //for each volume + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + if (dim3to7 < 2) + return bImg; + if (dim3to7 > kMaxSlice2D) + return bImg; + uint64_t imgSz = nii_ImgBytes(*hdr); + uint64_t sliceBytes = hdr->dim[1] * hdr->dim[2] * hdr->bitpix / 8; + unsigned char *outImg = (unsigned char *)malloc(imgSz); + memcpy(&outImg[0], &bImg[0], imgSz); + for (int i = 0; i < dim3to7; i++) { //for each volume int fromSlice = dti4D->sliceOrder[i]; //if (i < 10) printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", i, toSlice); //printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", fromSlice, i); @@ -2954,325 +2942,266 @@ unsigned char * nii_reorderSlicesX(unsigned char* bImg, struct nifti_1_header *h } else if (i != fromSlice) { uint64_t inPos = fromSlice * sliceBytes; uint64_t outPos = i * sliceBytes; - memcpy( &bImg[outPos], &outImg[inPos], sliceBytes); - } - } - free(outImg); - return bImg; + memcpy(&bImg[outPos], &outImg[inPos], sliceBytes); + } + } + free(outImg); + return bImg; } -/*unsigned char * nii_reorderSlicesX(unsigned char* bImg, struct nifti_1_header *hdr, struct TDTI4D *dti4D) { -//Philips can save slices in any random order... rearrange all of them - //if (sliceOrder == NULL) return bImg; - int dim3to7 = 1; - for (int i = 3; i < 8; i++) - if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; - if (dim3to7 < 2) return bImg; - //printMessage(" NOT reordering %d Philips slices.\n", dim3to7); return bImg; - uint64_t sliceBytes = hdr->dim[1]*hdr->dim[2]*hdr->bitpix/8; - unsigned char *sliceImg = (unsigned char *)malloc( sliceBytes); - //for (int i = 0; i < dim3to7; i++) { //for each volume - uint64_t imgSz = nii_ImgBytes(*hdr); - //this uses a lot of RAM, someday this could be done in place... - unsigned char *outImg = (unsigned char *)malloc( imgSz); - memcpy( &outImg[0],&bImg[0], imgSz); - - for (int i = 0; i < dim3to7; i++) { //for each volume - int toSlice = dti4D->sliceOrder[i]; - //if (i < 10) printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", i, toSlice); - if (i != toSlice) { - uint64_t inPos = i * sliceBytes; - uint64_t outPos = toSlice * sliceBytes; - memcpy( &bImg[outPos], &outImg[inPos], sliceBytes); - } - } - free(sliceImg); - free(outImg); - return bImg; -}*/ - -/*unsigned char * nii_XYTZ_XYZT(unsigned char* bImg, struct nifti_1_header *hdr, int seqRepeats) { - //Philips can save time as 3rd dimensions, NIFTI requires time is 4th dimension - int dim4to7 = 1; - for (int i = 4; i < 8; i++) - if (hdr->dim[i] > 1) dim4to7 = dim4to7 * hdr->dim[i]; - if ((hdr->dim[3] < 2) || (dim4to7 < 2)) return bImg; - printMessage("Converting XYTZ to XYZT with %d slices (Z) and %d volumes (T).\n",hdr->dim[3], dim4to7); - if ((dim4to7 % seqRepeats) != 0) { - printError("Patient position repeats %d times, but this does not evenly divide number of volumes (%d)\n", seqRepeats,dim4to7); - seqRepeats = 1; - } - uint64_t typeRepeats = dim4to7 / seqRepeats; - uint64_t sliceBytes = hdr->dim[1]*hdr->dim[2]*hdr->bitpix/8; - uint64_t seqBytes = sliceBytes * seqRepeats; - uint64_t typeBytes = seqBytes * hdr->dim[3]; - uint64_t imgSz = nii_ImgBytes(*hdr); - //this uses a lot of RAM, someday this could be done in place... - unsigned char *outImg = (unsigned char *)malloc( imgSz); - //memcpy(&tempImg[0], &bImg[0], imgSz); - uint64_t origPos = 0; - uint64_t Pos = 0; // - for (int t = 0; t < (int)typeRepeats; t++) { //for each volume - for (int s = 0; s < seqRepeats; s++) { - origPos = (t*typeBytes) +s*sliceBytes; - for (int z = 0; z < hdr->dim[3]; z++) { //for each slice - memcpy( &outImg[Pos],&bImg[origPos], sliceBytes); - Pos += sliceBytes; - origPos += seqBytes; - } - }//for s - } - free(bImg); - return outImg; -} //nii_XYTZ_XYZT() -*/ - -unsigned char * nii_byteswap(unsigned char *img, struct nifti_1_header *hdr){ - if (hdr->bitpix < 9) return img; - uint64_t nvox = nii_ImgBytes(*hdr) / (hdr->bitpix/8); - void *ar = (void*) img; - if (hdr->bitpix == 16) nifti_swap_2bytes( nvox , ar ); - if (hdr->bitpix == 32) nifti_swap_4bytes( nvox , ar ); - if (hdr->bitpix == 64) nifti_swap_8bytes( nvox , ar ); - return img; +unsigned char *nii_byteswap(unsigned char *img, struct nifti_1_header *hdr) { + if (hdr->bitpix < 9) + return img; + uint64_t nvox = nii_ImgBytes(*hdr) / (hdr->bitpix / 8); + void *ar = (void *)img; + if (hdr->bitpix == 16) + nifti_swap_2bytes(nvox, ar); + if (hdr->bitpix == 32) + nifti_swap_4bytes(nvox, ar); + if (hdr->bitpix == 64) + nifti_swap_8bytes(nvox, ar); + return img; } //nii_byteswap() #ifdef myEnableJasper -unsigned char * nii_loadImgCoreJasper(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) { - if (jas_init()) { - return NULL; - } - jas_stream_t *in; - jas_image_t *image; - jas_setdbglevel(0); - if (!(in = jas_stream_fopen(imgname, "rb"))) { - printError( "Cannot open input image file %s\n", imgname); - return NULL; - } - //int isSeekable = jas_stream_isseekable(in); - jas_stream_seek(in, dcm.imageStart, 0); - int infmt = jas_image_getfmt(in); - if (infmt < 0) { - printError( "Input image has unknown format %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes); - return NULL; - } - char opt[] = "\0"; - char *inopts = opt; - if (!(image = jas_image_decode(in, infmt, inopts))) { - printError("Cannot decode image data %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes); - return NULL; - } - int numcmpts; - int cmpts[4]; - switch (jas_clrspc_fam(jas_image_clrspc(image))) { - case JAS_CLRSPC_FAM_RGB: - if (jas_image_clrspc(image) != JAS_CLRSPC_SRGB) - printWarning("Inaccurate color\n"); - numcmpts = 3; - if ((cmpts[0] = jas_image_getcmptbytype(image, - JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R))) < 0 || - (cmpts[1] = jas_image_getcmptbytype(image, - JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G))) < 0 || - (cmpts[2] = jas_image_getcmptbytype(image, - JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B))) < 0) { - printError("Missing color component\n"); - return NULL; - } - break; - case JAS_CLRSPC_FAM_GRAY: - if (jas_image_clrspc(image) != JAS_CLRSPC_SGRAY) - printWarning("Inaccurate color\n"); - numcmpts = 1; - if ((cmpts[0] = jas_image_getcmptbytype(image, - JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_GRAY_Y))) < 0) { - printError("Missing color component\n"); - return NULL; - } - break; - default: - printError("Unsupported color space\n"); - return NULL; - break; - } - int width = jas_image_cmptwidth(image, cmpts[0]); - int height = jas_image_cmptheight(image, cmpts[0]); - int prec = jas_image_cmptprec(image, cmpts[0]); - int sgnd = jas_image_cmptsgnd(image, cmpts[0]); - #ifdef MY_DEBUG - printMessage("offset %d w*h %d*%d bpp %d sgnd %d components %d '%s' Jasper=%s\n",dcm.imageStart, width, height, prec, sgnd, numcmpts, imgname, jas_getversion()); - #endif - for (int cmptno = 0; cmptno < numcmpts; ++cmptno) { - if (jas_image_cmptwidth(image, cmpts[cmptno]) != width || - jas_image_cmptheight(image, cmpts[cmptno]) != height || - jas_image_cmptprec(image, cmpts[cmptno]) != prec || - jas_image_cmptsgnd(image, cmpts[cmptno]) != sgnd || - jas_image_cmpthstep(image, cmpts[cmptno]) != jas_image_cmpthstep(image, 0) || - jas_image_cmptvstep(image, cmpts[cmptno]) != jas_image_cmptvstep(image, 0) || - jas_image_cmpttlx(image, cmpts[cmptno]) != jas_image_cmpttlx(image, 0) || - jas_image_cmpttly(image, cmpts[cmptno]) != jas_image_cmpttly(image, 0)) { - printMessage("The NIfTI format cannot be used to represent an image with this geometry.\n"); - return NULL; - } - } - //extract the data - int bpp = (prec + 7) >> 3; //e.g. 12 bits requires 2 bytes - int imgbytes = bpp * width * height * numcmpts; - if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) { - printError("Catastrophic decompression error\n"); - return NULL; - } - jas_seqent_t v; - unsigned char *img = (unsigned char *)malloc(imgbytes); - uint16_t * img16ui = (uint16_t*) img; //unsigned 16-bit - int16_t * img16i = (int16_t*) img; //signed 16-bit - if (sgnd) bpp = -bpp; - if (bpp == -1) { - printError("Signed 8-bit DICOM?\n"); - return NULL; - } - jas_matrix_t *data; - jas_seqent_t *d; - data = 0; - int cmptno, y, x; - int pix = 0; - for (cmptno = 0; cmptno < numcmpts; ++cmptno) { - if (!(data = jas_matrix_create(1, width))) { - free(img); - return NULL; - } - } - //n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB - for (cmptno = 0; cmptno < numcmpts; ++cmptno) { - for (y = 0; y < height; ++y) { - if (jas_image_readcmpt(image, cmpts[cmptno], 0, y, width, 1, data)) { - free(img); - return NULL; - } - d = jas_matrix_getref(data, 0, 0); - for (x = 0; x < width; ++x) { - v = *d; - switch (bpp) { - case 1: - img[pix] = v; - break; - case 2: - img16ui[pix] = v; - break; - case -2: - img16i[pix] = v; - break; - } - pix ++; - ++d; - }//for x - } //for y - } //for each component - jas_matrix_destroy(data); - jas_image_destroy(image); - jas_image_clearfmts(); - return img; +unsigned char *nii_loadImgCoreJasper(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) { + if (jas_init()) { + return NULL; + } + jas_stream_t *in; + jas_image_t *image; + jas_setdbglevel(0); + if (!(in = jas_stream_fopen(imgname, "rb"))) { + printError("Cannot open input image file %s\n", imgname); + return NULL; + } + //int isSeekable = jas_stream_isseekable(in); + jas_stream_seek(in, dcm.imageStart, 0); + int infmt = jas_image_getfmt(in); + if (infmt < 0) { + printError("Input image has unknown format %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes); + return NULL; + } + char opt[] = "\0"; + char *inopts = opt; + if (!(image = jas_image_decode(in, infmt, inopts))) { + printError("Cannot decode image data %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes); + return NULL; + } + int numcmpts; + int cmpts[4]; + switch (jas_clrspc_fam(jas_image_clrspc(image))) { + case JAS_CLRSPC_FAM_RGB: + if (jas_image_clrspc(image) != JAS_CLRSPC_SRGB) + printWarning("Inaccurate color\n"); + numcmpts = 3; + if ((cmpts[0] = jas_image_getcmptbytype(image, + JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R))) < 0 || + (cmpts[1] = jas_image_getcmptbytype(image, + JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G))) < 0 || + (cmpts[2] = jas_image_getcmptbytype(image, + JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B))) < 0) { + printError("Missing color component\n"); + return NULL; + } + break; + case JAS_CLRSPC_FAM_GRAY: + if (jas_image_clrspc(image) != JAS_CLRSPC_SGRAY) + printWarning("Inaccurate color\n"); + numcmpts = 1; + if ((cmpts[0] = jas_image_getcmptbytype(image, + JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_GRAY_Y))) < 0) { + printError("Missing color component\n"); + return NULL; + } + break; + default: + printError("Unsupported color space\n"); + return NULL; + break; + } + int width = jas_image_cmptwidth(image, cmpts[0]); + int height = jas_image_cmptheight(image, cmpts[0]); + int prec = jas_image_cmptprec(image, cmpts[0]); + int sgnd = jas_image_cmptsgnd(image, cmpts[0]); +#ifdef MY_DEBUG + printMessage("offset %d w*h %d*%d bpp %d sgnd %d components %d '%s' Jasper=%s\n", dcm.imageStart, width, height, prec, sgnd, numcmpts, imgname, jas_getversion()); +#endif + for (int cmptno = 0; cmptno < numcmpts; ++cmptno) { + if (jas_image_cmptwidth(image, cmpts[cmptno]) != width || + jas_image_cmptheight(image, cmpts[cmptno]) != height || + jas_image_cmptprec(image, cmpts[cmptno]) != prec || + jas_image_cmptsgnd(image, cmpts[cmptno]) != sgnd || + jas_image_cmpthstep(image, cmpts[cmptno]) != jas_image_cmpthstep(image, 0) || + jas_image_cmptvstep(image, cmpts[cmptno]) != jas_image_cmptvstep(image, 0) || + jas_image_cmpttlx(image, cmpts[cmptno]) != jas_image_cmpttlx(image, 0) || + jas_image_cmpttly(image, cmpts[cmptno]) != jas_image_cmpttly(image, 0)) { + printMessage("The NIfTI format cannot be used to represent an image with this geometry.\n"); + return NULL; + } + } + //extract the data + int bpp = (prec + 7) >> 3; //e.g. 12 bits requires 2 bytes + int imgbytes = bpp * width * height * numcmpts; + if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) { + printError("Catastrophic decompression error\n"); + return NULL; + } + jas_seqent_t v; + unsigned char *img = (unsigned char *)malloc(imgbytes); + uint16_t *img16ui = (uint16_t *)img; //unsigned 16-bit + int16_t *img16i = (int16_t *)img; //signed 16-bit + if (sgnd) + bpp = -bpp; + if (bpp == -1) { + printError("Signed 8-bit DICOM?\n"); + return NULL; + } + jas_matrix_t *data; + jas_seqent_t *d; + data = 0; + int cmptno, y, x; + int pix = 0; + for (cmptno = 0; cmptno < numcmpts; ++cmptno) { + if (!(data = jas_matrix_create(1, width))) { + free(img); + return NULL; + } + } + //n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB + for (cmptno = 0; cmptno < numcmpts; ++cmptno) { + for (y = 0; y < height; ++y) { + if (jas_image_readcmpt(image, cmpts[cmptno], 0, y, width, 1, data)) { + free(img); + return NULL; + } + d = jas_matrix_getref(data, 0, 0); + for (x = 0; x < width; ++x) { + v = *d; + switch (bpp) { + case 1: + img[pix] = v; + break; + case 2: + img16ui[pix] = v; + break; + case -2: + img16i[pix] = v; + break; + } + pix++; + ++d; + } //for x + } //for y + } //for each component + jas_matrix_destroy(data); + jas_image_destroy(image); + jas_image_clearfmts(); + return img; } //nii_loadImgCoreJasper() #endif struct TJPEG { - long offset; - long size; + long offset; + long size; }; -TJPEG * decode_JPEG_SOF_0XC3_stack (const char *fn, int skipBytes, bool isVerbose, int frames, bool isLittleEndian) { +TJPEG *decode_JPEG_SOF_0XC3_stack(const char *fn, int skipBytes, bool isVerbose, int frames, bool isLittleEndian) { #define abortGoto() free(lOffsetRA); return NULL; - TJPEG *lOffsetRA = (TJPEG*) malloc(frames * sizeof(TJPEG)); - FILE *reader = fopen(fn, "rb"); - fseek(reader, 0, SEEK_END); - long lRawSz = ftell(reader)- skipBytes; - if (lRawSz <= 8) { - printError("Unable to open %s\n", fn); - abortGoto(); //read failure - } - fseek(reader, skipBytes, SEEK_SET); - unsigned char *lRawRA = (unsigned char*) malloc(lRawSz); - size_t lSz = fread(lRawRA, 1, lRawSz, reader); - fclose(reader); - if (lSz < (size_t)lRawSz) { - printError("Unable to read %s\n", fn); - abortGoto(); //read failure - } - long lRawPos = 0; //starting position - int frame = 0; - while ((frame < frames) && ((lRawPos+10) < lRawSz)) { - int tag = dcmInt(4,&lRawRA[lRawPos],isLittleEndian); - lRawPos += 4; //read tag - int tagLength = dcmInt(4,&lRawRA[lRawPos],isLittleEndian); - long tagEnd =lRawPos + tagLength + 4; - if (isVerbose) - printMessage("Tag %#x length %d end at %ld\n", tag, tagLength, tagEnd+skipBytes); - lRawPos += 4; //read tag length - if ((lRawRA[lRawPos] != 0xFF) || (lRawRA[lRawPos+1] != 0xD8) || (lRawRA[lRawPos +2] != 0xFF)) { - if (isVerbose) - printWarning("JPEG signature 0xFFD8FF not found at offset %d of %s\n", skipBytes, fn); - } else { - lOffsetRA[frame].offset = lRawPos+skipBytes; - lOffsetRA[frame].size = tagLength; - frame ++; - } - lRawPos = tagEnd; - } - free(lRawRA); - if (frame < frames) { - printMessage("Only found %d of %d JPEG fragments. Please use dcmdjpeg or gdcmconv to uncompress data.\n", frame, frames); - abortGoto(); - } - return lOffsetRA; + TJPEG *lOffsetRA = (TJPEG *)malloc(frames * sizeof(TJPEG)); + FILE *reader = fopen(fn, "rb"); + fseek(reader, 0, SEEK_END); + long lRawSz = ftell(reader) - skipBytes; + if (lRawSz <= 8) { + printError("Unable to open %s\n", fn); + abortGoto(); //read failure + } + fseek(reader, skipBytes, SEEK_SET); + unsigned char *lRawRA = (unsigned char *)malloc(lRawSz); + size_t lSz = fread(lRawRA, 1, lRawSz, reader); + fclose(reader); + if (lSz < (size_t)lRawSz) { + printError("Unable to read %s\n", fn); + abortGoto(); //read failure + } + long lRawPos = 0; //starting position + int frame = 0; + while ((frame < frames) && ((lRawPos + 10) < lRawSz)) { + int tag = dcmInt(4, &lRawRA[lRawPos], isLittleEndian); + lRawPos += 4; //read tag + int tagLength = dcmInt(4, &lRawRA[lRawPos], isLittleEndian); + long tagEnd = lRawPos + tagLength + 4; + if (isVerbose) + printMessage("Tag %#x length %d end at %ld\n", tag, tagLength, tagEnd + skipBytes); + lRawPos += 4; //read tag length + if ((lRawRA[lRawPos] != 0xFF) || (lRawRA[lRawPos + 1] != 0xD8) || (lRawRA[lRawPos + 2] != 0xFF)) { + if (isVerbose) + printWarning("JPEG signature 0xFFD8FF not found at offset %d of %s\n", skipBytes, fn); + } else { + lOffsetRA[frame].offset = lRawPos + skipBytes; + lOffsetRA[frame].size = tagLength; + frame++; + } + lRawPos = tagEnd; + } + free(lRawRA); + if (frame < frames) { + printMessage("Only found %d of %d JPEG fragments. Please use dcmdjpeg or gdcmconv to uncompress data.\n", frame, frames); + abortGoto(); + } + return lOffsetRA; } -unsigned char * nii_loadImgJPEGC3(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, bool isVerbose) { - //arcane and inefficient lossless compression method popularized by dcmcjpeg, examples at http://www.osirix-viewer.com/resources/dicom-image-library/ - int dimX, dimY, bits, frames; - //clock_t start = clock(); - // https://github.com/rii-mango/JPEGLosslessDecoderJS/blob/master/tests/data/jpeg_lossless_sel1-8bit.dcm - //N.B. this current code can not extract a 2D image that is saved as multiple fragments, for example see the JPLL files at - // ftp://medical.nema.org/MEDICAL/Dicom/DataSets/WG04/ - //Live javascript code that can handle these is at - // https://github.com/chafey/cornerstoneWADOImageLoader - //I have never seen these segmented images in the wild, so we will simply warn the user if we encounter such a file - //int Sz = JPEG_SOF_0XC3_sz (imgname, (dcm.imageStart - 4), dcm.isLittleEndian); - //printMessage("Sz %d %d\n", Sz, dcm.imageBytes ); - //This behavior is legal but appears extremely rare - //ftp://medical.nema.org/medical/dicom/final/cp900_ft.pdf - if (65536 == dcm.imageBytes) - printError("One frame may span multiple fragments. SOFxC3 lossless JPEG. Please extract with dcmdjpeg or gdcmconv.\n"); - unsigned char * ret = decode_JPEG_SOF_0XC3 (imgname, dcm.imageStart, isVerbose, &dimX, &dimY, &bits, &frames, 0); - if (ret == NULL) { - printMessage("Unable to decode JPEG. Please use dcmdjpeg to uncompress data.\n"); - return NULL; - } - //printMessage("JPEG %fms\n", ((double)(clock()-start))/1000); - if (hdr.dim[3] != frames) { //multi-slice image saved as multiple image fragments rather than a single image - //printMessage("Unable to decode all slices (%d/%d). Please use dcmdjpeg to uncompress data.\n", frames, hdr.dim[3]); - if (ret != NULL) free(ret); - TJPEG * offsetRA = decode_JPEG_SOF_0XC3_stack (imgname, dcm.imageStart-8, isVerbose, hdr.dim[3], dcm.isLittleEndian); - if (offsetRA == NULL) return NULL; - size_t slicesz = nii_SliceBytes(hdr); - size_t imgsz = slicesz * hdr.dim[3]; - size_t pos = 0; - unsigned char *bImg = (unsigned char *)malloc(imgsz); - for (int frame = 0; frame < hdr.dim[3]; frame++) { - if (isVerbose) - printMessage("JPEG frame %d has %ld bytes @ %ld\n", frame, offsetRA[frame].size, offsetRA[frame].offset); - unsigned char * ret = decode_JPEG_SOF_0XC3 (imgname, (int)offsetRA[frame].offset, false, &dimX, &dimY, &bits, &frames, (int)offsetRA[frame].size); - if (ret == NULL) { - printMessage("Unable to decode JPEG. Please use dcmdjpeg to uncompress data.\n"); - free(bImg); - return NULL; - } - memcpy(&bImg[pos], ret, slicesz); //dest, src, size - free(ret); - pos += slicesz; - } - free(offsetRA); - return bImg; - } - return ret; +unsigned char *nii_loadImgJPEGC3(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, bool isVerbose) { + //arcane and inefficient lossless compression method popularized by dcmcjpeg, examples at http://www.osirix-viewer.com/resources/dicom-image-library/ + int dimX, dimY, bits, frames; + //clock_t start = clock(); + // https://github.com/rii-mango/JPEGLosslessDecoderJS/blob/master/tests/data/jpeg_lossless_sel1-8bit.dcm + //N.B. this current code can not extract a 2D image that is saved as multiple fragments, for example see the JPLL files at + // ftp://medical.nema.org/MEDICAL/Dicom/DataSets/WG04/ + //Live javascript code that can handle these is at + // https://github.com/chafey/cornerstoneWADOImageLoader + //I have never seen these segmented images in the wild, so we will simply warn the user if we encounter such a file + //int Sz = JPEG_SOF_0XC3_sz (imgname, (dcm.imageStart - 4), dcm.isLittleEndian); + //printMessage("Sz %d %d\n", Sz, dcm.imageBytes ); + //This behavior is legal but appears extremely rare + //ftp://medical.nema.org/medical/dicom/final/cp900_ft.pdf + if (65536 == dcm.imageBytes) + printError("One frame may span multiple fragments. SOFxC3 lossless JPEG. Please extract with dcmdjpeg or gdcmconv.\n"); + unsigned char *ret = decode_JPEG_SOF_0XC3(imgname, dcm.imageStart, isVerbose, &dimX, &dimY, &bits, &frames, 0); + if (ret == NULL) { + printMessage("Unable to decode JPEG. Please use dcmdjpeg to uncompress data.\n"); + return NULL; + } + //printMessage("JPEG %fms\n", ((double)(clock()-start))/1000); + if (hdr.dim[3] != frames) { //multi-slice image saved as multiple image fragments rather than a single image + //printMessage("Unable to decode all slices (%d/%d). Please use dcmdjpeg to uncompress data.\n", frames, hdr.dim[3]); + if (ret != NULL) + free(ret); + TJPEG *offsetRA = decode_JPEG_SOF_0XC3_stack(imgname, dcm.imageStart - 8, isVerbose, hdr.dim[3], dcm.isLittleEndian); + if (offsetRA == NULL) + return NULL; + size_t slicesz = nii_SliceBytes(hdr); + size_t imgsz = slicesz * hdr.dim[3]; + size_t pos = 0; + unsigned char *bImg = (unsigned char *)malloc(imgsz); + for (int frame = 0; frame < hdr.dim[3]; frame++) { + if (isVerbose) + printMessage("JPEG frame %d has %ld bytes @ %ld\n", frame, offsetRA[frame].size, offsetRA[frame].offset); + unsigned char *ret = decode_JPEG_SOF_0XC3(imgname, (int)offsetRA[frame].offset, false, &dimX, &dimY, &bits, &frames, (int)offsetRA[frame].size); + if (ret == NULL) { + printMessage("Unable to decode JPEG. Please use dcmdjpeg to uncompress data.\n"); + free(bImg); + return NULL; + } + memcpy(&bImg[pos], ret, slicesz); //dest, src, size + free(ret); + pos += slicesz; + } + free(offsetRA); + return bImg; + } + return ret; } #ifndef F_OK @@ -3284,49 +3213,49 @@ unsigned char * nii_loadImgJPEGC3(char* imgname, struct nifti_1_header hdr, stru #ifdef myTurboJPEG //if turboJPEG instead of nanoJPEG for classic JPEG decompression //unsigned char * nii_loadImgJPEG50(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { -unsigned char * nii_loadImgJPEG50(char* imgname, struct TDICOMdata dcm) { -//decode classic JPEG using nanoJPEG - //printMessage("50 offset %d\n", dcm.imageStart); - if ((dcm.samplesPerPixel != 1) && (dcm.samplesPerPixel != 3)) { - printError("%d components (expected 1 or 3) in a JPEG image '%s'\n", dcm.samplesPerPixel, imgname); - return NULL; - } - if( access(imgname, F_OK ) == -1 ) { - printError("Unable to find '%s'\n", imgname); - return NULL; - } - //load compressed data - FILE *f = fopen(imgname, "rb"); - fseek(f, 0, SEEK_END); - long unsigned int _jpegSize = (long unsigned int) ftell(f); - _jpegSize = _jpegSize - dcm.imageStart; - if (_jpegSize < 8) { - printError("File too small\n"); - fclose(f); - return NULL; - } - unsigned char* _compressedImage = (unsigned char *)malloc(_jpegSize); - fseek(f, dcm.imageStart, SEEK_SET); - _jpegSize = (long unsigned int) fread(_compressedImage, 1, _jpegSize, f); - fclose(f); - int jpegSubsamp, width, height; - //printMessage("Decoding with turboJPEG\n"); +unsigned char *nii_loadImgJPEG50(char *imgname, struct TDICOMdata dcm) { + //decode classic JPEG using nanoJPEG + //printMessage("50 offset %d\n", dcm.imageStart); + if ((dcm.samplesPerPixel != 1) && (dcm.samplesPerPixel != 3)) { + printError("%d components (expected 1 or 3) in a JPEG image '%s'\n", dcm.samplesPerPixel, imgname); + return NULL; + } + if (access(imgname, F_OK) == -1) { + printError("Unable to find '%s'\n", imgname); + return NULL; + } + //load compressed data + FILE *f = fopen(imgname, "rb"); + fseek(f, 0, SEEK_END); + long unsigned int _jpegSize = (long unsigned int)ftell(f); + _jpegSize = _jpegSize - dcm.imageStart; + if (_jpegSize < 8) { + printError("File too small\n"); + fclose(f); + return NULL; + } + unsigned char *_compressedImage = (unsigned char *)malloc(_jpegSize); + fseek(f, dcm.imageStart, SEEK_SET); + _jpegSize = (long unsigned int)fread(_compressedImage, 1, _jpegSize, f); + fclose(f); + int jpegSubsamp, width, height; + //printMessage("Decoding with turboJPEG\n"); tjhandle _jpegDecompressor = tjInitDecompress(); tjDecompressHeader2(_jpegDecompressor, _compressedImage, _jpegSize, &width, &height, &jpegSubsamp); int COLOR_COMPONENTS = dcm.samplesPerPixel; //printMessage("turboJPEG h*w %d*%d sampling %d components %d\n", width, height, jpegSubsamp, COLOR_COMPONENTS); if ((jpegSubsamp == TJSAMP_GRAY) && (COLOR_COMPONENTS != 1)) { - printError("Grayscale jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname); + printError("Grayscale jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname); } if ((jpegSubsamp != TJSAMP_GRAY) && (COLOR_COMPONENTS != 3)) { - printError("Color jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname); + printError("Color jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname); } //unsigned char bImg[width*height*COLOR_COMPONENTS]; //!< will contain the decompressed image - unsigned char *bImg = (unsigned char *)malloc(width*height*COLOR_COMPONENTS); + unsigned char *bImg = (unsigned char *)malloc(width * height * COLOR_COMPONENTS); if (COLOR_COMPONENTS == 1) //TJPF_GRAY - tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0/*pitch*/, height, TJPF_GRAY, TJFLAG_FASTDCT); + tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0 /*pitch*/, height, TJPF_GRAY, TJFLAG_FASTDCT); else - tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0/*pitch*/, height, TJPF_RGB, TJFLAG_FASTDCT); + tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0 /*pitch*/, height, TJPF_RGB, TJFLAG_FASTDCT); //printMessage("turboJPEG h*w %d*%d (sampling %d)\n", width, height, jpegSubsamp); tjDestroy(_jpegDecompressor); return bImg; @@ -3335,111 +3264,112 @@ unsigned char * nii_loadImgJPEG50(char* imgname, struct TDICOMdata dcm) { #else //if turboJPEG else use nanojpeg... //unsigned char * nii_loadImgJPEG50(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { -unsigned char * nii_loadImgJPEG50(char* imgname, struct TDICOMdata dcm) { -//decode classic JPEG using nanoJPEG - //printMessage("50 offset %d\n", dcm.imageStart); - if( access(imgname, F_OK ) == -1 ) { - printError("Unable to find '%s'\n", imgname); - return NULL; - } - //load compressed data - FILE *f = fopen(imgname, "rb"); - fseek(f, 0, SEEK_END); - int size = (int) ftell(f); - size = size - dcm.imageStart; - if (size < 8) { - printError("File too small '%s'\n", imgname); - fclose(f); - return NULL; - } - char *buf = (char *)malloc(size); - fseek(f, dcm.imageStart, SEEK_SET); - size = (int) fread(buf, 1, size, f); - fclose(f); - //decode - njInit(); - if (njDecode(buf, size)) { - printError("Unable to decode JPEG image.\n"); - return NULL; - } - free(buf); - unsigned char *bImg = (unsigned char *)malloc(njGetImageSize()); - memcpy(bImg, njGetImage(), njGetImageSize()); //dest, src, size - njDone(); - return bImg; +unsigned char *nii_loadImgJPEG50(char *imgname, struct TDICOMdata dcm) { + //decode classic JPEG using nanoJPEG + //printMessage("50 offset %d\n", dcm.imageStart); + if (access(imgname, F_OK) == -1) { + printError("Unable to find '%s'\n", imgname); + return NULL; + } + //load compressed data + FILE *f = fopen(imgname, "rb"); + fseek(f, 0, SEEK_END); + int size = (int)ftell(f); + size = size - dcm.imageStart; + if (size < 8) { + printError("File too small '%s'\n", imgname); + fclose(f); + return NULL; + } + char *buf = (char *)malloc(size); + fseek(f, dcm.imageStart, SEEK_SET); + size = (int)fread(buf, 1, size, f); + fclose(f); + //decode + njInit(); + if (njDecode(buf, size)) { + printError("Unable to decode JPEG image.\n"); + return NULL; + } + free(buf); + unsigned char *bImg = (unsigned char *)malloc(njGetImageSize()); + memcpy(bImg, njGetImage(), njGetImageSize()); //dest, src, size + njDone(); + return bImg; } #endif #endif -uint32_t rleInt(int lIndex, unsigned char lBuffer[], bool swap) {//read binary 32-bit integer - uint32_t retVal = 0; - memcpy(&retVal, (char*)&lBuffer[lIndex * 4], 4); - if (!swap) return retVal; - uint32_t swapVal; - char *inInt = ( char* ) & retVal; - char *outInt = ( char* ) & swapVal; - outInt[0] = inInt[3]; - outInt[1] = inInt[2]; - outInt[2] = inInt[1]; - outInt[3] = inInt[0]; - return swapVal; +uint32_t rleInt(int lIndex, unsigned char lBuffer[], bool swap) { //read binary 32-bit integer + uint32_t retVal = 0; + memcpy(&retVal, (char *)&lBuffer[lIndex * 4], 4); + if (!swap) + return retVal; + uint32_t swapVal; + char *inInt = (char *)&retVal; + char *outInt = (char *)&swapVal; + outInt[0] = inInt[3]; + outInt[1] = inInt[2]; + outInt[2] = inInt[1]; + outInt[3] = inInt[0]; + return swapVal; } //rleInt() -unsigned char * nii_loadImgPMSCT_RLE1(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { -//Transfer Syntax 1.3.46.670589.33.1.4.1 also handled by TomoVision and GDCM's rle2img -//https://github.com/malaterre/GDCM/blob/a923f206060e85e8d81add565ae1b9dd7b210481/Examples/Cxx/rle2img.cxx -//see rle2img: Philips/ELSCINT1 run-length compression 07a1,1011= PMSCT_RLE1 - if (dcm.imageBytes < 66 ) { //64 for header+ 2 byte minimum image - printError("%d is not enough bytes for PMSCT_RLE1 compression '%s'\n", dcm.imageBytes, imgname); - return NULL; - } - int bytesPerSample = dcm.samplesPerPixel * (dcm.bitsAllocated / 8); +unsigned char *nii_loadImgPMSCT_RLE1(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { + //Transfer Syntax 1.3.46.670589.33.1.4.1 also handled by TomoVision and GDCM's rle2img + //https://github.com/malaterre/GDCM/blob/a923f206060e85e8d81add565ae1b9dd7b210481/Examples/Cxx/rle2img.cxx + //see rle2img: Philips/ELSCINT1 run-length compression 07a1,1011= PMSCT_RLE1 + if (dcm.imageBytes < 66) { //64 for header+ 2 byte minimum image + printError("%d is not enough bytes for PMSCT_RLE1 compression '%s'\n", dcm.imageBytes, imgname); + return NULL; + } + int bytesPerSample = dcm.samplesPerPixel * (dcm.bitsAllocated / 8); if (bytesPerSample != 2) { //there is an RGB variation of this format, but we have not seen it in the wild - printError("PMSCT_RLE1 should be 16-bits per sample (please report on Github and use pmsct_rgb1).\n"); - return NULL; + printError("PMSCT_RLE1 should be 16-bits per sample (please report on Github and use pmsct_rgb1).\n"); + return NULL; } - FILE *file = fopen(imgname , "rb"); + FILE *file = fopen(imgname, "rb"); if (!file) { - printError("Unable to open %s\n", imgname); - return NULL; - } + printError("Unable to open %s\n", imgname); + return NULL; + } fseek(file, 0, SEEK_END); - long fileLen=ftell(file); - if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes+dcm.imageStart))) { - printMessage("File not large enough to store image data: %s\n", imgname); - fclose(file); - return NULL; - } - fseek(file, (long) dcm.imageStart, SEEK_SET); + long fileLen = ftell(file); + if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) { + printMessage("File not large enough to store image data: %s\n", imgname); + fclose(file); + return NULL; + } + fseek(file, (long)dcm.imageStart, SEEK_SET); size_t imgsz = nii_ImgBytes(hdr); - char *cImg = (char *)malloc(dcm.imageBytes); //compressed input - size_t sz = fread(cImg, 1, dcm.imageBytes, file); + char *cImg = (char *)malloc(dcm.imageBytes); //compressed input + size_t sz = fread(cImg, 1, dcm.imageBytes, file); fclose(file); if (sz < (size_t)dcm.imageBytes) { - printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); - free(cImg); - return NULL; - } - if( imgsz == dcm.imageBytes ) {// Handle special case that data is not compressed: + printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); + free(cImg); + return NULL; + } + if (imgsz == dcm.imageBytes) { // Handle special case that data is not compressed: return (unsigned char *)cImg; } unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output // RLE pass: compressed -> temp (bImg -> tImg) char *tImg = (char *)malloc(imgsz); //temp output int o = 0; - for(size_t i = 0; i < dcm.imageBytes; ++i) { - if( cImg[i] == (char)0xa5 ) { - int repeat = (unsigned char)cImg[i+1] + 1; - char value = cImg[i+2]; - while(repeat) { - tImg[o] = value ; - o ++; - --repeat; + for (size_t i = 0; i < dcm.imageBytes; ++i) { + if (cImg[i] == (char)0xa5) { + int repeat = (unsigned char)cImg[i + 1] + 1; + char value = cImg[i + 2]; + while (repeat) { + tImg[o] = value; + o++; + --repeat; } - i+=2; + i += 2; } else { - tImg[o] = cImg[i]; - o ++; + tImg[o] = cImg[i]; + o++; } } //for i free(cImg); @@ -3449,61 +3379,61 @@ unsigned char * nii_loadImgPMSCT_RLE1(char* imgname, struct nifti_1_header hdr, //Delta encoding pass: temp -> output (tImg -> bImg) unsigned short delta = 0; o = 0; - int n16 = (int) imgsz >> 1; - unsigned short *bImg16 = (unsigned short *) bImg; - for(size_t i = 0; i < tempsize; ++i) { - if( tImg[i] == (unsigned char)0x5a ) { - unsigned char v1 = (unsigned char)tImg[i+1]; - unsigned char v2 = (unsigned char)tImg[i+2]; - unsigned short value = (unsigned short)(v2 * 256 + v1); - if (o < n16) - bImg16[o] = value; - o ++; - delta = value; - i+=2; - } else { - unsigned short value = (unsigned short)(tImg[i] + delta); - if (o < n16) - bImg16[o] = value; - o ++; - delta = value; - } - } //for i + int n16 = (int)imgsz >> 1; + unsigned short *bImg16 = (unsigned short *)bImg; + for (size_t i = 0; i < tempsize; ++i) { + if (tImg[i] == (unsigned char)0x5a) { + unsigned char v1 = (unsigned char)tImg[i + 1]; + unsigned char v2 = (unsigned char)tImg[i + 2]; + unsigned short value = (unsigned short)(v2 * 256 + v1); + if (o < n16) + bImg16[o] = value; + o++; + delta = value; + i += 2; + } else { + unsigned short value = (unsigned short)(tImg[i] + delta); + if (o < n16) + bImg16[o] = value; + o++; + delta = value; + } + } //for i //printMessage("Delta %d -> %d (of %d)\n", tempsize, 2*(o-1), imgsz); free(tImg); - return bImg; + return bImg; } // nii_loadImgPMSCT_RLE1() -unsigned char * nii_loadImgRLE(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { -//decompress PackBits run-length encoding https://en.wikipedia.org/wiki/PackBits - if (dcm.imageBytes < 66 ) { //64 for header+ 2 byte minimum image - printError("%d is not enough bytes for RLE compression '%s'\n", dcm.imageBytes, imgname); - return NULL; - } - FILE *file = fopen(imgname , "rb"); +unsigned char *nii_loadImgRLE(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { + //decompress PackBits run-length encoding https://en.wikipedia.org/wiki/PackBits + if (dcm.imageBytes < 66) { //64 for header+ 2 byte minimum image + printError("%d is not enough bytes for RLE compression '%s'\n", dcm.imageBytes, imgname); + return NULL; + } + FILE *file = fopen(imgname, "rb"); if (!file) { - printError("Unable to open %s\n", imgname); - return NULL; - } + printError("Unable to open %s\n", imgname); + return NULL; + } fseek(file, 0, SEEK_END); - long fileLen=ftell(file); - if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes+dcm.imageStart))) { - printMessage("File not large enough to store image data: %s\n", imgname); - fclose(file); - return NULL; - } - fseek(file, (long) dcm.imageStart, SEEK_SET); + long fileLen = ftell(file); + if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) { + printMessage("File not large enough to store image data: %s\n", imgname); + fclose(file); + return NULL; + } + fseek(file, (long)dcm.imageStart, SEEK_SET); size_t imgsz = nii_ImgBytes(hdr); - unsigned char *cImg = (unsigned char *)malloc(dcm.imageBytes); //compressed input - size_t sz = fread(cImg, 1, dcm.imageBytes, file); + unsigned char *cImg = (unsigned char *)malloc(dcm.imageBytes); //compressed input + size_t sz = fread(cImg, 1, dcm.imageBytes, file); fclose(file); if (sz < (size_t)dcm.imageBytes) { - printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); - free(cImg); - return NULL; - } - //read header http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_G.3.html - bool swap = (dcm.isLittleEndian != littleEndianPlatform()); + printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); + free(cImg); + return NULL; + } + //read header http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_G.3.html + bool swap = (dcm.isLittleEndian != littleEndianPlatform()); int bytesPerSample = dcm.samplesPerPixel * (dcm.bitsAllocated / 8); uint32_t bytesPerSampleRLE = rleInt(0, cImg, swap); if ((bytesPerSample < 0) || (bytesPerSampleRLE != (uint32_t)bytesPerSample)) { @@ -3514,8 +3444,8 @@ unsigned char * nii_loadImgRLE(char* imgname, struct nifti_1_header hdr, struct unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output for (size_t i = 0; i < imgsz; i++) bImg[i] = 0; - for (int i = 0; i < bytesPerSample; i++) { - uint32_t offset = rleInt(i+1, cImg, swap); + for (int i = 0; i < bytesPerSample; i++) { + uint32_t offset = rleInt(i + 1, cImg, swap); if ((dcm.imageBytes < 0) || (offset > (uint32_t)dcm.imageBytes)) { printError("RLE header error\n"); free(cImg); @@ -3525,8 +3455,8 @@ unsigned char * nii_loadImgRLE(char* imgname, struct nifti_1_header hdr, struct //save in platform's endian: // The first Segment is generated by stripping off the most significant byte of each Padded Composite Pixel Code... size_t vx = i; - if ((dcm.samplesPerPixel == 1) && (littleEndianPlatform())) //endian, except for RGB - vx = (bytesPerSample-1) - i; + if ((dcm.samplesPerPixel == 1) && (littleEndianPlatform())) //endian, except for RGB + vx = (bytesPerSample - 1) - i; while (vx < imgsz) { int8_t n = (int8_t)cImg[offset]; offset++; @@ -3538,7 +3468,7 @@ unsigned char * nii_loadImgRLE(char* imgname, struct nifti_1_header hdr, struct int8_t v = cImg[offset]; offset++; if (vx >= imgsz) - ;//printMessage("literal overflow %d %d\n", r, reps); + ; //printMessage("literal overflow %d %d\n", r, reps); else bImg[vx] = v; vx = vx + bytesPerSample; @@ -3549,7 +3479,7 @@ unsigned char * nii_loadImgRLE(char* imgname, struct nifti_1_header hdr, struct int reps = -(int)n + 1; for (int r = 0; r < reps; r++) { if (vx >= imgsz) - ;//printMessage("repeat overflow %d\n", reps); + ; //printMessage("repeat overflow %d\n", reps); else bImg[vx] = v; vx = vx + bytesPerSample; @@ -3558,69 +3488,69 @@ unsigned char * nii_loadImgRLE(char* imgname, struct nifti_1_header hdr, struct } //while vx < imgsz } //for i < bytesPerSample free(cImg); - return bImg; + return bImg; } // nii_loadImgRLE() #ifdef myDisableOpenJPEG - #ifndef myEnableJasper - //avoid compiler warning, see https://stackoverflow.com/questions/3599160/unused-parameter-warnings-in-c - #define UNUSED(x) (void)(x) - #endif +#ifndef myEnableJasper +//avoid compiler warning, see https://stackoverflow.com/questions/3599160/unused-parameter-warnings-in-c +#define UNUSED(x) (void)(x) +#endif #endif #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) //Support for JPEG-LS //JPEG-LS: Transfer Syntaxes 1.2.840.10008.1.2.4.80 1.2.840.10008.1.2.4.81 #ifdef myEnableJPEGLS1 //use CharLS v1.* requires c++03 - //-std=c++03 -DmyEnableJPEGLS1 charls1/header.cpp charls1/jpegls.cpp charls1/jpegmarkersegment.cpp charls1/interface.cpp charls1/jpegstreamwriter.cpp - #include "charls1/interface.h" +//-std=c++03 -DmyEnableJPEGLS1 charls1/header.cpp charls1/jpegls.cpp charls1/jpegmarkersegment.cpp charls1/interface.cpp charls1/jpegstreamwriter.cpp +#include "charls1/interface.h" #else //use latest release of CharLS: CharLS 2.x requires c++14 - //-std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp - #include "charls/charls.h" +//-std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp +#include "charls/charls.h" #endif #include "charls/publictypes.h" -unsigned char * nii_loadImgJPEGLS(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { +unsigned char *nii_loadImgJPEGLS(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { //load compressed data - FILE *file = fopen(imgname , "rb"); + FILE *file = fopen(imgname, "rb"); if (!file) { - printError("Unable to open %s\n", imgname); - return NULL; - } + printError("Unable to open %s\n", imgname); + return NULL; + } fseek(file, 0, SEEK_END); - long fileLen=ftell(file); - if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes+dcm.imageStart))) { - printMessage("File not large enough to store JPEG-LS data: %s\n", imgname); - fclose(file); - return NULL; - } - fseek(file, (long) dcm.imageStart, SEEK_SET); + long fileLen = ftell(file); + if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) { + printMessage("File not large enough to store JPEG-LS data: %s\n", imgname); + fclose(file); + return NULL; + } + fseek(file, (long)dcm.imageStart, SEEK_SET); unsigned char *cImg = (unsigned char *)malloc(dcm.imageBytes); //compressed input - size_t sz = fread(cImg, 1, dcm.imageBytes, file); + size_t sz = fread(cImg, 1, dcm.imageBytes, file); fclose(file); - if (sz < (size_t)dcm.imageBytes) { - printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); - free(cImg); - return NULL; - } + if (sz < (size_t)dcm.imageBytes) { + printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); + free(cImg); + return NULL; + } //create buffer for uncompressed data size_t imgsz = nii_ImgBytes(hdr); - unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output + unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output JlsParameters params = {}; - #ifdef myEnableJPEGLS1 - if(JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms) != OK ) { - #else +#ifdef myEnableJPEGLS1 + if (JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms) != OK) { +#else using namespace charls; if (JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) { - #endif +#endif printMessage("CharLS failed to read header.\n"); return NULL; } - #ifdef myEnableJPEGLS1 - if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms) != OK ) { - #else - if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) { - #endif +#ifdef myEnableJPEGLS1 + if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms) != OK) { +#else + if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) { +#endif free(bImg); printMessage("CharLS failed to read image.\n"); return NULL; @@ -3629,122 +3559,114 @@ unsigned char * nii_loadImgJPEGLS(char* imgname, struct nifti_1_header hdr, stru } #endif -unsigned char * nii_loadImgXL(char* imgname, struct nifti_1_header *hdr, struct TDICOMdata dcm, bool iVaries, int compressFlag, int isVerbose, struct TDTI4D *dti4D) { -//provided with a filename (imgname) and DICOM header (dcm), creates NIfTI header (hdr) and img - if (headerDcm2Nii(dcm, hdr, true) == EXIT_FAILURE) return NULL; //TOFU - unsigned char * img; - if (dcm.compressionScheme == kCompress50) { - #ifdef myDisableClassicJPEG - printMessage("Software not compiled to decompress classic JPEG DICOM images\n"); - return NULL; - #else - //img = nii_loadImgJPEG50(imgname, *hdr, dcm); - img = nii_loadImgJPEG50(imgname, dcm); - if (hdr->datatype ==DT_RGB24) //convert to planar - img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB);//do this BEFORE Y-Flip, or RGB order can be flipped - #endif - } else if (dcm.compressionScheme == kCompressJPEGLS) { - #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) - img = nii_loadImgJPEGLS(imgname, *hdr, dcm); - if (hdr->datatype ==DT_RGB24) //convert to planar - img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB);//do this BEFORE Y-Flip, or RGB order can be flipped - #else - printMessage("Software not compiled to decompress JPEG-LS DICOM images\n"); - return NULL; - #endif - } else if (dcm.compressionScheme == kCompressPMSCT_RLE1) { - img = nii_loadImgPMSCT_RLE1(imgname, *hdr, dcm); - } else if (dcm.compressionScheme == kCompressRLE) { - img = nii_loadImgRLE(imgname, *hdr, dcm); - if (hdr->datatype ==DT_RGB24) //convert to planar - img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB);//do this BEFORE Y-Flip, or RGB order can be flipped - - } else if (dcm.compressionScheme == kCompressC3) - img = nii_loadImgJPEGC3(imgname, *hdr, dcm, (isVerbose > 0)); - else - #ifndef myDisableOpenJPEG - if ( ((dcm.compressionScheme == kCompress50) || (dcm.compressionScheme == kCompressYes)) && (compressFlag != kCompressNone) ) - img = nii_loadImgCoreOpenJPEG(imgname, *hdr, dcm, compressFlag); - else - #else - #ifdef myEnableJasper - if ((dcm.compressionScheme == kCompressYes) && (compressFlag != kCompressNone) ) - img = nii_loadImgCoreJasper(imgname, *hdr, dcm, compressFlag); - else - #endif - #endif - if (dcm.compressionScheme == kCompressYes) { - printMessage("Software not set up to decompress DICOM\n"); - return NULL; - } else - img = nii_loadImgCore(imgname, *hdr, dcm.bitsAllocated, dcm.imageStart); - if (img == NULL) return img; - if ((dcm.compressionScheme == kCompressNone) && (dcm.isLittleEndian != littleEndianPlatform()) && (hdr->bitpix > 8)) - img = nii_byteswap(img, hdr); - if ((dcm.compressionScheme == kCompressNone) && (hdr->datatype ==DT_RGB24)) - img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB);//do this BEFORE Y-Flip, or RGB order can be flipped - dcm.isPlanarRGB = true; - if (dcm.CSA.mosaicSlices > 1) { - img = nii_demosaic(img, hdr, dcm.CSA.mosaicSlices, (dcm.manufacturer == kMANUFACTURER_UIH)); //, dcm.CSA.protocolSliceNumber1); - /* we will do this in nii_dicom_batch #ifdef obsolete_mosaic_flip - img = nii_flipImgY(img, hdr); - #endif*/ - } - if ((dti4D == NULL) && (!dcm.isFloat) && (iVaries)) //must do afte - img = nii_iVaries(img, hdr, NULL); - int nAcq = dcm.locationsInAcquisition; - if ((nAcq > 1) && (hdr->dim[0] < 4) && ((hdr->dim[3]%nAcq)==0) && (hdr->dim[3]>nAcq) ) { - hdr->dim[4] = hdr->dim[3]/nAcq; - hdr->dim[3] = nAcq; - hdr->dim[0] = 4; - } - if ((dti4D != NULL) && (dti4D->sliceOrder[0] >= 0)) - img = nii_reorderSlicesX(img, hdr, dti4D); - if ((dti4D != NULL) && (!dcm.isFloat) && (iVaries)) - img = nii_iVaries(img, hdr, dti4D); - - //~ - /*if (((dcm.patientPositionSequentialRepeats * 2) == dcm.patientPositionRepeats) && (dcm.isHasPhase) && (dcm.isHasMagnitude)) { - hdr->dim[3] = hdr->dim[3] / 2; - hdr->dim[4] = hdr->dim[4] * 2; +unsigned char *nii_loadImgXL(char *imgname, struct nifti_1_header *hdr, struct TDICOMdata dcm, bool iVaries, int compressFlag, int isVerbose, struct TDTI4D *dti4D) { + //provided with a filename (imgname) and DICOM header (dcm), creates NIfTI header (hdr) and img + if (headerDcm2Nii(dcm, hdr, true) == EXIT_FAILURE) + return NULL; //TOFU + unsigned char *img; + if (dcm.compressionScheme == kCompress50) { +#ifdef myDisableClassicJPEG + printMessage("Software not compiled to decompress classic JPEG DICOM images\n"); + return NULL; +#else + //img = nii_loadImgJPEG50(imgname, *hdr, dcm); + img = nii_loadImgJPEG50(imgname, dcm); + if (hdr->datatype == DT_RGB24) //convert to planar + img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped +#endif + } else if (dcm.compressionScheme == kCompressJPEGLS) { +#if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) + img = nii_loadImgJPEGLS(imgname, *hdr, dcm); + if (hdr->datatype == DT_RGB24) //convert to planar + img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped +#else + printMessage("Software not compiled to decompress JPEG-LS DICOM images\n"); + return NULL; +#endif + } else if (dcm.compressionScheme == kCompressPMSCT_RLE1) { + img = nii_loadImgPMSCT_RLE1(imgname, *hdr, dcm); + } else if (dcm.compressionScheme == kCompressRLE) { + img = nii_loadImgRLE(imgname, *hdr, dcm); + if (hdr->datatype == DT_RGB24) //convert to planar + img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped + } else if (dcm.compressionScheme == kCompressC3) + img = nii_loadImgJPEGC3(imgname, *hdr, dcm, (isVerbose > 0)); + else +#ifndef myDisableOpenJPEG + if (((dcm.compressionScheme == kCompress50) || (dcm.compressionScheme == kCompressYes)) && (compressFlag != kCompressNone)) + img = nii_loadImgCoreOpenJPEG(imgname, *hdr, dcm, compressFlag); + else +#else +#ifdef myEnableJasper + if ((dcm.compressionScheme == kCompressYes) && (compressFlag != kCompressNone)) + img = nii_loadImgCoreJasper(imgname, *hdr, dcm, compressFlag); + else +#endif +#endif + if (dcm.compressionScheme == kCompressYes) { + printMessage("Software not set up to decompress DICOM\n"); + return NULL; + } else + img = nii_loadImgCore(imgname, *hdr, dcm.bitsAllocated, dcm.imageStart); + if (img == NULL) + return img; + if ((dcm.compressionScheme == kCompressNone) && (dcm.isLittleEndian != littleEndianPlatform()) && (hdr->bitpix > 8)) + img = nii_byteswap(img, hdr); + if ((dcm.compressionScheme == kCompressNone) && (hdr->datatype == DT_RGB24)) + img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped + dcm.isPlanarRGB = true; + if (dcm.CSA.mosaicSlices > 1) { + img = nii_demosaic(img, hdr, dcm.CSA.mosaicSlices, (dcm.manufacturer == kMANUFACTURER_UIH)); //, dcm.CSA.protocolSliceNumber1); + } + if ((dti4D == NULL) && (!dcm.isFloat) && (iVaries)) //must do afte + img = nii_iVaries(img, hdr, NULL); + int nAcq = dcm.locationsInAcquisition; + if ((nAcq > 1) && (hdr->dim[0] < 4) && ((hdr->dim[3] % nAcq) == 0) && (hdr->dim[3] > nAcq)) { + hdr->dim[4] = hdr->dim[3] / nAcq; + hdr->dim[3] = nAcq; hdr->dim[0] = 4; - printMessage("Splitting Phase+Magnitude into two volumes for %d slices (Z) and %d volumes (T).\n",hdr->dim[3], hdr->dim[4]); - }*/ - headerDcm2NiiSForm(dcm,dcm, hdr, false); - return img; + } + if ((dti4D != NULL) && (dti4D->sliceOrder[0] >= 0)) + img = nii_reorderSlicesX(img, hdr, dti4D); + if ((dti4D != NULL) && (!dcm.isFloat) && (iVaries)) + img = nii_iVaries(img, hdr, dti4D); + headerDcm2NiiSForm(dcm, dcm, hdr, false); + return img; } //nii_loadImgXL() int isSQ(uint32_t groupElement) { //Detect sequence VR ("SQ") for implicit tags - static const int array_size = 35; - uint32_t array[array_size] = {0x2005+(uint32_t(0x140F)<<16), 0x0008+(uint32_t(0x1111)<<16), 0x0008+(uint32_t(0x1115)<<16), 0x0008+(uint32_t(0x1140)<<16), 0x0008+(uint32_t(0x1199)<<16), 0x0008+(uint32_t(0x2218)<<16), 0x0008+(uint32_t(0x9092)<<16), 0x0018+(uint32_t(0x9006)<<16), 0x0018+(uint32_t(0x9042)<<16), 0x0018+(uint32_t(0x9045)<<16), 0x0018+(uint32_t(0x9049)<<16), 0x0018+(uint32_t(0x9112)<<16), 0x0018+(uint32_t(0x9114)<<16), 0x0018+(uint32_t(0x9115)<<16), 0x0018+(uint32_t(0x9117)<<16), 0x0018+(uint32_t(0x9119)<<16), 0x0018+(uint32_t(0x9125)<<16), 0x0018+(uint32_t(0x9152)<<16), 0x0018+(uint32_t(0x9176)<<16), 0x0018+(uint32_t(0x9226)<<16), 0x0018+(uint32_t(0x9239)<<16), 0x0020+(uint32_t(0x9071)<<16), 0x0020+(uint32_t(0x9111)<<16), 0x0020+(uint32_t(0x9113)<<16), 0x0020+(uint32_t(0x9116)<<16), 0x0020+(uint32_t(0x9221)<<16), 0x0020+(uint32_t(0x9222)<<16), 0x0028+(uint32_t(0x9110)<<16), 0x0028+(uint32_t(0x9132)<<16), 0x0028+(uint32_t(0x9145)<<16), 0x0040+(uint32_t(0x0260)<<16), 0x0040+(uint32_t(0x0555)<<16), 0x0040+(uint32_t(0xa170)<<16), 0x5200+(uint32_t(0x9229)<<16), 0x5200+(uint32_t(0x9230)<<16)}; + static const int array_size = 35; + uint32_t array[array_size] = {0x2005 + (uint32_t(0x140F) << 16), 0x0008 + (uint32_t(0x1111) << 16), 0x0008 + (uint32_t(0x1115) << 16), 0x0008 + (uint32_t(0x1140) << 16), 0x0008 + (uint32_t(0x1199) << 16), 0x0008 + (uint32_t(0x2218) << 16), 0x0008 + (uint32_t(0x9092) << 16), 0x0018 + (uint32_t(0x9006) << 16), 0x0018 + (uint32_t(0x9042) << 16), 0x0018 + (uint32_t(0x9045) << 16), 0x0018 + (uint32_t(0x9049) << 16), 0x0018 + (uint32_t(0x9112) << 16), 0x0018 + (uint32_t(0x9114) << 16), 0x0018 + (uint32_t(0x9115) << 16), 0x0018 + (uint32_t(0x9117) << 16), 0x0018 + (uint32_t(0x9119) << 16), 0x0018 + (uint32_t(0x9125) << 16), 0x0018 + (uint32_t(0x9152) << 16), 0x0018 + (uint32_t(0x9176) << 16), 0x0018 + (uint32_t(0x9226) << 16), 0x0018 + (uint32_t(0x9239) << 16), 0x0020 + (uint32_t(0x9071) << 16), 0x0020 + (uint32_t(0x9111) << 16), 0x0020 + (uint32_t(0x9113) << 16), 0x0020 + (uint32_t(0x9116) << 16), 0x0020 + (uint32_t(0x9221) << 16), 0x0020 + (uint32_t(0x9222) << 16), 0x0028 + (uint32_t(0x9110) << 16), 0x0028 + (uint32_t(0x9132) << 16), 0x0028 + (uint32_t(0x9145) << 16), 0x0040 + (uint32_t(0x0260) << 16), 0x0040 + (uint32_t(0x0555) << 16), 0x0040 + (uint32_t(0xa170) << 16), 0x5200 + (uint32_t(0x9229) << 16), 0x5200 + (uint32_t(0x9230) << 16)}; for (int i = 0; i < array_size; i++) { - //if (array[i] == groupElement) printMessage(" implicitSQ %04x,%04x\n", groupElement & 65535,groupElement>>16); - if (array[i] == groupElement) - return 1; - } - return 0; + //if (array[i] == groupElement) printMessage(" implicitSQ %04x,%04x\n", groupElement & 65535,groupElement>>16); + if (array[i] == groupElement) + return 1; + } + return 0; } //isSQ() -int isDICOMfile(const char * fname) { //0=NotDICOM, 1=DICOM, 2=Maybe(not Part 10 compliant) - //Someday: it might be worthwhile to detect "IMGF" at offset 3228 to warn user if they attempt to convert Signa data - FILE *fp = fopen(fname, "rb"); - if (!fp) return 0; +int isDICOMfile(const char *fname) { //0=NotDICOM, 1=DICOM, 2=Maybe(not Part 10 compliant) + //Someday: it might be worthwhile to detect "IMGF" at offset 3228 to warn user if they attempt to convert Signa data + FILE *fp = fopen(fname, "rb"); + if (!fp) + return 0; fseek(fp, 0, SEEK_END); - long fileLen=ftell(fp); - if (fileLen < 256) { - fclose(fp); - return 0; - } + long fileLen = ftell(fp); + if (fileLen < 256) { + fclose(fp); + return 0; + } fseek(fp, 0, SEEK_SET); unsigned char buffer[256]; size_t sz = fread(buffer, 1, 256, fp); fclose(fp); - if (sz < 256) return 0; - if ((buffer[128] == 'D') && (buffer[129] == 'I') && (buffer[130] == 'C') && (buffer[131] == 'M')) - return 1; //valid DICOM - if ((buffer[0] == 8) && (buffer[1] == 0) && (buffer[3] == 0)) - return 2; //not valid Part 10 file, perhaps DICOM object - return 0; + if (sz < 256) + return 0; + if ((buffer[128] == 'D') && (buffer[129] == 'I') && (buffer[130] == 'C') && (buffer[131] == 'M')) + return 1; //valid DICOM + if ((buffer[0] == 8) && (buffer[1] == 0) && (buffer[3] == 0)) + return 2; //not valid Part 10 file, perhaps DICOM object + return 0; } //isDICOMfile() //START RIR 12/2017 Robert I. Reid @@ -3752,161 +3674,152 @@ int isDICOMfile(const char * fname) { //0=NotDICOM, 1=DICOM, 2=Maybe(not Part 10 // Gathering spot for all the info needed to get the b value and direction // for a volume. struct TVolumeDiffusion { - struct TDICOMdata* pdd; // The multivolume - struct TDTI4D* pdti4D; // permanent records. - - uint8_t manufacturer; // kMANUFACTURER_UNKNOWN, kMANUFACTURER_SIEMENS, etc. - - //void set_manufacturer(const uint8_t m) {manufacturer = m; update();} // unnecessary - - // Everything after this in the structure would be private if it were a C++ - // class, but it has been rewritten as a struct for C compatibility. I am - // using _ as a hint of that, although _ for privacy is not really a - // universal convention in C. Privacy is desired because immediately - // any of these are updated _update_tvd() should be called. - - bool _isAtFirstPatientPosition; // Limit b vals and vecs to 1 per volume. - - //float bVal0018_9087; // kDiffusion_b_value, always present in Philips/Siemens. - //float bVal2001_1003; // kDiffusionBFactor - // float dirRL2005_10b0; // kDiffusionDirectionRL - // float dirAP2005_10b1; // kDiffusionDirectionAP - // float dirFH2005_10b2; // kDiffusionDirectionFH - - // Philips diffusion scans tend to have a "trace" (average of the diffusion - // weighted volumes) volume tacked on, usually but not always at the end, - // so b is > 0, but the direction is meaningless. Most software versions - // explicitly set the direction to 0, but version 3 does not, making (0x18, - // 0x9075) necessary. - bool _isPhilipsNonDirectional; - - //char _directionality0018_9075[16]; // DiffusionDirectionality, not in Philips 2.6. - // float _orientation0018_9089[3]; // kDiffusionOrientation, always - // // present in Philips/Siemens for - // // volumes with a direction. - //char _seq0018_9117[64]; // MRDiffusionSequence, not in Philips 2.6. - - float _dtiV[4]; - double _symBMatrix[6]; - //uint16_t numDti; + struct TDICOMdata *pdd; // The multivolume + struct TDTI4D *pdti4D; // permanent records. + uint8_t manufacturer; // kMANUFACTURER_UNKNOWN, kMANUFACTURER_SIEMENS, etc. + + //void set_manufacturer(const uint8_t m) {manufacturer = m; update();} // unnecessary + + // Everything after this in the structure would be private if it were a C++ + // class, but it has been rewritten as a struct for C compatibility. I am + // using _ as a hint of that, although _ for privacy is not really a + // universal convention in C. Privacy is desired because immediately + // any of these are updated _update_tvd() should be called. + + bool _isAtFirstPatientPosition; // Limit b vals and vecs to 1 per volume. + + //float bVal0018_9087; // kDiffusion_b_value, always present in Philips/Siemens. + //float bVal2001_1003; // kDiffusionBFactor + // float dirRL2005_10b0; // kDiffusionDirectionRL + // float dirAP2005_10b1; // kDiffusionDirectionAP + // float dirFH2005_10b2; // kDiffusionDirectionFH + // Philips diffusion scans tend to have a "trace" (average of the diffusion + // weighted volumes) volume tacked on, usually but not always at the end, + // so b is > 0, but the direction is meaningless. Most software versions + // explicitly set the direction to 0, but version 3 does not, making (0x18, + // 0x9075) necessary. + bool _isPhilipsNonDirectional; + //char _directionality0018_9075[16]; // DiffusionDirectionality, not in Philips 2.6. + // float _orientation0018_9089[3]; // kDiffusionOrientation, always present in Philips/Siemens for volumes with a direction. + //char _seq0018_9117[64]; // MRDiffusionSequence, not in Philips 2.6. + float _dtiV[4]; + double _symBMatrix[6]; + //uint16_t numDti; }; -struct TVolumeDiffusion initTVolumeDiffusion(struct TDICOMdata* ptdd, struct TDTI4D* dti4D); -void clear_volume(struct TVolumeDiffusion* ptvd); // Blank the volume-specific members or set them to impossible values. -void set_directionality0018_9075(struct TVolumeDiffusion* ptvd, unsigned char* inbuf); -void set_orientation0018_9089(struct TVolumeDiffusion* ptvd, int lLength, unsigned char* inbuf, - bool isLittleEndian); -void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion* ptvd, bool iafpp); -int set_bValGE(struct TVolumeDiffusion* ptvd, int lLength, unsigned char* inbuf); -void set_diffusion_directionPhilips(struct TVolumeDiffusion* ptvd, float vec, const int axis); -void set_diffusion_directionGE(struct TVolumeDiffusion* ptvd, int lLength, unsigned char* inbuf, int axis); -void set_bVal(struct TVolumeDiffusion* ptvd, float b); -void set_bMatrix(struct TVolumeDiffusion* ptvd, float b, int component); -void _update_tvd(struct TVolumeDiffusion* ptvd); - -struct TVolumeDiffusion initTVolumeDiffusion(struct TDICOMdata* ptdd, struct TDTI4D* dti4D) { - struct TVolumeDiffusion tvd; - tvd.pdd = ptdd; - tvd.pdti4D = dti4D; - clear_volume(&tvd); - return tvd; +struct TVolumeDiffusion initTVolumeDiffusion(struct TDICOMdata *ptdd, struct TDTI4D *dti4D); +void clear_volume(struct TVolumeDiffusion *ptvd); // Blank the volume-specific members or set them to impossible values. +void set_directionality0018_9075(struct TVolumeDiffusion *ptvd, unsigned char *inbuf); +void set_orientation0018_9089(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, bool isLittleEndian); +void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion *ptvd, bool iafpp); +int set_bValGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf); +void set_diffusion_directionPhilips(struct TVolumeDiffusion *ptvd, float vec, const int axis); +void set_diffusion_directionGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, int axis); +void set_bVal(struct TVolumeDiffusion *ptvd, float b); +void set_bMatrix(struct TVolumeDiffusion *ptvd, float b, int component); +void _update_tvd(struct TVolumeDiffusion *ptvd); + +struct TVolumeDiffusion initTVolumeDiffusion(struct TDICOMdata *ptdd, struct TDTI4D *dti4D) { + struct TVolumeDiffusion tvd; + tvd.pdd = ptdd; + tvd.pdti4D = dti4D; + clear_volume(&tvd); + return tvd; } //initTVolumeDiffusion() -void clear_volume(struct TVolumeDiffusion* ptvd) { - ptvd->_isAtFirstPatientPosition = false; - ptvd->manufacturer = kMANUFACTURER_UNKNOWN; - //bVal0018_9087 = -1; - //ptvd->_directionality0018_9075[0] = 0; - //ptvd->seq0018_9117[0] = 0; - //bVal2001_1003 = -1; - // dirRL2005_10b0 = 2; - // dirAP2005_10b1 = 2; - // dirFH2005_10b2 = 2; - ptvd->_isPhilipsNonDirectional = false; - ptvd->_dtiV[0] = -1; - for(int i = 1; i < 4; ++i) - ptvd->_dtiV[i] = 2; - for(int i = 1; i < 6; ++i) - ptvd->_symBMatrix[i] = NAN; - //numDti = 0; -}//clear_volume() - -void set_directionality0018_9075(struct TVolumeDiffusion* ptvd, unsigned char* inbuf) { - //if(!strncmp(( char*)(inbuf), "BMATRIX", 4)) printf("FOUND BMATRIX----%s\n",inbuf ); - //n.b. strncmp returns 0 if the contents of both strings are equal, for boolean 0 = false! - // elsewhere we use strstr() which returns 0/null if match is not present - if(strncmp(( char*)(inbuf), "DIRECTIONAL", 11) && // strncmp = 0 for ==. - //strncmp(( char*)(inbuf), "NONE", 4) && //issue 256 - strncmp(( char*)(inbuf), "BMATRIX", 7)){ // Siemens XA10 - ptvd->_isPhilipsNonDirectional = true; - // Explicitly set the direction to 0 now, because there may - // not be a 0018,9089 for this frame. - for(int i = 1; i < 4; ++i) // 1-3 is intentional. - ptvd->_dtiV[i] = 0.0; - } - else{ - ptvd->_isPhilipsNonDirectional = false; - // Wait for 0018,9089 to get the direction. - } - _update_tvd(ptvd); +void clear_volume(struct TVolumeDiffusion *ptvd) { + ptvd->_isAtFirstPatientPosition = false; + ptvd->manufacturer = kMANUFACTURER_UNKNOWN; + //bVal0018_9087 = -1; + //ptvd->_directionality0018_9075[0] = 0; + //ptvd->seq0018_9117[0] = 0; + //bVal2001_1003 = -1; + // dirRL2005_10b0 = 2; + // dirAP2005_10b1 = 2; + // dirFH2005_10b2 = 2; + ptvd->_isPhilipsNonDirectional = false; + ptvd->_dtiV[0] = -1; + for (int i = 1; i < 4; ++i) + ptvd->_dtiV[i] = 2; + for (int i = 1; i < 6; ++i) + ptvd->_symBMatrix[i] = NAN; + //numDti = 0; +} //clear_volume() + +void set_directionality0018_9075(struct TVolumeDiffusion *ptvd, unsigned char *inbuf) { + //if(!strncmp(( char*)(inbuf), "BMATRIX", 4)) printf("FOUND BMATRIX----%s\n",inbuf ); + //n.b. strncmp returns 0 if the contents of both strings are equal, for boolean 0 = false! + // elsewhere we use strstr() which returns 0/null if match is not present + if (strncmp((char *)(inbuf), "DIRECTIONAL", 11) && // strncmp = 0 for ==. + //strncmp(( char*)(inbuf), "NONE", 4) && //issue 256 + strncmp((char *)(inbuf), "BMATRIX", 7)) { // Siemens XA10 + ptvd->_isPhilipsNonDirectional = true; + // Explicitly set the direction to 0 now, because there may + // not be a 0018,9089 for this frame. + for (int i = 1; i < 4; ++i) // 1-3 is intentional. + ptvd->_dtiV[i] = 0.0; + } else { + ptvd->_isPhilipsNonDirectional = false; + // Wait for 0018,9089 to get the direction. + } + _update_tvd(ptvd); } //set_directionality0018_9075() -int set_bValGE(struct TVolumeDiffusion* ptvd, int lLength, unsigned char* inbuf) { - //see Series 16 https://github.com/nikadon/cc-dcm2bids-wrapper/tree/master/dicom-qa-examples/ge-mr750-dwi-b-vals#table b750 = 1000000750\8\0\0 b1500 = 1000001500\8\0\0 - int bVal = dcmStrInt(lLength, inbuf); - bVal = (bVal % 10000); - ptvd->_dtiV[0] = bVal; - //printf("(0043,1039) '%s' Slop_int_6 -->%d \n", inbuf, bVal); - //dd.CSA.numDti = 1; // Always true for GE. - _update_tvd(ptvd); - return bVal; +int set_bValGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf) { + //see Series 16 https://github.com/nikadon/cc-dcm2bids-wrapper/tree/master/dicom-qa-examples/ge-mr750-dwi-b-vals#table b750 = 1000000750\8\0\0 b1500 = 1000001500\8\0\0 + int bVal = dcmStrInt(lLength, inbuf); + bVal = (bVal % 10000); + ptvd->_dtiV[0] = bVal; + //printf("(0043,1039) '%s' Slop_int_6 -->%d \n", inbuf, bVal); + //dd.CSA.numDti = 1; // Always true for GE. + _update_tvd(ptvd); + return bVal; } //set_bValGE() // axis: 0 -> x, 1 -> y , 2 -> z -void set_diffusion_directionPhilips(struct TVolumeDiffusion* ptvd, float vec, const int axis){ - ptvd->_dtiV[axis + 1] = vec; +void set_diffusion_directionPhilips(struct TVolumeDiffusion *ptvd, float vec, const int axis) { + ptvd->_dtiV[axis + 1] = vec; //printf("(2005,10b0..2) v[%d]=%g\n", axis, ptvd->_dtiV[axis + 1]); - _update_tvd(ptvd); -}//set_diffusion_directionPhilips() + _update_tvd(ptvd); +} //set_diffusion_directionPhilips() // axis: 0 -> x, 1 -> y , 2 -> z -void set_diffusion_directionGE(struct TVolumeDiffusion* ptvd, int lLength, unsigned char* inbuf, const int axis){ - ptvd->_dtiV[axis + 1] = dcmStrFloat(lLength, inbuf); +void set_diffusion_directionGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, const int axis) { + ptvd->_dtiV[axis + 1] = dcmStrFloat(lLength, inbuf); //printf("(0019,10bb..d) v[%d]=%g\n", axis, ptvd->_dtiV[axis + 1]); - _update_tvd(ptvd); -}//set_diffusion_directionGE() + _update_tvd(ptvd); +} //set_diffusion_directionGE() -void dcmMultiFloatDouble (size_t lByteLength, unsigned char lBuffer[], size_t lnFloats, float *lFloats, bool isLittleEndian) { - size_t floatlen = lByteLength / lnFloats; - for(size_t i = 0; i < lnFloats; ++i) - lFloats[i] = dcmFloatDouble((int)floatlen, lBuffer + i * floatlen, isLittleEndian); +void dcmMultiFloatDouble(size_t lByteLength, unsigned char lBuffer[], size_t lnFloats, float *lFloats, bool isLittleEndian) { + size_t floatlen = lByteLength / lnFloats; + for (size_t i = 0; i < lnFloats; ++i) + lFloats[i] = dcmFloatDouble((int)floatlen, lBuffer + i * floatlen, isLittleEndian); } //dcmMultiFloatDouble() - -void set_orientation0018_9089(struct TVolumeDiffusion* ptvd, int lLength, unsigned char* inbuf, bool isLittleEndian) { - if(ptvd->_isPhilipsNonDirectional){ - for(int i = 1; i < 4; ++i) // Deliberately ignore inbuf; it might be nonsense. - ptvd->_dtiV[i] = 0.0; - } - else - dcmMultiFloatDouble(lLength, inbuf, 3, ptvd->_dtiV + 1, isLittleEndian); - _update_tvd(ptvd); -}//set_orientation0018_9089() - -void set_bVal(struct TVolumeDiffusion* ptvd, const float b) { - ptvd->_dtiV[0] = b; - _update_tvd(ptvd); -}//set_bVal() - -void set_bMatrix(struct TVolumeDiffusion* ptvd, double b, int idx) { - if ((idx < 0) || (idx > 5)) return; - ptvd->_symBMatrix[idx] = b; - _update_tvd(ptvd); +void set_orientation0018_9089(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, bool isLittleEndian) { + if (ptvd->_isPhilipsNonDirectional) { + for (int i = 1; i < 4; ++i) // Deliberately ignore inbuf; it might be nonsense. + ptvd->_dtiV[i] = 0.0; + } else + dcmMultiFloatDouble(lLength, inbuf, 3, ptvd->_dtiV + 1, isLittleEndian); + _update_tvd(ptvd); +} //set_orientation0018_9089() + +void set_bVal(struct TVolumeDiffusion *ptvd, const float b) { + ptvd->_dtiV[0] = b; + _update_tvd(ptvd); +} //set_bVal() + +void set_bMatrix(struct TVolumeDiffusion *ptvd, double b, int idx) { + if ((idx < 0) || (idx > 5)) + return; + ptvd->_symBMatrix[idx] = b; + _update_tvd(ptvd); } -void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion* ptvd, const bool iafpp) { - ptvd->_isAtFirstPatientPosition = iafpp; - _update_tvd(ptvd); -}//set_isAtFirstPatientPosition_tvd() +void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion *ptvd, const bool iafpp) { + ptvd->_isAtFirstPatientPosition = iafpp; + _update_tvd(ptvd); +} //set_isAtFirstPatientPosition_tvd() // Update the diffusion info in dd and *pdti4D for a volume once all the // diffusion info for that volume has been read into pvd. @@ -3914,36 +3827,38 @@ void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion* ptvd, const bool // Note that depending on the scanner software the diffusion info can arrive in // different tags, in different orders (because of enclosing sequence tags), // and the values in some tags may be invalid, or may be essential, depending -// on the presence of other tags. Thus it is best to gather all the diffusion +// on the presence of other tags. Thus it is best to gather all the diffusion // info for a volume (frame) before taking action on it. // // On the other hand, dd and *pdti4D need to be updated as soon as the // diffusion info is ready, before diffusion info for the next volume is read // in. -void _update_tvd(struct TVolumeDiffusion* ptvd) { - // Figure out if we have both the b value and direction (if any) for this - // volume, and if isFirstPosition. - - // // GE (software version 27) is liable to NOT include kDiffusion_b_value for the - // // slice if it is 0, but should still have kDiffusionBFactor, which comes - // // after PatientPosition. - // if(isAtFirstPatientPosition && manufacturer == kMANUFACTURER_GE && dtiV[0] < 0) - // dtiV[0] = 0; // Implied 0. +void _update_tvd(struct TVolumeDiffusion *ptvd) { + // Figure out if we have both the b value and direction (if any) for this + // volume, and if isFirstPosition. + // // GE (software version 27) is liable to NOT include kDiffusion_b_value for the + // // slice if it is 0, but should still have kDiffusionBFactor, which comes + // // after PatientPosition. + // if(isAtFirstPatientPosition && manufacturer == kMANUFACTURER_GE && dtiV[0] < 0) + // dtiV[0] = 0; // Implied 0. bool isReady = (ptvd->_isAtFirstPatientPosition && (ptvd->_dtiV[0] >= 0)); - if(!isReady) return; //no B=0 - if(isReady){ - for(int i = 1; i < 4; ++i){ - if(ptvd->_dtiV[i] > 1){ - isReady = false; - break; - } - } - } - if(!isReady){ //bvecs NOT filled: see if symBMatrix filled - isReady = true; - for(int i = 1; i < 6; ++i) - if (isnan(ptvd->_symBMatrix[i])) isReady = false; - if(!isReady) return; // symBMatrix not filled + if (!isReady) + return; //no B=0 + if (isReady) { + for (int i = 1; i < 4; ++i) { + if (ptvd->_dtiV[i] > 1) { + isReady = false; + break; + } + } + } + if (!isReady) { //bvecs NOT filled: see if symBMatrix filled + isReady = true; + for (int i = 1; i < 6; ++i) + if (isnan(ptvd->_symBMatrix[i])) + isReady = false; + if (!isReady) + return; // symBMatrix not filled //START BRUKER KLUDGE //see issue 265: Bruker stores xx,xy,xz,yx,yy,yz instead of xx,xy,xz,yy,yz,zz // we can recover since xx+yy+zz = bval @@ -3955,120 +3870,128 @@ void _update_tvd(struct TVolumeDiffusion* ptvd) { double yz = ptvd->_symBMatrix[4]; //y*z double zz = ptvd->_symBMatrix[5]; //z*z bool isBrukerBug = false; - if ((xx < 0.0) || (yy < 0.0) || (zz < 0.0)) isBrukerBug = true; - double sumDiag = ptvd->_symBMatrix[0]+ptvd->_symBMatrix[3]+ptvd->_symBMatrix[5]; //if correct xx+yy+zz = bval + if ((xx < 0.0) || (yy < 0.0) || (zz < 0.0)) + isBrukerBug = true; + double sumDiag = ptvd->_symBMatrix[0] + ptvd->_symBMatrix[3] + ptvd->_symBMatrix[5]; //if correct xx+yy+zz = bval double bVecError = fabs(sumDiag - ptvd->pdd->CSA.dtiV[0]); - if (bVecError > 0.5) isBrukerBug = true; + if (bVecError > 0.5) + isBrukerBug = true; //next: check diagonals double x = sqrt(xx); double y = sqrt(yy); double z = sqrt(zz); - if ( (fabs((x*y)-xy)) > 0.5) isBrukerBug = true; - if ( (fabs((x*z)-xz)) > 0.5) isBrukerBug = true; - if ( (fabs((y*z)-yz)) > 0.5) isBrukerBug = true; - if (isBrukerBug) printWarning("Fixing corrupt bmat (issue 265). [%g %g %g %g %g %g]\n", xx,xy,xz,yy,yz,zz); + if ((fabs((x * y) - xy)) > 0.5) + isBrukerBug = true; + if ((fabs((x * z) - xz)) > 0.5) + isBrukerBug = true; + if ((fabs((y * z) - yz)) > 0.5) + isBrukerBug = true; + if (isBrukerBug) + printWarning("Fixing corrupt bmat (issue 265). [%g %g %g %g %g %g]\n", xx, xy, xz, yy, yz, zz); if (isBrukerBug) { ptvd->_symBMatrix[3] = ptvd->_symBMatrix[4]; ptvd->_symBMatrix[4] = ptvd->_symBMatrix[5]; //next: solve for zz given bvalue, xx, and yy ptvd->_symBMatrix[5] = ptvd->_dtiV[0] - ptvd->_symBMatrix[0] - ptvd->_symBMatrix[3]; - if ((ptvd->_symBMatrix[0] < 0.0) || (ptvd->_symBMatrix[5] < 0.0)) printError("DICOM BMatrix corrupt.\n"); + if ((ptvd->_symBMatrix[0] < 0.0) || (ptvd->_symBMatrix[5] < 0.0)) + printError("DICOM BMatrix corrupt.\n"); } //END BRUKER_KLUDGE - vec3 bVec = nifti_mat33_eig3(ptvd->_symBMatrix[0], ptvd->_symBMatrix[1], ptvd->_symBMatrix[2], ptvd->_symBMatrix[3], ptvd->_symBMatrix[4], ptvd->_symBMatrix[5]); + vec3 bVec = nifti_mat33_eig3(ptvd->_symBMatrix[0], ptvd->_symBMatrix[1], ptvd->_symBMatrix[2], ptvd->_symBMatrix[3], ptvd->_symBMatrix[4], ptvd->_symBMatrix[5]); ptvd->_dtiV[1] = bVec.v[0]; ptvd->_dtiV[2] = bVec.v[1]; ptvd->_dtiV[3] = bVec.v[2]; - //printf("bmat=[%g %g %g %g %g %g %g %g %g]\n", ptvd->_symBMatrix[0],ptvd->_symBMatrix[1],ptvd->_symBMatrix[2], ptvd->_symBMatrix[1],ptvd->_symBMatrix[3],ptvd->_symBMatrix[4], ptvd->_symBMatrix[2],ptvd->_symBMatrix[4],ptvd->_symBMatrix[5]); + //printf("bmat=[%g %g %g %g %g %g %g %g %g]\n", ptvd->_symBMatrix[0],ptvd->_symBMatrix[1],ptvd->_symBMatrix[2], ptvd->_symBMatrix[1],ptvd->_symBMatrix[3],ptvd->_symBMatrix[4], ptvd->_symBMatrix[2],ptvd->_symBMatrix[4],ptvd->_symBMatrix[5]); //printf("bmats=[%g %g %g %g %g %g];\n", ptvd->_symBMatrix[0],ptvd->_symBMatrix[1],ptvd->_symBMatrix[2],ptvd->_symBMatrix[3],ptvd->_symBMatrix[4],ptvd->_symBMatrix[5]); //printf("bvec=[%g %g %g];\n", ptvd->_dtiV[1], ptvd->_dtiV[2], ptvd->_dtiV[3]); //printf("bval=%g;\n\n", ptvd->_dtiV[0]); - } - if(!isReady) return; - // If still here, update dd and *pdti4D. - ptvd->pdd->CSA.numDti++; - if (ptvd->pdd->CSA.numDti == 2) { // First time we know that this is a 4D DTI dataset; - for(int i = 0; i < 4; ++i) // Start *pdti4D before ptvd->pdd->CSA.dtiV - ptvd->pdti4D->S[0].V[i] = ptvd->pdd->CSA.dtiV[i]; // is updated. - } - for(int i = 0; i < 4; ++i) // Update pdd - ptvd->pdd->CSA.dtiV[i] = ptvd->_dtiV[i]; - if((ptvd->pdd->CSA.numDti > 1) && (ptvd->pdd->CSA.numDti < kMaxDTI4D)){ // Update *pdti4D - //d.dti4D = (TDTI *)malloc(kMaxDTI4D * sizeof(TDTI)); - for(int i = 0; i < 4; ++i) - ptvd->pdti4D->S[ptvd->pdd->CSA.numDti - 1].V[i] = ptvd->_dtiV[i]; - } - clear_volume(ptvd); // clear the slate for the next volume. -}//_update_tvd() + } + if (!isReady) + return; + // If still here, update dd and *pdti4D. + ptvd->pdd->CSA.numDti++; + if (ptvd->pdd->CSA.numDti == 2) { // First time we know that this is a 4D DTI dataset; + for (int i = 0; i < 4; ++i) // Start *pdti4D before ptvd->pdd->CSA.dtiV + ptvd->pdti4D->S[0].V[i] = ptvd->pdd->CSA.dtiV[i]; // is updated. + } + for (int i = 0; i < 4; ++i) // Update pdd + ptvd->pdd->CSA.dtiV[i] = ptvd->_dtiV[i]; + if ((ptvd->pdd->CSA.numDti > 1) && (ptvd->pdd->CSA.numDti < kMaxDTI4D)) { // Update *pdti4D + //d.dti4D = (TDTI *)malloc(kMaxDTI4D * sizeof(TDTI)); + for (int i = 0; i < 4; ++i) + ptvd->pdti4D->S[ptvd->pdd->CSA.numDti - 1].V[i] = ptvd->_dtiV[i]; + } + clear_volume(ptvd); // clear the slate for the next volume. +} //_update_tvd() //END RIR struct TDCMdim { //DimensionIndexValues - uint32_t dimIdx[MAX_NUMBER_OF_DIMENSIONS]; - uint32_t diskPos; - float triggerDelayTime, TE, intenScale, intenIntercept, intenScalePhilips, RWVScale, RWVIntercept; - float V[4]; - bool isPhase; - bool isReal; - bool isImaginary; + uint32_t dimIdx[MAX_NUMBER_OF_DIMENSIONS]; + uint32_t diskPos; + float triggerDelayTime, TE, intenScale, intenIntercept, intenScalePhilips, RWVScale, RWVIntercept; + float V[4]; + bool isPhase; + bool isReal; + bool isImaginary; }; -void getFileNameX( char *pathParent, const char *path, int maxLen) {//if path is c:\d1\d2 then filename is 'd2' - const char *filename = strrchr(path, '/'); //UNIX - const char *filenamew = strrchr(path, '\\'); //Windows - if (filename == NULL) filename = filenamew; - //if ((filename != NULL) && (filenamew != NULL)) filename = std::max(filename, filenamew); - if ((filename != NULL) && (filenamew != NULL) && (filenamew > filename)) filename = filenamew; //for mixed file separators, e.g. "C:/dir\filenane.tmp" - if (filename == NULL) {//no path separator - strcpy(pathParent,path); - return; - } - filename++; - strncpy(pathParent,filename, maxLen-1); +void getFileNameX(char *pathParent, const char *path, int maxLen) { //if path is c:\d1\d2 then filename is 'd2' + const char *filename = strrchr(path, '/'); //UNIX + const char *filenamew = strrchr(path, '\\'); //Windows + if (filename == NULL) + filename = filenamew; + //if ((filename != NULL) && (filenamew != NULL)) filename = std::max(filename, filenamew); + if ((filename != NULL) && (filenamew != NULL) && (filenamew > filename)) + filename = filenamew; //for mixed file separators, e.g. "C:/dir\filenane.tmp" + if (filename == NULL) { //no path separator + strcpy(pathParent, path); + return; + } + filename++; + strncpy(pathParent, filename, maxLen - 1); } -void getFileName( char *pathParent, const char *path) {//if path is c:\d1\d2 then filename is 'd2' -getFileNameX(pathParent, path, kDICOMStr); +void getFileName(char *pathParent, const char *path) { //if path is c:\d1\d2 then filename is 'd2' + getFileNameX(pathParent, path, kDICOMStr); } -struct fidx -{ - float value; - int index; +struct fidx { + float value; + int index; }; -int fcmp(const void *a, const void *b) -{ - struct fidx *a1 = (struct fidx *)a; - struct fidx *a2 = (struct fidx *)b; - if ((*a1).value > (*a2).value) - return 1; - else if ((*a1).value < (*a2).value) - return -1; - else - return 0; +int fcmp(const void *a, const void *b) { + struct fidx *a1 = (struct fidx *)a; + struct fidx *a2 = (struct fidx *)b; + if ((*a1).value > (*a2).value) + return 1; + else if ((*a1).value < (*a2).value) + return -1; + else + return 0; } #ifdef USING_R // True iff dcm1 sorts *before* dcm2 -bool compareTDCMdim (const TDCMdim &dcm1, const TDCMdim &dcm2) { - for (int i=MAX_NUMBER_OF_DIMENSIONS-1; i >=0; i--){ - if(dcm1.dimIdx[i] < dcm2.dimIdx[i]) - return true; - else if(dcm1.dimIdx[i] > dcm2.dimIdx[i]) - return false; - } - return false; +bool compareTDCMdim(const TDCMdim &dcm1, const TDCMdim &dcm2) { + for (int i = MAX_NUMBER_OF_DIMENSIONS - 1; i >= 0; i--) { + if (dcm1.dimIdx[i] < dcm2.dimIdx[i]) + return true; + else if (dcm1.dimIdx[i] > dcm2.dimIdx[i]) + return false; + } + return false; } //compareTDCMdim() -bool compareTDCMdimRev (const TDCMdim &dcm1, const TDCMdim &dcm2) { +bool compareTDCMdimRev(const TDCMdim &dcm1, const TDCMdim &dcm2) { for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) { - if(dcm1.dimIdx[i] < dcm2.dimIdx[i]) - return true; - else if(dcm1.dimIdx[i] > dcm2.dimIdx[i]) - return false; - } - return false; + if (dcm1.dimIdx[i] < dcm2.dimIdx[i]) + return true; + else if (dcm1.dimIdx[i] > dcm2.dimIdx[i]) + return false; + } + return false; } //compareTDCMdimRev() #else @@ -4077,12 +4000,11 @@ int compareTDCMdim(void const *item1, void const *item2) { struct TDCMdim const *dcm1 = (const struct TDCMdim *)item1; struct TDCMdim const *dcm2 = (const struct TDCMdim *)item2; //for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) { - for(int i=MAX_NUMBER_OF_DIMENSIONS-1; i >=0; i--){ - - if(dcm1->dimIdx[i] < dcm2->dimIdx[i]) - return -1; - else if(dcm1->dimIdx[i] > dcm2->dimIdx[i]) - return 1; + for (int i = MAX_NUMBER_OF_DIMENSIONS - 1; i >= 0; i--) { + if (dcm1->dimIdx[i] < dcm2->dimIdx[i]) + return -1; + else if (dcm1->dimIdx[i] > dcm2->dimIdx[i]) + return 1; } return 0; } //compareTDCMdim() @@ -4091,378 +4013,373 @@ int compareTDCMdimRev(void const *item1, void const *item2) { struct TDCMdim const *dcm1 = (const struct TDCMdim *)item1; struct TDCMdim const *dcm2 = (const struct TDCMdim *)item2; for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) { - if(dcm1->dimIdx[i] < dcm2->dimIdx[i]) - return -1; - else if(dcm1->dimIdx[i] > dcm2->dimIdx[i]) - return 1; + if (dcm1->dimIdx[i] < dcm2->dimIdx[i]) + return -1; + else if (dcm1->dimIdx[i] > dcm2->dimIdx[i]) + return 1; } return 0; } //compareTDCMdimRev() #endif // USING_R -struct TDICOMdata readDICOMx(char * fname, struct TDCMprefs* prefs, struct TDTI4D *dti4D) { -//struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) { +struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D *dti4D) { + //struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) { int isVerbose = prefs->isVerbose; int compressFlag = prefs->compressFlag; struct TDICOMdata d = clear_dicom_data(); d.imageNum = 0; //not set - strcpy(d.protocolName, ""); //erase dummy with empty - strcpy(d.protocolName, ""); //erase dummy with empty - strcpy(d.seriesDescription, ""); //erase dummy with empty - strcpy(d.sequenceName, ""); //erase dummy with empty - //do not read folders - code specific to GCC (LLVM/Clang seems to recognize a small file size) + strcpy(d.protocolName, ""); //erase dummy with empty + strcpy(d.protocolName, ""); //erase dummy with empty + strcpy(d.seriesDescription, ""); //erase dummy with empty + strcpy(d.sequenceName, ""); //erase dummy with empty + //do not read folders - code specific to GCC (LLVM/Clang seems to recognize a small file size) dti4D->sliceOrder[0] = -1; dti4D->volumeOnsetTime[0] = -1; dti4D->decayFactor[0] = -1; dti4D->frameDuration[0] = -1; dti4D->intenScale[0] = 0.0; struct TVolumeDiffusion volDiffusion = initTVolumeDiffusion(&d, dti4D); - struct stat s; - if( stat(fname,&s) == 0 ) { - if( !(s.st_mode & S_IFREG) ){ - printMessage( "DICOM read fail: not a valid file (perhaps a directory) %s\n",fname); - return d; - } - } - bool isPart10prefix = true; - int isOK = isDICOMfile(fname); - if (isOK == 0) return d; - if (isOK == 2) { - d.isExplicitVR = false; - isPart10prefix = false; - } - FILE *file = fopen(fname, "rb"); + struct stat s; + if (stat(fname, &s) == 0) { + if (!(s.st_mode & S_IFREG)) { + printMessage("DICOM read fail: not a valid file (perhaps a directory) %s\n", fname); + return d; + } + } + bool isPart10prefix = true; + int isOK = isDICOMfile(fname); + if (isOK == 0) + return d; + if (isOK == 2) { + d.isExplicitVR = false; + isPart10prefix = false; + } + FILE *file = fopen(fname, "rb"); if (!file) { - printMessage("Unable to open file %s\n", fname); + printMessage("Unable to open file %s\n", fname); return d; } fseek(file, 0, SEEK_END); - long fileLen=ftell(file); //Get file length - if (fileLen < 256) { - printMessage( "File too small to be a DICOM image %s\n", fname); + long fileLen = ftell(file); //Get file length + if (fileLen < 256) { + printMessage("File too small to be a DICOM image %s\n", fname); return d; } - //Since size of DICOM header is unknown, we will load it in 1mb segments - //This uses less RAM and makes is faster for computers with slow disk access - //Benefit is largest for 4D images. - //To disable caching and load entire file to RAM, compile with "-dmyLoadWholeFileToReadHeader" - //To implement the segments, we define these variables: - // fileLen = size of file in bytes - // MaxBufferSz = maximum size of buffer in bytes - // Buffer = array with n elements, where n is smaller of fileLen or MaxBufferSz - // lPos = position in Buffer (indexed from 0), 0..(n-1) - // lFileOffset = offset of Buffer in file: true file position is lOffset+lPos (initially 0) - #ifdef myLoadWholeFileToReadHeader +//Since size of DICOM header is unknown, we will load it in 1mb segments +//This uses less RAM and makes is faster for computers with slow disk access +//Benefit is largest for 4D images. +//To disable caching and load entire file to RAM, compile with "-dmyLoadWholeFileToReadHeader" +//To implement the segments, we define these variables: +// fileLen = size of file in bytes +// MaxBufferSz = maximum size of buffer in bytes +// Buffer = array with n elements, where n is smaller of fileLen or MaxBufferSz +// lPos = position in Buffer (indexed from 0), 0..(n-1) +// lFileOffset = offset of Buffer in file: true file position is lOffset+lPos (initially 0) +#ifdef myLoadWholeFileToReadHeader size_t MaxBufferSz = fileLen; - #else +#else size_t MaxBufferSz = 1000000; //ideally size of DICOM header, but this varies from 2D to 4D files - #endif +#endif if (MaxBufferSz > (size_t)fileLen) MaxBufferSz = fileLen; //printf("%d -> %d\n", MaxBufferSz, fileLen); long lFileOffset = 0; fseek(file, 0, SEEK_SET); //Allocate memory - unsigned char *buffer=(unsigned char *)malloc(MaxBufferSz+1); + unsigned char *buffer = (unsigned char *)malloc(MaxBufferSz + 1); if (!buffer) { - printError( "Memory exhausted!"); - fclose(file); + printError("Memory exhausted!"); + fclose(file); return d; } //Read file contents into buffer size_t sz = fread(buffer, 1, MaxBufferSz, file); if (sz < MaxBufferSz) { - printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname); - fclose(file); - return d; - } - #ifdef myLoadWholeFileToReadHeader + printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname); + fclose(file); + return d; + } +#ifdef myLoadWholeFileToReadHeader fclose(file); - #endif - //DEFINE DICOM TAGS -#define kUnused 0x0001+(0x0001 << 16 ) -#define kStart 0x0002+(0x0000 << 16 ) -#define kMediaStorageSOPClassUID 0x0002+(0x0002 << 16 ) -#define kMediaStorageSOPInstanceUID 0x0002+(0x0003 << 16 ) -#define kTransferSyntax 0x0002+(0x0010 << 16) -#define kImplementationVersionName 0x0002+(0x0013 << 16) -#define kSourceApplicationEntityTitle 0x0002+(0x0016 << 16 ) -#define kDirectoryRecordSequence 0x0004+(0x1220 << 16 ) -//#define kSpecificCharacterSet 0x0008+(0x0005 << 16 ) //someday we should handle foreign characters... -#define kImageTypeTag 0x0008+(0x0008 << 16 ) -//#define kSOPInstanceUID 0x0008+(0x0018 << 16 ) //Philips inserts time as last item, e.g. ?.?.?.YYYYMMDDHHmmSS.SSSS -// not reliable https://neurostars.org/t/heudiconv-no-extraction-of-slice-timing-data-based-on-philips-dicoms/2201/21 -#define kStudyDate 0x0008+(0x0020 << 16 ) -#define kAcquisitionDate 0x0008+(0x0022 << 16 ) -#define kAcquisitionDateTime 0x0008+(0x002A << 16 ) -#define kStudyTime 0x0008+(0x0030 << 16 ) -#define kSeriesTime 0x0008+(0x0031 << 16 ) -#define kAcquisitionTime 0x0008+(0x0032 << 16 ) //TM -//#define kContentTime 0x0008+(0x0033 << 16 ) //TM -#define kModality 0x0008+(0x0060 << 16 ) //CS -#define kManufacturer 0x0008+(0x0070 << 16 ) -#define kInstitutionName 0x0008+(0x0080 << 16 ) -#define kInstitutionAddress 0x0008+(0x0081 << 16 ) -#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 kAcquisitionContrast (uint32_t) 0x0008+(0x9209 << 16 )//'0008' '9209' 'CS' 'AcquisitionContrast' -#define kPatientName 0x0010+(0x0010 << 16 ) -#define kPatientID 0x0010+(0x0020 << 16 ) -#define kAccessionNumber 0x0008+(0x0050 << 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 kDeidentificationMethod 0x0012+(0x0063 << 16)//[DICOMANON, issue 383 -#define kBodyPartExamined 0x0018+(0x0015 << 16) -#define kBodyPartExamined 0x0018+(0x0015 << 16) -#define kScanningSequence 0x0018+(0x0020 << 16) -#define kSequenceVariant 0x0018+(0x0021 << 16) -#define kScanOptions 0x0018+(0x0022 << 16) -#define kMRAcquisitionType 0x0018+(0x0023 << 16) -#define kSequenceName 0x0018+(0x0024 << 16) -#define kRadiopharmaceutical 0x0018+(0x0031 << 16 ) //LO -#define kZThick 0x0018+(0x0050 << 16 ) -#define kTR 0x0018+(0x0080 << 16 ) -#define kTE 0x0018+(0x0081 << 16 ) -#define kTI 0x0018+(0x0082 << 16) // Inversion time -#define kNumberOfAverages 0x0018+(0x0083 << 16 ) //DS -#define kImagingFrequency 0x0018+(0x0084 << 16 ) //DS -//#define kEffectiveTE 0x0018+(0x9082 << 16 ) -#define kEchoNum 0x0018+(0x0086 << 16 ) //IS -#define kMagneticFieldStrength 0x0018+(0x0087 << 16 ) //DS -#define kZSpacing 0x0018+(0x0088 << 16 ) //'DS' 'SpacingBetweenSlices' -#define kPhaseEncodingSteps 0x0018+(0x0089 << 16 ) //'IS' -#define kEchoTrainLength 0x0018+(0x0091 << 16 ) //IS -#define kPercentSampling 0x0018+(0x0093 << 16 ) //'DS' -#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 ) -#define kTriggerTime 0x0018+(0x1060 << 16 ) //DS -#define kRadionuclideTotalDose 0x0018+(0x1074<< 16 ) -#define kRadionuclideHalfLife 0x0018+(0x1075<< 16 ) -#define kRadionuclidePositronFraction 0x0018+(0x1076<< 16 ) -#define kGantryTilt 0x0018+(0x1120 << 16 ) -#define kXRayExposure 0x0018+(0x1152 << 16 ) -#define kConvolutionKernel 0x0018+(0x1210 << 16 ) //SH -#define kFrameDuration 0x0018+(0x1242 << 16 ) //IS -#define kReceiveCoilName 0x0018+(0x1250 << 16 ) // SH -#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 kInversionRecovery 0x0018+uint32_t(0x9009 << 16 ) //'CS' 'YES'/'NO' -#define kSpoiling 0x0018+uint32_t(0x9016 << 16 ) //'CS' -#define kEchoPlanarPulseSequence 0x0018+uint32_t(0x9018 << 16 ) //'CS' 'YES'/'NO' -#define kMagnetizationTransferAttribute 0x0018+uint32_t(0x9020 << 16 ) //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE' - -#define kRectilinearPhaseEncodeReordering 0x0018+uint32_t(0x9034 << 16) //'CS' 'REVERSE_LINEAR'/'LINEAR' -#define kPartialFourierDirection 0x0018+uint32_t(0x9036 << 16) //'CS' -#define kParallelReductionFactorInPlane 0x0018+uint32_t(0x9069<< 16 ) //FD -#define kAcquisitionDuration 0x0018+uint32_t(0x9073<< 16 ) //FD -//#define kFrameAcquisitionDateTime 0x0018+uint32_t(0x9074<< 16 ) //DT "20181019212528.232500" -#define kDiffusionDirectionality 0x0018+uint32_t(0x9075<< 16 ) // NONE, ISOTROPIC, or DIRECTIONAL -#define kParallelAcquisitionTechnique 0x0018+uint32_t(0x9078<< 16 ) //CS: SENSE, SMASH -#define kInversionTimes 0x0018+uint32_t(0x9079<< 16 ) //FD -#define kPartialFourier 0x0018+uint32_t(0x9081<< 16 ) //CS -const uint32_t kEffectiveTE = 0x0018+ (0x9082 << 16); -//#define kDiffusionBFactorSiemens 0x0019+(0x100C<< 16 ) // 0019;000C;SIEMENS MR HEADER ;B_value -#define kDiffusion_bValue 0x0018+uint32_t(0x9087<< 16 ) // FD -#define kDiffusionOrientation 0x0018+uint32_t(0x9089<< 16 ) // FD, seen in enhanced - // DICOM from Philips 5.* - // and Siemens XA10. -#define kImagingFrequency2 0x0018+uint32_t(0x9098 << 16 ) //FD -#define kParallelReductionFactorOutOfPlane 0x0018+uint32_t(0x9155<< 16 ) //FD -//#define kFrameAcquisitionDuration 0x0018+uint32_t(0x9220 << 16 ) //FD -#define kDiffusionBValueXX 0x0018+uint32_t(0x9602 << 16 ) //FD -#define kDiffusionBValueXY 0x0018+uint32_t(0x9603 << 16 ) //FD -#define kDiffusionBValueXZ 0x0018+uint32_t(0x9604 << 16 ) //FD -#define kDiffusionBValueYY 0x0018+uint32_t(0x9605 << 16 ) //FD -#define kDiffusionBValueYZ 0x0018+uint32_t(0x9606 << 16 ) //FD -#define kDiffusionBValueZZ 0x0018+uint32_t(0x9607 << 16 ) //FD -#define kMREchoSequence 0x0018+uint32_t(0x9114<< 16 ) //SQ -#define kMRAcquisitionPhaseEncodingStepsInPlane 0x0018+uint32_t(0x9231<< 16 ) //US -#define kNumberOfImagesInMosaic 0x0019+(0x100A<< 16 ) //US NumberOfImagesInMosaic -#define kSeriesPlaneGE 0x0019+(0x1017<< 16 ) //SS -#define kDwellTime 0x0019+(0x1018<< 16 ) //IS in NSec, see https://github.com/rordenlab/dcm2niix/issues/127 +#endif + //DEFINE DICOM TAGS +#define kUnused 0x0001 + (0x0001 << 16) +#define kStart 0x0002 + (0x0000 << 16) +#define kMediaStorageSOPClassUID 0x0002 + (0x0002 << 16) +#define kMediaStorageSOPInstanceUID 0x0002 + (0x0003 << 16) +#define kTransferSyntax 0x0002 + (0x0010 << 16) +#define kImplementationVersionName 0x0002 + (0x0013 << 16) +#define kSourceApplicationEntityTitle 0x0002 + (0x0016 << 16) +#define kDirectoryRecordSequence 0x0004 + (0x1220 << 16) +//#define kSpecificCharacterSet 0x0008+(0x0005 << 16 ) //someday we should handle foreign characters... +#define kImageTypeTag 0x0008 + (0x0008 << 16) +//#define kSOPInstanceUID 0x0008+(0x0018 << 16 ) //Philips inserts time as last item, e.g. ?.?.?.YYYYMMDDHHmmSS.SSSS +// not reliable https://neurostars.org/t/heudiconv-no-extraction-of-slice-timing-data-based-on-philips-dicoms/2201/21 +#define kStudyDate 0x0008 + (0x0020 << 16) +#define kAcquisitionDate 0x0008 + (0x0022 << 16) +#define kAcquisitionDateTime 0x0008 + (0x002A << 16) +#define kStudyTime 0x0008 + (0x0030 << 16) +#define kSeriesTime 0x0008 + (0x0031 << 16) +#define kAcquisitionTime 0x0008 + (0x0032 << 16) //TM +//#define kContentTime 0x0008+(0x0033 << 16 ) //TM +#define kModality 0x0008 + (0x0060 << 16) //CS +#define kManufacturer 0x0008 + (0x0070 << 16) +#define kInstitutionName 0x0008 + (0x0080 << 16) +#define kInstitutionAddress 0x0008 + (0x0081 << 16) +#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 kAcquisitionContrast (uint32_t)0x0008 + (0x9209 << 16) //'0008' '9209' 'CS' 'AcquisitionContrast' +#define kPatientName 0x0010 + (0x0010 << 16) +#define kPatientID 0x0010 + (0x0020 << 16) +#define kAccessionNumber 0x0008 + (0x0050 << 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 kDeidentificationMethod 0x0012 + (0x0063 << 16) //[DICOMANON, issue 383 +#define kBodyPartExamined 0x0018 + (0x0015 << 16) +#define kBodyPartExamined 0x0018 + (0x0015 << 16) +#define kScanningSequence 0x0018 + (0x0020 << 16) +#define kSequenceVariant 0x0018 + (0x0021 << 16) +#define kScanOptions 0x0018 + (0x0022 << 16) +#define kMRAcquisitionType 0x0018 + (0x0023 << 16) +#define kSequenceName 0x0018 + (0x0024 << 16) +#define kRadiopharmaceutical 0x0018 + (0x0031 << 16) //LO +#define kZThick 0x0018 + (0x0050 << 16) +#define kTR 0x0018 + (0x0080 << 16) +#define kTE 0x0018 + (0x0081 << 16) +#define kTI 0x0018 + (0x0082 << 16) // Inversion time +#define kNumberOfAverages 0x0018 + (0x0083 << 16) //DS +#define kImagingFrequency 0x0018 + (0x0084 << 16) //DS +//#define kEffectiveTE 0x0018+(0x9082 << 16 ) +#define kEchoNum 0x0018 + (0x0086 << 16) //IS +#define kMagneticFieldStrength 0x0018 + (0x0087 << 16) //DS +#define kZSpacing 0x0018 + (0x0088 << 16) //'DS' 'SpacingBetweenSlices' +#define kPhaseEncodingSteps 0x0018 + (0x0089 << 16) //'IS' +#define kEchoTrainLength 0x0018 + (0x0091 << 16) //IS +#define kPercentSampling 0x0018 + (0x0093 << 16) //'DS' +#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) +#define kTriggerTime 0x0018 + (0x1060 << 16) //DS +#define kRadionuclideTotalDose 0x0018 + (0x1074 << 16) +#define kRadionuclideHalfLife 0x0018 + (0x1075 << 16) +#define kRadionuclidePositronFraction 0x0018 + (0x1076 << 16) +#define kGantryTilt 0x0018 + (0x1120 << 16) +#define kXRayExposure 0x0018 + (0x1152 << 16) +#define kConvolutionKernel 0x0018 + (0x1210 << 16) //SH +#define kFrameDuration 0x0018 + (0x1242 << 16) //IS +#define kReceiveCoilName 0x0018 + (0x1250 << 16) // SH +#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 kInversionRecovery 0x0018 + uint32_t(0x9009 << 16) //'CS' 'YES'/'NO' +#define kSpoiling 0x0018 + uint32_t(0x9016 << 16) //'CS' +#define kEchoPlanarPulseSequence 0x0018 + uint32_t(0x9018 << 16) //'CS' 'YES'/'NO' +#define kMagnetizationTransferAttribute 0x0018 + uint32_t(0x9020 << 16) //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE' +#define kRectilinearPhaseEncodeReordering 0x0018 + uint32_t(0x9034 << 16) //'CS' 'REVERSE_LINEAR'/'LINEAR' +#define kPartialFourierDirection 0x0018 + uint32_t(0x9036 << 16) //'CS' +#define kParallelReductionFactorInPlane 0x0018 + uint32_t(0x9069 << 16) //FD +#define kAcquisitionDuration 0x0018 + uint32_t(0x9073 << 16) //FD +//#define kFrameAcquisitionDateTime 0x0018+uint32_t(0x9074<< 16 ) //DT "20181019212528.232500" +#define kDiffusionDirectionality 0x0018 + uint32_t(0x9075 << 16) // NONE, ISOTROPIC, or DIRECTIONAL +#define kParallelAcquisitionTechnique 0x0018 + uint32_t(0x9078 << 16) //CS: SENSE, SMASH +#define kInversionTimes 0x0018 + uint32_t(0x9079 << 16) //FD +#define kPartialFourier 0x0018 + uint32_t(0x9081 << 16) //CS + const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); +//#define kDiffusionBFactorSiemens 0x0019+(0x100C<< 16 ) // 0019;000C;SIEMENS MR HEADER;B_value +#define kDiffusion_bValue 0x0018 + uint32_t(0x9087 << 16) // FD +#define kDiffusionOrientation 0x0018 + uint32_t(0x9089 << 16) // FD, seen in enhanced DICOM from Philips 5.* and Siemens XA10. +#define kImagingFrequency2 0x0018 + uint32_t(0x9098 << 16) //FD +#define kParallelReductionFactorOutOfPlane 0x0018 + uint32_t(0x9155 << 16) //FD +//#define kFrameAcquisitionDuration 0x0018+uint32_t(0x9220 << 16 ) //FD +#define kDiffusionBValueXX 0x0018 + uint32_t(0x9602 << 16) //FD +#define kDiffusionBValueXY 0x0018 + uint32_t(0x9603 << 16) //FD +#define kDiffusionBValueXZ 0x0018 + uint32_t(0x9604 << 16) //FD +#define kDiffusionBValueYY 0x0018 + uint32_t(0x9605 << 16) //FD +#define kDiffusionBValueYZ 0x0018 + uint32_t(0x9606 << 16) //FD +#define kDiffusionBValueZZ 0x0018 + uint32_t(0x9607 << 16) //FD +#define kMREchoSequence 0x0018 + uint32_t(0x9114 << 16) //SQ +#define kMRAcquisitionPhaseEncodingStepsInPlane 0x0018 + uint32_t(0x9231 << 16) //US +#define kNumberOfImagesInMosaic 0x0019 + (0x100A << 16) //US NumberOfImagesInMosaic +#define kSeriesPlaneGE 0x0019 + (0x1017 << 16) //SS +#define kDwellTime 0x0019 + (0x1018 << 16) //IS in NSec, see https://github.com/rordenlab/dcm2niix/issues/127 //https://nmrimaging.wordpress.com/2011/12/20/when-we-process/ -// https://nciphub.org/groups/qindicom/wiki/DiffusionrelatedDICOMtags:experienceacrosssites?action=pdf -#define kDiffusion_bValueSiemens 0x0019+(0x100C<< 16 ) //IS -#define kSliceTimeSiemens 0x0019+(0x1029<< 16) ///FD -#define kDiffusionGradientDirectionSiemens 0x0019+(0x100E<< 16 ) //FD -#define kNumberOfDiffusionDirectionGE 0x0019+(0x10E0<< 16) ///DS NumberOfDiffusionDirection:UserData24 -#define kLastScanLoc 0x0019+(0x101B<< 16 ) -#define kRawDataRunNumberGE 0x0019+(0x10A2<< 16 ) //SL -#define kMaxEchoNumGE 0x0019+(0x10a9<< 16 ) //DS -#define kDiffusionDirectionGEX 0x0019+(0x10BB<< 16 ) //DS phase diffusion direction -#define kDiffusionDirectionGEY 0x0019+(0x10BC<< 16 ) //DS frequency diffusion direction -#define kDiffusionDirectionGEZ 0x0019+(0x10BD<< 16 ) //DS slice diffusion direction -#define kPulseSequenceNameGE 0x0019+(0x109C<< 16 ) //LO 'epiRT' or 'epi' -#define kInternalPulseSequenceNameGE 0x0019+(0x109E<< 16 ) //LO 'EPI' or 'EPI2' -#define kSharedFunctionalGroupsSequence 0x5200+uint32_t(0x9229<< 16 ) // SQ -#define kPerFrameFunctionalGroupsSequence 0x5200+uint32_t(0x9230<< 16 ) // SQ -#define kBandwidthPerPixelPhaseEncode 0x0019+(0x1028<< 16 ) //FD -#define kStudyID 0x0020+(0x0010 << 16 ) -#define kSeriesNum 0x0020+(0x0011 << 16 ) -#define kAcquNum 0x0020+(0x0012 << 16 ) -#define kImageNum 0x0020+(0x0013 << 16 ) -#define kStudyInstanceUID 0x0020+(0x000D << 16 ) -#define kSeriesInstanceUID 0x0020+(0x000E << 16 ) -#define kImagePositionPatient 0x0020+(0x0032 << 16 ) // Actually ! -#define kOrientationACR 0x0020+(0x0035 << 16 ) -//#define kTemporalPositionIdentifier 0x0020+(0x0100 << 16 ) //IS -#define kOrientation 0x0020+(0x0037 << 16 ) -//#define kTemporalPosition 0x0020+(0x0100 << 16 ) //IS -//#define kNumberOfTemporalPositions 0x0020+(0x0105 << 16 ) //IS public tag for NumberOfDynamicScans -#define kTemporalResolution 0x0020+(0x0110 << 16 ) //DS -#define kImagesInAcquisition 0x0020+(0x1002 << 16 ) //IS -//#define kSliceLocation 0x0020+(0x1041 << 16 ) //DS would be useful if not optional type 3 -#define kImageComments 0x0020+(0x4000<< 16 )// '0020' '4000' 'LT' 'ImageComments' -#define kFrameContentSequence 0x0020+uint32_t(0x9111<< 16 ) //SQ -#define kTriggerDelayTime 0x0020+uint32_t(0x9153<< 16 ) //FD -#define kDimensionIndexValues 0x0020+uint32_t(0x9157<< 16 ) // UL n-dimensional index of frame. -#define kInStackPositionNumber 0x0020+uint32_t(0x9057<< 16 ) // UL can help determine slices in volume - -#define kTemporalPositionIndex 0x0020+uint32_t(0x9128<< 16 ) // UL -#define kDimensionIndexPointer 0x0020+uint32_t(0x9165<< 16 ) +// https://nciphub.org/groups/qindicom/wiki/DiffusionrelatedDICOMtags:experienceacrosssites?action=pdf +#define kDiffusion_bValueSiemens 0x0019 + (0x100C << 16) //IS +#define kSliceTimeSiemens 0x0019 + (0x1029 << 16) ///FD +#define kDiffusionGradientDirectionSiemens 0x0019 + (0x100E << 16) //FD +#define kNumberOfDiffusionDirectionGE 0x0019 + (0x10E0 << 16) ///DS NumberOfDiffusionDirection:UserData24 +#define kLastScanLoc 0x0019 + (0x101B << 16) +#define kRawDataRunNumberGE 0x0019 + (0x10A2 << 16)//SL +#define kMaxEchoNumGE 0x0019 + (0x10a9 << 16) //DS +#define kDiffusionDirectionGEX 0x0019 + (0x10BB << 16) //DS phase diffusion direction +#define kDiffusionDirectionGEY 0x0019 + (0x10BC << 16) //DS frequency diffusion direction +#define kDiffusionDirectionGEZ 0x0019 + (0x10BD << 16) //DS slice diffusion direction +#define kPulseSequenceNameGE 0x0019 + (0x109C << 16) //LO 'epiRT' or 'epi' +#define kInternalPulseSequenceNameGE 0x0019 + (0x109E << 16) //LO 'EPI' or 'EPI2' +#define kSharedFunctionalGroupsSequence 0x5200 + uint32_t(0x9229 << 16) // SQ +#define kPerFrameFunctionalGroupsSequence 0x5200 + uint32_t(0x9230 << 16) // SQ +#define kBandwidthPerPixelPhaseEncode 0x0019 + (0x1028 << 16) //FD +#define kStudyID 0x0020 + (0x0010 << 16) +#define kSeriesNum 0x0020 + (0x0011 << 16) +#define kAcquNum 0x0020 + (0x0012 << 16) +#define kImageNum 0x0020 + (0x0013 << 16) +#define kStudyInstanceUID 0x0020 + (0x000D << 16) +#define kSeriesInstanceUID 0x0020 + (0x000E << 16) +#define kImagePositionPatient 0x0020 + (0x0032 << 16) // Actually ! +#define kOrientationACR 0x0020 + (0x0035 << 16) +//#define kTemporalPositionIdentifier 0x0020+(0x0100 << 16 ) //IS +#define kOrientation 0x0020 + (0x0037 << 16) +//#define kTemporalPosition 0x0020+(0x0100 << 16 ) //IS +//#define kNumberOfTemporalPositions 0x0020+(0x0105 << 16 ) //IS public tag for NumberOfDynamicScans +#define kTemporalResolution 0x0020 + (0x0110 << 16) //DS +#define kImagesInAcquisition 0x0020 + (0x1002 << 16) //IS +//#define kSliceLocation 0x0020+(0x1041 << 16 ) //DS would be useful if not optional type 3 +#define kImageComments 0x0020 + (0x4000 << 16) // '0020' '4000' 'LT' 'ImageComments' +#define kFrameContentSequence 0x0020 + uint32_t(0x9111 << 16) //SQ +#define kTriggerDelayTime 0x0020 + uint32_t(0x9153 << 16) //FD +#define kDimensionIndexValues 0x0020 + uint32_t(0x9157 << 16) // UL n-dimensional index of frame. +#define kInStackPositionNumber 0x0020 + uint32_t(0x9057 << 16) // UL can help determine slices in volume +#define kTemporalPositionIndex 0x0020 + uint32_t(0x9128 << 16) // UL +#define kDimensionIndexPointer 0x0020 + uint32_t(0x9165 << 16) //Private Group 21 as Used by Siemens: -#define kSequenceVariant21 0x0021+(0x105B<< 16 )//CS -#define kPATModeText 0x0021+(0x1009<< 16 )//LO, see kImaPATModeText -#define kTimeAfterStart 0x0021+(0x1104<< 16 )//DS -#define kPhaseEncodingDirectionPositiveSiemens 0x0021+(0x111C<< 16 )//IS -//#define kRealDwellTime 0x0021+(0x1142<< 16 )//IS -#define kBandwidthPerPixelPhaseEncode21 0x0021+(0x1153<< 16 )//FD -#define kCoilElements 0x0021+(0x114F<< 16 )//LO -#define kAcquisitionMatrixText21 0x0021+(0x1158 << 16 ) //SH +#define kSequenceVariant21 0x0021 + (0x105B << 16) //CS +#define kPATModeText 0x0021 + (0x1009 << 16) //LO, see kImaPATModeText +#define kTimeAfterStart 0x0021 + (0x1104 << 16) //DS +#define kPhaseEncodingDirectionPositiveSiemens 0x0021 + (0x111C << 16) //IS +//#define kRealDwellTime 0x0021+(0x1142<< 16 )//IS +#define kBandwidthPerPixelPhaseEncode21 0x0021 + (0x1153 << 16) //FD +#define kCoilElements 0x0021 + (0x114F << 16) //LO +#define kAcquisitionMatrixText21 0x0021 + (0x1158 << 16) //SH //Private Group 21 as used by GE: -#define kLocationsInAcquisitionGE 0x0021+(0x104F<< 16 )//SS 'LocationsInAcquisitionGE' -#define kRTIA_timer 0x0021+(0x105E<< 16 )//DS -#define kProtocolDataBlockGE 0x0025+(0x101B<< 16 )//OB -#define kNumberOfPointsPerArm 0x0027+(0x1060<< 16 )//FL -#define kNumberOfArms 0x0027+(0x1061<< 16 )//FL -#define kNumberOfExcitations 0x0027+(0x1062<< 16 )//FL -#define kSamplesPerPixel 0x0028+(0x0002 << 16 ) -#define kPhotometricInterpretation 0x0028+(0x0004 << 16 ) -#define kPlanarRGB 0x0028+(0x0006 << 16 ) -#define kDim3 0x0028+(0x0008 << 16 ) //number of frames - for Philips this is Dim3*Dim4 -#define kDim2 0x0028+(0x0010 << 16 ) -#define kDim1 0x0028+(0x0011 << 16 ) -#define kXYSpacing 0x0028+(0x0030 << 16 ) //DS 'PixelSpacing' -#define kBitsAllocated 0x0028+(0x0100 << 16 ) -#define kBitsStored 0x0028+(0x0101 << 16 )//US 'BitsStored' -#define kIsSigned 0x0028+(0x0103 << 16 ) //PixelRepresentation -#define kPixelPaddingValue 0x0028+(0x0120 << 16 ) // https://github.com/rordenlab/dcm2niix/issues/262 -#define kFloatPixelPaddingValue 0x0028+(0x0122 << 16 ) // https://github.com/rordenlab/dcm2niix/issues/262 -#define kIntercept 0x0028+(0x1052 << 16 ) -#define kSlope 0x0028+(0x1053 << 16 ) -//#define kRescaleType 0x0028+(0x1053 << 16 ) //LO e.g. for Philips Fieldmap: [Hz] -//#define kSpectroscopyDataPointColumns 0x0028+(0x9002 << 16 ) //IS -#define kGeiisFlag 0x0029+(0x0010 << 16 ) //warn user if dreaded GEIIS was used to process image -#define kCSAImageHeaderInfo 0x0029+(0x1010 << 16 ) -#define kCSASeriesHeaderInfo 0x0029+(0x1020 << 16 ) -#define kStudyComments 0x0032+(0x4000<< 16 )//LT StudyComments -//#define kObjectGraphics 0x0029+(0x1210 << 16 ) //0029,1210 syngoPlatformOOGInfo Object Oriented Graphics -#define kProcedureStepDescription 0x0040+(0x0254 << 16 ) -#define kRealWorldIntercept 0x0040+uint32_t(0x9224 << 16 ) //IS dicm2nii's SlopInt_6_9 -#define kRealWorldSlope 0x0040+uint32_t(0x9225 << 16 ) //IS dicm2nii's SlopInt_6_9 -#define kUserDefineDataGE 0x0043+(0x102A << 16 ) //OB -#define kEffectiveEchoSpacingGE 0x0043+(0x102C << 16 ) //SS -#define kImageTypeGE 0x0043+(0x102F << 16 ) //SS 0/1/2/3 for magnitude/phase/real/imaginary -#define kDiffusion_bValueGE 0x0043+(0x1039 << 16 ) //IS dicm2nii's SlopInt_6_9 -#define kEpiRTGroupDelayGE 0x0043+(0x107C << 16 ) //FL -#define kAssetRFactorsGE 0x0043+(0x1083 << 16 ) //DS -#define kASLContrastTechniqueGE 0x0043+(0x10A3 << 16 ) //CS -#define kASLLabelingTechniqueGE 0x0043+(0x10A4 << 16 ) //LO -#define kDurationLabelPulseGE 0x0043+(0x10A5 << 16 ) //IS -#define kMultiBandGE 0x0043+(0x10B6 << 16 ) //LO -#define kAcquisitionMatrixText 0x0051+(0x100B << 16 ) //LO -#define kImageOrientationText 0x0051+(0x100E << 16 ) // -#define kCoilSiemens 0x0051+(0x100F << 16 ) -#define kImaPATModeText 0x0051+(0x1011 << 16 ) -#define kLocationsInAcquisition 0x0054+(0x0081 << 16 ) -#define kUnitsPT 0x0054+(0x1001<< 16 ) //CS -#define kAttenuationCorrectionMethod 0x0054+(0x1101<< 16 ) //LO -#define kDecayCorrection 0x0054+(0x1102<< 16 ) //CS -#define kReconstructionMethod 0x0054+(0x1103<< 16 ) //LO -#define kDecayFactor 0x0054+(0x1321<< 16 ) //LO +#define kLocationsInAcquisitionGE 0x0021 + (0x104F << 16) //SS 'LocationsInAcquisitionGE' +#define kRTIA_timer 0x0021 + (0x105E << 16) //DS +#define kProtocolDataBlockGE 0x0025 + (0x101B << 16) //OB +#define kNumberOfPointsPerArm 0x0027 + (0x1060 << 16) //FL +#define kNumberOfArms 0x0027 + (0x1061 << 16) //FL +#define kNumberOfExcitations 0x0027 + (0x1062 << 16) //FL +#define kSamplesPerPixel 0x0028 + (0x0002 << 16) +#define kPhotometricInterpretation 0x0028 + (0x0004 << 16) +#define kPlanarRGB 0x0028 + (0x0006 << 16) +#define kDim3 0x0028 + (0x0008 << 16) //number of frames - for Philips this is Dim3*Dim4 +#define kDim2 0x0028 + (0x0010 << 16) +#define kDim1 0x0028 + (0x0011 << 16) +#define kXYSpacing 0x0028 + (0x0030 << 16) //DS 'PixelSpacing' +#define kBitsAllocated 0x0028 + (0x0100 << 16) +#define kBitsStored 0x0028 + (0x0101 << 16) //US 'BitsStored' +#define kIsSigned 0x0028 + (0x0103 << 16) //PixelRepresentation +#define kPixelPaddingValue 0x0028 + (0x0120 << 16) // https://github.com/rordenlab/dcm2niix/issues/262 +#define kFloatPixelPaddingValue 0x0028 + (0x0122 << 16) // https://github.com/rordenlab/dcm2niix/issues/262 +#define kIntercept 0x0028 + (0x1052 << 16) +#define kSlope 0x0028 + (0x1053 << 16) +//#define kRescaleType 0x0028+(0x1053 << 16 ) //LO e.g. for Philips Fieldmap: [Hz] +//#define kSpectroscopyDataPointColumns 0x0028+(0x9002 << 16 ) //IS +#define kGeiisFlag 0x0029 + (0x0010 << 16) //warn user if dreaded GEIIS was used to process image +#define kCSAImageHeaderInfo 0x0029 + (0x1010 << 16) +#define kCSASeriesHeaderInfo 0x0029 + (0x1020 << 16) +#define kStudyComments 0x0032 + (0x4000 << 16) //LT StudyComments +//#define kObjectGraphics 0x0029+(0x1210 << 16 ) //0029,1210 syngoPlatformOOGInfo Object Oriented Graphics +#define kProcedureStepDescription 0x0040 + (0x0254 << 16) +#define kRealWorldIntercept 0x0040 + uint32_t(0x9224 << 16) //IS dicm2nii's SlopInt_6_9 +#define kRealWorldSlope 0x0040 + uint32_t(0x9225 << 16) //IS dicm2nii's SlopInt_6_9 +#define kUserDefineDataGE 0x0043 + (0x102A << 16) //OB +#define kEffectiveEchoSpacingGE 0x0043 + (0x102C << 16) //SS +#define kImageTypeGE 0x0043 + (0x102F << 16) //SS 0/1/2/3 for magnitude/phase/real/imaginary +#define kDiffusion_bValueGE 0x0043 + (0x1039 << 16) //IS dicm2nii's SlopInt_6_9 +#define kEpiRTGroupDelayGE 0x0043 + (0x107C << 16) //FL +#define kAssetRFactorsGE 0x0043 + (0x1083 << 16) //DS +#define kASLContrastTechniqueGE 0x0043 + (0x10A3 << 16) //CS +#define kASLLabelingTechniqueGE 0x0043 + (0x10A4 << 16) //LO +#define kDurationLabelPulseGE 0x0043 + (0x10A5 << 16) //IS +#define kMultiBandGE 0x0043 + (0x10B6 << 16) //LO +#define kAcquisitionMatrixText 0x0051 + (0x100B << 16) //LO +#define kImageOrientationText 0x0051 + (0x100E << 16) // +#define kCoilSiemens 0x0051 + (0x100F << 16) +#define kImaPATModeText 0x0051 + (0x1011 << 16) +#define kLocationsInAcquisition 0x0054 + (0x0081 << 16) +#define kUnitsPT 0x0054 + (0x1001 << 16) //CS +#define kAttenuationCorrectionMethod 0x0054 + (0x1101 << 16) //LO +#define kDecayCorrection 0x0054 + (0x1102 << 16) //CS +#define kReconstructionMethod 0x0054 + (0x1103 << 16) //LO +#define kDecayFactor 0x0054 + (0x1321 << 16) //LO //ftp://dicom.nema.org/MEDICAL/dicom/2014c/output/chtml/part03/sect_C.8.9.4.html //If ImageType is REPROJECTION we slice direction is reversed - need example to test -// #define kSeriesType 0x0054+(0x1000 << 16 ) -#define kDoseCalibrationFactor 0x0054+(0x1322<< 16 ) -#define kPETImageIndex 0x0054+(0x1330<< 16 ) -#define kPEDirectionDisplayedUIH 0x0065+(0x1005<< 16 )//SH -#define kDiffusion_bValueUIH 0x0065+(0x1009<< 16 ) //FD -#define kParallelInformationUIH 0x0065+(0x100D<< 16 ) //SH -#define kNumberOfImagesInGridUIH 0x0065+(0x1050<< 16 ) //DS -#define kDiffusionGradientDirectionUIH 0x0065+(0x1037<< 16 ) //FD -//#define kMRVFrameSequenceUIH 0x0065+(0x1050<< 16 ) //SQ -#define kPhaseEncodingDirectionPositiveUIH 0x0065+(0x1058<< 16 )//IS issue410 -#define kIconImageSequence 0x0088+(0x0200 << 16 ) -#define kElscintIcon 0x07a3+(0x10ce << 16 ) //see kGeiisFlag and https://github.com/rordenlab/dcm2niix/issues/239 -#define kPMSCT_RLE1 0x07a1+(0x100a << 16 ) //Elscint/Philips compression -#define kPrivateCreator 0x2001+(0x0010 << 16 )// LO (Private creator is any tag where group is odd and element is x0010-x00FF -#define kDiffusion_bValuePhilips 0x2001+(0x1003 << 16 )// FL -#define kCardiacSync 0x2001+(0x1010 << 16 ) //CS -//#define kDiffusionDirectionPhilips 0x2001+(0x1004 << 16 )//CS Diffusion Direction -#define kSliceNumberMrPhilips 0x2001+(0x100A << 16 ) //IS Slice_Number_MR -#define kSliceOrient 0x2001+(0x100B << 16 )//2001,100B Philips slice orientation (TRANSVERSAL, AXIAL, SAGITTAL) -#define kEPIFactorPhilips 0x2001+(0x1013 << 16 ) //SL -#define kPrepulseDelay 0x2001+(0x101B << 16 ) //FL -#define kPrepulseType 0x2001+(0x101C << 16 ) //CS -#define kRespirationSync 0x2001+(0x101F << 16 ) //CS -#define kNumberOfSlicesMrPhilips 0x2001+(0x1018 << 16 )//SL 0x2001, 0x1018 ), "Number_of_Slices_MR" -#define kPartialMatrixScannedPhilips 0x2001+(0x1019 << 16 )// CS -#define kWaterFatShiftPhilips 0x2001+(0x1022 << 16 ) //FL -//#define kMRSeriesAcquisitionNumber 0x2001+(0x107B << 16 ) //IS -//#define kNumberOfLocationsPhilips 0x2001+(0x1015 << 16 ) //SS -//#define kStackSliceNumber 0x2001+(0x1035 << 16 )//? Potential way to determine slice order for Philips? -#define kNumberOfDynamicScans 0x2001+(0x1081 << 16 )//'2001' '1081' 'IS' 'NumberOfDynamicScans' -#define kMRAcquisitionTypePhilips 0x2005+(0x106F << 16) -#define kAngulationAP 0x2005+(0x1071 << 16)//'2005' '1071' 'FL' 'MRStackAngulationAP' -#define kAngulationFH 0x2005+(0x1072 << 16)//'2005' '1072' 'FL' 'MRStackAngulationFH' -#define kAngulationRL 0x2005+(0x1073 << 16)//'2005' '1073' 'FL' 'MRStackAngulationRL' -#define kMRStackOffcentreAP 0x2005+(0x1078 << 16) -#define kMRStackOffcentreFH 0x2005+(0x1079 << 16) -#define kMRStackOffcentreRL 0x2005+(0x107A << 16) -#define kPhilipsSlope 0x2005+(0x100E << 16 ) -#define kMRImageDynamicScanBeginTime 0x2005+(0x10a0 << 16) //FL -#define kDiffusionDirectionRL 0x2005+(0x10B0 << 16) -#define kDiffusionDirectionAP 0x2005+(0x10B1 << 16) -#define kDiffusionDirectionFH 0x2005+(0x10B2 << 16) -#define kPrivatePerFrameSq 0x2005+(0x140F << 16) -#define kMRImageDiffBValueNumber 0x2005+(0x1412 << 16) //IS -//#define kMRImageGradientOrientationNumber 0x2005+(0x1413 << 16) //IS -#define kWaveformSq 0x5400+(0x0100 << 16) -#define kSpectroscopyData 0x5600+(0x0020 << 16) //OF -#define kImageStart 0x7FE0+(0x0010 << 16 ) -#define kImageStartFloat 0x7FE0+(0x0008 << 16 ) -#define kImageStartDouble 0x7FE0+(0x0009 << 16 ) -uint32_t kItemTag = 0xFFFE +(0xE000 << 16 ); -uint32_t kItemDelimitationTag = 0xFFFE +(0xE00D << 16 ); -uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); +// #define kSeriesType 0x0054+(0x1000 << 16 ) +#define kDoseCalibrationFactor 0x0054 + (0x1322 << 16) +#define kPETImageIndex 0x0054 + (0x1330 << 16) +#define kPEDirectionDisplayedUIH 0x0065 + (0x1005 << 16) //SH +#define kDiffusion_bValueUIH 0x0065 + (0x1009 << 16) //FD +#define kParallelInformationUIH 0x0065 + (0x100D << 16) //SH +#define kNumberOfImagesInGridUIH 0x0065 + (0x1050 << 16) //DS +#define kDiffusionGradientDirectionUIH 0x0065 + (0x1037 << 16) //FD +//#define kMRVFrameSequenceUIH 0x0065+(0x1050<< 16 ) //SQ +#define kPhaseEncodingDirectionPositiveUIH 0x0065 + (0x1058 << 16) //IS issue410 +#define kIconImageSequence 0x0088 + (0x0200 << 16) +#define kElscintIcon 0x07a3 + (0x10ce << 16) //see kGeiisFlag and https://github.com/rordenlab/dcm2niix/issues/239 +#define kPMSCT_RLE1 0x07a1 + (0x100a << 16) //Elscint/Philips compression +#define kPrivateCreator 0x2001 + (0x0010 << 16) // LO (Private creator is any tag where group is odd and element is x0010-x00FF +#define kDiffusion_bValuePhilips 0x2001 + (0x1003 << 16) // FL +#define kCardiacSync 0x2001 + (0x1010 << 16) //CS +//#define kDiffusionDirectionPhilips 0x2001+(0x1004 << 16 )//CS Diffusion Direction +#define kSliceNumberMrPhilips 0x2001 + (0x100A << 16) //IS Slice_Number_MR +#define kSliceOrient 0x2001 + (0x100B << 16) //2001,100B Philips slice orientation (TRANSVERSAL, AXIAL, SAGITTAL) +#define kEPIFactorPhilips 0x2001 + (0x1013 << 16) //SL +#define kPrepulseDelay 0x2001 + (0x101B << 16) //FL +#define kPrepulseType 0x2001 + (0x101C << 16) //CS +#define kRespirationSync 0x2001 + (0x101F << 16) //CS +#define kNumberOfSlicesMrPhilips 0x2001 + (0x1018 << 16) //SL 0x2001, 0x1018 ), "Number_of_Slices_MR" +#define kPartialMatrixScannedPhilips 0x2001 + (0x1019 << 16) // CS +#define kWaterFatShiftPhilips 0x2001 + (0x1022 << 16) //FL +//#define kMRSeriesAcquisitionNumber 0x2001+(0x107B << 16 ) //IS +//#define kNumberOfLocationsPhilips 0x2001+(0x1015 << 16 ) //SS +//#define kStackSliceNumber 0x2001+(0x1035 << 16 )//? Potential way to determine slice order for Philips? +#define kNumberOfDynamicScans 0x2001 + (0x1081 << 16) //'2001' '1081' 'IS' 'NumberOfDynamicScans' +#define kMRAcquisitionTypePhilips 0x2005 + (0x106F << 16) +#define kAngulationAP 0x2005 + (0x1071 << 16) //'2005' '1071' 'FL' 'MRStackAngulationAP' +#define kAngulationFH 0x2005 + (0x1072 << 16) //'2005' '1072' 'FL' 'MRStackAngulationFH' +#define kAngulationRL 0x2005 + (0x1073 << 16) //'2005' '1073' 'FL' 'MRStackAngulationRL' +#define kMRStackOffcentreAP 0x2005 + (0x1078 << 16) +#define kMRStackOffcentreFH 0x2005 + (0x1079 << 16) +#define kMRStackOffcentreRL 0x2005 + (0x107A << 16) +#define kPhilipsSlope 0x2005 + (0x100E << 16) +#define kMRImageDynamicScanBeginTime 0x2005 + (0x10a0 << 16) //FL +#define kDiffusionDirectionRL 0x2005 + (0x10B0 << 16) +#define kDiffusionDirectionAP 0x2005 + (0x10B1 << 16) +#define kDiffusionDirectionFH 0x2005 + (0x10B2 << 16) +#define kPrivatePerFrameSq 0x2005 + (0x140F << 16) +#define kMRImageDiffBValueNumber 0x2005 + (0x1412 << 16) //IS +//#define kMRImageGradientOrientationNumber 0x2005+(0x1413 << 16) //IS +#define kWaveformSq 0x5400 + (0x0100 << 16) +#define kSpectroscopyData 0x5600 + (0x0020 << 16) //OF +#define kImageStart 0x7FE0 + (0x0010 << 16) +#define kImageStartFloat 0x7FE0 + (0x0008 << 16) +#define kImageStartDouble 0x7FE0 + (0x0009 << 16) + uint32_t kItemTag = 0xFFFE + (0xE000 << 16); + uint32_t kItemDelimitationTag = 0xFFFE + (0xE00D << 16); + uint32_t kSequenceDelimitationItemTag = 0xFFFE + (0xE0DD << 16); #define salvageAgfa #ifdef salvageAgfa //issue435 - // handle PrivateCreator renaming e.g. 0021,10xx -> 0021,11xx - // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/siemens.tpl - // https://github.com/neurolabusc/dcm_qa_agfa - // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.8.html - #define kMaxRemaps 16 //no vendor uses more than 5 private creator groups +// handle PrivateCreator renaming e.g. 0021,10xx -> 0021,11xx +// https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/siemens.tpl +// https://github.com/neurolabusc/dcm_qa_agfa +// http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.8.html +#define kMaxRemaps 16 //no vendor uses more than 5 private creator groups //we need to keep track of multiple remappings, e.g. issue 437 2005,0014->2005,0012; 2005,0015->2005,0011 int nRemaps = 0; - uint32_t privateCreatorMasks[kMaxRemaps]; //0 -> none - uint32_t privateCreatorRemaps[kMaxRemaps]; //0 -> none - //uint32_t privateCreatorMask = 0; //0 -> none - //uint32_t privateCreatorRemap = 0; //0 -> none + uint32_t privateCreatorMasks[kMaxRemaps]; //0 -> none + uint32_t privateCreatorRemaps[kMaxRemaps]; //0 -> none #endif double TE = 0.0; //most recent echo time recorded float temporalResolutionMS = 0.0; @@ -4479,259 +4396,250 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); int philMRImageDiffBValueNumber = 0; int sqDepth = 0; int acquisitionTimesGE_UIH = 0; - int sqDepth00189114 = -1; - bool hasDwiDirectionality = false; - //float sliceLocation = INFINITY; //useless since this tag is optional - //int numFirstPatientPosition = 0; - int nDimIndxVal = -1; //tracks Philips kDimensionIndexValues - int locationsInAcquisitionGE = 0; - int PETImageIndex = 0; - int inStackPositionNumber = 0; - uint32_t dimensionIndexPointer[MAX_NUMBER_OF_DIMENSIONS]; - size_t dimensionIndexPointerCounter = 0; - int maxInStackPositionNumber = 0; + int sqDepth00189114 = -1; + bool hasDwiDirectionality = false; + //float sliceLocation = INFINITY; //useless since this tag is optional + //int numFirstPatientPosition = 0; + int nDimIndxVal = -1; //tracks Philips kDimensionIndexValues + int locationsInAcquisitionGE = 0; + int PETImageIndex = 0; + int inStackPositionNumber = 0; + uint32_t dimensionIndexPointer[MAX_NUMBER_OF_DIMENSIONS]; + size_t dimensionIndexPointerCounter = 0; + int maxInStackPositionNumber = 0; int temporalPositionIndex = 0; int maxTemporalPositionIndex = 0; - //int temporalPositionIdentifier = 0; - int locationsInAcquisitionPhilips = 0; - int imagesInAcquisition = 0; - //int sumSliceNumberMrPhilips = 0; - int sliceNumberMrPhilips = 0; - int numberOfFrames = 0; - //int MRImageGradientOrientationNumber = 0; - //int minGradNum = kMaxDTI4D + 1; - //int maxGradNum = -1; - int numberOfDynamicScans = 0; - //int mRSeriesAcquisitionNumber = 0; - uint32_t lLength; - uint32_t groupElement; - long lPos = 0; - bool isPhilipsDerived = false; - //bool isPhilipsDiffusion = false; - if (isPart10prefix) { //for part 10 files, skip preamble and prefix - lPos = 128+4; //4-byte signature starts at 128 - groupElement = buffer[lPos] | (buffer[lPos+1] << 8) | (buffer[lPos+2] << 16) | (buffer[lPos+3] << 24); - if (groupElement != kStart) - printMessage("DICOM appears corrupt: first group:element should be 0x0002:0x0000 '%s'\n", fname); - } else { //no isPart10prefix - need to work out if this is explicit VR! - if (isVerbose > 1) - printMessage("DICOM preamble and prefix missing: this is not a valid DICOM image.\n"); - //See Toshiba Aquilion images from https://www.aliza-dicom-viewer.com/download/datasets - lLength = buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24); - if (lLength > fileLen) { - if (isVerbose > 1) - printMessage("Guessing this is an explicit VR image.\n"); - d.isExplicitVR = true; - } - } - char vr[2]; - //float intenScalePhilips = 0.0; - char seriesTimeTxt[kDICOMStr] = ""; - char acquisitionDateTimeTxt[kDICOMStr] = ""; - char imageType1st[kDICOMStr] = ""; - bool isEncapsulatedData = false; - int multiBandFactor = 0; - int frequencyRows = 0; - int numberOfImagesInMosaic = 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) - bool isOrient = false; - //bool isDcm4Che = false; - bool isMoCo = false; - bool isPaletteColor = false; - bool isInterpolated = false; - bool isIconImageSequence = false; - int sqDepthIcon = -1; - bool isSwitchToImplicitVR = false; - bool isSwitchToBigEndian = false; - bool isAtFirstPatientPosition = false; //for 3d and 4d files: flag is true for slices at same position as first slice - bool isMosaic = false; + //int temporalPositionIdentifier = 0; + int locationsInAcquisitionPhilips = 0; + int imagesInAcquisition = 0; + //int sumSliceNumberMrPhilips = 0; + int sliceNumberMrPhilips = 0; + int numberOfFrames = 0; + //int MRImageGradientOrientationNumber = 0; + //int minGradNum = kMaxDTI4D + 1; + //int maxGradNum = -1; + int numberOfDynamicScans = 0; + //int mRSeriesAcquisitionNumber = 0; + uint32_t lLength; + uint32_t groupElement; + long lPos = 0; + bool isPhilipsDerived = false; + //bool isPhilipsDiffusion = false; + if (isPart10prefix) { //for part 10 files, skip preamble and prefix + lPos = 128 + 4; //4-byte signature starts at 128 + groupElement = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); + if (groupElement != kStart) + printMessage("DICOM appears corrupt: first group:element should be 0x0002:0x0000 '%s'\n", fname); + } else { //no isPart10prefix - need to work out if this is explicit VR! + if (isVerbose > 1) + printMessage("DICOM preamble and prefix missing: this is not a valid DICOM image.\n"); + //See Toshiba Aquilion images from https://www.aliza-dicom-viewer.com/download/datasets + lLength = buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24); + if (lLength > fileLen) { + if (isVerbose > 1) + printMessage("Guessing this is an explicit VR image.\n"); + d.isExplicitVR = true; + } + } + char vr[2]; + //float intenScalePhilips = 0.0; + char seriesTimeTxt[kDICOMStr] = ""; + char acquisitionDateTimeTxt[kDICOMStr] = ""; + char imageType1st[kDICOMStr] = ""; + bool isEncapsulatedData = false; + int multiBandFactor = 0; + int frequencyRows = 0; + int numberOfImagesInMosaic = 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) + bool isOrient = false; + //bool isDcm4Che = false; + bool isMoCo = false; + bool isPaletteColor = false; + bool isInterpolated = false; + bool isIconImageSequence = false; + int sqDepthIcon = -1; + bool isSwitchToImplicitVR = false; + bool isSwitchToBigEndian = false; + bool isAtFirstPatientPosition = false; //for 3d and 4d files: flag is true for slices at same position as first slice + bool isMosaic = false; bool isGEfieldMap = false; //issue501 - int patientPositionNum = 0; - float B0Philips = -1.0; - float vRLPhilips = 0.0; - float vAPPhilips = 0.0; - float vFHPhilips = 0.0; - bool isPhase = false; - bool isReal = false; - bool isImaginary = false; - bool isMagnitude = false; - d.seriesNum = -1; - //start issue 372: - vec3 sliceV; //cross-product of kOrientation 0020,0037 - sliceV.v[0] = NAN; - float sliceMM[kMaxSlice2D]; - int nSliceMM = 0; - float minSliceMM = INFINITY; - float maxSliceMM = -INFINITY; - float minDynamicScanBeginTime = INFINITY; - float maxDynamicScanBeginTime = -INFINITY; - float minPatientPosition[4] = {NAN, NAN, NAN, NAN}; - float maxPatientPosition[4] = {NAN, NAN, NAN, NAN}; - //end issue 372 - //float frameAcquisitionDuration = 0.0; //issue369 - float patientPositionPrivate[4] = {NAN, NAN, NAN, NAN}; - float patientPosition[4] = {NAN, NAN, NAN, NAN}; //used to compute slice direction for Philips 4D - //float patientPositionPublic[4] = {NAN, NAN, NAN, NAN}; //used to compute slice direction for Philips 4D - float patientPositionEndPhilips[4] = {NAN, NAN, NAN, NAN}; - float patientPositionStartPhilips[4] = {NAN, NAN, NAN, NAN}; - //struct TDTI philDTI[kMaxDTI4D]; - //for (int i = 0; i < kMaxDTI4D; i++) - // philDTI[i].V[0] = -1; - //array for storing DimensionIndexValues + int patientPositionNum = 0; + float B0Philips = -1.0; + float vRLPhilips = 0.0; + float vAPPhilips = 0.0; + float vFHPhilips = 0.0; + bool isPhase = false; + bool isReal = false; + bool isImaginary = false; + bool isMagnitude = false; + d.seriesNum = -1; + //start issue 372: + vec3 sliceV; //cross-product of kOrientation 0020,0037 + sliceV.v[0] = NAN; + float sliceMM[kMaxSlice2D]; + int nSliceMM = 0; + float minSliceMM = INFINITY; + float maxSliceMM = -INFINITY; + float minDynamicScanBeginTime = INFINITY; + float maxDynamicScanBeginTime = -INFINITY; + float minPatientPosition[4] = {NAN, NAN, NAN, NAN}; + float maxPatientPosition[4] = {NAN, NAN, NAN, NAN}; + //end issue 372 + //float frameAcquisitionDuration = 0.0; //issue369 + float patientPositionPrivate[4] = {NAN, NAN, NAN, NAN}; + float patientPosition[4] = {NAN, NAN, NAN, NAN}; //used to compute slice direction for Philips 4D + //float patientPositionPublic[4] = {NAN, NAN, NAN, NAN}; //used to compute slice direction for Philips 4D + float patientPositionEndPhilips[4] = {NAN, NAN, NAN, NAN}; + float patientPositionStartPhilips[4] = {NAN, NAN, NAN, NAN}; + //struct TDTI philDTI[kMaxDTI4D]; + //for (int i = 0; i < kMaxDTI4D; i++) + // philDTI[i].V[0] = -1; + //array for storing DimensionIndexValues int numDimensionIndexValues = 0; #ifdef USING_R - // Allocating a large array on the stack, as below, vexes valgrind and may cause overflow - std::vector dcmDim(kMaxSlice2D); + // Allocating a large array on the stack, as below, vexes valgrind and may cause overflow + std::vector dcmDim(kMaxSlice2D); #else - TDCMdim dcmDim[kMaxSlice2D]; + TDCMdim dcmDim[kMaxSlice2D]; #endif - for (int i = 0; i < kMaxSlice2D; i++) { - dcmDim[i].diskPos = i; - for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; j++) - dcmDim[i].dimIdx[j] = 0; - } - //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html - //The array nestPos tracks explicit lengths for Data Element Tag of Value (FFFE,E000) - //a delimiter (fffe,e000) can have an explicit length, in which case there is no delimiter (fffe,e00d) - // fffe,e000 can provide explicit lengths, to demonstrate ./dcmconv +ti ex.DCM im.DCM - #define kMaxNestPost 128 - int nNestPos = 0; - size_t nestPos[kMaxNestPost]; - while ((d.imageStart == 0) && ((lPos+8+lFileOffset) < fileLen)) { - #ifndef myLoadWholeFileToReadHeader //read one segment at a time - if ((size_t)(lPos + 128) > MaxBufferSz) { //avoid overreading the file - lFileOffset = lFileOffset + lPos; - if ((lFileOffset+MaxBufferSz) > (size_t)fileLen) MaxBufferSz = fileLen - lFileOffset; + for (int i = 0; i < kMaxSlice2D; i++) { + dcmDim[i].diskPos = i; + for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; j++) + dcmDim[i].dimIdx[j] = 0; + } +//http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html +//The array nestPos tracks explicit lengths for Data Element Tag of Value (FFFE,E000) +//a delimiter (fffe,e000) can have an explicit length, in which case there is no delimiter (fffe,e00d) +// fffe,e000 can provide explicit lengths, to demonstrate ./dcmconv +ti ex.DCM im.DCM +#define kMaxNestPost 128 + int nNestPos = 0; + size_t nestPos[kMaxNestPost]; + while ((d.imageStart == 0) && ((lPos + 8 + lFileOffset) < fileLen)) { +#ifndef myLoadWholeFileToReadHeader //read one segment at a time + if ((size_t)(lPos + 128) > MaxBufferSz) { //avoid overreading the file + lFileOffset = lFileOffset + lPos; + if ((lFileOffset + MaxBufferSz) > (size_t)fileLen) + MaxBufferSz = fileLen - lFileOffset; fseek(file, lFileOffset, SEEK_SET); size_t sz = fread(buffer, 1, MaxBufferSz, file); if (sz < MaxBufferSz) { - printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname); - fclose(file); - return d; - } + printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname); + fclose(file); + return d; + } lPos = 0; - } - #endif - if (d.isLittleEndian) - groupElement = buffer[lPos] | (buffer[lPos+1] << 8) | (buffer[lPos+2] << 16) | (buffer[lPos+3] << 24); - else - groupElement = buffer[lPos+1] | (buffer[lPos] << 8) | (buffer[lPos+3] << 16) | (buffer[lPos+2] << 24); - if ((isSwitchToBigEndian) && ((groupElement & 0xFFFF) != 2)) { - isSwitchToBigEndian = false; - d.isLittleEndian = false; - groupElement = buffer[lPos+1] | (buffer[lPos] << 8) | (buffer[lPos+3] << 16) | (buffer[lPos+2] << 24); - }//transfer syntax requests switching endian after group 0002 - if ((isSwitchToImplicitVR) && ((groupElement & 0xFFFF) != 2)) { - isSwitchToImplicitVR = false; - d.isExplicitVR = false; - } //transfer syntax requests switching VR after group 0001 - //uint32_t group = (groupElement & 0xFFFF); - lPos += 4; - //issue409 - icons can have their own sub-sections... keep reading until we get to the icon image? - //if ((groupElement == kItemDelimitationTag) || (groupElement == kSequenceDelimitationItemTag)) isIconImageSequence = false; - //if (groupElement == kItemTag) sqDepth++; - bool unNest = false; - while ((nNestPos > 0) && (nestPos[nNestPos] <= (lFileOffset+lPos))) { - nNestPos--; - sqDepth--; - unNest = true; - if ((sqDepthIcon >= 0) && (sqDepth <= sqDepthIcon)) { //issue415 - sqDepthIcon = -1; - isIconImageSequence = false; } - - } - if (groupElement == kItemDelimitationTag) { //end of item with undefined length - sqDepth--; - unNest = true; - if ((sqDepthIcon >= 0) && (sqDepth <= sqDepthIcon)) { //issue415 - sqDepthIcon = -1; - isIconImageSequence = false; +#endif + if (d.isLittleEndian) + groupElement = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); + else + groupElement = buffer[lPos + 1] | (buffer[lPos] << 8) | (buffer[lPos + 3] << 16) | (buffer[lPos + 2] << 24); + if ((isSwitchToBigEndian) && ((groupElement & 0xFFFF) != 2)) { + isSwitchToBigEndian = false; + d.isLittleEndian = false; + groupElement = buffer[lPos + 1] | (buffer[lPos] << 8) | (buffer[lPos + 3] << 16) | (buffer[lPos + 2] << 24); + } //transfer syntax requests switching endian after group 0002 + if ((isSwitchToImplicitVR) && ((groupElement & 0xFFFF) != 2)) { + isSwitchToImplicitVR = false; + d.isExplicitVR = false; + } //transfer syntax requests switching VR after group 0001 + //uint32_t group = (groupElement & 0xFFFF); + lPos += 4; + //issue409 - icons can have their own sub-sections... keep reading until we get to the icon image? + //if ((groupElement == kItemDelimitationTag) || (groupElement == kSequenceDelimitationItemTag)) isIconImageSequence = false; + //if (groupElement == kItemTag) sqDepth++; + bool unNest = false; + while ((nNestPos > 0) && (nestPos[nNestPos] <= (lFileOffset + lPos))) { + nNestPos--; + sqDepth--; + unNest = true; + if ((sqDepthIcon >= 0) && (sqDepth <= sqDepthIcon)) { //issue415 + sqDepthIcon = -1; + isIconImageSequence = false; + } } - } - - if (unNest) { - is2005140FSQ = false; - if (sqDepth < 0) sqDepth = 0; //should not happen, but protect for faulty anonymization - //if we leave the folder MREchoSequence 0018,9114 - if (( nDimIndxVal > 0) && ((d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_BRUKER) || (d.manufacturer == kMANUFACTURER_PHILIPS)) && (sqDepth00189114 >= sqDepth)) { - sqDepth00189114 = -1; //triggered - //printf("slice %d---> 0020,9157 = %d %d %d\n", inStackPositionNumber, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2]); - if (inStackPositionNumber > 0) { - //for images without SliceNumberMrPhilips (2001,100A) - int sliceNumber = inStackPositionNumber; - //printf("slice %d \n", sliceNumber); - if ((sliceNumber == 1) && (!isnan(patientPosition[1])) ) { - for (int k = 0; k < 4; k++) - patientPositionStartPhilips[k] = patientPosition[k]; - } else if ((sliceNumber == 1) && (!isnan(patientPositionPrivate[1]))) { - for (int k = 0; k < 4; k++) - patientPositionStartPhilips[k] = patientPositionPrivate[k]; + if (groupElement == kItemDelimitationTag) { //end of item with undefined length + sqDepth--; + unNest = true; + if ((sqDepthIcon >= 0) && (sqDepth <= sqDepthIcon)) { //issue415 + sqDepthIcon = -1; + isIconImageSequence = false; + } + } + if (unNest) { + is2005140FSQ = false; + if (sqDepth < 0) + sqDepth = 0; //should not happen, but protect for faulty anonymization + //if we leave the folder MREchoSequence 0018,9114 + if ((nDimIndxVal > 0) && ((d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_BRUKER) || (d.manufacturer == kMANUFACTURER_PHILIPS)) && (sqDepth00189114 >= sqDepth)) { + sqDepth00189114 = -1; //triggered + //printf("slice %d---> 0020,9157 = %d %d %d\n", inStackPositionNumber, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2]); + if (inStackPositionNumber > 0) { + //for images without SliceNumberMrPhilips (2001,100A) + int sliceNumber = inStackPositionNumber; + //printf("slice %d \n", sliceNumber); + if ((sliceNumber == 1) && (!isnan(patientPosition[1]))) { + for (int k = 0; k < 4; k++) + patientPositionStartPhilips[k] = patientPosition[k]; + } else if ((sliceNumber == 1) && (!isnan(patientPositionPrivate[1]))) { + for (int k = 0; k < 4; k++) + patientPositionStartPhilips[k] = patientPositionPrivate[k]; + } + if ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPosition[1]))) { + for (int k = 0; k < 4; k++) + patientPositionEndPhilips[k] = patientPosition[k]; + } else if ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPositionPrivate[1]))) { + for (int k = 0; k < 4; k++) + patientPositionEndPhilips[k] = patientPositionPrivate[k]; + } + patientPosition[1] = NAN; + patientPositionPrivate[1] = NAN; } - if ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPosition[1])) ) { - for (int k = 0; k < 4; k++) - patientPositionEndPhilips[k] = patientPosition[k]; - } else if ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPositionPrivate[1])) ) { - for (int k = 0; k < 4; k++) - patientPositionEndPhilips[k] = patientPositionPrivate[k]; + inStackPositionNumber = 0; + if (numDimensionIndexValues >= kMaxSlice2D) { + printError("Too many slices to track dimensions. Only up to %d are supported\n", kMaxSlice2D); + break; } - patientPosition[1] = NAN; - patientPositionPrivate[1] = NAN; - } - inStackPositionNumber = 0; - if (numDimensionIndexValues >= kMaxSlice2D) { - printError("Too many slices to track dimensions. Only up to %d are supported\n", kMaxSlice2D); - break; - } - uint32_t dimensionIndexOrder[MAX_NUMBER_OF_DIMENSIONS]; - for(size_t i = 0; i < nDimIndxVal; i++) - dimensionIndexOrder[i] = i; - - // Bruker Enhanced MR IOD: reorder dimensions to ensure InStackPositionNumber corresponds to the first one - // This will ensure correct ordering of slices in 4D datasets - /* - if (d.manufacturer == kMANUFACTURER_BRUKER) { - for(size_t i = 1; i < dimensionIndexPointerCounter; i++){ - if (dimensionIndexPointer[i] == kInStackPositionNumber){ - //swap with first - dimensionIndexOrder[i] = 0; - dimensionIndexOrder[0] = i; - } - } - }*/ //Canon and Bruker reverse dimensionIndexItem order relative to Philips: new versions introduce compareTDCMdimRev - int ndim = nDimIndxVal; - //printf("%d: %d %d %d %d\n", ndim, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2], d.dimensionIndexValues[3]); - for (int i = 0; i < ndim; i++) - dcmDim[numDimensionIndexValues].dimIdx[i] = d.dimensionIndexValues[dimensionIndexOrder[i]]; - dcmDim[numDimensionIndexValues].TE = TE; - dcmDim[numDimensionIndexValues].intenScale = d.intenScale; - dcmDim[numDimensionIndexValues].intenIntercept = d.intenIntercept; - dcmDim[numDimensionIndexValues].isPhase = isPhase; - dcmDim[numDimensionIndexValues].isReal = isReal; - dcmDim[numDimensionIndexValues].isImaginary = isImaginary; - dcmDim[numDimensionIndexValues].intenScalePhilips = d.intenScalePhilips; - dcmDim[numDimensionIndexValues].RWVScale = d.RWVScale; - dcmDim[numDimensionIndexValues].RWVIntercept = d.RWVIntercept; - if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime)) - dcmDim[numDimensionIndexValues].triggerDelayTime = 0.0; //issue395 - else - dcmDim[numDimensionIndexValues].triggerDelayTime = d.triggerDelayTime; - dcmDim[numDimensionIndexValues].V[0] = -1.0; - #ifdef MY_DEBUG - if (numDimensionIndexValues < 19) { - printMessage("dimensionIndexValues0020x9157[%d] = [", numDimensionIndexValues); + uint32_t dimensionIndexOrder[MAX_NUMBER_OF_DIMENSIONS]; + for (size_t i = 0; i < nDimIndxVal; i++) + dimensionIndexOrder[i] = i; + // Bruker Enhanced MR IOD: reorder dimensions to ensure InStackPositionNumber corresponds to the first one + // This will ensure correct ordering of slices in 4D datasets + //Canon and Bruker reverse dimensionIndexItem order relative to Philips: new versions introduce compareTDCMdimRev + int ndim = nDimIndxVal; + //printf("%d: %d %d %d %d\n", ndim, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2], d.dimensionIndexValues[3]); for (int i = 0; i < ndim; i++) - printMessage("%d ", d.dimensionIndexValues[i]); - printMessage("]\n"); - //printMessage("B0= %g num=%d\n", B0Philips, gradNum); - } else return d; - #endif - //next: add diffusion if reported - if (B0Philips >= 0.0) { //diffusion parameters - // Philips does not always provide 2005,1413 (MRImageGradientOrientationNumber) and sometimes after dimensionIndexValues - /*int gradNum = 0; + dcmDim[numDimensionIndexValues].dimIdx[i] = d.dimensionIndexValues[dimensionIndexOrder[i]]; + dcmDim[numDimensionIndexValues].TE = TE; + dcmDim[numDimensionIndexValues].intenScale = d.intenScale; + dcmDim[numDimensionIndexValues].intenIntercept = d.intenIntercept; + dcmDim[numDimensionIndexValues].isPhase = isPhase; + dcmDim[numDimensionIndexValues].isReal = isReal; + dcmDim[numDimensionIndexValues].isImaginary = isImaginary; + dcmDim[numDimensionIndexValues].intenScalePhilips = d.intenScalePhilips; + dcmDim[numDimensionIndexValues].RWVScale = d.RWVScale; + dcmDim[numDimensionIndexValues].RWVIntercept = d.RWVIntercept; + if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime)) + dcmDim[numDimensionIndexValues].triggerDelayTime = 0.0; //issue395 + else + dcmDim[numDimensionIndexValues].triggerDelayTime = d.triggerDelayTime; + dcmDim[numDimensionIndexValues].V[0] = -1.0; +#ifdef MY_DEBUG + if (numDimensionIndexValues < 19) { + printMessage("dimensionIndexValues0020x9157[%d] = [", numDimensionIndexValues); + for (int i = 0; i < ndim; i++) + printMessage("%d ", d.dimensionIndexValues[i]); + printMessage("]\n"); + //printMessage("B0= %g num=%d\n", B0Philips, gradNum); + } else + return d; +#endif + //next: add diffusion if reported + if (B0Philips >= 0.0) { //diffusion parameters + // Philips does not always provide 2005,1413 (MRImageGradientOrientationNumber) and sometimes after dimensionIndexValues + /*int gradNum = 0; for (int i = 0; i < ndim; i++) if (d.dimensionIndexValues[i] > 0) gradNum = d.dimensionIndexValues[i]; if (gradNum <= 0) break; @@ -4743,2330 +4651,2330 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); next two lines attempt to skip ADC maps we could also increment gradNum for ADC if we wanted... */ - if (isPhilipsDerived) { - //gradNum ++; - B0Philips = 2000.0; - vRLPhilips = 0.0; - vAPPhilips = 0.0; - vFHPhilips = 0.0; - } - if (B0Philips == 0.0) { - //printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips); - vRLPhilips = 0.0; - vAPPhilips = 0.0; - vFHPhilips = 0.0; - } - //if ((MRImageGradientOrientationNumber > 0) && ((gradNum != MRImageGradientOrientationNumber)) break; - /*if (gradNum < minGradNum) minGradNum = gradNum; + if (isPhilipsDerived) { + //gradNum ++; + B0Philips = 2000.0; + vRLPhilips = 0.0; + vAPPhilips = 0.0; + vFHPhilips = 0.0; + } + if (B0Philips == 0.0) { + //printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips); + vRLPhilips = 0.0; + vAPPhilips = 0.0; + vFHPhilips = 0.0; + } + //if ((MRImageGradientOrientationNumber > 0) && ((gradNum != MRImageGradientOrientationNumber)) break; + /*if (gradNum < minGradNum) minGradNum = gradNum; if (gradNum >= maxGradNum) maxGradNum = gradNum; if (gradNum >= kMaxDTI4D) { printError("Number of DTI gradients exceeds 'kMaxDTI4D (%d).\n", kMaxDTI4D); } else { - gradNum = gradNum - 1;//index from 0 + gradNum = gradNum - 1; //index from 0 philDTI[gradNum].V[0] = B0Philips; philDTI[gradNum].V[1] = vRLPhilips; philDTI[gradNum].V[2] = vAPPhilips; philDTI[gradNum].V[3] = vFHPhilips; }*/ - dcmDim[numDimensionIndexValues].V[0] = B0Philips; - dcmDim[numDimensionIndexValues].V[1] = vRLPhilips; - dcmDim[numDimensionIndexValues].V[2] = vAPPhilips; - dcmDim[numDimensionIndexValues].V[3] = vFHPhilips; - isPhilipsDerived = false; - //printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips); - //!!! 16032018 : next line as well as definition of B0Philips may need to be set to zero if Philips omits DiffusionBValue tag for B=0 - B0Philips = -1.0; //Philips may skip reporting B-values for B=0 volumes, so zero these - vRLPhilips = 0.0; - vAPPhilips = 0.0; - vFHPhilips = 0.0; - //MRImageGradientOrientationNumber = 0; - }//diffusion parameters - numDimensionIndexValues ++; - nDimIndxVal = -1; //we need DimensionIndexValues - } //record dimensionIndexValues slice information - } //groupElement == kItemDelimitationTag : delimit item exits folder - if (groupElement == kItemTag) { - uint32_t slen = dcmInt(4,&buffer[lPos],d.isLittleEndian); - uint32_t kUndefinedLen = 0xFFFFFFFF; - if (slen != kUndefinedLen) { - nNestPos++; - if (nNestPos >= kMaxNestPost) nNestPos = kMaxNestPost - 1; - nestPos[nNestPos] = slen+lFileOffset+lPos; - } - lLength = 4; - sqDepth++; - //return d; - } else if (( (groupElement == kItemDelimitationTag) || (groupElement == kSequenceDelimitationItemTag)) && (!isEncapsulatedData)) { - vr[0] = 'N'; - vr[1] = 'A'; - lLength = 4; - } else if (d.isExplicitVR) { - vr[0] = buffer[lPos]; vr[1] = buffer[lPos+1]; - if (buffer[lPos+1] < 'A') {//implicit vr with 32-bit length - if (d.isLittleEndian) - lLength = buffer[lPos] | (buffer[lPos+1] << 8) | (buffer[lPos+2] << 16) | (buffer[lPos+3] << 24); - else - lLength = buffer[lPos+3] | (buffer[lPos+2] << 8) | (buffer[lPos+1] << 16) | (buffer[lPos] << 24); - lPos += 4; - } else if ( ((buffer[lPos] == 'U') && (buffer[lPos+1] == 'N')) - || ((buffer[lPos] == 'U') && (buffer[lPos+1] == 'C')) - || ((buffer[lPos] == 'U') && (buffer[lPos+1] == 'R')) - || ((buffer[lPos] == 'U') && (buffer[lPos+1] == 'T')) - || ((buffer[lPos] == 'U') && (buffer[lPos+1] == 'V')) - || ((buffer[lPos] == 'O') && (buffer[lPos+1] == 'B')) - || ((buffer[lPos] == 'O') && (buffer[lPos+1] == 'D')) - || ((buffer[lPos] == 'O') && (buffer[lPos+1] == 'F')) - || ((buffer[lPos] == 'O') && (buffer[lPos+1] == 'L')) - | ((buffer[lPos] == 'O') && (buffer[lPos+1] == 'V')) - || ((buffer[lPos] == 'O') && (buffer[lPos+1] == 'W')) - || ((buffer[lPos] == 'S') && (buffer[lPos+1] == 'V')) - ) { //VR= UN, OB, OW, SQ || ((buffer[lPos] == 'S') && (buffer[lPos+1] == 'Q')) - //for example of UC/UR/UV/OD/OF/OL/OV/SV see VR conformance test https://www.aliza-dicom-viewer.com/download/datasets - lPos = lPos + 4; //skip 2 byte VR string and 2 reserved bytes = 4 bytes - if (d.isLittleEndian) - lLength = buffer[lPos] | (buffer[lPos+1] << 8) | (buffer[lPos+2] << 16) | (buffer[lPos+3] << 24); - else - lLength = buffer[lPos+3] | (buffer[lPos+2] << 8) | (buffer[lPos+1] << 16) | (buffer[lPos] << 24); - lPos = lPos + 4; //skip 4 byte length - } else if ((buffer[lPos] == 'S') && (buffer[lPos+1] == 'Q')) { - lLength = 8; //Sequence Tag - //printMessage(" !!!SQ\t%04x,%04x\n", groupElement & 65535,groupElement>>16); - } else { //explicit VR with 16-bit length - if ((d.isLittleEndian) ) - lLength = buffer[lPos+2] | (buffer[lPos+3] << 8); - else - lLength = buffer[lPos+3] | (buffer[lPos+2] << 8); - lPos += 4; //skip 2 byte VR string and 2 length bytes = 4 bytes - } - } else { //implicit VR - vr[0] = 'U'; - vr[1] = 'N'; - if (d.isLittleEndian) - lLength = buffer[lPos] | (buffer[lPos+1] << 8) | (buffer[lPos+2] << 16) | (buffer[lPos+3] << 24); - else - lLength = buffer[lPos+3] | (buffer[lPos+2] << 8) | (buffer[lPos+1] << 16) | (buffer[lPos] << 24); - lPos += 4; //we have loaded the 32-bit length - if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isSQ(groupElement))) { //https://github.com/rordenlab/dcm2niix/issues/144 - vr[0] = 'S'; - vr[1] = 'Q'; - lLength = 0; //Do not skip kItemTag - required to determine nesting of Philips Enhanced - } - if ((d.manufacturer != kMANUFACTURER_PHILIPS) && (isSQ(groupElement))) { //https://github.com/rordenlab/dcm2niix/issues/144 - vr[0] = 'S'; - vr[1] = 'Q'; - lLength = 0; //Do not skip kItemTag - required to determine nesting of Philips Enhanced - } - } //if explicit else implicit VR - if (lLength == 0xFFFFFFFF) { - lLength = 8; //SQ (Sequences) use 0xFFFFFFFF [4294967295] to denote unknown length - //09032018 - do not count these as SQs: Horos does not count even groups - //uint32_t special = dcmInt(4,&buffer[lPos],d.isLittleEndian); - - //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html - - //if (special != ksqDelim) { - vr[0] = 'S'; - vr[1] = 'Q'; - //} - } - /* //Handle SQs: for explicit these have VR=SQ - if ((vr[0] == 'S') && (vr[1] == 'Q')) { - //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html - uint32_t special = dcmInt(4,&buffer[lPos],d.isLittleEndian); - uint32_t slen = dcmInt(4,&buffer[lPos+4],d.isLittleEndian); - //if (d.isExplicitVR) - // slen = dcmInt(4,&buffer[lPos+8],d.isLittleEndian); + dcmDim[numDimensionIndexValues].V[0] = B0Philips; + dcmDim[numDimensionIndexValues].V[1] = vRLPhilips; + dcmDim[numDimensionIndexValues].V[2] = vAPPhilips; + dcmDim[numDimensionIndexValues].V[3] = vFHPhilips; + isPhilipsDerived = false; + //printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips); + //!!! 16032018 : next line as well as definition of B0Philips may need to be set to zero if Philips omits DiffusionBValue tag for B=0 + B0Philips = -1.0; //Philips may skip reporting B-values for B=0 volumes, so zero these + vRLPhilips = 0.0; + vAPPhilips = 0.0; + vFHPhilips = 0.0; + //MRImageGradientOrientationNumber = 0; + } //diffusion parameters + numDimensionIndexValues++; + nDimIndxVal = -1; //we need DimensionIndexValues + } //record dimensionIndexValues slice information + } //groupElement == kItemDelimitationTag : delimit item exits folder + if (groupElement == kItemTag) { + uint32_t slen = dcmInt(4, &buffer[lPos], d.isLittleEndian); uint32_t kUndefinedLen = 0xFFFFFFFF; - //printError(" SPECIAL >>>>t%04x,%04x %08x %08x\n", groupElement & 65535,groupElement>>16, special, slen); + if (slen != kUndefinedLen) { + nNestPos++; + if (nNestPos >= kMaxNestPost) + nNestPos = kMaxNestPost - 1; + nestPos[nNestPos] = slen + lFileOffset + lPos; + } + lLength = 4; + sqDepth++; //return d; - is2005140FSQ = (groupElement == kPrivatePerFrameSq); - //if (isNextSQis2005140FSQ) is2005140FSQ = true; - //isNextSQis2005140FSQ = false; - if (special == kSequenceDelimitationItemTag) { - //unknown - } else if (slen == kUndefinedLen) { - sqDepth++; - if ((sqDepthPrivate == 0) && ((groupElement & 65535) % 2)) - sqDepthPrivate = sqDepth; //in a private SQ: ignore contents - } else if ((is2005140FSQ) || ((groupElement & 65535) % 2)) {//private SQ of known length - lets jump over this! - slen = lFileOffset + lPos + slen; - if ((sqEndPrivate < 0) || (slen > sqEndPrivate)) //sqEndPrivate is signed - sqEndPrivate = slen; //if nested private SQs, remember the end address of the top parent SQ + } else if (((groupElement == kItemDelimitationTag) || (groupElement == kSequenceDelimitationItemTag)) && (!isEncapsulatedData)) { + vr[0] = 'N'; + vr[1] = 'A'; + lLength = 4; + } else if (d.isExplicitVR) { + vr[0] = buffer[lPos]; + vr[1] = buffer[lPos + 1]; + if (buffer[lPos + 1] < 'A') { //implicit vr with 32-bit length + if (d.isLittleEndian) + lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); + else + lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24); + lPos += 4; + } else if (((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'N')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'C')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'R')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'T')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'V')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'B')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'D')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'F')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'L')) | ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'V')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'W')) || ((buffer[lPos] == 'S') && (buffer[lPos + 1] == 'V'))) { //VR= UN, OB, OW, SQ || ((buffer[lPos] == 'S') && (buffer[lPos+1] == 'Q')) + //for example of UC/UR/UV/OD/OF/OL/OV/SV see VR conformance test https://www.aliza-dicom-viewer.com/download/datasets + lPos = lPos + 4; //skip 2 byte VR string and 2 reserved bytes = 4 bytes + if (d.isLittleEndian) + lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); + else + lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24); + lPos = lPos + 4; //skip 4 byte length + } else if ((buffer[lPos] == 'S') && (buffer[lPos + 1] == 'Q')) { + lLength = 8; //Sequence Tag + //printMessage(" !!!SQ\t%04x,%04x\n", groupElement & 65535,groupElement>>16); + } else { //explicit VR with 16-bit length + if ((d.isLittleEndian)) + lLength = buffer[lPos + 2] | (buffer[lPos + 3] << 8); + else + lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8); + lPos += 4; //skip 2 byte VR string and 2 length bytes = 4 bytes + } + } else { //implicit VR + vr[0] = 'U'; + vr[1] = 'N'; + if (d.isLittleEndian) + lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); + else + lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24); + lPos += 4; //we have loaded the 32-bit length + if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isSQ(groupElement))) { //https://github.com/rordenlab/dcm2niix/issues/144 + vr[0] = 'S'; + vr[1] = 'Q'; + lLength = 0; //Do not skip kItemTag - required to determine nesting of Philips Enhanced + } + if ((d.manufacturer != kMANUFACTURER_PHILIPS) && (isSQ(groupElement))) { //https://github.com/rordenlab/dcm2niix/issues/144 + vr[0] = 'S'; + vr[1] = 'Q'; + lLength = 0; //Do not skip kItemTag - required to determine nesting of Philips Enhanced + } + } //if explicit else implicit VR + if (lLength == 0xFFFFFFFF) { + lLength = 8; //SQ (Sequences) use 0xFFFFFFFF [4294967295] to denote unknown length + //09032018 - do not count these as SQs: Horos does not count even groups + //uint32_t special = dcmInt(4,&buffer[lPos],d.isLittleEndian); + //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html + //if (special != ksqDelim) { + vr[0] = 'S'; + vr[1] = 'Q'; + //} + } + if ((groupElement == kItemTag) && (isEncapsulatedData)) { //use this to find image fragment for compressed datasets, e.g. JPEG transfer syntax + d.imageBytes = dcmInt(4, &buffer[lPos], d.isLittleEndian); + lPos = lPos + 4; + lLength = d.imageBytes; + if (d.imageBytes > 128) { + encapsulatedDataFragments++; + if (encapsulatedDataFragmentStart == 0) + encapsulatedDataFragmentStart = (int)lPos + (int)lFileOffset; } } - //next: look for required tags - if ((groupElement == kItemTag) && (isEncapsulatedData)) { - d.imageBytes = dcmInt(4,&buffer[lPos],d.isLittleEndian); - printMessage("compressed data %d-> %ld\n",d.imageBytes, lPos); - - d.imageBytes = dcmInt(4,&buffer[lPos-4],d.isLittleEndian); - printMessage("compressed data %d-> %ld\n",d.imageBytes, lPos); - if (d.imageBytes > 128) { - encapsulatedDataFragments++; - if (encapsulatedDataFragmentStart == 0) - encapsulatedDataFragmentStart = (int)lPos + (int)lFileOffset; - } - } - if ((sqEndPrivate > 0) && ((lFileOffset + lPos) > sqEndPrivate)) - sqEndPrivate = -1; //end of private SQ with defined length - if (groupElement == kSequenceDelimitationItemTag) { //end of private SQ with undefined length - sqDepth--; - if (sqDepth < sqDepthPrivate) { - sqDepthPrivate = 0; //no longer in a private SQ - } - } - if (sqDepth < 0) sqDepth = 0;*/ - if ((groupElement == kItemTag) && (isEncapsulatedData)) { //use this to find image fragment for compressed datasets, e.g. JPEG transfer syntax - d.imageBytes = dcmInt(4,&buffer[lPos],d.isLittleEndian); - lPos = lPos + 4; - lLength = d.imageBytes; - if (d.imageBytes > 128) { - encapsulatedDataFragments++; - if (encapsulatedDataFragmentStart == 0) - encapsulatedDataFragmentStart = (int)lPos + (int)lFileOffset; - } - } - if ((isIconImageSequence) && ((groupElement & 0x0028) == 0x0028 )) groupElement = kUnused; //ignore icon dimensions - #ifdef salvageAgfa //issue435 + if ((isIconImageSequence) && ((groupElement & 0x0028) == 0x0028)) + groupElement = kUnused; //ignore icon dimensions +#ifdef salvageAgfa //issue435 //Handle remapping using integers, and slower but simpler approach is with strings: // https://github.com/pydicom/pydicom/blob/master/pydicom/_private_dict.py - if (((groupElement & 65535) % 2) == 0) goto skipRemap; //remap odd (private) groups + if (((groupElement & 65535) % 2) == 0) + goto skipRemap; //remap odd (private) groups //printf("tag %04x,%04x\n", groupElement & 65535, groupElement >> 16); - - if (((groupElement>>16) >= 0x10) && ((groupElement>>16) <= 0xFF)) { //tags (gggg,0010-00FF) may define new remapping + if (((groupElement >> 16) >= 0x10) && ((groupElement >> 16) <= 0xFF)) { //tags (gggg,0010-00FF) may define new remapping //if remapping tag - //first: see if this remapping overwrites existing tag - uint32_t privateCreatorMask = 0; //0 -> none - uint32_t privateCreatorRemap = 0; //0 -> none + //first: see if this remapping overwrites existing tag + uint32_t privateCreatorMask = 0; //0 -> none + uint32_t privateCreatorRemap = 0; //0 -> none privateCreatorMask = (groupElement & 65535) + ((groupElement & 0xFFFF0000) << 8); if (nRemaps > 0) { int j = 0; for (int i = 0; i < nRemaps; i++) //remove duplicate remapping //copy all remaps except exact match - if (privateCreatorMasks[i] != privateCreatorMask) { + if (privateCreatorMasks[i] != privateCreatorMask) { privateCreatorMasks[j] = privateCreatorMasks[i]; privateCreatorRemaps[j] = privateCreatorRemaps[i]; j++; } nRemaps = j; - } + } //see if this is known private vendor tag privateCreatorRemap = 0; char privateCreator[kDICOMStr]; dcmStr(lLength, &buffer[lPos], privateCreator); //next lines determine remapping, append as needed - //Siemens https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/siemens.tpl - if (strstr(privateCreator, "SIEMENS MR HEADER") != NULL) privateCreatorRemap = 0x0019 +(0x1000 << 16 ); - if (strstr(privateCreator, "SIEMENS MR SDS 01") != NULL) privateCreatorRemap = 0x0021 +(0x1000 << 16 ); - if (strstr(privateCreator, "SIEMENS MR SDI 02") != NULL) privateCreatorRemap = 0x0021 +(0x1100 << 16 ); - if (strstr(privateCreator, "SIEMENS CSA HEADER") != NULL) privateCreatorRemap = 0x0029 +(0x1000 << 16 ); - if (strstr(privateCreator, "SIEMENS MR HEADER") != NULL) privateCreatorRemap = 0x0051 +(0x1000 << 16 ); + //Siemens https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/siemens.tpl + if (strstr(privateCreator, "SIEMENS MR HEADER") != NULL) + privateCreatorRemap = 0x0019 + (0x1000 << 16); + if (strstr(privateCreator, "SIEMENS MR SDS 01") != NULL) + privateCreatorRemap = 0x0021 + (0x1000 << 16); + if (strstr(privateCreator, "SIEMENS MR SDI 02") != NULL) + privateCreatorRemap = 0x0021 + (0x1100 << 16); + if (strstr(privateCreator, "SIEMENS CSA HEADER") != NULL) + privateCreatorRemap = 0x0029 + (0x1000 << 16); + if (strstr(privateCreator, "SIEMENS MR HEADER") != NULL) + privateCreatorRemap = 0x0051 + (0x1000 << 16); //GE https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/gems.tpl - if (strstr(privateCreator, "GEMS_ACQU_01") != NULL) privateCreatorRemap = 0x0019 +(0x1000 << 16 ); - if (strstr(privateCreator, "GEMS_RELA_01") != NULL) privateCreatorRemap = 0x0021 +(0x1000 << 16 ); - if (strstr(privateCreator, "GEMS_SERS_01") != NULL) privateCreatorRemap = 0x0025 +(0x1000 << 16 ); - if (strstr(privateCreator, "GEMS_PARM_01") != NULL) privateCreatorRemap = 0x0043 +(0x1000 << 16 ); + if (strstr(privateCreator, "GEMS_ACQU_01") != NULL) + privateCreatorRemap = 0x0019 + (0x1000 << 16); + if (strstr(privateCreator, "GEMS_RELA_01") != NULL) + privateCreatorRemap = 0x0021 + (0x1000 << 16); + if (strstr(privateCreator, "GEMS_SERS_01") != NULL) + privateCreatorRemap = 0x0025 + (0x1000 << 16); + if (strstr(privateCreator, "GEMS_PARM_01") != NULL) + privateCreatorRemap = 0x0043 + (0x1000 << 16); //ELSCINT https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/elscint.tpl int grp = (groupElement & 65535); - if ((grp == 0x07a1) && (strstr(privateCreator, "ELSCINT1") != NULL)) privateCreatorRemap = 0x07a1 +(0x1000 << 16 ); - if ((grp == 0x07a3) && (strstr(privateCreator, "ELSCINT1") != NULL)) privateCreatorRemap = 0x07a3 +(0x1000 << 16 ); + if ((grp == 0x07a1) && (strstr(privateCreator, "ELSCINT1") != NULL)) + privateCreatorRemap = 0x07a1 + (0x1000 << 16); + if ((grp == 0x07a3) && (strstr(privateCreator, "ELSCINT1") != NULL)) + privateCreatorRemap = 0x07a3 + (0x1000 << 16); //Philips https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/philips.tpl - if (strstr(privateCreator, "PHILIPS IMAGING DD 001") != NULL) privateCreatorRemap = 0x2001 +(0x1000 << 16 ); - if (strstr(privateCreator, "Philips Imaging DD 001") != NULL) privateCreatorRemap = 0x2001 +(0x1000 << 16 ); - if (strstr(privateCreator, "PHILIPS MR IMAGING DD 001") != NULL) privateCreatorRemap = 0x2005 +(0x1000 << 16 ); - if (strstr(privateCreator, "Philips MR Imaging DD 001") != NULL) privateCreatorRemap = 0x2005 +(0x1000 << 16 ); - if (strstr(privateCreator, "PHILIPS MR IMAGING DD 005") != NULL) privateCreatorRemap = 0x2005 +(0x1400 << 16 ); - if (strstr(privateCreator, "Philips MR Imaging DD 005") != NULL) privateCreatorRemap = 0x2005 +(0x1400 << 16 ); + if (strstr(privateCreator, "PHILIPS IMAGING DD 001") != NULL) + privateCreatorRemap = 0x2001 + (0x1000 << 16); + if (strstr(privateCreator, "Philips Imaging DD 001") != NULL) + privateCreatorRemap = 0x2001 + (0x1000 << 16); + if (strstr(privateCreator, "PHILIPS MR IMAGING DD 001") != NULL) + privateCreatorRemap = 0x2005 + (0x1000 << 16); + if (strstr(privateCreator, "Philips MR Imaging DD 001") != NULL) + privateCreatorRemap = 0x2005 + (0x1000 << 16); + if (strstr(privateCreator, "PHILIPS MR IMAGING DD 005") != NULL) + privateCreatorRemap = 0x2005 + (0x1400 << 16); + if (strstr(privateCreator, "Philips MR Imaging DD 005") != NULL) + privateCreatorRemap = 0x2005 + (0x1400 << 16); //UIH https://github.com/neurolabusc/dcm_qa_uih - if (strstr(privateCreator, "Image Private Header") != NULL) privateCreatorRemap = 0x0065 +(0x1000 << 16 ); + if (strstr(privateCreator, "Image Private Header") != NULL) + privateCreatorRemap = 0x0065 + (0x1000 << 16); //sanity check: group should match - if (grp != (privateCreatorRemap & 65535)) privateCreatorRemap = 0; - if (privateCreatorRemap == 0) goto skipRemap; //this is not a known private group - if (privateCreatorRemap == privateCreatorMask) goto skipRemap; //the remapping and mask are identical 2005,1000 -> 2005,1000 - if ((nRemaps + 1) >=kMaxRemaps) goto skipRemap; //all slots full (should never happen) + if (grp != (privateCreatorRemap & 65535)) + privateCreatorRemap = 0; + if (privateCreatorRemap == 0) + goto skipRemap; //this is not a known private group + if (privateCreatorRemap == privateCreatorMask) + goto skipRemap; //the remapping and mask are identical 2005,1000 -> 2005,1000 + if ((nRemaps + 1) >= kMaxRemaps) + goto skipRemap; //all slots full (should never happen) //add new remapping privateCreatorMasks[nRemaps] = privateCreatorMask; - privateCreatorRemaps[nRemaps] = privateCreatorRemap; - //printf("new remapping %04x,%04x -> %04x,%04x\n", privateCreatorMask & 65535, privateCreatorMask >> 16, privateCreatorRemap & 65535, privateCreatorRemap >> 16); + privateCreatorRemaps[nRemaps] = privateCreatorRemap; + //printf("new remapping %04x,%04x -> %04x,%04x\n", privateCreatorMask & 65535, privateCreatorMask >> 16, privateCreatorRemap & 65535, privateCreatorRemap >> 16); if (isVerbose > 1) - printMessage("new remapping (%d) %04x,%02xxy -> %04x,%02xxy\n", nRemaps, privateCreatorMask & 65535, privateCreatorMask >> 24, privateCreatorRemap & 65535, privateCreatorRemap >> 24); + printMessage("new remapping (%d) %04x,%02xxy -> %04x,%02xxy\n", nRemaps, privateCreatorMask & 65535, privateCreatorMask >> 24, privateCreatorRemap & 65535, privateCreatorRemap >> 24); nRemaps += 1; //for (int i = 0; i < nRemaps; i++) - // printf(" %d = %04x,%02xxy -> %04x,%02xxy\n", i, privateCreatorMasks[i] & 65535, privateCreatorMasks[i] >> 24, privateCreatorRemaps[i] & 65535, privateCreatorRemaps[i] >> 24); - goto skipRemap; + // printf(" %d = %04x,%02xxy -> %04x,%02xxy\n", i, privateCreatorMasks[i] & 65535, privateCreatorMasks[i] >> 24, privateCreatorRemaps[i] & 65535, privateCreatorRemaps[i] >> 24); + goto skipRemap; } - - if (nRemaps < 1) goto skipRemap; + if (nRemaps < 1) + goto skipRemap; { - uint32_t remappedGroupElement = 0; - for (int i = 0; i < nRemaps; i++) - if ((groupElement & 0xFF00FFFF) == (privateCreatorMasks[i] & 0xFF00FFFF)) - remappedGroupElement = privateCreatorRemaps[i] + (groupElement & 0x00FF0000); - if (remappedGroupElement == 0) goto skipRemap; - if (isVerbose > 1) - printMessage("remapping %04x,%04x -> %04x,%04x\n", groupElement & 65535, groupElement >> 16, remappedGroupElement & 65535, remappedGroupElement >> 16); - groupElement = remappedGroupElement; + uint32_t remappedGroupElement = 0; + for (int i = 0; i < nRemaps; i++) + if ((groupElement & 0xFF00FFFF) == (privateCreatorMasks[i] & 0xFF00FFFF)) + remappedGroupElement = privateCreatorRemaps[i] + (groupElement & 0x00FF0000); + if (remappedGroupElement == 0) + goto skipRemap; + if (isVerbose > 1) + printMessage("remapping %04x,%04x -> %04x,%04x\n", groupElement & 65535, groupElement >> 16, remappedGroupElement & 65535, remappedGroupElement >> 16); + groupElement = remappedGroupElement; } - skipRemap: - #endif // salvageAgfa + skipRemap: +#endif // salvageAgfa if ((lLength % 2) != 0) { //https://www.nitrc.org/forum/forum.php?thread_id=11827&forum_id=4703 - printMessage("Illegal DICOM tag %04x,%04x (odd element length %d): %s\n", groupElement & 65535,groupElement>>16, lLength, fname); + printMessage("Illegal DICOM tag %04x,%04x (odd element length %d): %s\n", groupElement & 65535, groupElement >> 16, lLength, fname); //proper to return here, but we can carry on as a hail mary // d.isValid = false; //return d; } - switch ( groupElement ) { - case kMediaStorageSOPClassUID: { - char mediaUID[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], mediaUID); - //Philips "XX_" files - //see https://github.com/rordenlab/dcm2niix/issues/328 - if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.66") != NULL) d.isRawDataStorage = true; - if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.1") != NULL) d.isRawDataStorage = true; //Private MR Spectrum Storage - if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.2") != NULL) d.isRawDataStorage = true; //Private MR Series Data Storage - if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.4") != NULL) d.isRawDataStorage = true; //Private MR Examcard Storage - if (d.isRawDataStorage) d.isDerived = true; - if (d.isRawDataStorage) printMessage("Skipping non-image DICOM: %s\n", fname); - //Philips "PS_" files - if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.11.1") != NULL) d.isGrayscaleSoftcopyPresentationState = true; - if (d.isGrayscaleSoftcopyPresentationState) d.isDerived = true; - break; - } - case kMediaStorageSOPInstanceUID : {// 0002, 0003 - //char SOPInstanceUID[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], d.instanceUID); - //printMessage(">>%s\n", d.seriesInstanceUID); - d.instanceUidCrc = mz_crc32X((unsigned char*) &d.instanceUID, strlen(d.instanceUID)); - break; - } - case kTransferSyntax: { - char transferSyntax[kDICOMStr]; - strcpy(transferSyntax, ""); - dcmStr(lLength, &buffer[lPos], transferSyntax); - if (strcmp(transferSyntax, "1.2.840.10008.1.2.1") == 0) - ; //default isExplicitVR=true; //d.isLittleEndian=true - else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.50") == 0) { - d.compressionScheme = kCompress50; - //printMessage("Lossy JPEG: please decompress with Osirix or dcmdjpg. %s\n", transferSyntax); - //d.imageStart = 1;//abort as invalid (imageStart MUST be >128) - } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.51") == 0) { - d.compressionScheme = kCompress50; - //printMessage("Lossy JPEG: please decompress with Osirix or dcmdjpg. %s\n", transferSyntax); - //d.imageStart = 1;//abort as invalid (imageStart MUST be >128) - //uJPEG does not decode these: ..53 ...55 - // } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.53") == 0) { - // d.compressionScheme = kCompress50; - } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.57") == 0) { - //d.isCompressed = true; - //https://www.medicalconnections.co.uk/kb/Transfer_Syntax should be SOF = 0xC3 - d.compressionScheme = kCompressC3; - //printMessage("Ancient JPEG-lossless (SOF type 0xc3): please check conversion\n"); - } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.70") == 0) { - d.compressionScheme = kCompressC3; - } else if ((strcmp(transferSyntax, "1.2.840.10008.1.2.4.80") == 0) || (strcmp(transferSyntax, "1.2.840.10008.1.2.4.81") == 0)){ - #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) - d.compressionScheme = kCompressJPEGLS; - #else - printWarning("Unsupported transfer syntax '%s' (decode with 'dcmdjpls jpg.dcm raw.dcm' or 'gdcmconv -w jpg.dcm raw.dcm', or recompile dcm2niix with JPEGLS support)\n",transferSyntax); - d.imageStart = 1;//abort as invalid (imageStart MUST be >128) - #endif - } else if (strcmp(transferSyntax, "1.3.46.670589.33.1.4.1") == 0) { - d.compressionScheme = kCompressPMSCT_RLE1; - //printMessage("Unsupported transfer syntax '%s' (decode with rle2img)\n",transferSyntax); - //d.imageStart = 1;//abort as invalid (imageStart MUST be >128) - } 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"); - } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.5") == 0) - d.compressionScheme = kCompressRLE; //run length - else if (strcmp(transferSyntax, "1.2.840.10008.1.2.2") == 0) - isSwitchToBigEndian = true; //isExplicitVR=true; - else if (strcmp(transferSyntax, "1.2.840.10008.1.2") == 0) - isSwitchToImplicitVR = true; //d.isLittleEndian=true - else { - if (lLength < 1) //"1.2.840.10008.1.2" - printWarning("Missing transfer syntax: assuming default (1.2.840.10008.1.2)\n"); - else { - printWarning("Unsupported transfer syntax '%s' (see www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage)\n",transferSyntax); - d.imageStart = 1;//abort as invalid (imageStart MUST be >128) - } - } - break;} //{} provide scope for variable 'transferSyntax - /*case kImplementationVersionName: { - char impTxt[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], impTxt); - int slen = (int) strlen(impTxt); - if((slen < 6) || (strstr(impTxt, "OSIRIX") == NULL) ) break; - printError("OSIRIX Detected\n"); - break; }*/ - case kImplementationVersionName: { - char impTxt[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], impTxt); - int slen = (int) strlen(impTxt); - if ((slen > 5) && (strstr(impTxt, "MATLAB") != NULL) ) - isMATLAB = true; - if((slen < 5) || (strstr(impTxt, "XA10A") == NULL) ) break; - d.isXA10A = true; - - break; } - case kSourceApplicationEntityTitle: { - char saeTxt[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], saeTxt); - int slen = (int) strlen(saeTxt); - if((slen < 5) || (strstr(saeTxt, "oasis") == NULL) ) break; - d.isSegamiOasis = true; - break; } - case kDirectoryRecordSequence: { - d.isRawDataStorage = true; - break; - } - case kImageTypeTag: { - bool is1st = strlen(d.imageType) == 0; - dcmStr(lLength, &buffer[lPos], d.imageType, false); //<-distinguish spaces from pathdelim: [ORIGINAL\PHASE MAP\FFE] should return "PHASE MAP" not "PHASE_MAP" - int slen; - slen = (int) strlen(d.imageType); - if (slen > 1) { - for (int i = 0; i 5) && strstr(d.imageType, "_MOCO_") ) { - //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_") ) - d.isDerived = true; - if((slen > 5) && strstr(d.imageType, "_TRACE_") ) - d.isDerived = true; - if((slen > 5) && strstr(d.imageType, "_FA_") ) - d.isDerived = true; - if((slen > 12) && strstr(d.imageType, "_DIFFUSION_") ) - d.isDiffusion = true; - //if (strcmp(transferSyntax, "ORIGINAL_PRIMARY_M_ND_MOSAIC") == 0) - if((slen > 5) && !strcmp(d.imageType + slen - 6, "MOSAIC") ) - isMosaic = true; - //const char* prefix = "MOSAIC"; - const char *pos = strstr(d.imageType, "MOSAIC"); - //const char p = (const char *) d.imageType; - //p = (const char) strstr(d.imageType, "MOSAIC"); - //const char* p = strstr(d.imageType, "MOSAIC"); - if (pos != NULL) - isMosaic = true; - //isNonImage 0008,0008 = DERIVED,CSAPARALLEL,POSDISP - // sometime ComplexImageComponent 0008,9208 is missing - see ADNI data - // attempt to detect non-images, see https://github.com/scitran/data/blob/a516fdc39d75a6e4ac75d0e179e18f3a5fc3c0af/scitran/data/medimg/dcm/mr/siemens.py - //For Philips combinations see Table 3-28 Table 3-28: Valid combinations of Image Type applied values - // http://incenter.medical.philips.com/doclib/enc/fetch/2000/4504/577242/577256/588723/5144873/5144488/5144982/DICOM_Conformance_Statement_Intera_R7%2c_R8_and_R9.pdf%3fnodeid%3d5147977%26vernum%3d-2 - if((slen > 3) && (strstr(d.imageType, "_R_") != NULL) ) { - d.isHasReal = true; - isReal = true; - } - if((slen > 3) && (strstr(d.imageType, "_M_") != NULL) ) { - d.isHasMagnitude = true; - isMagnitude = true; - } - if((slen > 3) && (strstr(d.imageType, "_I_") != NULL) ) { - d.isHasImaginary = true; - isImaginary = true; - } - if((slen > 3) && (strstr(d.imageType, "_P_") != NULL) ) { - d.isHasPhase = true; - isPhase = true; - } - if((slen > 6) && (strstr(d.imageType, "_REAL_") != NULL) ) { - d.isHasReal = true; - isReal = true; - } - if((slen > 11) && (strstr(d.imageType, "_MAGNITUDE_") != NULL) ) { - d.isHasMagnitude = true; - isMagnitude = true; - } - if((slen > 11) && (strstr(d.imageType, "_IMAGINARY_") != NULL) ) { - d.isHasImaginary = true; - isImaginary = true; - } - if((slen > 6) && (strstr(d.imageType, "PHASE") != NULL) ) { - d.isHasPhase = true; - isPhase = true; - } - if((slen > 6) && (strstr(d.imageType, "DERIVED") != NULL) ) - d.isDerived = true; - //if((slen > 4) && (strstr(typestr, "DIS2D") != NULL) ) - // d.isNonImage = true; - //not mutually exclusive: possible for Philips enhanced DICOM to store BOTH magnitude and phase in the same image - break; } - case kAcquisitionDate: - char acquisitionDateTxt[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], acquisitionDateTxt); - d.acquisitionDate = atof(acquisitionDateTxt); - break; - case kAcquisitionDateTime: - //char acquisitionDateTimeTxt[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], acquisitionDateTimeTxt); - //printMessage("%s\n",acquisitionDateTimeTxt); - break; - case kStudyDate: - dcmStr(lLength, &buffer[lPos], d.studyDate); - break; - case kModality: - if (lLength < 2) break; - if ((buffer[lPos]=='C') && (toupper(buffer[lPos+1]) == 'R')) - d.modality = kMODALITY_CR; - else if ((buffer[lPos]=='C') && (toupper(buffer[lPos+1]) == 'T')) - d.modality = kMODALITY_CT; - if ((buffer[lPos]=='M') && (toupper(buffer[lPos+1]) == 'R')) - d.modality = kMODALITY_MR; - if ((buffer[lPos]=='P') && (toupper(buffer[lPos+1]) == 'T')) - d.modality = kMODALITY_PT; - if ((buffer[lPos]=='U') && (toupper(buffer[lPos+1]) == 'S')) - d.modality = kMODALITY_US; - break; - case kManufacturer: - if (d.manufacturer == kMANUFACTURER_UNKNOWN) - d.manufacturer = dcmStrManufacturer (lLength, &buffer[lPos]); - volDiffusion.manufacturer = d.manufacturer; - break; - case kInstitutionName: - dcmStr(lLength, &buffer[lPos], d.institutionName); - break; - case kInstitutionAddress: //VR is "ST": 1024 chars maximum - dcmStr(lLength, &buffer[lPos], d.institutionAddress); - break; - case kReferringPhysicianName: - dcmStr(lLength, &buffer[lPos], d.referringPhysicianName); - break; - case kComplexImageComponent: - if (is2005140FSQ) break; //see Maastricht DICOM data for magnitude data with this field set as REAL! https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging - if (lLength < 2) break; - //issue 256: Philips files report real ComplexImageComponent but Magnitude ImageType https://github.com/rordenlab/dcm2niix/issues/256 - isPhase = false; - isReal = 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')) - isReal = true; - if ((buffer[lPos]=='I') && (toupper(buffer[lPos+1]) == 'M')) - isImaginary = true; - if ((buffer[lPos]=='P') && (toupper(buffer[lPos+1]) == 'H')) - isPhase = true; - if ((buffer[lPos]=='M') && (toupper(buffer[lPos+1]) == 'A')) - isMagnitude = true; - //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 (isMagnitude) d.isHasMagnitude = true; - break; - case kAcquisitionContrast: - char acqContrast[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], acqContrast); - if (((int) strlen(acqContrast) > 8) && (strstr(acqContrast, "DIFFUSION") != NULL)) - d.isDiffusion = true; - break; - case kAcquisitionTime : { - char acquisitionTimeTxt[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], acquisitionTimeTxt); - d.acquisitionTime = atof(acquisitionTimeTxt); - if (d.manufacturer != kMANUFACTURER_UIH) break; - //UIH slice timing- do not use for Siemens as Siemens de-identification can corrupt this field https://github.com/rordenlab/dcm2niix/issues/236 - d.CSA.sliceTiming[acquisitionTimesGE_UIH] = d.acquisitionTime; - acquisitionTimesGE_UIH ++; - break; } - //case kContentTime : - // char contentTimeTxt[kDICOMStr]; - // dcmStr(lLength, &buffer[lPos], contentTimeTxt); - // contentTime = atof(contentTimeTxt); - // break; - case kSeriesTime : - dcmStr(lLength, &buffer[lPos], seriesTimeTxt); - break; - case kStudyTime : - if (strlen(d.studyTime) < 2) - dcmStr(lLength, &buffer[lPos], d.studyTime); - break; - case kPatientName : - dcmStr(lLength, &buffer[lPos], d.patientName); - break; - case kAnatomicalOrientationType: { - char aotTxt[kDICOMStr]; //ftp://dicom.nema.org/MEDICAL/dicom/2015b/output/chtml/part03/sect_C.7.6.2.html#sect_C.7.6.2.1.1 - dcmStr(lLength, &buffer[lPos], aotTxt); - int slen = (int) strlen(aotTxt); - if((slen < 9) || (strstr(aotTxt, "QUADRUPED") == NULL) ) break; - printError("Anatomical Orientation Type (0010,2210) is QUADRUPED: rotate coordinates accordingly\n"); - break; } - case kDeidentificationMethod: { //issue 383 - char anonTxt[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], anonTxt); - int slen = (int) strlen(anonTxt); - if((slen < 10) || (strstr(anonTxt, "DICOMANON") == NULL) ) break; - isDICOMANON = true; - printWarning("Matlab DICOMANON can scramble SeriesInstanceUID (0020,000e) and remove crucial data (see issue 383). \n"); - break; } - case kPatientID : - if (strlen(d.patientID) > 1) break; - dcmStr(lLength, &buffer[lPos], d.patientID); - break; - case kAccessionNumber : - dcmStr(lLength, &buffer[lPos], d.accessionNumber); - break; - case kPatientBirthDate : - dcmStr(lLength, &buffer[lPos], d.patientBirthDate); - break; - case kPatientSex : { - //must be M,F,O: http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.2.html - char patientSex = toupper(buffer[lPos]); - if ((patientSex == 'M') || (patientSex == 'F') || (patientSex == 'O')) - d.patientSex = patientSex; - 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; - case kDerivationDescription : { - //strcmp(transferSyntax, "1.2.840.10008.1.2") - char derivationDescription[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], derivationDescription);//strcasecmp, strcmp - if (strcasecmp(derivationDescription, "MEDCOM_RESAMPLED") == 0) d.isResampled = true; - break; - } - case kDeviceSerialNumber : { - dcmStr(lLength, &buffer[lPos], d.deviceSerialNumber); - break; - } - case kSoftwareVersions : { - dcmStr(lLength, &buffer[lPos], d.softwareVersions); - int slen = (int) strlen(d.softwareVersions); - if((slen > 4) && (strstr(d.softwareVersions, "XA11") != NULL) ) d.isXA10A = true; - if((slen > 4) && (strstr(d.softwareVersions, "XA20") != NULL) ) d.isXA10A = true; - if((slen > 4) && (strstr(d.softwareVersions, "XA30") != NULL) ) d.isXA10A = true; - if((slen < 5) || (strstr(d.softwareVersions, "XA10") == NULL) ) break; - d.isXA10A = true; - break; - } - case kProtocolName : { - dcmStr(lLength, &buffer[lPos], d.protocolName); - break; } - case kPatientOrient : - dcmStr(lLength, &buffer[lPos], d.patientOrient); - break; - case kInversionRecovery : // CS [YES],[NO] - if (lLength < 2) break; - if (toupper(buffer[lPos]) == 'Y') - d.isIR = true; - break; - case kSpoiling : // CS 0018,9016 - if (lLength < 2) break; - char sTxt[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], sTxt); - if ((strstr(sTxt, "RF") != NULL) && (strstr(sTxt, "RF") != NULL)) - d.spoiling = kSPOILING_RF_AND_GRADIENT; - else if (strstr(sTxt, "RF") != NULL) - d.spoiling = kSPOILING_RF; - else if (strstr(sTxt, "GRADIENT") != NULL) - d.spoiling = kSPOILING_GRADIENT; - else if (strstr(sTxt, "NONE") != NULL) - d.spoiling = kSPOILING_NONE; - break; - case kEchoPlanarPulseSequence : // CS [YES],[NO] - if (lLength < 2) break; - if (toupper(buffer[lPos]) == 'Y') - d.isEPI = true; - break; - case kMagnetizationTransferAttribute : //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE' - if (lLength < 2) break; //https://github.com/bids-standard/bids-specification/pull/681 - if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos]) == 'F')) // OFF_RESONANCE - d.mtState = 1; //TRUE - if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos]) == 'N')) // ON_RESONANCE and NONE - d.mtState = 0; //FALSE - if ((toupper(buffer[lPos]) == 'N') && (toupper(buffer[lPos]) == 'O')) // ON_RESONANCE and NONE - d.mtState = 0; //FALSE - break; - case kRectilinearPhaseEncodeReordering : { //'CS' [REVERSE_LINEAR],[LINEAR],[CENTRIC],[REVERSE_CENTRIC] - if (d.manufacturer != kMANUFACTURER_GE) break; //only found in GE software beginning with RX27 - if (lLength < 2) break; - if (toupper(buffer[lPos]) == 'L') - d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; - if (toupper(buffer[lPos]) == 'R') - d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; - break; } - case kPartialFourierDirection : { //'CS' PHASE FREQUENCY SLICE_SELECT COMBINATION - if (lLength < 2) break; - if (toupper(buffer[lPos]) == 'P') - d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_PHASE; - if (toupper(buffer[lPos]) == 'F') - d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_FREQUENCY; - if (toupper(buffer[lPos]) == 'S') - d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT; - if (toupper(buffer[lPos]) == 'C') - d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_COMBINATION; - break; } - - case kParallelReductionFactorInPlane: - if (d.manufacturer == kMANUFACTURER_SIEMENS) break; - d.accelFactPE = dcmFloatDouble(lLength, &buffer[lPos],d.isLittleEndian); - break; - case kAcquisitionDuration: - //n.b. used differently by different vendors https://github.com/rordenlab/dcm2niix/issues/225 - d.acquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos],d.isLittleEndian); - break; - //in theory, 0018,9074 could provide XA10 slice time information, but scrambled by XA10 de-identification: better to use 0021,1104 - //case kFrameAcquisitionDateTime: { - // //(0018,9074) DT [20190621095516.140000] YYYYMMDDHHMMSS - // //see https://github.com/rordenlab/dcm2niix/issues/303 - // char dateTime[kDICOMStr]; - // dcmStr(lLength, &buffer[lPos], dateTime); - // printf("%s\tkFrameAcquisitionDateTime\n", dateTime); - //} - case kDiffusionDirectionality : {// 0018, 9075 - set_directionality0018_9075(&volDiffusion, (&buffer[lPos])); - if ((d.manufacturer != kMANUFACTURER_PHILIPS) || (lLength < 10)) break; - char dir[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], dir); - if (strcmp(dir, "ISOTROPIC") == 0) - isPhilipsDerived = true; - break; } - case kParallelAcquisitionTechnique: //CS - dcmStr(lLength, &buffer[lPos], d.parallelAcquisitionTechnique); - break; - case kInversionTimes : {//issue 380 - if ((lLength < 8) || ((lLength % 8) != 0)) break; - d.TI = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); - /* - //see issue385 : Philips reports Implausible InversionTimes - int nTI = lLength / 8; - if (nTI > 1) { - bool isTIvaries = false; - for (int i = 1; i < nTI; i++) { - float ti = dcmFloatDouble(8, &buffer[lPos+(i*8)],d.isLittleEndian); - if (!isSameFloatGE(ti, d.TI)) isTIvaries = true; - } - if (isTIvaries) printWarning("0018,9079 reports multiple inversion times: %s\n", fname); + switch (groupElement) { + case kMediaStorageSOPClassUID: { + char mediaUID[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], mediaUID); + //Philips "XX_" files + //see https://github.com/rordenlab/dcm2niix/issues/328 + if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.66") != NULL) + d.isRawDataStorage = true; + if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.1") != NULL) + d.isRawDataStorage = true; //Private MR Spectrum Storage + if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.2") != NULL) + d.isRawDataStorage = true; //Private MR Series Data Storage + if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.4") != NULL) + d.isRawDataStorage = true; //Private MR Examcard Storage + if (d.isRawDataStorage) + d.isDerived = true; + if (d.isRawDataStorage) + printMessage("Skipping non-image DICOM: %s\n", fname); + //Philips "PS_" files + if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.11.1") != NULL) + d.isGrayscaleSoftcopyPresentationState = true; + if (d.isGrayscaleSoftcopyPresentationState) + d.isDerived = true; + break; + } + case kMediaStorageSOPInstanceUID: { // 0002, 0003 + //char SOPInstanceUID[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], d.instanceUID); + //printMessage(">>%s\n", d.seriesInstanceUID); + d.instanceUidCrc = mz_crc32X((unsigned char *)&d.instanceUID, strlen(d.instanceUID)); + break; + } + case kTransferSyntax: { + char transferSyntax[kDICOMStr]; + strcpy(transferSyntax, ""); + dcmStr(lLength, &buffer[lPos], transferSyntax); + if (strcmp(transferSyntax, "1.2.840.10008.1.2.1") == 0) + ; //default isExplicitVR=true; //d.isLittleEndian=true + else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.50") == 0) { + d.compressionScheme = kCompress50; + //printMessage("Lossy JPEG: please decompress with Osirix or dcmdjpg. %s\n", transferSyntax); + //d.imageStart = 1; //abort as invalid (imageStart MUST be >128) + } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.51") == 0) { + d.compressionScheme = kCompress50; + } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.57") == 0) { + //d.isCompressed = true; + //https://www.medicalconnections.co.uk/kb/Transfer_Syntax should be SOF = 0xC3 + d.compressionScheme = kCompressC3; + //printMessage("Ancient JPEG-lossless (SOF type 0xc3): please check conversion\n"); + } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.70") == 0) { + d.compressionScheme = kCompressC3; + } else if ((strcmp(transferSyntax, "1.2.840.10008.1.2.4.80") == 0) || (strcmp(transferSyntax, "1.2.840.10008.1.2.4.81") == 0)) { +#if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) + d.compressionScheme = kCompressJPEGLS; +#else + printWarning("Unsupported transfer syntax '%s' (decode with 'dcmdjpls jpg.dcm raw.dcm' or 'gdcmconv -w jpg.dcm raw.dcm', or recompile dcm2niix with JPEGLS support)\n", transferSyntax); + d.imageStart = 1; //abort as invalid (imageStart MUST be >128) +#endif + } else if (strcmp(transferSyntax, "1.3.46.670589.33.1.4.1") == 0) { + d.compressionScheme = kCompressPMSCT_RLE1; + //printMessage("Unsupported transfer syntax '%s' (decode with rle2img)\n",transferSyntax); + //d.imageStart = 1; //abort as invalid (imageStart MUST be >128) + } 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"); + } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.5") == 0) + d.compressionScheme = kCompressRLE; //run length + else if (strcmp(transferSyntax, "1.2.840.10008.1.2.2") == 0) + isSwitchToBigEndian = true; //isExplicitVR=true; + else if (strcmp(transferSyntax, "1.2.840.10008.1.2") == 0) + isSwitchToImplicitVR = true; //d.isLittleEndian=true + else { + if (lLength < 1) //"1.2.840.10008.1.2" + printWarning("Missing transfer syntax: assuming default (1.2.840.10008.1.2)\n"); + else { + printWarning("Unsupported transfer syntax '%s' (see www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage)\n", transferSyntax); + d.imageStart = 1; //abort as invalid (imageStart MUST be >128) } - */ - break; } - case kPartialFourier : //(0018,9081) CS [YES],[NO] - if (lLength < 2) break; - if (toupper(buffer[lPos]) == 'Y') - d.isPartialFourier = true; - break; - case kMREchoSequence : - if (d.manufacturer != kMANUFACTURER_BRUKER) break; - if (sqDepth == 0) sqDepth = 1; //should not happen, in case faulty anonymization - sqDepth00189114 = sqDepth - 1; - break; - case kMRAcquisitionPhaseEncodingStepsInPlane : - d.phaseEncodingLines = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kNumberOfImagesInMosaic : - if (d.manufacturer == kMANUFACTURER_SIEMENS) - numberOfImagesInMosaic = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kSeriesPlaneGE : //SS 2=Axi, 4=Sag, 8=Cor, 16=Obl, 256=3plane - if (d.manufacturer != kMANUFACTURER_GE) break; - if (dcmInt(lLength,&buffer[lPos],d.isLittleEndian) == 256) d.isLocalizer = true; - break; - case kDwellTime : - d.dwellTime = dcmStrInt(lLength, &buffer[lPos]); - break; - case kDiffusion_bValueSiemens : - if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - //issue409 - B0Philips = dcmStrInt(lLength, &buffer[lPos]); - set_bVal(&volDiffusion, B0Philips); - break; - case kSliceTimeSiemens : {//Array of FD (64-bit double) - if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - if ((lLength < 8) || ((lLength % 8) != 0)) break; - int nSlicesTimes = lLength / 8; - if (nSlicesTimes > kMaxEPI3D) break; - d.CSA.mosaicSlices = nSlicesTimes; - //printf(">>>> %d\n", nSlicesTimes); - //issue 296: for images de-identified to remove readCSAImageHeader - for (int z = 0; z < nSlicesTimes; z++) - d.CSA.sliceTiming[z] = dcmFloatDouble(8, &buffer[lPos+(z*8)],d.isLittleEndian); - //for (int z = 0; z < nSlicesTimes; z++) - // printf("%d>>>%g\n", z+1, d.CSA.sliceTiming[z]); - checkSliceTimes(&d.CSA, nSlicesTimes, isVerbose, d.is3DAcq); - //d.CSA.dtiV[0] = dcmStrInt(lLength, &buffer[lPos]); - //d.CSA.numDti = 1; - break; } - case kDiffusionGradientDirectionSiemens : { - if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - float v[4]; - dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); - //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v); - //printf(">>>%g %g %g\n", v[0], v[1], v[2]); - d.CSA.dtiV[1] = v[0]; - d.CSA.dtiV[2] = v[1]; - d.CSA.dtiV[3] = v[2]; - break; } - case kNumberOfDiffusionDirectionGE : { - if (d.manufacturer != kMANUFACTURER_GE) break; - float f = dcmStrFloat(lLength, &buffer[lPos]); - d.numberOfDiffusionDirectionGE = round(f); - break; } - case kLastScanLoc : - d.lastScanLoc = dcmStrFloat(lLength, &buffer[lPos]); - break; - /*case kDiffusionBFactorSiemens : - if (d.manufacturer == kMANUFACTURER_SIEMENS) - printMessage("last scan location %f\n,",dcmStrFloat(lLength, &buffer[lPos])); - - break;*/ - //GE bug: multiple echos can create identical instance numbers - // in theory, one could detect as kRawDataRunNumberGE varies - // sliceN of echoE will have the same value for all timepoints - // this value does not appear indexed - // different echoes record same echo time. - // use multiEchoSortGEDICOM.py to salvage - case kRawDataRunNumberGE : - if (d.manufacturer != kMANUFACTURER_GE) - break; - d.rawDataRunNumber = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kMaxEchoNumGE : - if (d.manufacturer != kMANUFACTURER_GE) - break; - d.maxEchoNumGE = round(dcmStrFloat(lLength, &buffer[lPos])); - break; - case kDiffusionDirectionGEX : - if (d.manufacturer == kMANUFACTURER_GE) - set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 0); - break; - case kDiffusionDirectionGEY : - if (d.manufacturer == kMANUFACTURER_GE) - set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 1); - break; - case kDiffusionDirectionGEZ : - if (d.manufacturer == kMANUFACTURER_GE) - set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 2); - break; - case kPulseSequenceNameGE : { //LO 'epi'/'epiRT' - if (d.manufacturer != kMANUFACTURER_GE) break; - char epiStr[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], epiStr); - if ((strstr(epiStr, "epi") != NULL) && (strstr(epiStr, "epi2") == NULL)){ - d.epiVersionGE = 0; //-1 = not epi, 0 = epi, 1 = epiRT - } - if (strstr(epiStr, "epi2") != NULL){ - d.epiVersionGE = 2; //-1 = not epi, 0 = epi, 1 = epiRT, 2 = epi2 - } - if (strstr(epiStr, "epiRT") != NULL){ - d.epiVersionGE = 1; //-1 = not epi, 0 = epi, 1 = epiRT - } - if (strcmp(epiStr, "3db0map") == 0){ - isGEfieldMap = true; //issue501 - } - break; - } - case kInternalPulseSequenceNameGE : { //LO 'EPI'(gradient echo)/'EPI2'(spin echo): - if (d.manufacturer != kMANUFACTURER_GE) break; - char epiStr[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], epiStr); - if (strcmp(epiStr, "EPI") == 0){ - d.internalepiVersionGE = 1; //-1 = not EPI, 1 = EPI, 2 = EPI2 - if (d.epiVersionGE != 1){ // 1 = epiRT by kEpiRTGroupDelayGE or kPulseSequenceNameGE - d.epiVersionGE = 0; // 0 = epi (multi-phase epi) - } - } - if (strcmp(epiStr, "EPI2") == 0){ - d.internalepiVersionGE = 2; //-1 = not epi, 1 = EPI, 2 = EPI2 - } - if (strcmp(epiStr, "B0map") == 0){ - isGEfieldMap = true; //issue501 - } - break; - } - case kBandwidthPerPixelPhaseEncode: - d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos],d.isLittleEndian); - break; - case kStudyInstanceUID : // 0020,000D - dcmStr(lLength, &buffer[lPos], d.studyInstanceUID); - break; - case kSeriesInstanceUID : // 0020,000E - dcmStr(lLength, &buffer[lPos], d.seriesInstanceUID); - //printMessage(">>%s\n", d.seriesInstanceUID); - d.seriesUidCrc = mz_crc32X((unsigned char*) &d.seriesInstanceUID, strlen(d.seriesInstanceUID)); - break; - case kImagePositionPatient : { - if (is2005140FSQ) { - dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &patientPositionPrivate[0]); - break; + } + break; + } //{} provide scope for variable 'transferSyntax + case kImplementationVersionName: { + char impTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], impTxt); + int slen = (int)strlen(impTxt); + if ((slen > 5) && (strstr(impTxt, "MATLAB") != NULL)) + isMATLAB = true; + if ((slen < 5) || (strstr(impTxt, "XA10A") == NULL)) + break; + d.isXA10A = true; + break; + } + case kSourceApplicationEntityTitle: { + char saeTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], saeTxt); + int slen = (int)strlen(saeTxt); + if ((slen < 5) || (strstr(saeTxt, "oasis") == NULL)) + break; + d.isSegamiOasis = true; + break; + } + case kDirectoryRecordSequence: { + d.isRawDataStorage = true; + break; + } + case kImageTypeTag: { + bool is1st = strlen(d.imageType) == 0; + dcmStr(lLength, &buffer[lPos], d.imageType, false); //<-distinguish spaces from pathdelim: [ORIGINAL\PHASE MAP\FFE] should return "PHASE MAP" not "PHASE_MAP" + int slen; + slen = (int)strlen(d.imageType); + if (slen > 1) { + for (int i = 0; i < slen; i++) + if (d.imageType[i] == '\\') + d.imageType[i] = '_'; + } + if (is1st) + strcpy(imageType1st, d.imageType); + if ((slen > 5) && strstr(d.imageType, "_MOCO_")) { + //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_")) + d.isDerived = true; + if ((slen > 5) && strstr(d.imageType, "_TRACE_")) + d.isDerived = true; + if ((slen > 5) && strstr(d.imageType, "_FA_")) + d.isDerived = true; + if ((slen > 12) && strstr(d.imageType, "_DIFFUSION_")) + d.isDiffusion = true; + //if (strcmp(transferSyntax, "ORIGINAL_PRIMARY_M_ND_MOSAIC") == 0) + if ((slen > 5) && !strcmp(d.imageType + slen - 6, "MOSAIC")) + isMosaic = true; + //const char* prefix = "MOSAIC"; + const char *pos = strstr(d.imageType, "MOSAIC"); + //const char p = (const char *) d.imageType; + //p = (const char) strstr(d.imageType, "MOSAIC"); + //const char* p = strstr(d.imageType, "MOSAIC"); + if (pos != NULL) + isMosaic = true; + //isNonImage 0008,0008 = DERIVED,CSAPARALLEL,POSDISP + // sometime ComplexImageComponent 0008,9208 is missing - see ADNI data + // attempt to detect non-images, see https://github.com/scitran/data/blob/a516fdc39d75a6e4ac75d0e179e18f3a5fc3c0af/scitran/data/medimg/dcm/mr/siemens.py + //For Philips combinations see Table 3-28 Table 3-28: Valid combinations of Image Type applied values + // http://incenter.medical.philips.com/doclib/enc/fetch/2000/4504/577242/577256/588723/5144873/5144488/5144982/DICOM_Conformance_Statement_Intera_R7%2c_R8_and_R9.pdf%3fnodeid%3d5147977%26vernum%3d-2 + if ((slen > 3) && (strstr(d.imageType, "_R_") != NULL)) { + d.isHasReal = true; + isReal = true; + } + if ((slen > 3) && (strstr(d.imageType, "_M_") != NULL)) { + d.isHasMagnitude = true; + isMagnitude = true; + } + if ((slen > 3) && (strstr(d.imageType, "_I_") != NULL)) { + d.isHasImaginary = true; + isImaginary = true; + } + if ((slen > 3) && (strstr(d.imageType, "_P_") != NULL)) { + d.isHasPhase = true; + isPhase = true; + } + if ((slen > 6) && (strstr(d.imageType, "_REAL_") != NULL)) { + d.isHasReal = true; + isReal = true; + } + if ((slen > 11) && (strstr(d.imageType, "_MAGNITUDE_") != NULL)) { + d.isHasMagnitude = true; + isMagnitude = true; + } + if ((slen > 11) && (strstr(d.imageType, "_IMAGINARY_") != NULL)) { + d.isHasImaginary = true; + isImaginary = true; + } + if ((slen > 6) && (strstr(d.imageType, "PHASE") != NULL)) { + d.isHasPhase = true; + isPhase = true; + } + if ((slen > 6) && (strstr(d.imageType, "DERIVED") != NULL)) + d.isDerived = true; + //if((slen > 4) && (strstr(typestr, "DIS2D") != NULL) ) + // d.isNonImage = true; + //not mutually exclusive: possible for Philips enhanced DICOM to store BOTH magnitude and phase in the same image + break; + } + case kAcquisitionDate: + char acquisitionDateTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], acquisitionDateTxt); + d.acquisitionDate = atof(acquisitionDateTxt); + break; + case kAcquisitionDateTime: + //char acquisitionDateTimeTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], acquisitionDateTimeTxt); + //printMessage("%s\n",acquisitionDateTimeTxt); + break; + case kStudyDate: + dcmStr(lLength, &buffer[lPos], d.studyDate); + break; + case kModality: + if (lLength < 2) + break; + if ((buffer[lPos] == 'C') && (toupper(buffer[lPos + 1]) == 'R')) + d.modality = kMODALITY_CR; + else if ((buffer[lPos] == 'C') && (toupper(buffer[lPos + 1]) == 'T')) + d.modality = kMODALITY_CT; + if ((buffer[lPos] == 'M') && (toupper(buffer[lPos + 1]) == 'R')) + d.modality = kMODALITY_MR; + if ((buffer[lPos] == 'P') && (toupper(buffer[lPos + 1]) == 'T')) + d.modality = kMODALITY_PT; + if ((buffer[lPos] == 'U') && (toupper(buffer[lPos + 1]) == 'S')) + d.modality = kMODALITY_US; + break; + case kManufacturer: + if (d.manufacturer == kMANUFACTURER_UNKNOWN) + d.manufacturer = dcmStrManufacturer(lLength, &buffer[lPos]); + volDiffusion.manufacturer = d.manufacturer; + break; + case kInstitutionName: + dcmStr(lLength, &buffer[lPos], d.institutionName); + break; + case kInstitutionAddress: //VR is "ST": 1024 chars maximum + dcmStr(lLength, &buffer[lPos], d.institutionAddress); + break; + case kReferringPhysicianName: + dcmStr(lLength, &buffer[lPos], d.referringPhysicianName); + break; + case kComplexImageComponent: + if (is2005140FSQ) + break; //see Maastricht DICOM data for magnitude data with this field set as REAL! https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging + if (lLength < 2) + break; + //issue 256: Philips files report real ComplexImageComponent but Magnitude ImageType https://github.com/rordenlab/dcm2niix/issues/256 + isPhase = false; + isReal = 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')) + isReal = true; + if ((buffer[lPos] == 'I') && (toupper(buffer[lPos + 1]) == 'M')) + isImaginary = true; + if ((buffer[lPos] == 'P') && (toupper(buffer[lPos + 1]) == 'H')) + isPhase = true; + if ((buffer[lPos] == 'M') && (toupper(buffer[lPos + 1]) == 'A')) + isMagnitude = true; + //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 (isMagnitude) + d.isHasMagnitude = true; + break; + case kAcquisitionContrast: + char acqContrast[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], acqContrast); + if (((int)strlen(acqContrast) > 8) && (strstr(acqContrast, "DIFFUSION") != NULL)) + d.isDiffusion = true; + break; + case kAcquisitionTime: { + char acquisitionTimeTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], acquisitionTimeTxt); + d.acquisitionTime = atof(acquisitionTimeTxt); + if (d.manufacturer != kMANUFACTURER_UIH) + break; + //UIH slice timing- do not use for Siemens as Siemens de-identification can corrupt this field https://github.com/rordenlab/dcm2niix/issues/236 + d.CSA.sliceTiming[acquisitionTimesGE_UIH] = d.acquisitionTime; + acquisitionTimesGE_UIH++; + break; + } + case kSeriesTime: + dcmStr(lLength, &buffer[lPos], seriesTimeTxt); + break; + case kStudyTime: + if (strlen(d.studyTime) < 2) + dcmStr(lLength, &buffer[lPos], d.studyTime); + break; + case kPatientName: + dcmStr(lLength, &buffer[lPos], d.patientName); + break; + case kAnatomicalOrientationType: { + char aotTxt[kDICOMStr]; //ftp://dicom.nema.org/MEDICAL/dicom/2015b/output/chtml/part03/sect_C.7.6.2.html#sect_C.7.6.2.1.1 + dcmStr(lLength, &buffer[lPos], aotTxt); + int slen = (int)strlen(aotTxt); + if ((slen < 9) || (strstr(aotTxt, "QUADRUPED") == NULL)) + break; + printError("Anatomical Orientation Type (0010,2210) is QUADRUPED: rotate coordinates accordingly\n"); + break; + } + case kDeidentificationMethod: { //issue 383 + char anonTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], anonTxt); + int slen = (int)strlen(anonTxt); + if ((slen < 10) || (strstr(anonTxt, "DICOMANON") == NULL)) + break; + isDICOMANON = true; + printWarning("Matlab DICOMANON can scramble SeriesInstanceUID (0020,000e) and remove crucial data (see issue 383). \n"); + break; + } + case kPatientID: + if (strlen(d.patientID) > 1) + break; + dcmStr(lLength, &buffer[lPos], d.patientID); + break; + case kAccessionNumber: + dcmStr(lLength, &buffer[lPos], d.accessionNumber); + break; + case kPatientBirthDate: + dcmStr(lLength, &buffer[lPos], d.patientBirthDate); + break; + case kPatientSex: { + //must be M,F,O: http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.2.html + char patientSex = toupper(buffer[lPos]); + if ((patientSex == 'M') || (patientSex == 'F') || (patientSex == 'O')) + d.patientSex = patientSex; + 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; + case kDerivationDescription: { + //strcmp(transferSyntax, "1.2.840.10008.1.2") + char derivationDescription[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], derivationDescription); //strcasecmp, strcmp + if (strcasecmp(derivationDescription, "MEDCOM_RESAMPLED") == 0) + d.isResampled = true; + break; + } + case kDeviceSerialNumber: { + dcmStr(lLength, &buffer[lPos], d.deviceSerialNumber); + break; + } + case kSoftwareVersions: { + dcmStr(lLength, &buffer[lPos], d.softwareVersions); + int slen = (int)strlen(d.softwareVersions); + if ((slen > 4) && (strstr(d.softwareVersions, "XA11") != NULL)) + d.isXA10A = true; + if ((slen > 4) && (strstr(d.softwareVersions, "XA20") != NULL)) + d.isXA10A = true; + if ((slen > 4) && (strstr(d.softwareVersions, "XA30") != NULL)) + d.isXA10A = true; + if ((slen < 5) || (strstr(d.softwareVersions, "XA10") == NULL)) + break; + d.isXA10A = true; + break; + } + case kProtocolName: { + dcmStr(lLength, &buffer[lPos], d.protocolName); + break; + } + case kPatientOrient: + dcmStr(lLength, &buffer[lPos], d.patientOrient); + break; + case kInversionRecovery: // CS [YES],[NO] + if (lLength < 2) + break; + if (toupper(buffer[lPos]) == 'Y') + d.isIR = true; + break; + case kSpoiling: // CS 0018,9016 + if (lLength < 2) + break; + char sTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], sTxt); + if ((strstr(sTxt, "RF") != NULL) && (strstr(sTxt, "RF") != NULL)) + d.spoiling = kSPOILING_RF_AND_GRADIENT; + else if (strstr(sTxt, "RF") != NULL) + d.spoiling = kSPOILING_RF; + else if (strstr(sTxt, "GRADIENT") != NULL) + d.spoiling = kSPOILING_GRADIENT; + else if (strstr(sTxt, "NONE") != NULL) + d.spoiling = kSPOILING_NONE; + break; + case kEchoPlanarPulseSequence: // CS [YES],[NO] + if (lLength < 2) + break; + if (toupper(buffer[lPos]) == 'Y') + d.isEPI = true; + break; + case kMagnetizationTransferAttribute: //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE' + if (lLength < 2) + break; //https://github.com/bids-standard/bids-specification/pull/681 + if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos+1]) == 'F')) // OFF_RESONANCE + d.mtState = 1; //TRUE + if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos+1]) == 'N')) // ON_RESONANCE and NONE + d.mtState = 0; //FALSE + if ((toupper(buffer[lPos]) == 'N') && (toupper(buffer[lPos+1]) == 'O')) // ON_RESONANCE and NONE + d.mtState = 0; //FALSE + break; + case kRectilinearPhaseEncodeReordering: { //'CS' [REVERSE_LINEAR],[LINEAR],[CENTRIC],[REVERSE_CENTRIC] + if (d.manufacturer != kMANUFACTURER_GE) + break; //only found in GE software beginning with RX27 + if (lLength < 2) + break; + if (toupper(buffer[lPos]) == 'L') + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; + if (toupper(buffer[lPos]) == 'R') + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; + break; + } + case kPartialFourierDirection: { //'CS' PHASE FREQUENCY SLICE_SELECT COMBINATION + if (lLength < 2) + break; + if (toupper(buffer[lPos]) == 'P') + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_PHASE; + if (toupper(buffer[lPos]) == 'F') + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_FREQUENCY; + if (toupper(buffer[lPos]) == 'S') + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT; + if (toupper(buffer[lPos]) == 'C') + d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_COMBINATION; + break; + } + case kParallelReductionFactorInPlane: + if (d.manufacturer == kMANUFACTURER_SIEMENS) + break; + d.accelFactPE = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kAcquisitionDuration: + //n.b. used differently by different vendors https://github.com/rordenlab/dcm2niix/issues/225 + d.acquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + //in theory, 0018,9074 could provide XA10 slice time information, but scrambled by XA10 de-identification: better to use 0021,1104 + //case kFrameAcquisitionDateTime: { + // //(0018,9074) DT [20190621095516.140000] YYYYMMDDHHMMSS + // //see https://github.com/rordenlab/dcm2niix/issues/303 + // char dateTime[kDICOMStr]; + // dcmStr(lLength, &buffer[lPos], dateTime); + // printf("%s\tkFrameAcquisitionDateTime\n", dateTime); + //} + case kDiffusionDirectionality: { // 0018, 9075 + set_directionality0018_9075(&volDiffusion, (&buffer[lPos])); + if ((d.manufacturer != kMANUFACTURER_PHILIPS) || (lLength < 10)) + break; + char dir[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], dir); + if (strcmp(dir, "ISOTROPIC") == 0) + isPhilipsDerived = true; + break; + } + case kParallelAcquisitionTechnique: //CS + dcmStr(lLength, &buffer[lPos], d.parallelAcquisitionTechnique); + break; + case kInversionTimes: { //issue 380 + if ((lLength < 8) || ((lLength % 8) != 0)) + break; + d.TI = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + } + case kPartialFourier: //(0018,9081) CS [YES],[NO] + if (lLength < 2) + break; + if (toupper(buffer[lPos]) == 'Y') + d.isPartialFourier = true; + break; + case kMREchoSequence: + if (d.manufacturer != kMANUFACTURER_BRUKER) + break; + if (sqDepth == 0) + sqDepth = 1; //should not happen, in case faulty anonymization + sqDepth00189114 = sqDepth - 1; + break; + case kMRAcquisitionPhaseEncodingStepsInPlane: + d.phaseEncodingLines = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kNumberOfImagesInMosaic: + if (d.manufacturer == kMANUFACTURER_SIEMENS) + numberOfImagesInMosaic = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kSeriesPlaneGE: //SS 2=Axi, 4=Sag, 8=Cor, 16=Obl, 256=3plane + if (d.manufacturer != kMANUFACTURER_GE) + break; + if (dcmInt(lLength, &buffer[lPos], d.isLittleEndian) == 256) + d.isLocalizer = true; + break; + case kDwellTime: + d.dwellTime = dcmStrInt(lLength, &buffer[lPos]); + break; + case kDiffusion_bValueSiemens: + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + //issue409 + B0Philips = dcmStrInt(lLength, &buffer[lPos]); + set_bVal(&volDiffusion, B0Philips); + break; + case kSliceTimeSiemens: { //Array of FD (64-bit double) + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + if ((lLength < 8) || ((lLength % 8) != 0)) + break; + int nSlicesTimes = lLength / 8; + if (nSlicesTimes > kMaxEPI3D) + break; + d.CSA.mosaicSlices = nSlicesTimes; + //printf(">>>> %d\n", nSlicesTimes); + //issue 296: for images de-identified to remove readCSAImageHeader + for (int z = 0; z < nSlicesTimes; z++) + d.CSA.sliceTiming[z] = dcmFloatDouble(8, &buffer[lPos + (z * 8)], d.isLittleEndian); + //for (int z = 0; z < nSlicesTimes; z++) + // printf("%d>>>%g\n", z+1, d.CSA.sliceTiming[z]); + checkSliceTimes(&d.CSA, nSlicesTimes, isVerbose, d.is3DAcq); + //d.CSA.dtiV[0] = dcmStrInt(lLength, &buffer[lPos]); + //d.CSA.numDti = 1; + break; + } + case kDiffusionGradientDirectionSiemens: { + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + float v[4]; + dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); + //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v); + //printf(">>>%g %g %g\n", v[0], v[1], v[2]); + d.CSA.dtiV[1] = v[0]; + d.CSA.dtiV[2] = v[1]; + d.CSA.dtiV[3] = v[2]; + break; + } + case kNumberOfDiffusionDirectionGE: { + if (d.manufacturer != kMANUFACTURER_GE) + break; + float f = dcmStrFloat(lLength, &buffer[lPos]); + d.numberOfDiffusionDirectionGE = round(f); + break; + } + case kLastScanLoc: + d.lastScanLoc = dcmStrFloat(lLength, &buffer[lPos]); + break; + //GE bug: multiple echos can create identical instance numbers + // in theory, one could detect as kRawDataRunNumberGE varies + // sliceN of echoE will have the same value for all timepoints + // this value does not appear indexed + // different echoes record same echo time. + // use multiEchoSortGEDICOM.py to salvage + case kRawDataRunNumberGE: + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.rawDataRunNumber = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kMaxEchoNumGE: + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.maxEchoNumGE = round(dcmStrFloat(lLength, &buffer[lPos])); + break; + case kDiffusionDirectionGEX: + if (d.manufacturer == kMANUFACTURER_GE) + set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 0); + break; + case kDiffusionDirectionGEY: + if (d.manufacturer == kMANUFACTURER_GE) + set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 1); + break; + case kDiffusionDirectionGEZ: + if (d.manufacturer == kMANUFACTURER_GE) + set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 2); + break; + case kPulseSequenceNameGE: { //LO 'epi'/'epiRT' + if (d.manufacturer != kMANUFACTURER_GE) + break; + char epiStr[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], epiStr); + if ((strstr(epiStr, "epi") != NULL) && (strstr(epiStr, "epi2") == NULL)) { + d.epiVersionGE = 0; //-1 = not epi, 0 = epi, 1 = epiRT + } + if (strstr(epiStr, "epi2") != NULL) { + d.epiVersionGE = 2; //-1 = not epi, 0 = epi, 1 = epiRT, 2 = epi2 + } + if (strstr(epiStr, "epiRT") != NULL) { + d.epiVersionGE = 1; //-1 = not epi, 0 = epi, 1 = epiRT + } + if (strcmp(epiStr, "3db0map") == 0) { + isGEfieldMap = true; //issue501 + } + break; + } + case kInternalPulseSequenceNameGE: { //LO 'EPI'(gradient echo)/'EPI2'(spin echo): + if (d.manufacturer != kMANUFACTURER_GE) + break; + char epiStr[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], epiStr); + if (strcmp(epiStr, "EPI") == 0) { + d.internalepiVersionGE = 1; //-1 = not EPI, 1 = EPI, 2 = EPI2 + if (d.epiVersionGE != 1) { // 1 = epiRT by kEpiRTGroupDelayGE or kPulseSequenceNameGE + d.epiVersionGE = 0; // 0 = epi (multi-phase epi) } - patientPositionNum++; - isAtFirstPatientPosition = true; - //char dx[kDICOMStr]; - //dcmStr(lLength, &buffer[lPos], dx); - //printMessage("*%s*", dx); - dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &patientPosition[0]); //slice position - if (isnan(d.patientPosition[1])) { - //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &d.patientPosition[0]); //slice position - for (int k = 0; k < 4; k++) - d.patientPosition[k] = patientPosition[k]; - } else { - //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &d.patientPositionLast[0]); //slice direction for 4D - for (int k = 0; k < 4; k++) - d.patientPositionLast[k] = patientPosition[k]; - if ((isFloatDiff(d.patientPositionLast[1],d.patientPosition[1])) || - (isFloatDiff(d.patientPositionLast[2],d.patientPosition[2])) || - (isFloatDiff(d.patientPositionLast[3],d.patientPosition[3])) ) { - isAtFirstPatientPosition = false; //this slice is not at position of 1st slice + } + if (strcmp(epiStr, "EPI2") == 0) { + d.internalepiVersionGE = 2; //-1 = not epi, 1 = EPI, 2 = EPI2 + } + if (strcmp(epiStr, "B0map") == 0) { + isGEfieldMap = true; //issue501 + } + break; + } + case kBandwidthPerPixelPhaseEncode: + d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kStudyInstanceUID: // 0020,000D + dcmStr(lLength, &buffer[lPos], d.studyInstanceUID); + break; + case kSeriesInstanceUID: // 0020,000E + dcmStr(lLength, &buffer[lPos], d.seriesInstanceUID); + //printMessage(">>%s\n", d.seriesInstanceUID); + d.seriesUidCrc = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID)); + break; + case kImagePositionPatient: { + if (is2005140FSQ) { + dcmMultiFloat(lLength, (char *)&buffer[lPos], 3, &patientPositionPrivate[0]); + break; + } + patientPositionNum++; + isAtFirstPatientPosition = true; + //char dx[kDICOMStr]; + //dcmStr(lLength, &buffer[lPos], dx); + //printMessage("*%s*", dx); + dcmMultiFloat(lLength, (char *)&buffer[lPos], 3, &patientPosition[0]); //slice position + if (isnan(d.patientPosition[1])) { + //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &d.patientPosition[0]); //slice position + for (int k = 0; k < 4; k++) + d.patientPosition[k] = patientPosition[k]; + } else { + //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &d.patientPositionLast[0]); //slice direction for 4D + for (int k = 0; k < 4; k++) + d.patientPositionLast[k] = patientPosition[k]; + if ((isFloatDiff(d.patientPositionLast[1], d.patientPosition[1])) || + (isFloatDiff(d.patientPositionLast[2], d.patientPosition[2])) || + (isFloatDiff(d.patientPositionLast[3], d.patientPosition[3]))) { + isAtFirstPatientPosition = false; //this slice is not at position of 1st slice //if (d.patientPositionSequentialRepeats == 0) //this is the first slice with different position // d.patientPositionSequentialRepeats = patientPositionNum-1; - } //if different position from 1st slice in file - } //if not first slice in file - set_isAtFirstPatientPosition_tvd(&volDiffusion, isAtFirstPatientPosition); - //if (isAtFirstPatientPosition) numFirstPatientPosition++; - if (isVerbose > 0) //verbose > 1 will report full DICOM tag - printMessage(" Patient Position 0020,0032 (#,@,X,Y,Z)\t%d\t%ld\t%g\t%g\t%g\n", patientPositionNum, lPos, patientPosition[1], patientPosition[2], patientPosition[3]); - if ((isOrient) && (nSliceMM < kMaxSlice2D)) { - vec3 pos = setVec3(patientPosition[1], patientPosition[2], patientPosition[3]); - sliceMM[nSliceMM] = dotProduct(pos, sliceV); - if (sliceMM[nSliceMM] < minSliceMM) { - minSliceMM = sliceMM[nSliceMM]; - for (int k = 0; k < 4; k++) - minPatientPosition[k] = patientPosition[k]; - } - if (sliceMM[nSliceMM] > maxSliceMM) { - maxSliceMM = sliceMM[nSliceMM]; - for (int k = 0; k < 4; k++) - maxPatientPosition[k] = patientPosition[k]; - } - nSliceMM++; - } - break; } - 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; - case kSeriesNum: - d.seriesNum = dcmStrInt(lLength, &buffer[lPos]); - break; - case kAcquNum: - d.acquNum = dcmStrInt(lLength, &buffer[lPos]); - break; - case kImageNum: - //Enhanced Philips also uses this in once per file SQ 0008,1111 - //Enhanced Philips also uses this once per slice in SQ 2005,140f - if (d.imageNum < 1) d.imageNum = dcmStrInt(lLength, &buffer[lPos]); //Philips renames each image again in 2001,9000, which can lead to duplicates + } //if different position from 1st slice in file + } //if not first slice in file + set_isAtFirstPatientPosition_tvd(&volDiffusion, isAtFirstPatientPosition); + //if (isAtFirstPatientPosition) numFirstPatientPosition++; + if (isVerbose > 0) //verbose > 1 will report full DICOM tag + printMessage(" Patient Position 0020,0032 (#,@,X,Y,Z)\t%d\t%ld\t%g\t%g\t%g\n", patientPositionNum, lPos, patientPosition[1], patientPosition[2], patientPosition[3]); + if ((isOrient) && (nSliceMM < kMaxSlice2D)) { + vec3 pos = setVec3(patientPosition[1], patientPosition[2], patientPosition[3]); + sliceMM[nSliceMM] = dotProduct(pos, sliceV); + if (sliceMM[nSliceMM] < minSliceMM) { + minSliceMM = sliceMM[nSliceMM]; + for (int k = 0; k < 4; k++) + minPatientPosition[k] = patientPosition[k]; + } + if (sliceMM[nSliceMM] > maxSliceMM) { + maxSliceMM = sliceMM[nSliceMM]; + for (int k = 0; k < 4; k++) + maxPatientPosition[k] = patientPosition[k]; + } + nSliceMM++; + } + break; + } + 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; + case kSeriesNum: + d.seriesNum = dcmStrInt(lLength, &buffer[lPos]); + break; + case kAcquNum: + d.acquNum = dcmStrInt(lLength, &buffer[lPos]); + break; + case kImageNum: + //Enhanced Philips also uses this in once per file SQ 0008,1111 + //Enhanced Philips also uses this once per slice in SQ 2005,140f + if (d.imageNum < 1) + d.imageNum = dcmStrInt(lLength, &buffer[lPos]); //Philips renames each image again in 2001,9000, which can lead to duplicates + break; + case kInStackPositionNumber: + if ((d.manufacturer != kMANUFACTURER_CANON) && (d.manufacturer != kMANUFACTURER_HITACHI) && (d.manufacturer != kMANUFACTURER_UNKNOWN) && (d.manufacturer != kMANUFACTURER_PHILIPS) && (d.manufacturer != kMANUFACTURER_BRUKER)) + break; + inStackPositionNumber = dcmInt(4, &buffer[lPos], d.isLittleEndian); + //if (inStackPositionNumber == 1) numInStackPositionNumber1 ++; + //printf("<%d>\n",inStackPositionNumber); + if (inStackPositionNumber > maxInStackPositionNumber) + maxInStackPositionNumber = inStackPositionNumber; + break; + case kTemporalPositionIndex: + temporalPositionIndex = dcmInt(4, &buffer[lPos], d.isLittleEndian); + if (temporalPositionIndex > maxTemporalPositionIndex) + maxTemporalPositionIndex = temporalPositionIndex; + break; + case kDimensionIndexPointer: + dimensionIndexPointer[dimensionIndexPointerCounter++] = dcmAttributeTag(&buffer[lPos], d.isLittleEndian); + break; + case kFrameContentSequence: + //if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //see https://github.com/rordenlab/dcm2niix/issues/241 + if (sqDepth == 0) + sqDepth = 1; //should not happen, in case faulty anonymization + sqDepth00189114 = sqDepth - 1; + break; + case kTriggerDelayTime: { //0x0020+uint32_t(0x9153<< 16 ) //FD + if (prefs->isIgnoreTriggerTimes) + break; //issue499 + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + //if (isVerbose < 2) break; + double trigger = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + d.triggerDelayTime = trigger; + if (isSameFloatGE(d.triggerDelayTime, 0.0)) + d.triggerDelayTime = 0.0; //double to single + break; + } + case kDimensionIndexValues: { // kImageNum is not enough for 4D series from Philips 5.*. + if (lLength < 4) + break; + nDimIndxVal = lLength / 4; + if (nDimIndxVal > MAX_NUMBER_OF_DIMENSIONS) { + printError("%d is too many dimensions. Only up to %d are supported\n", nDimIndxVal, MAX_NUMBER_OF_DIMENSIONS); + nDimIndxVal = MAX_NUMBER_OF_DIMENSIONS; // Truncate + } + dcmMultiLongs(4 * nDimIndxVal, &buffer[lPos], nDimIndxVal, d.dimensionIndexValues, d.isLittleEndian); + break; + } + case kPhotometricInterpretation: { + char interp[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], interp); + if (strcmp(interp, "PALETTE_COLOR") == 0) + isPaletteColor = true; + //printError("Photometric Interpretation 'PALETTE COLOR' not supported\n"); + break; + } + case kPlanarRGB: + d.isPlanarRGB = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kDim3: + d.xyzDim[3] = dcmStrInt(lLength, &buffer[lPos]); + numberOfFrames = d.xyzDim[3]; + break; + case kSamplesPerPixel: + d.samplesPerPixel = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kDim2: + d.xyzDim[2] = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kDim1: + d.xyzDim[1] = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + //order is Row,Column e.g. YX + case kXYSpacing: { + float yx[3]; + dcmMultiFloat(lLength, (char *)&buffer[lPos], 2, yx); + d.xyzMM[1] = yx[2]; + d.xyzMM[2] = yx[1]; + break; + } + case kImageComments: + dcmStr(lLength, &buffer[lPos], d.imageComments, true); + break; + //group 21: siemens + //g21 + case kPATModeText: { //e.g. Siemens iPAT x2 listed as "p2" + char accelStr[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], accelStr); + char *ptr; + dcmStrDigitsOnlyKey('p', accelStr); //e.g. if "p2s4" return "2", if "s4" return "" + 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 kTimeAfterStart: + //0021,1104 see https://github.com/rordenlab/dcm2niix/issues/303 + // 0021,1104 6@159630 DS 4.635 + // 0021,1104 2@161164 DS 0 + if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - case kInStackPositionNumber: - if ((d.manufacturer != kMANUFACTURER_CANON) && (d.manufacturer != kMANUFACTURER_HITACHI) && (d.manufacturer != kMANUFACTURER_UNKNOWN) && (d.manufacturer != kMANUFACTURER_PHILIPS) && (d.manufacturer != kMANUFACTURER_BRUKER)) break; - inStackPositionNumber = dcmInt(4,&buffer[lPos],d.isLittleEndian); - //if (inStackPositionNumber == 1) numInStackPositionNumber1 ++; - //printf("<%d>\n",inStackPositionNumber); - if (inStackPositionNumber > maxInStackPositionNumber) maxInStackPositionNumber = inStackPositionNumber; + if (acquisitionTimesGE_UIH >= kMaxEPI3D) break; - case kTemporalPositionIndex: - temporalPositionIndex = dcmInt(4,&buffer[lPos],d.isLittleEndian); - if (temporalPositionIndex > maxTemporalPositionIndex) maxTemporalPositionIndex = temporalPositionIndex; + d.CSA.sliceTiming[acquisitionTimesGE_UIH] = dcmStrFloat(lLength, &buffer[lPos]); + d.CSA.sliceTiming[acquisitionTimesGE_UIH] *= 1000.0; //convert sec to msec + //printf("x\t%d\t%g\tkTimeAfterStart\n", acquisitionTimesGE_UIH, d.CSA.sliceTiming[acquisitionTimesGE_UIH]); + acquisitionTimesGE_UIH++; + break; + case kPhaseEncodingDirectionPositiveSiemens: { + if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - case kDimensionIndexPointer: - dimensionIndexPointer[dimensionIndexPointerCounter++] = dcmAttributeTag(&buffer[lPos],d.isLittleEndian); - break; - case kFrameContentSequence : - //if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //see https://github.com/rordenlab/dcm2niix/issues/241 - if (sqDepth == 0) sqDepth = 1; //should not happen, in case faulty anonymization - sqDepth00189114 = sqDepth - 1; - break; - case kTriggerDelayTime: { //0x0020+uint32_t(0x9153<< 16 ) //FD - if (prefs->isIgnoreTriggerTimes) break;//issue499 - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - //if (isVerbose < 2) break; - double trigger = dcmFloatDouble(lLength, &buffer[lPos],d.isLittleEndian); - d.triggerDelayTime = trigger; - if (isSameFloatGE(d.triggerDelayTime, 0.0)) d.triggerDelayTime = 0.0; //double to single - break; } - case kDimensionIndexValues: { // kImageNum is not enough for 4D series from Philips 5.*. - if (lLength < 4) break; - nDimIndxVal = lLength / 4; - if(nDimIndxVal > MAX_NUMBER_OF_DIMENSIONS){ - printError("%d is too many dimensions. Only up to %d are supported\n", nDimIndxVal, - MAX_NUMBER_OF_DIMENSIONS); - nDimIndxVal = MAX_NUMBER_OF_DIMENSIONS; // Truncate - } - dcmMultiLongs(4 * nDimIndxVal, &buffer[lPos], nDimIndxVal, d.dimensionIndexValues, d.isLittleEndian); - break; } - case kPhotometricInterpretation: { - char interp[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], interp); - if (strcmp(interp, "PALETTE_COLOR") == 0) - isPaletteColor = true; - //printError("Photometric Interpretation 'PALETTE COLOR' not supported\n"); - break; } - case kPlanarRGB: - d.isPlanarRGB = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kDim3: - d.xyzDim[3] = dcmStrInt(lLength, &buffer[lPos]); - numberOfFrames = d.xyzDim[3]; - break; - case kSamplesPerPixel: - d.samplesPerPixel = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kDim2: - d.xyzDim[2] = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kDim1: - d.xyzDim[1] = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - //order is Row,Column e.g. YX - case kXYSpacing:{ - float yx[3]; - dcmMultiFloat(lLength, (char*)&buffer[lPos], 2, yx); - d.xyzMM[1] = yx[2]; - d.xyzMM[2] = yx[1]; - break; } - //case kXYSpacing: - // dcmMultiFloat(lLength, (char*)&buffer[lPos], 2, d.xyzMM); - // break; - case kImageComments: - dcmStr(lLength, &buffer[lPos], d.imageComments, true); - break; - //group 21: siemens - //g21 - case kPATModeText : { //e.g. Siemens iPAT x2 listed as "p2" - char accelStr[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], accelStr); - char *ptr; - dcmStrDigitsOnlyKey('p', accelStr); //e.g. if "p2s4" return "2", if "s4" return "" - 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 kTimeAfterStart: - //0021,1104 see https://github.com/rordenlab/dcm2niix/issues/303 - // 0021,1104 6@159630 DS 4.635 - // 0021,1104 2@161164 DS 0 - if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - if (acquisitionTimesGE_UIH >= kMaxEPI3D) break; - d.CSA.sliceTiming[acquisitionTimesGE_UIH] = dcmStrFloat(lLength, &buffer[lPos]); - d.CSA.sliceTiming[acquisitionTimesGE_UIH] *= 1000.0; //convert sec to msec - //printf("x\t%d\t%g\tkTimeAfterStart\n", acquisitionTimesGE_UIH, d.CSA.sliceTiming[acquisitionTimesGE_UIH]); - acquisitionTimesGE_UIH ++; - break; - case kPhaseEncodingDirectionPositiveSiemens: { - if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - int ph = dcmStrInt(lLength, &buffer[lPos]); - if (ph == 0) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; - if (ph == 1) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; - break; } - //case kRealDwellTime : //https://github.com/rordenlab/dcm2niix/issues/240 - // if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - // d.dwellTime = dcmStrInt(lLength, &buffer[lPos]); - // break; - case kBandwidthPerPixelPhaseEncode21: - if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos],d.isLittleEndian); - break; - case kCoilElements: - if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - dcmStr(lLength, &buffer[lPos], d.coilElements); + int ph = dcmStrInt(lLength, &buffer[lPos]); + if (ph == 0) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; + if (ph == 1) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; + break; + } + //case kRealDwellTime : //https://github.com/rordenlab/dcm2niix/issues/240 + // if (d.manufacturer != kMANUFACTURER_SIEMENS) break; + // d.dwellTime = dcmStrInt(lLength, &buffer[lPos]); + // break; + case kBandwidthPerPixelPhaseEncode21: + if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - //group 21: GE - case kLocationsInAcquisitionGE: - locationsInAcquisitionGE = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kRTIA_timer: - if (d.manufacturer != kMANUFACTURER_GE) break; - //see dicm2nii slice timing from 0021,105E DS RTIA_timer - d.rtia_timerGE = dcmStrFloat(lLength, &buffer[lPos]); //RefAcqTimes = t/10; end % in ms - //printf("%s\t%g\n", fname, d.rtia_timerGE); - break; - case kProtocolDataBlockGE : - if (d.manufacturer != kMANUFACTURER_GE) break; - d.protocolBlockLengthGE = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - d.protocolBlockStartGE = (int)lPos+(int)lFileOffset+4; - //printError("ProtocolDataBlockGE %d @ %d\n", d.protocolBlockLengthGE, d.protocolBlockStartGE); - break; - case kNumberOfExcitations : //FL - if (d.manufacturer != kMANUFACTURER_GE) - break; - d.numberOfExcitations = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - break; - case kNumberOfArms : //FL - if (d.manufacturer != kMANUFACTURER_GE) - break; - d.numberOfArms = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - break; - case kNumberOfPointsPerArm : //FL - if (d.manufacturer != kMANUFACTURER_GE) - break; - d.numberOfPointsPerArm = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - break; - case kDoseCalibrationFactor : - d.doseCalibrationFactor = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kPETImageIndex : - PETImageIndex = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kPEDirectionDisplayedUIH : - if (d.manufacturer != kMANUFACTURER_UIH) break; - dcmStr(lLength, &buffer[lPos], d.phaseEncodingDirectionDisplayedUIH); - break; - case kDiffusion_bValueUIH : { - if (d.manufacturer != kMANUFACTURER_UIH) break; - float v[4]; - dcmMultiFloatDouble(lLength, &buffer[lPos], 1, v, d.isLittleEndian); - B0Philips = v[0]; - set_bVal(&volDiffusion, v[0]); - break; } - case kParallelInformationUIH: {//SENSE factor (0065,100d) SH [F:2S] - if (d.manufacturer != kMANUFACTURER_UIH) break; - char accelStr[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], accelStr); - //char *ptr; - dcmStrDigitsDotOnlyKey(':', accelStr); //e.g. if "p2s4" return "2", if "s4" return "" - d.accelFactPE = atof(accelStr); - break; } - case kNumberOfImagesInGridUIH : - if (d.manufacturer != kMANUFACTURER_UIH) break; - d.numberOfImagesInGridUIH = dcmStrFloat(lLength, &buffer[lPos]); - d.CSA.mosaicSlices = d.numberOfImagesInGridUIH; - break; - case kPhaseEncodingDirectionPositiveUIH: { - if (d.manufacturer != kMANUFACTURER_UIH) break; - int ph = dcmStrInt(lLength, &buffer[lPos]); - if (ph == 1) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; - if (ph == 0) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; - break; } - case kDiffusionGradientDirectionUIH : { //0065,1037 - //0.03712929804225321\-0.5522387869760447\-0.8328587749392602 - if (d.manufacturer != kMANUFACTURER_UIH) break; - float v[4]; - dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); - //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v); - //printf(">>>%g %g %g\n", v[0], v[1], v[2]); - d.CSA.dtiV[1] = v[0]; - d.CSA.dtiV[2] = v[1]; - d.CSA.dtiV[3] = v[2]; - //vRLPhilips = v[0]; - //vAPPhilips = v[1]; - //vFHPhilips = v[2]; - break; } - - case kBitsAllocated : - d.bitsAllocated = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kBitsStored : - d.bitsStored = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kIsSigned : //http://dicomiseasy.blogspot.com/2012/08/chapter-12-pixel-data.html - d.isSigned = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kPixelPaddingValue : - // According to the DICOM standard, this can be either unsigned (US) or signed (SS). Currently this - // is used only in nii_saveNII3Dtilt() which only allows DT_INT16, so treat it as signed. - d.pixelPaddingValue = (float) (short) dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kFloatPixelPaddingValue : - d.pixelPaddingValue = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); - break; - case kTR : - d.TR = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kTE : - TE = dcmStrFloat(lLength, &buffer[lPos]); - if (d.TE <= 0.0) - d.TE = TE; - break; - case kNumberOfAverages : - d.numberOfAverages = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kImagingFrequency : - d.imagingFrequency = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kTriggerTime: { - if (prefs->isIgnoreTriggerTimes) break;//issue499 + d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kCoilElements: + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + dcmStr(lLength, &buffer[lPos], d.coilElements); + break; + //group 21: GE + case kLocationsInAcquisitionGE: + locationsInAcquisitionGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kRTIA_timer: + if (d.manufacturer != kMANUFACTURER_GE) + break; + //see dicm2nii slice timing from 0021,105E DS RTIA_timer + d.rtia_timerGE = dcmStrFloat(lLength, &buffer[lPos]); //RefAcqTimes = t/10; end % in ms + //printf("%s\t%g\n", fname, d.rtia_timerGE); + break; + case kProtocolDataBlockGE: + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.protocolBlockLengthGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + d.protocolBlockStartGE = (int)lPos + (int)lFileOffset + 4; + //printError("ProtocolDataBlockGE %d @ %d\n", d.protocolBlockLengthGE, d.protocolBlockStartGE); + break; + case kNumberOfExcitations: //FL + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.numberOfExcitations = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kNumberOfArms: //FL + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.numberOfArms = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kNumberOfPointsPerArm: //FL + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.numberOfPointsPerArm = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kDoseCalibrationFactor: + d.doseCalibrationFactor = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kPETImageIndex: + PETImageIndex = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kPEDirectionDisplayedUIH: + if (d.manufacturer != kMANUFACTURER_UIH) + break; + dcmStr(lLength, &buffer[lPos], d.phaseEncodingDirectionDisplayedUIH); + break; + case kDiffusion_bValueUIH: { + if (d.manufacturer != kMANUFACTURER_UIH) + break; + float v[4]; + dcmMultiFloatDouble(lLength, &buffer[lPos], 1, v, d.isLittleEndian); + B0Philips = v[0]; + set_bVal(&volDiffusion, v[0]); + break; + } + case kParallelInformationUIH: { //SENSE factor (0065,100d) SH [F:2S] + if (d.manufacturer != kMANUFACTURER_UIH) + break; + char accelStr[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], accelStr); + //char *ptr; + dcmStrDigitsDotOnlyKey(':', accelStr); //e.g. if "p2s4" return "2", if "s4" return "" + d.accelFactPE = atof(accelStr); + break; + } + case kNumberOfImagesInGridUIH: + if (d.manufacturer != kMANUFACTURER_UIH) + break; + d.numberOfImagesInGridUIH = dcmStrFloat(lLength, &buffer[lPos]); + d.CSA.mosaicSlices = d.numberOfImagesInGridUIH; + break; + case kPhaseEncodingDirectionPositiveUIH: { + if (d.manufacturer != kMANUFACTURER_UIH) + break; + int ph = dcmStrInt(lLength, &buffer[lPos]); + if (ph == 1) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; + if (ph == 0) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; + break; + } + case kDiffusionGradientDirectionUIH: { + if (d.manufacturer != kMANUFACTURER_UIH) + break; + float v[4]; + dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); + //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v); + //printf(">>>%g %g %g\n", v[0], v[1], v[2]); + d.CSA.dtiV[1] = v[0]; + d.CSA.dtiV[2] = v[1]; + d.CSA.dtiV[3] = v[2]; + //vRLPhilips = v[0]; + //vAPPhilips = v[1]; + //vFHPhilips = v[2]; + break; + } + case kBitsAllocated: + d.bitsAllocated = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kBitsStored: + d.bitsStored = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kIsSigned: //http://dicomiseasy.blogspot.com/2012/08/chapter-12-pixel-data.html + d.isSigned = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kPixelPaddingValue: + // According to the DICOM standard, this can be either unsigned (US) or signed (SS). Currently this + // is used only in nii_saveNII3Dtilt() which only allows DT_INT16, so treat it as signed. + d.pixelPaddingValue = (float)(short)dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kFloatPixelPaddingValue: + d.pixelPaddingValue = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kTR: + d.TR = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kTE: + TE = dcmStrFloat(lLength, &buffer[lPos]); + if (d.TE <= 0.0) + d.TE = TE; + break; + case kNumberOfAverages: + d.numberOfAverages = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kImagingFrequency: + d.imagingFrequency = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kTriggerTime: { + if (prefs->isIgnoreTriggerTimes) + break; //issue499 //untested method to detect slice timing for GE PSD “epi” with multiphase option // will not work for current PSD “epiRT” (BrainWave RT, fMRI/DTI package provided by Medical Numerics) - if ((d.manufacturer != kMANUFACTURER_GE) && (d.manufacturer != kMANUFACTURER_PHILIPS)) break; //issue384 - d.triggerDelayTime = dcmStrFloat(lLength, &buffer[lPos]); //???? issue 336 - if (d.manufacturer != kMANUFACTURER_GE) break; - d.CSA.sliceTiming[acquisitionTimesGE_UIH] = d.triggerDelayTime; - //printf("%g\n", d.CSA.sliceTiming[acquisitionTimesGE_UIH]); - acquisitionTimesGE_UIH ++; - break; } - case kRadionuclideTotalDose : - d.radionuclideTotalDose = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kEffectiveTE : { - TE = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); - if (d.TE <= 0.0) - d.TE = TE; - break; } - case kTI : - d.TI = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kEchoNum : - d.echoNum = dcmStrInt(lLength, &buffer[lPos]); - break; - case kMagneticFieldStrength : - d.fieldStrength = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kZSpacing : - d.zSpacing = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kPhaseEncodingSteps : - d.phaseEncodingSteps = dcmStrInt(lLength, &buffer[lPos]); - break; - case kEchoTrainLength : - d.echoTrainLength = dcmStrInt(lLength, &buffer[lPos]); - break; - case kPercentSampling : - d.percentSampling = dcmStrFloat(lLength, &buffer[lPos]); - case kPhaseFieldofView : - d.phaseFieldofView = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kPixelBandwidth : - /*if (d.manufacturer == kMANUFACTURER_PHILIPS) { - //Private SQs can report different (more precise?) pixel bandwidth values than in the main header! - // https://github.com/rordenlab/dcm2niix/issues/170 - if (is2005140FSQ) break; - if ((lFileOffset + lPos) < sqEndPrivate) break; //inside private SQ, SQ has defined length - if (sqDepthPrivate > 0) break; //inside private SQ, SQ has undefined length - }*/ - d.pixelBandwidth = dcmStrFloat(lLength, &buffer[lPos]); - //printWarning(" PixelBandwidth (0018,0095)====> %g @%d\n", d.pixelBandwidth, lPos); - break; - case kAcquisitionMatrix : - if (lLength == 8) { - uint16_t acquisitionMatrix[4]; - dcmMultiShorts(lLength, &buffer[lPos], 4, &acquisitionMatrix[0],d.isLittleEndian); //slice position - //phaseEncodingLines stored in either image columns or rows - if (acquisitionMatrix[3] > 0) - d.phaseEncodingLines = acquisitionMatrix[3]; - if (acquisitionMatrix[2] > 0) - d.phaseEncodingLines = acquisitionMatrix[2]; - if (acquisitionMatrix[1] > 0) - frequencyRows = acquisitionMatrix[1]; - if (acquisitionMatrix[0] > 0) - frequencyRows = acquisitionMatrix[0]; - } - break; - case kFlipAngle : - d.flipAngle = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kRadionuclideHalfLife : - d.radionuclideHalfLife = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kRadionuclidePositronFraction : - d.radionuclidePositronFraction = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kGantryTilt : - d.gantryTilt = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kXRayExposure : //CTs do not have echo times, we use this field to detect different exposures: https://github.com/neurolabusc/dcm2niix/pull/48 - if (d.TE == 0) {// for CT we will use exposure (0018,1152) whereas for MR we use echo time (0018,0081) - d.isXRay = true; - d.TE = dcmStrFloat(lLength, &buffer[lPos]); - } - break; - case kConvolutionKernel: //CS - dcmStr(lLength, &buffer[lPos], d.convolutionKernel); - break; - case kFrameDuration : - d.frameDuration = dcmStrInt(lLength, &buffer[lPos]); - break; - case kReceiveCoilName : - dcmStr(lLength, &buffer[lPos], d.coilName); - if (strlen(d.coilName) < 1) break; - d.coilCrc = mz_crc32X((unsigned char*) &d.coilName, strlen(d.coilName)); + if ((d.manufacturer != kMANUFACTURER_GE) && (d.manufacturer != kMANUFACTURER_PHILIPS)) + break; //issue384 + d.triggerDelayTime = dcmStrFloat(lLength, &buffer[lPos]); //???? issue 336 + if (d.manufacturer != kMANUFACTURER_GE) break; - case kSlope : - d.intenScale = dcmStrFloat(lLength, &buffer[lPos]); - break; - //case kSpectroscopyDataPointColumns : - // d.xyzDim[4] = dcmInt(4,&buffer[lPos],d.isLittleEndian); - // break; - case kPhilipsSlope : - if ((lLength == 4) && (d.manufacturer == kMANUFACTURER_PHILIPS)) - d.intenScalePhilips = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - break; - - case kMRImageDynamicScanBeginTime: { //FL - if (lLength != 4) break; - MRImageDynamicScanBeginTime = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - if (MRImageDynamicScanBeginTime < minDynamicScanBeginTime) minDynamicScanBeginTime = MRImageDynamicScanBeginTime; - if (MRImageDynamicScanBeginTime > maxDynamicScanBeginTime) maxDynamicScanBeginTime = MRImageDynamicScanBeginTime; - break; - } - case kIntercept : - d.intenIntercept = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kRadiopharmaceutical : - dcmStr(lLength, &buffer[lPos], d.radiopharmaceutical); - break; - case kZThick : - d.xyzMM[3] = dcmStrFloat(lLength, &buffer[lPos]); - d.zThick = d.xyzMM[3]; - break; - case kAcquisitionMatrixText21: - //fall through to kAcquisitionMatrixText - case kAcquisitionMatrixText : { - if (d.manufacturer == kMANUFACTURER_SIEMENS) { - char matStr[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], matStr); - char* pPosition = strchr(matStr, 'I'); - if (pPosition != NULL) - isInterpolated = true; - } - break; } - case kImageOrientationText: //issue522 - if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - dcmStr(lLength, &buffer[lPos], d.imageOrientationText); - break; - case kCoilSiemens : { - if (d.manufacturer == kMANUFACTURER_SIEMENS) { - //see if image from single coil "H12" or an array "HEA;HEP" - //char coilStr[kDICOMStr]; - //int coilNum; - dcmStr(lLength, &buffer[lPos], d.coilName); - if (strlen(d.coilName) < 1) break; - //printf("-->%s\n", coilStr); - //d.coilName = coilStr; - //if (coilStr[0] == 'C') break; //kludge as Nova 32-channel defaults to "C:A32" https://github.com/rordenlab/dcm2niix/issues/187 - //char *ptr; - //dcmStrDigitsOnly(coilStr); - //coilNum = (int)strtol(coilStr, &ptr, 10); - d.coilCrc = mz_crc32X((unsigned char*) &d.coilName, strlen(d.coilName)); - - //printf("%d:%s\n", d.coilNum, coilStr); - //if (*ptr != '\0') - // d.coilNum = 0; - } - break; } - case kImaPATModeText : { //e.g. Siemens iPAT x2 listed as "p2" - char accelStr[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], accelStr); - char *ptr; - dcmStrDigitsOnlyKey('p', accelStr); //e.g. if "p2s4" return "2", if "s4" return "" - 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; - break; } - case kLocationsInAcquisition : - d.locationsInAcquisition = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kUnitsPT: //CS - dcmStr(lLength, &buffer[lPos], d.unitsPT); - break; - case kAttenuationCorrectionMethod: //LO - dcmStr(lLength, &buffer[lPos], d.attenuationCorrectionMethod); - break; - case kDecayCorrection: //CS - dcmStr(lLength, &buffer[lPos], d.decayCorrection); - break; - case kReconstructionMethod: //LO - dcmStr(lLength, &buffer[lPos], d.reconstructionMethod); - break; - case kDecayFactor : - d.decayFactor = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kIconImageSequence: - isIconImageSequence = true; - if (sqDepthIcon < 0) sqDepthIcon = sqDepth; - break; - /*case kStackSliceNumber: { //https://github.com/Kevin-Mattheus-Moerman/GIBBON/blob/master/dicomDict/PMS-R32-dict.txt - int stackSliceNumber = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - printMessage("StackSliceNumber %d\n",stackSliceNumber); - break; - }*/ - //case kMRSeriesAcquisitionNumber: // 0x2001+(0x107B << 16 ) //IS - // mRSeriesAcquisitionNumber = dcmStrInt(lLength, &buffer[lPos]); - // break; - case kNumberOfDynamicScans: - //~d.numberOfDynamicScans = dcmStrInt(lLength, &buffer[lPos]); - 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 - 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'); - //dcmStr(lLength, &buffer[lPos], d.mrAcquisitionType); - break; - case kBodyPartExamined : { - dcmStr(lLength, &buffer[lPos], d.bodyPartExamined); - break; - } - case kScanningSequence : { - dcmStr(lLength, &buffer[lPos], d.scanningSequence); - //According to the DICOM standard 0018,9018 is REQUIRED for EPI raw data - // http://dicom.nema.org/MEDICAL/Dicom/2015c/output/chtml/part03/sect_C.8.13.4.html - //In practice, this is not the case for all vendors - //Fortunately, the combination of 0018,0020 and 0018,9018 appears to reliably detect EPI data - //Siemens (pre-XA) omits 0018,9018, but reports [EP] for 0018,0020 (regardless of SE/GR) - //Siemens (XA) reports 0018,9018 but omits 0018,0020 - //Canon/Toshiba omits 0018,9018, but reports [SE\EP];[GR\EP] for 0018,0020 - //GE omits 0018,9018, but reports [EP\GR];[EP\SE] for 0018,0020 - //Philips reports 0018,9018, but reports [SE];[GR] for 0018,0020 - if ((lLength > 1) && (strstr(d.scanningSequence, "IR") != NULL)) - d.isIR = true; - if ((lLength > 1) && (strstr(d.scanningSequence, "EP") != NULL)) - d.isEPI = true; - break; //warp - } - case kSequenceVariant21 : - if (d.manufacturer != kMANUFACTURER_SIEMENS) break; //see GE dataset in dcm_qa_nih - //fall through... - case kSequenceVariant : { - dcmStr(lLength, &buffer[lPos], d.sequenceVariant); - break; - } - case kScanOptions: - dcmStr(lLength, &buffer[lPos], d.scanOptions); - if ((lLength > 1) && (strstr(d.scanOptions, "PFF") != NULL)) - d.isPartialFourier = true; //e.g. GE does not populate (0018,9081) - break; - case kSequenceName : { - dcmStr(lLength, &buffer[lPos], d.sequenceName); - break; - } - case kMRAcquisitionTypePhilips: //kMRAcquisitionType - if (lLength > 1) d.is3DAcq = (buffer[lPos]=='3') && (toupper(buffer[lPos+1]) == 'D'); - break; - case kAngulationRL: - d.angulation[1] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - break; - case kAngulationAP: - d.angulation[2] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - break; - case kAngulationFH: - d.angulation[3] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - break; - case kMRStackOffcentreRL: - d.stackOffcentre[1] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - break; - case kMRStackOffcentreAP: - d.stackOffcentre[2] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - break; - case kMRStackOffcentreFH: - d.stackOffcentre[3] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - break; - case kSliceOrient: { - char orientStr[kDICOMStr]; - orientStr[0] = 'X'; //avoid compiler warning: orientStr filled by dcmStr - dcmStr(lLength, &buffer[lPos], orientStr); - if (toupper(orientStr[0])== 'S') - d.sliceOrient = kSliceOrientSag; //sagittal - else if (toupper(orientStr[0])== 'C') - d.sliceOrient = kSliceOrientCor; //coronal - else - d.sliceOrient = kSliceOrientTra; //transverse (axial) - break; } - case kElscintIcon : - printWarning("Assuming icon SQ 07a3,10ce.\n"); - isIconImageSequence = true; - if (sqDepthIcon < 0) sqDepthIcon = sqDepth; - break; - case kPMSCT_RLE1 : - //https://groups.google.com/forum/#!topic/comp.protocols.dicom/8HuP_aNy9Pc - //https://discourse.slicer.org/t/fail-to-load-pet-ct-gemini/8158/3 - // d.compressionScheme = kCompressPMSCT_RLE1; //force RLE - if (d.compressionScheme != kCompressPMSCT_RLE1) break; - d.imageStart = (int)lPos + (int)lFileOffset; - d.imageBytes = lLength; + d.CSA.sliceTiming[acquisitionTimesGE_UIH] = d.triggerDelayTime; + //printf("%g\n", d.CSA.sliceTiming[acquisitionTimesGE_UIH]); + acquisitionTimesGE_UIH++; + break; + } + case kRadionuclideTotalDose: + d.radionuclideTotalDose = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kEffectiveTE: { + TE = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + if (d.TE <= 0.0) + d.TE = TE; + break; + } + case kTI: + d.TI = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kEchoNum: + d.echoNum = dcmStrInt(lLength, &buffer[lPos]); + break; + case kMagneticFieldStrength: + d.fieldStrength = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kZSpacing: + d.zSpacing = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kPhaseEncodingSteps: + d.phaseEncodingSteps = dcmStrInt(lLength, &buffer[lPos]); + break; + case kEchoTrainLength: + d.echoTrainLength = dcmStrInt(lLength, &buffer[lPos]); + break; + case kPercentSampling: + d.percentSampling = dcmStrFloat(lLength, &buffer[lPos]); + case kPhaseFieldofView: + d.phaseFieldofView = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kPixelBandwidth: + d.pixelBandwidth = dcmStrFloat(lLength, &buffer[lPos]); + //printWarning(" PixelBandwidth (0018,0095)====> %g @%d\n", d.pixelBandwidth, lPos); + break; + case kAcquisitionMatrix: + if (lLength == 8) { + uint16_t acquisitionMatrix[4]; + dcmMultiShorts(lLength, &buffer[lPos], 4, &acquisitionMatrix[0], d.isLittleEndian); //slice position + //phaseEncodingLines stored in either image columns or rows + if (acquisitionMatrix[3] > 0) + d.phaseEncodingLines = acquisitionMatrix[3]; + if (acquisitionMatrix[2] > 0) + d.phaseEncodingLines = acquisitionMatrix[2]; + if (acquisitionMatrix[1] > 0) + frequencyRows = acquisitionMatrix[1]; + if (acquisitionMatrix[0] > 0) + frequencyRows = acquisitionMatrix[0]; + } + break; + case kFlipAngle: + d.flipAngle = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kRadionuclideHalfLife: + d.radionuclideHalfLife = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kRadionuclidePositronFraction: + d.radionuclidePositronFraction = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kGantryTilt: + d.gantryTilt = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kXRayExposure: //CTs do not have echo times, we use this field to detect different exposures: https://github.com/neurolabusc/dcm2niix/pull/48 + if (d.TE == 0) { // for CT we will use exposure (0018,1152) whereas for MR we use echo time (0018,0081) + d.isXRay = true; + d.TE = dcmStrFloat(lLength, &buffer[lPos]); + } + break; + case kConvolutionKernel: //CS + dcmStr(lLength, &buffer[lPos], d.convolutionKernel); + break; + case kFrameDuration: + d.frameDuration = dcmStrInt(lLength, &buffer[lPos]); + break; + case kReceiveCoilName: + dcmStr(lLength, &buffer[lPos], d.coilName); + if (strlen(d.coilName) < 1) + break; + d.coilCrc = mz_crc32X((unsigned char *)&d.coilName, strlen(d.coilName)); + break; + case kSlope: + d.intenScale = dcmStrFloat(lLength, &buffer[lPos]); + break; + //case kSpectroscopyDataPointColumns : + // d.xyzDim[4] = dcmInt(4,&buffer[lPos],d.isLittleEndian); + // break; + case kPhilipsSlope: + if ((lLength == 4) && (d.manufacturer == kMANUFACTURER_PHILIPS)) + d.intenScalePhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kMRImageDynamicScanBeginTime: { //FL + if (lLength != 4) + break; + MRImageDynamicScanBeginTime = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + if (MRImageDynamicScanBeginTime < minDynamicScanBeginTime) + minDynamicScanBeginTime = MRImageDynamicScanBeginTime; + if (MRImageDynamicScanBeginTime > maxDynamicScanBeginTime) + maxDynamicScanBeginTime = MRImageDynamicScanBeginTime; + break; + } + case kIntercept: + d.intenIntercept = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kRadiopharmaceutical: + dcmStr(lLength, &buffer[lPos], d.radiopharmaceutical); + break; + case kZThick: + d.xyzMM[3] = dcmStrFloat(lLength, &buffer[lPos]); + d.zThick = d.xyzMM[3]; + break; + case kAcquisitionMatrixText21: + //fall through to kAcquisitionMatrixText + case kAcquisitionMatrixText: { + if (d.manufacturer == kMANUFACTURER_SIEMENS) { + char matStr[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], matStr); + char *pPosition = strchr(matStr, 'I'); + if (pPosition != NULL) + isInterpolated = true; + } + break; + } + case kImageOrientationText: //issue522 + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + dcmStr(lLength, &buffer[lPos], d.imageOrientationText); + break; + case kCoilSiemens: { + if (d.manufacturer == kMANUFACTURER_SIEMENS) { + //see if image from single coil "H12" or an array "HEA;HEP" + //char coilStr[kDICOMStr]; + //int coilNum; + dcmStr(lLength, &buffer[lPos], d.coilName); + if (strlen(d.coilName) < 1) + break; + //printf("-->%s\n", coilStr); + //d.coilName = coilStr; + //if (coilStr[0] == 'C') break; //kludge as Nova 32-channel defaults to "C:A32" https://github.com/rordenlab/dcm2niix/issues/187 + //char *ptr; + //dcmStrDigitsOnly(coilStr); + //coilNum = (int)strtol(coilStr, &ptr, 10); + d.coilCrc = mz_crc32X((unsigned char *)&d.coilName, strlen(d.coilName)); + //printf("%d:%s\n", d.coilNum, coilStr); + //if (*ptr != '\0') + // d.coilNum = 0; + } + break; + } + case kImaPATModeText: { //e.g. Siemens iPAT x2 listed as "p2" + char accelStr[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], accelStr); + char *ptr; + dcmStrDigitsOnlyKey('p', accelStr); //e.g. if "p2s4" return "2", if "s4" return "" + 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; + break; + } + case kLocationsInAcquisition: + d.locationsInAcquisition = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kUnitsPT: //CS + dcmStr(lLength, &buffer[lPos], d.unitsPT); + break; + case kAttenuationCorrectionMethod: //LO + dcmStr(lLength, &buffer[lPos], d.attenuationCorrectionMethod); + break; + case kDecayCorrection: //CS + dcmStr(lLength, &buffer[lPos], d.decayCorrection); + break; + case kReconstructionMethod: //LO + dcmStr(lLength, &buffer[lPos], d.reconstructionMethod); + break; + case kDecayFactor: + d.decayFactor = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kIconImageSequence: + isIconImageSequence = true; + if (sqDepthIcon < 0) + sqDepthIcon = sqDepth; + break; + //case kMRSeriesAcquisitionNumber: // 0x2001+(0x107B << 16 ) //IS + // mRSeriesAcquisitionNumber = dcmStrInt(lLength, &buffer[lPos]); + // break; + case kNumberOfDynamicScans: + //~d.numberOfDynamicScans = dcmStrInt(lLength, &buffer[lPos]); + 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 + 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'); + //dcmStr(lLength, &buffer[lPos], d.mrAcquisitionType); + break; + case kBodyPartExamined: { + dcmStr(lLength, &buffer[lPos], d.bodyPartExamined); + break; + } + case kScanningSequence: { + dcmStr(lLength, &buffer[lPos], d.scanningSequence); + //According to the DICOM standard 0018,9018 is REQUIRED for EPI raw data + // http://dicom.nema.org/MEDICAL/Dicom/2015c/output/chtml/part03/sect_C.8.13.4.html + //In practice, this is not the case for all vendors + //Fortunately, the combination of 0018,0020 and 0018,9018 appears to reliably detect EPI data + //Siemens (pre-XA) omits 0018,9018, but reports [EP] for 0018,0020 (regardless of SE/GR) + //Siemens (XA) reports 0018,9018 but omits 0018,0020 + //Canon/Toshiba omits 0018,9018, but reports [SE\EP];[GR\EP] for 0018,0020 + //GE omits 0018,9018, but reports [EP\GR];[EP\SE] for 0018,0020 + //Philips reports 0018,9018, but reports [SE];[GR] for 0018,0020 + if ((lLength > 1) && (strstr(d.scanningSequence, "IR") != NULL)) + d.isIR = true; + if ((lLength > 1) && (strstr(d.scanningSequence, "EP") != NULL)) + d.isEPI = true; + break; //warp + } + case kSequenceVariant21: + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; //see GE dataset in dcm_qa_nih + //fall through... + case kSequenceVariant: { + dcmStr(lLength, &buffer[lPos], d.sequenceVariant); + break; + } + case kScanOptions: + dcmStr(lLength, &buffer[lPos], d.scanOptions); + if ((lLength > 1) && (strstr(d.scanOptions, "PFF") != NULL)) + d.isPartialFourier = true; //e.g. GE does not populate (0018,9081) + break; + case kSequenceName: { + dcmStr(lLength, &buffer[lPos], d.sequenceName); + break; + } + case kMRAcquisitionTypePhilips: //kMRAcquisitionType + if (lLength > 1) + d.is3DAcq = (buffer[lPos] == '3') && (toupper(buffer[lPos + 1]) == 'D'); + break; + case kAngulationRL: + d.angulation[1] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kAngulationAP: + d.angulation[2] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kAngulationFH: + d.angulation[3] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kMRStackOffcentreRL: + d.stackOffcentre[1] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kMRStackOffcentreAP: + d.stackOffcentre[2] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kMRStackOffcentreFH: + d.stackOffcentre[3] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kSliceOrient: { + char orientStr[kDICOMStr]; + orientStr[0] = 'X'; //avoid compiler warning: orientStr filled by dcmStr + dcmStr(lLength, &buffer[lPos], orientStr); + if (toupper(orientStr[0]) == 'S') + d.sliceOrient = kSliceOrientSag; //sagittal + else if (toupper(orientStr[0]) == 'C') + d.sliceOrient = kSliceOrientCor; //coronal + else + d.sliceOrient = kSliceOrientTra; //transverse (axial) + break; + } + case kElscintIcon: + printWarning("Assuming icon SQ 07a3,10ce.\n"); + isIconImageSequence = true; + if (sqDepthIcon < 0) + sqDepthIcon = sqDepth; + break; + case kPMSCT_RLE1: + //https://groups.google.com/forum/#!topic/comp.protocols.dicom/8HuP_aNy9Pc + //https://discourse.slicer.org/t/fail-to-load-pet-ct-gemini/8158/3 + // d.compressionScheme = kCompressPMSCT_RLE1; //force RLE + if (d.compressionScheme != kCompressPMSCT_RLE1) break; - case kPrivateCreator : { - if (d.manufacturer != kMANUFACTURER_UNKNOWN) break; - d.manufacturer = dcmStrManufacturer (lLength, &buffer[lPos]); - volDiffusion.manufacturer = d.manufacturer; - break; } - case kDiffusion_bValuePhilips : - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - B0Philips = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - set_bVal(&volDiffusion, B0Philips); - break; - // case kDiffusionBFactor: // 2001,1003 - // if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition)) { - // d.CSA.numDti++; //increment with BFactor: on Philips slices with B=0 have B-factor but no diffusion directions - // if (d.CSA.numDti == 2) { //First time we know that this is a 4D DTI dataset - // //d.dti4D = (TDTI *)malloc(kMaxDTI4D * sizeof(TDTI)); - // dti4D->S[0].V[0] = d.CSA.dtiV[0]; - // dti4D->S[0].V[1] = d.CSA.dtiV[1]; - // dti4D->S[0].V[2] = d.CSA.dtiV[2]; - // dti4D->S[0].V[3] = d.CSA.dtiV[3]; - // } - // d.CSA.dtiV[0] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - // if ((d.CSA.numDti > 1) && (d.CSA.numDti < kMaxDTI4D)) - // dti4D->S[d.CSA.numDti-1].V[0] = d.CSA.dtiV[0]; - // /*if ((d.CSA.numDti > 0) && (d.CSA.numDti <= kMaxDTIv)) - // d.CSA.dtiV[d.CSA.numDti-1][0] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian);*/ - // } - // break; - - /* - case kDiffusionDirectionPhilips: {// - //note not useful: does not report precise direction, both B=0 and Isotropic scans labelled "I" so does not tell us if image is oblique - //http://incenter.medical.philips.com/doclib/enc/fetch/2000/4504/577242/577256/588723/5144873/5144488/5144982/DICOM_Conformance_Statement_Ingenia_R4.1.pdf%3fnodeid%3d8124182%26vernum%3d-2 - //CS: Possible values: P (PreparationDirection), M (MeasurementDirection),S (Selection Direction),O(Oblique Direction),I (Isotropic),Only applicable for diffusion scans. - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - char diffDir[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], diffDir); - printf(">>%s %s\n", diffDir, fname); + d.imageStart = (int)lPos + (int)lFileOffset; + d.imageBytes = lLength; + break; + case kPrivateCreator: { + if (d.manufacturer != kMANUFACTURER_UNKNOWN) break; - } - */ - case kCardiacSync : //CS [TRIGGERED],[NO] - if (lLength < 2) break; - if (toupper(buffer[lPos]) != 'N') - isTriggerSynced = true; - break; - case kDiffusion_bValue: // 0018,9087 - if (d.manufacturer == kMANUFACTURER_UNKNOWN ) { - d.manufacturer = kMANUFACTURER_PHILIPS; - printWarning("Found 0018,9087 but manufacturer (0008,0070) unknown: assuming Philips.\n"); - } - - // Note that this is ahead of kImagePositionPatient (0020,0032), so - // isAtFirstPatientPosition is not necessarily set yet. - // Philips uses this tag too, at least as of 5.1, but they also - // use kDiffusionBFactor (see above), and we do not want to - // double count. More importantly, with Philips this tag - // (sometimes?) gets repeated in a nested sequence with the - // value *unset*! - // GE started using this tag in 27, and annoyingly, NOT including - // the b value if it is 0 for the slice. - - //if((d.manufacturer != kMANUFACTURER_PHILIPS) || !is2005140FSQ){ - // d.CSA.numDti++; - // if (d.CSA.numDti == 2) { //First time we know that this is a 4D DTI dataset - // //d.dti4D = (TDTI *)malloc(kMaxDTI4D * sizeof(TDTI)); - // dti4D->S[0].V[0] = d.CSA.dtiV[0]; - // dti4D->S[0].V[1] = d.CSA.dtiV[1]; - // dti4D->S[0].V[2] = d.CSA.dtiV[2]; - // dti4D->S[0].V[3] = d.CSA.dtiV[3]; - // } - B0Philips = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); - //d.CSA.dtiV[0] = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); - set_bVal(&volDiffusion, dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian)); - // if ((d.CSA.numDti > 1) && (d.CSA.numDti < kMaxDTI4D)) - // dti4D->S[d.CSA.numDti-1].V[0] = d.CSA.dtiV[0]; - //} - break; - case kDiffusionOrientation: // 0018, 9089 - // Note that this is ahead of kImagePositionPatient (0020,0032), so - // isAtFirstPatientPosition is not necessarily set yet. - // Philips uses this tag too, at least as of 5.1, but they also - // use kDiffusionDirectionRL, etc., and we do not want to double - // count. More importantly, with Philips this tag (sometimes?) - // gets repeated in a nested sequence with the value *unset*! - // if (((d.manufacturer == kMANUFACTURER_SIEMENS) || - // ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ)) && - // (isAtFirstPatientPosition || isnan(d.patientPosition[1]))) - - //if((d.manufacturer == kMANUFACTURER_SIEMENS) || ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ)) - if((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_HITACHI) || (d.manufacturer == kMANUFACTURER_SIEMENS) || (d.manufacturer == kMANUFACTURER_PHILIPS)) { - //for kMANUFACTURER_HITACHI see https://nciphub.org/groups/qindicom/wiki/StandardcompliantenhancedmultiframeDWI - float v[4]; - //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v); - //dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); - dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); - vRLPhilips = v[0]; - vAPPhilips = v[1]; - vFHPhilips = v[2]; - //printMessage("><>< 0018,9089:\t%g\t%g\t%g\n", v[0], v[1], v[2]); + d.manufacturer = dcmStrManufacturer(lLength, &buffer[lPos]); + volDiffusion.manufacturer = d.manufacturer; + break; + } + case kDiffusion_bValuePhilips: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + B0Philips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + set_bVal(&volDiffusion, B0Philips); + break; + case kCardiacSync: //CS [TRIGGERED],[NO] + if (lLength < 2) + break; + if (toupper(buffer[lPos]) != 'N') + isTriggerSynced = true; + break; + case kDiffusion_bValue: // 0018,9087 + if (d.manufacturer == kMANUFACTURER_UNKNOWN) { + d.manufacturer = kMANUFACTURER_PHILIPS; + printWarning("Found 0018,9087 but manufacturer (0008,0070) unknown: assuming Philips.\n"); + } + // Note that this is ahead of kImagePositionPatient (0020,0032), so + // isAtFirstPatientPosition is not necessarily set yet. + // Philips uses this tag too, at least as of 5.1, but they also + // use kDiffusionBFactor (see above), and we do not want to + // double count. More importantly, with Philips this tag + // (sometimes?) gets repeated in a nested sequence with the + // value *unset*! + // GE started using this tag in 27, and annoyingly, NOT including + // the b value if it is 0 for the slice. + //if((d.manufacturer != kMANUFACTURER_PHILIPS) || !is2005140FSQ){ + // d.CSA.numDti++; + // if (d.CSA.numDti == 2) { //First time we know that this is a 4D DTI dataset + // dti4D->S[0].V[0] = d.CSA.dtiV[0]; + // dti4D->S[0].V[1] = d.CSA.dtiV[1]; + // dti4D->S[0].V[2] = d.CSA.dtiV[2]; + // dti4D->S[0].V[3] = d.CSA.dtiV[3]; + //} + B0Philips = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + //d.CSA.dtiV[0] = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bVal(&volDiffusion, dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian)); + // if ((d.CSA.numDti > 1) && (d.CSA.numDti < kMaxDTI4D)) + // dti4D->S[d.CSA.numDti-1].V[0] = d.CSA.dtiV[0]; + //} + break; + case kDiffusionOrientation: // 0018, 9089 + // Note that this is ahead of kImagePositionPatient (0020,0032), so + // isAtFirstPatientPosition is not necessarily set yet. + // Philips uses this tag too, at least as of 5.1, but they also + // use kDiffusionDirectionRL, etc., and we do not want to double + // count. More importantly, with Philips this tag (sometimes?) + // gets repeated in a nested sequence with the value *unset*! + // if (((d.manufacturer == kMANUFACTURER_SIEMENS) || + // ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ)) && + // (isAtFirstPatientPosition || isnan(d.patientPosition[1]))) + //if((d.manufacturer == kMANUFACTURER_SIEMENS) || ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ)) + if ((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_HITACHI) || (d.manufacturer == kMANUFACTURER_SIEMENS) || (d.manufacturer == kMANUFACTURER_PHILIPS)) { + //for kMANUFACTURER_HITACHI see https://nciphub.org/groups/qindicom/wiki/StandardcompliantenhancedmultiframeDWI + float v[4]; + //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v); + //dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); + dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); + vRLPhilips = v[0]; + vAPPhilips = v[1]; + vFHPhilips = v[2]; + //printMessage("><>< 0018,9089:\t%g\t%g\t%g\n", v[0], v[1], v[2]); //https://github.com/rordenlab/dcm2niix/issues/256 //d.CSA.dtiV[1] = v[0]; //d.CSA.dtiV[2] = v[1]; //d.CSA.dtiV[3] = v[2]; //printMessage("><>< 0018,9089: DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); - hasDwiDirectionality = true; + hasDwiDirectionality = true; d.isBVecWorldCoordinates = true; //e.g. Canon saved image space coordinates in Comments, world space in 0018, 9089 - set_orientation0018_9089(&volDiffusion, lLength, &buffer[lPos], d.isLittleEndian); - } - break; - // case kSharedFunctionalGroupsSequence: - // if ((d.manufacturer == kMANUFACTURER_SIEMENS) && isAtFirstPatientPosition) { - // break; // For now - need to figure out how to get the nested - // // part of buffer[lPos]. - // } - // break; - - //case kSliceNumberMrPhilips : - // sliceNumberMrPhilips3D = dcmStrInt(lLength, &buffer[lPos]); - // break; - case kImagingFrequency2 : - d.imagingFrequency = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); - break; - case kParallelReductionFactorOutOfPlane: - if (d.manufacturer == kMANUFACTURER_SIEMENS) break; - d.accelFactOOP = dcmFloatDouble(lLength, &buffer[lPos],d.isLittleEndian); - break; - //case kFrameAcquisitionDuration : - // frameAcquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); //issue369 - // break; - case kDiffusionBValueXX : { - if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix - double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); - set_bMatrix(&volDiffusion, bMat, 0); - break; } - case kDiffusionBValueXY : { - if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix - double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); - set_bMatrix(&volDiffusion, bMat, 1); - break; } - case kDiffusionBValueXZ : { - if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix - double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); - set_bMatrix(&volDiffusion, bMat, 2); - break; } - case kDiffusionBValueYY : { - if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix - double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); - set_bMatrix(&volDiffusion, bMat, 3); - break; } - case kDiffusionBValueYZ : { - if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix - double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); - set_bMatrix(&volDiffusion, bMat, 4); - break; } - case kDiffusionBValueZZ : { - if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix - double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); - set_bMatrix(&volDiffusion, bMat, 5); - d.isVectorFromBMatrix = true; - break; } - case kSliceNumberMrPhilips : { - if (d.manufacturer != kMANUFACTURER_PHILIPS) - break; - sliceNumberMrPhilips = dcmStrInt(lLength, &buffer[lPos]); - int sliceNumber = sliceNumberMrPhilips; - //use public patientPosition if it exists - fall back to private patient position - if ((sliceNumber == 1) && (!isnan(patientPosition[1])) ) { - for (int k = 0; k < 4; k++) - patientPositionStartPhilips[k] = patientPosition[k]; - } else if ((sliceNumber == 1) && (!isnan(patientPositionPrivate[1]))) { - for (int k = 0; k < 4; k++) - patientPositionStartPhilips[k] = patientPositionPrivate[k]; - } - if ((sliceNumber == locationsInAcquisitionPhilips) && (!isnan(patientPosition[1])) ) { - for (int k = 0; k < 4; k++) - patientPositionEndPhilips[k] = patientPosition[k]; - } else if ((sliceNumber == locationsInAcquisitionPhilips) && (!isnan(patientPositionPrivate[1])) ) { - for (int k = 0; k < 4; k++) - patientPositionEndPhilips[k] = patientPositionPrivate[k]; - } - break; } - case kEPIFactorPhilips : - if (d.manufacturer != kMANUFACTURER_PHILIPS) - break; - echoTrainLengthPhil = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kPrepulseDelay : //FL - if (d.manufacturer != kMANUFACTURER_PHILIPS) - break; - d.TI = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - break; - case kPrepulseType : //CS [INV] - if (d.manufacturer != kMANUFACTURER_PHILIPS) - break; - if (lLength < 3) break; - if ((toupper(buffer[lPos]) != 'I') && (toupper(buffer[lPos+1]) != 'N') && (toupper(buffer[lPos+2]) != 'V')) - d.isIR = true; - break; - case kRespirationSync : //CS [TRIGGERED],[NO] - if (lLength < 2) break; - if (toupper(buffer[lPos]) != 'N') - isTriggerSynced = true; - break; - case kNumberOfSlicesMrPhilips : - if (d.manufacturer != kMANUFACTURER_PHILIPS) - break; - locationsInAcquisitionPhilips = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - //printMessage("====> locationsInAcquisitionPhilips\t%d\n", locationsInAcquisitionPhilips); + set_orientation0018_9089(&volDiffusion, lLength, &buffer[lPos], d.isLittleEndian); + } + break; + //case kSliceNumberMrPhilips : + // sliceNumberMrPhilips3D = dcmStrInt(lLength, &buffer[lPos]); + // break; + case kImagingFrequency2: + d.imagingFrequency = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kParallelReductionFactorOutOfPlane: + if (d.manufacturer == kMANUFACTURER_SIEMENS) + break; + d.accelFactOOP = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + break; + //case kFrameAcquisitionDuration : + // frameAcquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); //issue369 + // break; + case kDiffusionBValueXX: { + if (!(d.manufacturer == kMANUFACTURER_BRUKER)) + break; //other manufacturers provide bvec directly, rather than bmatrix + double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bMatrix(&volDiffusion, bMat, 0); + break; + } + case kDiffusionBValueXY: { + if (!(d.manufacturer == kMANUFACTURER_BRUKER)) + break; //other manufacturers provide bvec directly, rather than bmatrix + double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bMatrix(&volDiffusion, bMat, 1); + break; + } + case kDiffusionBValueXZ: { + if (!(d.manufacturer == kMANUFACTURER_BRUKER)) + break; //other manufacturers provide bvec directly, rather than bmatrix + double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bMatrix(&volDiffusion, bMat, 2); + break; + } + case kDiffusionBValueYY: { + if (!(d.manufacturer == kMANUFACTURER_BRUKER)) + break; //other manufacturers provide bvec directly, rather than bmatrix + double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bMatrix(&volDiffusion, bMat, 3); + break; + } + case kDiffusionBValueYZ: { + if (!(d.manufacturer == kMANUFACTURER_BRUKER)) + break; //other manufacturers provide bvec directly, rather than bmatrix + double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bMatrix(&volDiffusion, bMat, 4); + break; + } + case kDiffusionBValueZZ: { + if (!(d.manufacturer == kMANUFACTURER_BRUKER)) + break; //other manufacturers provide bvec directly, rather than bmatrix + double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + set_bMatrix(&volDiffusion, bMat, 5); + d.isVectorFromBMatrix = true; + break; + } + case kSliceNumberMrPhilips: { + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + sliceNumberMrPhilips = dcmStrInt(lLength, &buffer[lPos]); + int sliceNumber = sliceNumberMrPhilips; + //use public patientPosition if it exists - fall back to private patient position + if ((sliceNumber == 1) && (!isnan(patientPosition[1]))) { + for (int k = 0; k < 4; k++) + patientPositionStartPhilips[k] = patientPosition[k]; + } else if ((sliceNumber == 1) && (!isnan(patientPositionPrivate[1]))) { + for (int k = 0; k < 4; k++) + patientPositionStartPhilips[k] = patientPositionPrivate[k]; + } + if ((sliceNumber == locationsInAcquisitionPhilips) && (!isnan(patientPosition[1]))) { + for (int k = 0; k < 4; k++) + patientPositionEndPhilips[k] = patientPosition[k]; + } else if ((sliceNumber == locationsInAcquisitionPhilips) && (!isnan(patientPositionPrivate[1]))) { + for (int k = 0; k < 4; k++) + patientPositionEndPhilips[k] = patientPositionPrivate[k]; + } + break; + } + case kEPIFactorPhilips: + if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - case kPartialMatrixScannedPhilips : - if (d.manufacturer != kMANUFACTURER_PHILIPS) - break; - if (lLength < 2) break; - if (toupper(buffer[lPos]) == 'Y') - d.isPartialFourier = true; - break; - case kWaterFatShiftPhilips : - if (d.manufacturer != kMANUFACTURER_PHILIPS) - break; - d.waterFatShift = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); + echoTrainLengthPhil = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kPrepulseDelay: //FL + if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - case kDiffusionDirectionRL: - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - vRLPhilips = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - set_diffusion_directionPhilips(&volDiffusion, vRLPhilips, 0); + d.TI = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kPrepulseType: //CS [INV] + if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - case kDiffusionDirectionAP: - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - vAPPhilips = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - set_diffusion_directionPhilips(&volDiffusion, vAPPhilips, 1); + if (lLength < 3) break; - case kDiffusionDirectionFH: - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - vFHPhilips = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - set_diffusion_directionPhilips(&volDiffusion, vFHPhilips, 2); + if ((toupper(buffer[lPos]) != 'I') && (toupper(buffer[lPos + 1]) != 'N') && (toupper(buffer[lPos + 2]) != 'V')) + d.isIR = true; + break; + case kRespirationSync: //CS [TRIGGERED],[NO] + if (lLength < 2) break; - // case kDiffusionDirectionRL: - // if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition)) { - // d.CSA.dtiV[1] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - // if ((d.CSA.numDti > 1) && (d.CSA.numDti < kMaxDTI4D)) - // dti4D->S[d.CSA.numDti-1].V[1] = d.CSA.dtiV[1]; - // } - // /*if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition) && (d.CSA.numDti > 0) && (d.CSA.numDti <= kMaxDTIv)) - // d.CSA.dtiV[d.CSA.numDti-1][1] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian);*/ - // break; - // case kDiffusionDirectionAP: - // if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition)) { - // d.CSA.dtiV[2] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - // if ((d.CSA.numDti > 1) && (d.CSA.numDti < kMaxDTI4D)) - // dti4D->S[d.CSA.numDti-1].V[2] = d.CSA.dtiV[2]; - // } - // /*if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition) && (d.CSA.numDti > 0) && (d.CSA.numDti <= kMaxDTIv)) - // d.CSA.dtiV[d.CSA.numDti-1][2] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian);*/ - // break; - // case kDiffusionDirectionFH: - // if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition)) { - // d.CSA.dtiV[3] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - // if ((d.CSA.numDti > 1) && (d.CSA.numDti < kMaxDTI4D)) - // dti4D->S[d.CSA.numDti-1].V[3] = d.CSA.dtiV[3]; - // //printMessage("dti XYZ %g %g %g\n",d.CSA.dtiV[1],d.CSA.dtiV[2],d.CSA.dtiV[3]); - // } - // /*if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition) && (d.CSA.numDti > 0) && (d.CSA.numDti <= kMaxDTIv)) - // d.CSA.dtiV[d.CSA.numDti-1][3] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian);*/ - // //http://www.na-mic.org/Wiki/index.php/NAMIC_Wiki:DTI:DICOM_for_DWI_and_DTI - // break; - //~~ - case kPrivatePerFrameSq : - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - //if ((vr[0] == 'S') && (vr[1] == 'Q')) break; - //if (!is2005140FSQwarned) - // printWarning("expected VR of 2005,140F to be 'SQ' (prior DICOM->DICOM conversion error?)\n"); - is2005140FSQ = true; - //is2005140FSQwarned = true; - //case kMRImageGradientOrientationNumber : - // if (d.manufacturer == kMANUFACTURER_PHILIPS) - // MRImageGradientOrientationNumber = dcmStrInt(lLength, &buffer[lPos]); - break; - case kMRImageDiffBValueNumber: - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - philMRImageDiffBValueNumber = dcmStrInt(lLength, &buffer[lPos]); - break; - case kWaveformSq: - d.imageStart = 1; //abort!!! - printMessage("Skipping DICOM (audio not image) '%s'\n", fname); - break; - case kSpectroscopyData: //kSpectroscopyDataPointColumns - printMessage("Skipping Spectroscopy DICOM '%s'\n", fname); - d.imageStart = (int)lPos + (int)lFileOffset; - break; - case kCSAImageHeaderInfo: - if ((lPos + lLength) > fileLen) break; - readCSAImageHeader(&buffer[lPos], lLength, &d.CSA, isVerbose, d.is3DAcq); //, dti4D); - if (!d.isHasPhase) - d.isHasPhase = d.CSA.isPhaseMap; - break; - //case kObjectGraphics: - // printMessage("---->%d,",lLength); - // break; - case kCSASeriesHeaderInfo: - if ((lPos + lLength) > fileLen) break; - d.CSA.SeriesHeader_offset = (int)lPos; - d.CSA.SeriesHeader_length = lLength; - break; - case kRealWorldIntercept: - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - d.RWVIntercept = dcmFloatDouble(lLength, &buffer[lPos],d.isLittleEndian); - if (isSameFloat(0.0, d.intenIntercept)) //give precedence to standard value - d.intenIntercept = d.RWVIntercept; - break; - case kRealWorldSlope: - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - d.RWVScale = dcmFloatDouble(lLength, &buffer[lPos],d.isLittleEndian); - if (d.RWVScale > 1.0E38) - d.RWVScale = 0.0; - else if (isSameFloat(1.0, d.intenScale)) //give precedence to standard value - d.intenScale = d.RWVScale; - break; - case kUserDefineDataGE: { //0043,102A - if ((d.manufacturer != kMANUFACTURER_GE) || (lLength < 128)) break; - #define MY_DEBUG_GE // <- uncomment this to use following code to infer GE phase encoding direction - #ifdef MY_DEBUG_GE - int isVerboseX = isVerbose; //for debugging only - in standard release we will enable user defined "isVerbose" - //int isVerboseX = 2; - if (isVerboseX > 1) printMessage(" UserDefineDataGE file offset/length %ld %u\n", lFileOffset+lPos, lLength); - if (lLength < 916) { //minimum size is hdr_offset=0, read 0x0394 - printMessage(" GE header too small to be valid (A)\n"); - break; - } - //debug code to export binary data - /* - char str[kDICOMStr]; - sprintf(str, "%s_ge.bin",fname); - FILE *pFile = fopen(str, "wb"); + if (toupper(buffer[lPos]) != 'N') + isTriggerSynced = true; + break; + case kNumberOfSlicesMrPhilips: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + locationsInAcquisitionPhilips = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + //printMessage("====> locationsInAcquisitionPhilips\t%d\n", locationsInAcquisitionPhilips); + break; + case kPartialMatrixScannedPhilips: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + if (lLength < 2) + break; + if (toupper(buffer[lPos]) == 'Y') + d.isPartialFourier = true; + break; + case kWaterFatShiftPhilips: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + d.waterFatShift = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kDiffusionDirectionRL: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + vRLPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + set_diffusion_directionPhilips(&volDiffusion, vRLPhilips, 0); + break; + case kDiffusionDirectionAP: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + vAPPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + set_diffusion_directionPhilips(&volDiffusion, vAPPhilips, 1); + break; + case kDiffusionDirectionFH: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + vFHPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + set_diffusion_directionPhilips(&volDiffusion, vFHPhilips, 2); + break; + case kPrivatePerFrameSq: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + //if ((vr[0] == 'S') && (vr[1] == 'Q')) break; + //if (!is2005140FSQwarned) + // printWarning("expected VR of 2005,140F to be 'SQ' (prior DICOM->DICOM conversion error?)\n"); + is2005140FSQ = true; + //is2005140FSQwarned = true; + //case kMRImageGradientOrientationNumber : + // if (d.manufacturer == kMANUFACTURER_PHILIPS) + // MRImageGradientOrientationNumber = dcmStrInt(lLength, &buffer[lPos]); + break; + case kMRImageDiffBValueNumber: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + philMRImageDiffBValueNumber = dcmStrInt(lLength, &buffer[lPos]); + break; + case kWaveformSq: + d.imageStart = 1; //abort!!! + printMessage("Skipping DICOM (audio not image) '%s'\n", fname); + break; + case kSpectroscopyData: //kSpectroscopyDataPointColumns + printMessage("Skipping Spectroscopy DICOM '%s'\n", fname); + d.imageStart = (int)lPos + (int)lFileOffset; + break; + case kCSAImageHeaderInfo: + if ((lPos + lLength) > fileLen) + break; + readCSAImageHeader(&buffer[lPos], lLength, &d.CSA, isVerbose, d.is3DAcq); //, dti4D); + if (!d.isHasPhase) + d.isHasPhase = d.CSA.isPhaseMap; + break; + case kCSASeriesHeaderInfo: + if ((lPos + lLength) > fileLen) + break; + d.CSA.SeriesHeader_offset = (int)lPos; + d.CSA.SeriesHeader_length = lLength; + break; + case kRealWorldIntercept: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + d.RWVIntercept = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + if (isSameFloat(0.0, d.intenIntercept)) //give precedence to standard value + d.intenIntercept = d.RWVIntercept; + break; + case kRealWorldSlope: + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + d.RWVScale = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); + if (d.RWVScale > 1.0E38) + d.RWVScale = 0.0; + else if (isSameFloat(1.0, d.intenScale)) //give precedence to standard value + d.intenScale = d.RWVScale; + break; + case kUserDefineDataGE: { //0043,102A + if ((d.manufacturer != kMANUFACTURER_GE) || (lLength < 128)) + break; +#define MY_DEBUG_GE // <- uncomment this to use following code to infer GE phase encoding direction +#ifdef MY_DEBUG_GE + int isVerboseX = isVerbose; //for debugging only - in standard release we will enable user defined "isVerbose" + //int isVerboseX = 2; + if (isVerboseX > 1) + printMessage(" UserDefineDataGE file offset/length %ld %u\n", lFileOffset + lPos, lLength); + if (lLength < 916) { //minimum size is hdr_offset=0, read 0x0394 + printMessage(" GE header too small to be valid (A)\n"); + break; + } + //debug code to export binary data + /* + char str[kDICOMStr]; + sprintf(str, "%s_ge.bin",fname); + FILE *pFile = fopen(str, "wb"); fwrite(&buffer[lPos], 1, lLength, pFile); - fclose (pFile); - */ - if ((size_t)(lPos + lLength) > MaxBufferSz) { - //we could re-read the buffer in this case, however in practice GE headers are concise so we never see this issue - printMessage(" GE header overflows buffer\n"); - break; - } - uint16_t hdr_offset = dcmInt(2,&buffer[lPos+24],true); - if (isVerboseX > 1) printMessage(" header offset: %d\n", hdr_offset); - if (lLength < (hdr_offset+916)) { //minimum size is hdr_offset=0, read 0x0394 - printMessage(" GE header too small to be valid (B)\n"); - break; - } - //size_t hdr = lPos+hdr_offset; - float version = dcmFloat(4,&buffer[lPos + hdr_offset],true); - if (isVerboseX > 1) printMessage(" version %g\n", version); - if (version < 5.0 || version > 40.0) { - //printMessage(" GE header file format incorrect %g\n", version); - break; - } - //char const *hdr = &buffer[lPos + hdr_offset]; - char *hdr = (char *)&buffer[lPos + hdr_offset]; - int epi_chk_off = 0x003a; - int pepolar_off = 0x0030; - int kydir_off = 0x0394; - if (version >= 25.002) { - hdr += 0x004c; - kydir_off -= 0x008c; - } - //int seqOrInter =dcmInt(2,(unsigned char*)(hdr + pepolar_off-638),true); - //int seqOrInter2 =dcmInt(2,(unsigned char*)(hdr + kydir_off-638),true); - //printf("%d %d<<<\n", seqOrInter,seqOrInter2); - //check if EPI - if (true) { - //int check = *(short const *)(hdr + epi_chk_off) & 0x800; - int check =dcmInt(2,(unsigned char*)hdr + epi_chk_off,true) & 0x800; - if (check == 0) { - if (isVerboseX > 1) printMessage("%s: Warning: Data is not EPI\n", fname); - break; - } - } - //Check for PE polarity - // int flag1 = *(short const *)(hdr + pepolar_off) & 0x0004; - //Check for ky direction (view order) - // int flag2 = *(int const *)(hdr + kydir_off); - int phasePolarityFlag = dcmInt(2,(unsigned char*)hdr + pepolar_off,true) & 0x0004; - //Check for ky direction (view order) - int sliceOrderFlag = dcmInt(2,(unsigned char*)hdr + kydir_off,true); - if (isVerboseX > 1) - printMessage(" GE phasePolarity/sliceOrder flags %d %d\n", phasePolarityFlag, sliceOrderFlag); - if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_FLIPPED) + fclose (pFile); + */ + if ((size_t)(lPos + lLength) > MaxBufferSz) { + //we could re-read the buffer in this case, however in practice GE headers are concise so we never see this issue + printMessage(" GE header overflows buffer\n"); + break; + } + uint16_t hdr_offset = dcmInt(2, &buffer[lPos + 24], true); + if (isVerboseX > 1) + printMessage(" header offset: %d\n", hdr_offset); + if (lLength < (hdr_offset + 916)) { //minimum size is hdr_offset=0, read 0x0394 + printMessage(" GE header too small to be valid (B)\n"); + break; + } + //size_t hdr = lPos+hdr_offset; + float version = dcmFloat(4, &buffer[lPos + hdr_offset], true); + if (isVerboseX > 1) + printMessage(" version %g\n", version); + if (version < 5.0 || version > 40.0) { + //printMessage(" GE header file format incorrect %g\n", version); + break; + } + //char const *hdr = &buffer[lPos + hdr_offset]; + char *hdr = (char *)&buffer[lPos + hdr_offset]; + int epi_chk_off = 0x003a; + int pepolar_off = 0x0030; + int kydir_off = 0x0394; + if (version >= 25.002) { + hdr += 0x004c; + kydir_off -= 0x008c; + } + //int seqOrInter =dcmInt(2,(unsigned char*)(hdr + pepolar_off-638),true); + //int seqOrInter2 =dcmInt(2,(unsigned char*)(hdr + kydir_off-638),true); + //printf("%d %d<<<\n", seqOrInter,seqOrInter2); + //check if EPI + if (true) { + //int check = *(short const *)(hdr + epi_chk_off) & 0x800; + int check = dcmInt(2, (unsigned char *)hdr + epi_chk_off, true) & 0x800; + if (check == 0) { + if (isVerboseX > 1) + printMessage("%s: Warning: Data is not EPI\n", fname); + break; + } + } + //Check for PE polarity + // int flag1 = *(short const *)(hdr + pepolar_off) & 0x0004; + //Check for ky direction (view order) + // int flag2 = *(int const *)(hdr + kydir_off); + int phasePolarityFlag = dcmInt(2, (unsigned char *)hdr + pepolar_off, true) & 0x0004; + //Check for ky direction (view order) + int sliceOrderFlag = dcmInt(2, (unsigned char *)hdr + kydir_off, true); + if (isVerboseX > 1) + printMessage(" GE phasePolarity/sliceOrder flags %d %d\n", phasePolarityFlag, sliceOrderFlag); + if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_FLIPPED) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; + if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) + d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; + if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP) { + //https://cfmriweb.ucsd.edu/Howto/3T/operatingtips.html + if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; - if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) + else d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; - if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP) { - //https://cfmriweb.ucsd.edu/Howto/3T/operatingtips.html - if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) - d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; - else - d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; + } +//if (sliceOrderFlag == kGE_SLICE_ORDER_TOP_DOWN) +// d.sliceOrderGE = kGE_SLICE_ORDER_TOP_DOWN; +//if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP) +// d.sliceOrderGE = kGE_SLICE_ORDER_BOTTOM_UP; +#endif + break; + } + case kEffectiveEchoSpacingGE: + if (d.manufacturer == kMANUFACTURER_GE) + d.effectiveEchoSpacingGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kImageTypeGE: { //0/1/2/3 for magnitude/phase/real/imaginary + if (d.manufacturer != kMANUFACTURER_GE) + break; + int dt = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + if (dt == 0) + d.isHasMagnitude = true; + if (dt == 1) + d.isHasPhase = true; + if (dt == 2) + d.isHasReal = true; + if (dt == 3) + d.isHasImaginary = true; + break; + } + case kDiffusion_bValueGE: + if (d.manufacturer == kMANUFACTURER_GE) { + d.CSA.dtiV[0] = (float)set_bValGE(&volDiffusion, lLength, &buffer[lPos]); + d.CSA.numDti = 1; + } + break; + case kEpiRTGroupDelayGE: //FL + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.groupDelay = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + d.groupDelay *= 1000.0; //sec -> ms + // If kEpiRTGroupDelayGE (0043,107C) exists, epiRT + d.epiVersionGE = 1; //-1 = not epi, 0 = epi, 1 = epiRT + break; + case kAssetRFactorsGE: { //DS issue427GE + if (d.manufacturer != kMANUFACTURER_GE) + break; + float PhaseSlice[3]; + dcmMultiFloat(lLength, (char *)&buffer[lPos], 2, PhaseSlice); + if (PhaseSlice[1] > 0.0) + d.accelFactPE = 1.0f / PhaseSlice[1]; + if (PhaseSlice[2] > 0.0) + d.accelFactOOP = 1.0f / PhaseSlice[2]; + break; + } + case kASLContrastTechniqueGE: { //CS + if (d.manufacturer != kMANUFACTURER_GE) + break; + char st[kDICOMStr]; + //aslFlagsGE + dcmStr(lLength, &buffer[lPos], st); + if (strstr(st, "PSEUDOCONTINUOUS") != NULL) + d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_PSEUDOCONTINUOUS); + else if (strstr(st, "CONTINUOUS") != NULL) + d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_CONTINUOUS); + break; + } + case kASLLabelingTechniqueGE: { //LO issue427GE + if (d.manufacturer != kMANUFACTURER_GE) + break; + char st[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], st); + if (strstr(st, "3D continuous") != NULL) + d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_3DCASL); + if (strstr(st, "3D pulsed continuous") != NULL) + d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_3DPCASL); + break; + } + case kDurationLabelPulseGE: { //IS + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.durationLabelPulseGE = dcmStrInt(lLength, &buffer[lPos]); + break; + } + case kMultiBandGE: { //LO issue427GE + if (d.manufacturer != kMANUFACTURER_GE) + break; + //LO array: Value 1 = Multiband factor, Value 2 = Slice FOV Shift Factor, Value 3 = Calibration method + int mb = dcmStrInt(lLength, &buffer[lPos]); + if (mb > 1) + d.CSA.multiBandFactor = mb; + break; + } + case kGeiisFlag: + if ((lLength > 4) && (buffer[lPos] == 'G') && (buffer[lPos + 1] == 'E') && (buffer[lPos + 2] == 'I') && (buffer[lPos + 3] == 'I')) { + //read a few digits, as bug is specific to GEIIS, while GEMS are fine + printWarning("GEIIS violates the DICOM standard. Inspect results and admonish your vendor.\n"); + isIconImageSequence = true; + if (sqDepthIcon < 0) + sqDepthIcon = sqDepth; + //geiisBug = true; //compressed thumbnails do not follow transfer syntax! GE should not re-use pulbic tags for these proprietary images http://sonca.kasshin.net/gdcm/Doc/GE_ImageThumbnails + } + break; + case kStudyComments: { + //char commentStr[kDICOMStr]; + //dcmStr(lLength, &buffer[lPos], commentStr); + //printf(">> %s\n", commentStr); + break; + } + case kProcedureStepDescription: + dcmStr(lLength, &buffer[lPos], d.procedureStepDescription); + break; + case kOrientationACR: //use in emergency if kOrientation is not present! + if (!isOrient) + dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, d.orient); + break; + case kOrientation: { + if (isOrient) { //already read orient - read for this slice to see if it varies (localizer) + float orient[7]; + dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, orient); + if ((!isSameFloatGE(d.orient[1], orient[1]) || !isSameFloatGE(d.orient[2], orient[2]) || !isSameFloatGE(d.orient[3], orient[3]) || + !isSameFloatGE(d.orient[4], orient[4]) || !isSameFloatGE(d.orient[5], orient[5]) || !isSameFloatGE(d.orient[6], orient[6]))) { + if (!d.isLocalizer) + printMessage("slice orientation varies (localizer?) [%g %g %g %g %g %g] != [%g %g %g %g %g %g]\n", + d.orient[1], d.orient[2], d.orient[3], d.orient[4], d.orient[5], d.orient[6], + orient[1], orient[2], orient[3], orient[4], orient[5], orient[6]); + d.isLocalizer = true; } - //if (sliceOrderFlag == kGE_SLICE_ORDER_TOP_DOWN) - // d.sliceOrderGE = kGE_SLICE_ORDER_TOP_DOWN; - //if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP) - // d.sliceOrderGE = kGE_SLICE_ORDER_BOTTOM_UP; - #endif + } + dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, d.orient); + vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]); + vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]); + sliceV = crossProduct(readV, phaseV); + //printf("sliceV %g %g %g\n", sliceV.v[0], sliceV.v[1], sliceV.v[2]); + isOrient = true; + break; + } + case kTemporalResolution: + temporalResolutionMS = dcmStrFloat(lLength, &buffer[lPos]); + break; + case kImagesInAcquisition: + imagesInAcquisition = dcmStrInt(lLength, &buffer[lPos]); + break; + //case kSliceLocation : //optional so useless, infer from image position patient (0020,0032) and image orientation (0020,0037) + // sliceLocation = dcmStrFloat(lLength, &buffer[lPos]); + // break; + case kImageStart: + //if ((!geiisBug) && (!isIconImageSequence)) //do not exit for proprietary thumbnails + if (isIconImageSequence) { + //20200116 see example from Tashrif Bilah that saves GEIIS thumbnails uncompressed + // therefore, the next couple lines are not a perfect detection for GEIIS thumbnail icons + //int imgBytes = (d.xyzDim[1] * d.xyzDim[2] * int(d.bitsAllocated / 8)); + //if (imgBytes == lLength) + // isIconImageSequence = false; + if ((isIconImageSequence) && (sqDepth < 1)) + printWarning("Assuming 7FE0,0010 refers to an icon not the main image\n"); + } + if ((d.compressionScheme == kCompressNone) && (!isIconImageSequence)) //do not exit for proprietary thumbnails + d.imageStart = (int)lPos + (int)lFileOffset; + //geiisBug = false; + //http://www.dclunie.com/medical-image-faq/html/part6.html + //unlike raw data, Encapsulated data is stored as Fragments contained in Items that are the Value field of Pixel Data + if ((d.compressionScheme != kCompressNone) && (!isIconImageSequence)) { + lLength = 0; + isEncapsulatedData = true; + encapsulatedDataImageStart = (int)lPos + (int)lFileOffset; + } + isIconImageSequence = false; + break; + case kImageStartFloat: + d.isFloat = true; + if (!isIconImageSequence) //do not exit for proprietary thumbnails + d.imageStart = (int)lPos + (int)lFileOffset; + isIconImageSequence = false; + break; + case kImageStartDouble: + printWarning("Double-precision DICOM conversion untested: please provide samples to developer\n"); + d.isFloat = true; + if (!isIconImageSequence) //do not exit for proprietary thumbnails + d.imageStart = (int)lPos + (int)lFileOffset; + isIconImageSequence = false; + break; + } //switch/case for groupElement + if ((((groupElement >> 8) & 0xFF) == 0x60) && (groupElement % 2 == 0) && ((groupElement & 0xFF) < 0x1E)) { //Group 60xx: OverlayGroup http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.9.html + //even group numbers 0x6000..0x601E + int overlayN = ((groupElement & 0xFF) >> 1); + //printf("%08x %d %d\n", groupElement, (groupElement & 0xFF), overlayN); + int element = groupElement >> 16; + switch (element) { + case 0x0010: //US OverlayRows + overlayRows = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; - } - case kEffectiveEchoSpacingGE: - if (d.manufacturer == kMANUFACTURER_GE) d.effectiveEchoSpacingGE = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case kImageTypeGE: { //0/1/2/3 for magnitude/phase/real/imaginary - if (d.manufacturer != kMANUFACTURER_GE) break; - int dt = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - if (dt == 0) d.isHasMagnitude = true; - if (dt == 1) d.isHasPhase = true; - if (dt == 2) d.isHasReal = true; - if (dt == 3) d.isHasImaginary = true; - break; } - case kDiffusion_bValueGE : - if (d.manufacturer == kMANUFACTURER_GE) { - d.CSA.dtiV[0] = (float)set_bValGE(&volDiffusion, lLength, &buffer[lPos]); - d.CSA.numDti = 1; - } - break; - case kEpiRTGroupDelayGE : //FL - if (d.manufacturer != kMANUFACTURER_GE) - break; - d.groupDelay = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - d.groupDelay *= 1000.0; //sec -> ms - // If kEpiRTGroupDelayGE (0043,107C) exists, epiRT - d.epiVersionGE = 1; //-1 = not epi, 0 = epi, 1 = epiRT + case 0x0011: //US OverlayColumns + overlayCols = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; - case kAssetRFactorsGE: { //DS issue427GE - if (d.manufacturer != kMANUFACTURER_GE) break; - float PhaseSlice[3]; - dcmMultiFloat(lLength, (char*)&buffer[lPos], 2, PhaseSlice); - if (PhaseSlice[1] > 0.0) - d.accelFactPE = 1.0f / PhaseSlice[1]; - if (PhaseSlice[2] > 0.0) - d.accelFactOOP = 1.0f / PhaseSlice[2]; - break; - } - case kASLContrastTechniqueGE: { //CS - if (d.manufacturer != kMANUFACTURER_GE) break; - char st[kDICOMStr]; - //aslFlagsGE - dcmStr(lLength, &buffer[lPos], st); - if (strstr(st, "PSEUDOCONTINUOUS") != NULL) - d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_PSEUDOCONTINUOUS); - else if (strstr(st, "CONTINUOUS") != NULL) - d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_CONTINUOUS); + case 0x0050: { //SSx2! OverlayOrigin + if (lLength != 4) + break; + int row = dcmInt(2, &buffer[lPos], d.isLittleEndian); + int col = dcmInt(2, &buffer[lPos + 2], d.isLittleEndian); + if ((row == 1) && (col == 1)) + break; + printMessage("Unsupported overlay origin %d/%d\n", row, col); + overlayOK = false; break; - } - case kASLLabelingTechniqueGE: { //LO issue427GE - if (d.manufacturer != kMANUFACTURER_GE) break; - char st[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], st); - if (strstr(st, "3D continuous") != NULL) - d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_3DCASL); - if (strstr(st, "3D pulsed continuous") != NULL) - d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_3DPCASL); - break; - } - case kDurationLabelPulseGE: { //IS - if (d.manufacturer != kMANUFACTURER_GE) break; - d.durationLabelPulseGE = dcmStrInt(lLength, &buffer[lPos]); - break; - } - case kMultiBandGE: { //LO issue427GE - if (d.manufacturer != kMANUFACTURER_GE) break; - //LO array: Value 1 = Multiband factor, Value 2 = Slice FOV Shift Factor, Value 3 = Calibration method - int mb = dcmStrInt(lLength, &buffer[lPos]); - if (mb > 1) d.CSA.multiBandFactor = mb; - break; - } - case kGeiisFlag: - if ((lLength > 4) && (buffer[lPos]=='G') && (buffer[lPos+1]=='E') && (buffer[lPos+2]=='I') && (buffer[lPos+3]=='I')) { - //read a few digits, as bug is specific to GEIIS, while GEMS are fine - printWarning("GEIIS violates the DICOM standard. Inspect results and admonish your vendor.\n"); - isIconImageSequence = true; - if (sqDepthIcon < 0) sqDepthIcon = sqDepth; - //geiisBug = true; //compressed thumbnails do not follow transfer syntax! GE should not re-use pulbic tags for these proprietary images http://sonca.kasshin.net/gdcm/Doc/GE_ImageThumbnails - } - break; - case kStudyComments: { - //char commentStr[kDICOMStr]; - //dcmStr(lLength, &buffer[lPos], commentStr); - //printf(">> %s\n", commentStr); - break; } - case kProcedureStepDescription: - dcmStr(lLength, &buffer[lPos], d.procedureStepDescription); - break; - case kOrientationACR : //use in emergency if kOrientation is not present! - if (!isOrient) dcmMultiFloat(lLength, (char*)&buffer[lPos], 6, d.orient); - break; - //case kTemporalPositionIdentifier : - // temporalPositionIdentifier = dcmStrInt(lLength, &buffer[lPos]); - // break; - case kOrientation : { - if (isOrient) { //already read orient - read for this slice to see if it varies (localizer) - float orient[7]; - dcmMultiFloat(lLength, (char*)&buffer[lPos], 6, orient); - if ((!isSameFloatGE(d.orient[1], orient[1]) || !isSameFloatGE(d.orient[2], orient[2]) || !isSameFloatGE(d.orient[3], orient[3]) || - !isSameFloatGE(d.orient[4], orient[4]) || !isSameFloatGE(d.orient[5], orient[5]) || !isSameFloatGE(d.orient[6], orient[6]) ) ) { - if (!d.isLocalizer) - printMessage("slice orientation varies (localizer?) [%g %g %g %g %g %g] != [%g %g %g %g %g %g]\n", - d.orient[1], d.orient[2], d.orient[3],d.orient[4], d.orient[5], d.orient[6], - orient[1], orient[2], orient[3],orient[4], orient[5], orient[6]); - d.isLocalizer = true; - } - } - dcmMultiFloat(lLength, (char*)&buffer[lPos], 6, d.orient); - vec3 readV = setVec3(d.orient[1],d.orient[2],d.orient[3]); - vec3 phaseV = setVec3(d.orient[4],d.orient[5],d.orient[6]); - sliceV = crossProduct(readV ,phaseV); - //printf("sliceV %g %g %g\n", sliceV.v[0], sliceV.v[1], sliceV.v[2]); - isOrient = true; - break; } - case kTemporalResolution : - temporalResolutionMS = dcmStrFloat(lLength, &buffer[lPos]); - break; - case kImagesInAcquisition : - imagesInAcquisition = dcmStrInt(lLength, &buffer[lPos]); - break; - //case kSliceLocation : //optional so useless, infer from image position patient (0020,0032) and image orientation (0020,0037) - // sliceLocation = dcmStrFloat(lLength, &buffer[lPos]); - // break; - case kImageStart: - //if ((!geiisBug) && (!isIconImageSequence)) //do not exit for proprietary thumbnails - if (isIconImageSequence) { - //20200116 see example from Tashrif Bilah that saves GEIIS thumbnails uncompressed - // therefore, the next couple lines are not a perfect detection for GEIIS thumbnail icons - //int imgBytes = (d.xyzDim[1] * d.xyzDim[2] * int(d.bitsAllocated / 8)); - //if (imgBytes == lLength) - // isIconImageSequence = false; - if ((isIconImageSequence) && (sqDepth < 1)) printWarning("Assuming 7FE0,0010 refers to an icon not the main image\n"); - - } - if ((d.compressionScheme == kCompressNone ) && (!isIconImageSequence)) //do not exit for proprietary thumbnails - d.imageStart = (int)lPos + (int)lFileOffset; - //geiisBug = false; - //http://www.dclunie.com/medical-image-faq/html/part6.html - //unlike raw data, Encapsulated data is stored as Fragments contained in Items that are the Value field of Pixel Data - if ((d.compressionScheme != kCompressNone) && (!isIconImageSequence)) { - lLength = 0; - isEncapsulatedData = true; - encapsulatedDataImageStart = (int)lPos + (int)lFileOffset; - } - isIconImageSequence = false; - break; - case kImageStartFloat: - d.isFloat = true; - if (!isIconImageSequence) //do not exit for proprietary thumbnails - d.imageStart = (int)lPos + (int)lFileOffset; - isIconImageSequence = false; - break; - case kImageStartDouble: - printWarning("Double-precision DICOM conversion untested: please provide samples to developer\n"); - d.isFloat = true; - if (!isIconImageSequence) //do not exit for proprietary thumbnails - d.imageStart = (int)lPos + (int)lFileOffset; - isIconImageSequence = false; - break; - } //switch/case for groupElement - if ((((groupElement >>8) & 0xFF) == 0x60) && (groupElement % 2 == 0) && ((groupElement & 0xFF) < 0x1E)) { //Group 60xx: OverlayGroup http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.9.html - //even group numbers 0x6000..0x601E - int overlayN = ((groupElement & 0xFF) >> 1); - //printf("%08x %d %d\n", groupElement, (groupElement & 0xFF), overlayN); - int element = groupElement>>16; - switch(element) { - case 0x0010: //US OverlayRows - overlayRows = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case 0x0011: //US OverlayColumns - overlayCols = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - break; - case 0x0050: {//SSx2! OverlayOrigin - if (lLength != 4) break; - int row = dcmInt(2,&buffer[lPos],d.isLittleEndian); - int col = dcmInt(2,&buffer[lPos+2],d.isLittleEndian); - if ((row == 1) && (col == 1)) break; - printMessage("Unsupported overlay origin %d/%d\n", row, col); - overlayOK = false; - break; - } - case 0x0100: {//US OverlayBitsAllocated - int bits = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - if (bits == 1) break; - //old style Burned-In - printMessage("Illegal/Obsolete DICOM: Overlay Bits Allocated must be 1, not %d\n", bits); - overlayOK = false; - break; - } - case 0x0102: {//US OverlayBitPosition - int pos = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - if (pos == 0) break; - //old style Burned-In - printMessage("Illegal/Obsolete DICOM: Overlay Bit Position shall be 0, not %d\n", pos); - overlayOK = false; - break; - } - case 0x3000: { - d.overlayStart[overlayN] = (int)lPos + (int)lFileOffset; - d.isHasOverlay = true; - break; - } - } - }//Group 60xx even values 0x6000..0x601E https://www.medicalconnections.co.uk/kb/Number-Of-Overlays-In-Image/ - + case 0x0100: { //US OverlayBitsAllocated + int bits = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + if (bits == 1) + break; + //old style Burned-In + printMessage("Illegal/Obsolete DICOM: Overlay Bits Allocated must be 1, not %d\n", bits); + overlayOK = false; + break; + } + case 0x0102: { //US OverlayBitPosition + int pos = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + if (pos == 0) + break; + //old style Burned-In + printMessage("Illegal/Obsolete DICOM: Overlay Bit Position shall be 0, not %d\n", pos); + overlayOK = false; + break; + } + case 0x3000: { + d.overlayStart[overlayN] = (int)lPos + (int)lFileOffset; + d.isHasOverlay = true; + break; + } + } + } //Group 60xx even values 0x6000..0x601E https://www.medicalconnections.co.uk/kb/Number-Of-Overlays-In-Image/ #ifndef USING_R - if (isVerbose > 1) { - //dcm2niix i fast because it does not use a dictionary. - // this is a very incomplete DICOM header report, and not a substitute for tools like dcmdump - // the purpose is to see how dcm2niix has parsed the image for diagnostics - // this section will report very little for implicit data - //if (d.isHasReal) printf("r");else printf("m"); - char str[kDICOMStr]; - sprintf(str, "%*c%04x,%04x %u@%ld ", sqDepth+1, ' ', groupElement & 65535,groupElement>>16, lLength, lFileOffset+lPos); + if (isVerbose > 1) { + //dcm2niix i fast because it does not use a dictionary. + // this is a very incomplete DICOM header report, and not a substitute for tools like dcmdump + // the purpose is to see how dcm2niix has parsed the image for diagnostics + // this section will report very little for implicit data + //if (d.isHasReal) printf("r");else printf("m"); + char str[kDICOMStr]; + sprintf(str, "%*c%04x,%04x %u@%ld ", sqDepth + 1, ' ', groupElement & 65535, groupElement >> 16, lLength, lFileOffset + lPos); bool isStr = false; if (d.isExplicitVR) { sprintf(str, "%s%c%c ", str, vr[0], vr[1]); - if ((vr[0]=='F') && (vr[1]=='D')) sprintf(str, "%s%g ", str, dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian)); - if ((vr[0]=='F') && (vr[1]=='L')) sprintf(str, "%s%g ", str, dcmFloat(lLength, &buffer[lPos], d.isLittleEndian)); - if ((vr[0]=='S') && (vr[1]=='S')) sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); - if ((vr[0]=='S') && (vr[1]=='L')) sprintf(str, "%s%d ", str, dcmInt(lLength,&buffer[lPos],d.isLittleEndian)); - if ((vr[0]=='U') && (vr[1]=='S')) sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); - if ((vr[0]=='U') && (vr[1]=='L')) sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); - if ((vr[0]=='A') && (vr[1]=='E')) isStr = true; - if ((vr[0]=='A') && (vr[1]=='S')) isStr = true; + if ((vr[0] == 'F') && (vr[1] == 'D')) + sprintf(str, "%s%g ", str, dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian)); + if ((vr[0] == 'F') && (vr[1] == 'L')) + sprintf(str, "%s%g ", str, dcmFloat(lLength, &buffer[lPos], d.isLittleEndian)); + if ((vr[0] == 'S') && (vr[1] == 'S')) + sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + if ((vr[0] == 'S') && (vr[1] == 'L')) + sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + if ((vr[0] == 'U') && (vr[1] == 'S')) + sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + if ((vr[0] == 'U') && (vr[1] == 'L')) + sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + if ((vr[0] == 'A') && (vr[1] == 'E')) + isStr = true; + if ((vr[0] == 'A') && (vr[1] == 'S')) + isStr = true; //if ((vr[0]=='A') && (vr[1]=='T')) isStr = xxx; - if ((vr[0]=='C') && (vr[1]=='S')) isStr = true; - if ((vr[0]=='D') && (vr[1]=='A')) isStr = true; - if ((vr[0]=='D') && (vr[1]=='S')) isStr = true; - if ((vr[0]=='D') && (vr[1]=='T')) isStr = true; - if ((vr[0]=='I') && (vr[1]=='S')) isStr = true; - if ((vr[0]=='L') && (vr[1]=='O')) isStr = true; - if ((vr[0]=='L') && (vr[1]=='T')) isStr = true; + if ((vr[0] == 'C') && (vr[1] == 'S')) + isStr = true; + if ((vr[0] == 'D') && (vr[1] == 'A')) + isStr = true; + if ((vr[0] == 'D') && (vr[1] == 'S')) + isStr = true; + if ((vr[0] == 'D') && (vr[1] == 'T')) + isStr = true; + if ((vr[0] == 'I') && (vr[1] == 'S')) + isStr = true; + if ((vr[0] == 'L') && (vr[1] == 'O')) + isStr = true; + if ((vr[0] == 'L') && (vr[1] == 'T')) + isStr = true; //if ((vr[0]=='O') && (vr[1]=='B')) isStr = xxx; //if ((vr[0]=='O') && (vr[1]=='D')) isStr = xxx; //if ((vr[0]=='O') && (vr[1]=='F')) isStr = xxx; //if ((vr[0]=='O') && (vr[1]=='W')) isStr = xxx; - if ((vr[0]=='P') && (vr[1]=='N')) isStr = true; - if ((vr[0]=='S') && (vr[1]=='H')) isStr = true; - if ((vr[0]=='S') && (vr[1]=='T')) isStr = true; - if ((vr[0]=='T') && (vr[1]=='M')) isStr = true; - if ((vr[0]=='U') && (vr[1]=='I')) isStr = true; - if ((vr[0]=='U') && (vr[1]=='T')) isStr = true; + if ((vr[0] == 'P') && (vr[1] == 'N')) + isStr = true; + if ((vr[0] == 'S') && (vr[1] == 'H')) + isStr = true; + if ((vr[0] == 'S') && (vr[1] == 'T')) + isStr = true; + if ((vr[0] == 'T') && (vr[1] == 'M')) + isStr = true; + if ((vr[0] == 'U') && (vr[1] == 'I')) + isStr = true; + if ((vr[0] == 'U') && (vr[1] == 'T')) + isStr = true; } else isStr = (lLength > 12); //implicit encoding: not always true as binary vectors may exceed 12 bytes, but often true - if (lLength > 128) { - sprintf(str, "%s<%d bytes> ", str, lLength); - printMessage("%s\n", str); - } else if (isStr) { //if length is greater than 8 bytes (+4 hdr) the MIGHT be a string - char tagStr[kDICOMStr]; - //tagStr[0] = 'X'; //avoid compiler warning: orientStr filled by dcmStr - strcpy(tagStr,""); - if (lLength > 0) - dcmStr(lLength, &buffer[lPos], tagStr); - if (strlen(tagStr) > 1) { - for (size_t pos = 0; pos') || (tagStr[pos] == ':') - || (tagStr[pos] == '"') || (tagStr[pos] == '\\') || (tagStr[pos] == '/') - || (tagStr[pos] < 32) //issue398 - //|| (tagStr[pos] == '^') || (tagStr[pos] < 33) - || (tagStr[pos] == '*') || (tagStr[pos] == '|') || (tagStr[pos] == '?')) - tagStr[pos] = '_'; + if (lLength > 128) { + sprintf(str, "%s<%d bytes> ", str, lLength); + printMessage("%s\n", str); + } else if (isStr) { //if length is greater than 8 bytes (+4 hdr) the MIGHT be a string + char tagStr[kDICOMStr]; + //tagStr[0] = 'X'; //avoid compiler warning: orientStr filled by dcmStr + strcpy(tagStr, ""); + if (lLength > 0) + dcmStr(lLength, &buffer[lPos], tagStr); + if (strlen(tagStr) > 1) { + for (size_t pos = 0; pos < strlen(tagStr); pos++) + if ((tagStr[pos] == '<') || (tagStr[pos] == '>') || (tagStr[pos] == ':') || (tagStr[pos] == '"') || (tagStr[pos] == '\\') || (tagStr[pos] == '/') || (tagStr[pos] < 32) //issue398 + //|| (tagStr[pos] == '^') || (tagStr[pos] < 33) + || (tagStr[pos] == '*') || (tagStr[pos] == '|') || (tagStr[pos] == '?')) + tagStr[pos] = '_'; } printMessage("%s %s\n", str, tagStr); - } else - printMessage("%s\n", str); - //if (d.isExplicitVR) printMessage(" VR=%c%c\n", vr[0], vr[1]); - } //printMessage(" tag=%04x,%04x length=%u pos=%ld %c%c nest=%d\n", groupElement & 65535,groupElement>>16, lLength, lPos,vr[0], vr[1], nest); + } else + printMessage("%s\n", str); + //if (d.isExplicitVR) printMessage(" VR=%c%c\n", vr[0], vr[1]); + } //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); - } //while d.imageStart == 0 - free (buffer); - if (d.bitsStored < 0) d.isValid = false; - if (d.bitsStored == 1) printWarning("1-bit binary DICOMs not supported\n"); //maybe not valid - no examples to test - //printf("%d bval=%g bvec=%g %g %g<<<\n", d.CSA.numDti, d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); - //printMessage("><>< DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); - if (encapsulatedDataFragmentStart > 0) { - if (encapsulatedDataFragments > 1) { - printError("Compressed image stored as %d fragments: decompress with gdcmconv, Osirix, dcmdjpeg or dcmjp2k %s\n", encapsulatedDataFragments, fname); - } else { - d.imageStart = encapsulatedDataFragmentStart; - } - } else if ((isEncapsulatedData) && (d.imageStart < 128)) { - //http://www.dclunie.com/medical-image-faq/html/part6.html + lPos = lPos + (lLength); + } //while d.imageStart == 0 + free(buffer); + if (d.bitsStored < 0) + d.isValid = false; + if (d.bitsStored == 1) + printWarning("1-bit binary DICOMs not supported\n"); //maybe not valid - no examples to test + //printf("%d bval=%g bvec=%g %g %g<<<\n", d.CSA.numDti, d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); + //printMessage("><>< DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); + if (encapsulatedDataFragmentStart > 0) { + if (encapsulatedDataFragments > 1) { + printError("Compressed image stored as %d fragments: decompress with gdcmconv, Osirix, dcmdjpeg or dcmjp2k %s\n", encapsulatedDataFragments, fname); + } else { + d.imageStart = encapsulatedDataFragmentStart; + } + } else if ((isEncapsulatedData) && (d.imageStart < 128)) { + //http://www.dclunie.com/medical-image-faq/html/part6.html //Uncompressed data (unencapsulated) is sent in DICOM as a series of raw bytes or words (little or big endian) in the Value field of the Pixel Data element (7FE0,0010). Encapsulated data on the other hand is sent not as raw bytes or words but as Fragments contained in Items that are the Value field of Pixel Data - printWarning("DICOM violation (contact vendor): compressed image without image fragments, assuming image offset defined by 0x7FE0,x0010: %s\n", fname); - d.imageStart = encapsulatedDataImageStart; - } - if ((d.manufacturer == kMANUFACTURER_GE) && (d.groupDelay > 0.0)) - d.TR += d.groupDelay; //Strangely, for GE the sample rate is (0018,0080) + ((0043,107c) * 1000.0) - if ((d.modality == kMODALITY_PT) && (PETImageIndex > 0)) { - d.imageNum = PETImageIndex; //https://github.com/rordenlab/dcm2niix/issues/184 - //printWarning("PET scan using 0054,1330 for image number %d\n", PETImageIndex); - } - if (d.isHasOverlay) { - if ((overlayCols > 0) && (d.xyzDim[1] != overlayCols)) overlayOK = false; - if ((overlayRows > 0) && (d.xyzDim[2] != overlayRows)) overlayOK = false; - if (!overlayOK) - d.isHasOverlay = false; - } - //Recent Philips images include DateTime (0008,002A) but not separate date and time (0008,0022 and 0008,0032) - #define kYYYYMMDDlen 8 //how many characters to encode year,month,day in "YYYYDDMM" format - if ((strlen(acquisitionDateTimeTxt) > (kYYYYMMDDlen+5)) && (!isFloatDiff(d.acquisitionTime, 0.0f)) && (!isFloatDiff(d.acquisitionDate, 0.0f)) ) { + printWarning("DICOM violation (contact vendor): compressed image without image fragments, assuming image offset defined by 0x7FE0,x0010: %s\n", fname); + d.imageStart = encapsulatedDataImageStart; + } + if ((d.manufacturer == kMANUFACTURER_GE) && (d.groupDelay > 0.0)) + d.TR += d.groupDelay; //Strangely, for GE the sample rate is (0018,0080) + ((0043,107c) * 1000.0) + if ((d.modality == kMODALITY_PT) && (PETImageIndex > 0)) { + d.imageNum = PETImageIndex; //https://github.com/rordenlab/dcm2niix/issues/184 + //printWarning("PET scan using 0054,1330 for image number %d\n", PETImageIndex); + } + if (d.isHasOverlay) { + if ((overlayCols > 0) && (d.xyzDim[1] != overlayCols)) + overlayOK = false; + if ((overlayRows > 0) && (d.xyzDim[2] != overlayRows)) + overlayOK = false; + if (!overlayOK) + d.isHasOverlay = false; + } +//Recent Philips images include DateTime (0008,002A) but not separate date and time (0008,0022 and 0008,0032) +#define kYYYYMMDDlen 8 //how many characters to encode year,month,day in "YYYYDDMM" format + if ((strlen(acquisitionDateTimeTxt) > (kYYYYMMDDlen + 5)) && (!isFloatDiff(d.acquisitionTime, 0.0f)) && (!isFloatDiff(d.acquisitionDate, 0.0f))) { // 20161117131643.80000 -> date 20161117 time 131643.80000 //printMessage("acquisitionDateTime %s\n",acquisitionDateTimeTxt); - char acquisitionDateTxt[kDICOMStr]; - memcpy(acquisitionDateTxt, acquisitionDateTimeTxt, kYYYYMMDDlen); + char acquisitionDateTxt[kDICOMStr]; + memcpy(acquisitionDateTxt, acquisitionDateTimeTxt, kYYYYMMDDlen); acquisitionDateTxt[kYYYYMMDDlen] = '\0'; // IMPORTANT! - d.acquisitionDate = atof(acquisitionDateTxt); - char acquisitionTimeTxt[kDICOMStr]; + d.acquisitionDate = atof(acquisitionDateTxt); + char acquisitionTimeTxt[kDICOMStr]; int timeLen = (int)strlen(acquisitionDateTimeTxt) - kYYYYMMDDlen; - strncpy(acquisitionTimeTxt, &acquisitionDateTimeTxt[kYYYYMMDDlen], timeLen); + strncpy(acquisitionTimeTxt, &acquisitionDateTimeTxt[kYYYYMMDDlen], timeLen); acquisitionTimeTxt[timeLen] = '\0'; // IMPORTANT! d.acquisitionTime = atof(acquisitionTimeTxt); - } - d.dateTime = (atof(d.studyDate)* 1000000) + atof(d.studyTime); - //printMessage("slices in Acq %d %d %g %g\n",locationsInAcquisitionGE, d.locationsInAcquisition, d.xyzMM[3], d.zSpacing); - if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.locationsInAcquisition == 0)) - d.locationsInAcquisition = locationsInAcquisitionPhilips; - if ((d.manufacturer == kMANUFACTURER_GE) && (imagesInAcquisition > 0)) - d.locationsInAcquisition = imagesInAcquisition; //e.g. if 72 slices acquired but interpolated as 144 - if ((d.manufacturer == kMANUFACTURER_GE) && (d.locationsInAcquisition > 0) && (locationsInAcquisitionGE > 0) && (d.locationsInAcquisition != locationsInAcquisitionGE) ) { - if (isVerbose) - printMessage("Check number of slices, discrepancy between tags (0020,1002; 0021,104F; 0054,0081) (%d vs %d) %s\n", locationsInAcquisitionGE, d.locationsInAcquisition, fname); - /* SAH.start: Fix for ZIP2 */ - int zipFactor = (int) roundf(d.xyzMM[3] / d.zSpacing); - if (zipFactor > 1) { - d.interp3D = zipFactor; - //printMessage("Issue 373: Check for ZIP2 Factor: %d SliceThickness+SliceGap: %f, SpacingBetweenSlices: %f \n", zipFactor, d.xyzMM[3], d.zSpacing); - locationsInAcquisitionGE *= zipFactor; // Multiply number of slices by ZIP factor. Do this prior to checking for conflict below (?). - } - if (isGEfieldMap) { //issue501 : to do check zip factor - //Volume 1) derived phase field map [Hz] and 2) magnitude volume. - d.isDerived = (d.imageNum <= locationsInAcquisitionGE); //first volume - d.isRealIsPhaseMapHz = d.isDerived; - d.isHasReal = d.isDerived; - } - /* SAH.end */ - if (locationsInAcquisitionGE < d.locationsInAcquisition) { - d.locationsInAcquisitionConflict = d.locationsInAcquisition; - d.locationsInAcquisition = locationsInAcquisitionGE; - } else - d.locationsInAcquisitionConflict = locationsInAcquisitionGE; - } - if ((d.manufacturer == kMANUFACTURER_GE) && (d.locationsInAcquisition == 0)) - d.locationsInAcquisition = locationsInAcquisitionGE; - if (d.zSpacing > 0.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])) { - d.CSA.numDti = d.xyzDim[3]; //issue506 - 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! - } - if ((d.imageStart > 144) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1)) - d.isValid = true; - //if ((d.imageStart > 144) && (d.xyzDim[1] >= 1) && (d.xyzDim[2] >= 1) && (d.xyzDim[4] > 1)) //Spectroscopy - // d.isValid = true; - if ((d.xyzMM[1] > FLT_EPSILON) && (d.xyzMM[2] < FLT_EPSILON)) { - printMessage("Please check voxel size\n"); - d.xyzMM[2] = d.xyzMM[1]; - } - if ((d.xyzMM[2] > FLT_EPSILON) && (d.xyzMM[1] < FLT_EPSILON)) { - printMessage("Please check voxel size\n"); - d.xyzMM[1] = d.xyzMM[2]; - } - if ((d.xyzMM[3] < FLT_EPSILON)) { - printMessage("Unable to determine slice thickness: please check voxel size\n"); - d.xyzMM[3] = 1.0; - } - //printMessage("Patient Position\t%g\t%g\t%g\tThick\t%g\n",d.patientPosition[1],d.patientPosition[2],d.patientPosition[3], d.xyzMM[3]); - //printMessage("Patient Position\t%g\t%g\t%g\tThick\t%g\tStart\t%d\n",d.patientPosition[1],d.patientPosition[2],d.patientPosition[3], d.xyzMM[3], d.imageStart); - // printMessage("ser %ld\n", d.seriesNum); - //int kEchoMult = 100; //For Siemens/GE Series 1,2,3... save 2nd echo as 201, 3rd as 301, etc - //if (d.seriesNum > 100) - // kEchoMult = 10; //For Philips data Saved as Series 101,201,301... save 2nd echo as 111, 3rd as 121, etc - //if (coilNum > 0) //segment images with multiple coils - // d.seriesNum = d.seriesNum + (100*coilNum); - //if (d.echoNum > 1) //segment images with multiple echoes - // d.seriesNum = d.seriesNum + (kEchoMult*d.echoNum); - if (isPaletteColor) { - d.isValid = false; - d.isDerived = true; //to my knowledge, palette images always derived - printWarning("Photometric Interpretation 'PALETTE COLOR' not supported\n"); - } - if ((d.compressionScheme == kCompress50) && (d.bitsAllocated > 8) ) { - //dcmcjpg with +ee can create .51 syntax images that are 8,12,16,24-bit: we can only decode 8/24-bit - printError("Unable to decode %d-bit images with Transfer Syntax 1.2.840.10008.1.2.4.51, decompress with dcmdjpg or gdcmconv\n", d.bitsAllocated); - d.isValid = false; - } - if ((isMosaic) && (d.CSA.mosaicSlices < 1) && (numberOfImagesInMosaic < 1) && (!isInterpolated) && (d.phaseEncodingLines > 0) && (frequencyRows > 0) && ((d.xyzDim[1] % frequencyRows) == 0) && ((d.xyzDim[1] / frequencyRows) > 2) && ((d.xyzDim[2] % d.phaseEncodingLines) == 0) && ((d.xyzDim[2] / d.phaseEncodingLines) > 2) ) { - //n.b. in future check if frequency is in row or column direction (and same with phase) - // >2 avoids detecting interpolated as mosaic, in future perhaps check "isInterpolated" - numberOfImagesInMosaic = (d.xyzDim[1]/frequencyRows) * (d.xyzDim[2]/d.phaseEncodingLines); - printWarning("Guessing this is a mosaic up to %d slices (issue 337).\n", numberOfImagesInMosaic); - } - if ((numberOfImagesInMosaic > 1) && (d.CSA.mosaicSlices < 1)) - d.CSA.mosaicSlices = numberOfImagesInMosaic; - if (d.isXA10A) d.manufacturer = kMANUFACTURER_SIEMENS; //XA10A mosaics omit Manufacturer 0008,0070! - if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (isMosaic) && (d.CSA.mosaicSlices < 1) && (d.phaseEncodingSteps > 0) && ((d.xyzDim[1] % d.phaseEncodingSteps) == 0) && ((d.xyzDim[2] % d.phaseEncodingSteps) == 0) ) { - d.CSA.mosaicSlices = (d.xyzDim[1] / d.phaseEncodingSteps) * (d.xyzDim[2] / d.phaseEncodingSteps); - printWarning("Mosaic inferred without CSA header (check number of slices and spatial orientation)\n"); - } - if ((d.isXA10A) && (isMosaic) && (d.CSA.mosaicSlices < 1)) - d.CSA.mosaicSlices = -1; //mark as bogus DICOM - if ((!d.isXA10A) && (isMosaic) && (d.CSA.mosaicSlices < 1)) //See Erlangen Vida dataset - never reports "XA10" but mosaics have no attributes - printWarning("0008,0008=MOSAIC but number of slices not specified: %s\n", fname); - if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.dtiV[1] < -1.0) && (d.CSA.dtiV[2] < -1.0) && (d.CSA.dtiV[3] < -1.0)) - d.CSA.dtiV[0] = 0; //SiemensTrio-Syngo2004A reports B=0 images as having impossible b-vectors. - /* - if ((d.manufacturer == kMANUFACTURER_GE) && (d.modality == kMODALITY_MR) && (strlen(d.seriesDescription) > 1)) { - //issue 473, 476: swap protocolName and seriesDescription - char tempTxt[kDICOMStr] = ""; - strcpy(tempTxt, d.protocolName); - strcpy(d.protocolName, d.seriesDescription); - strcpy(d.seriesDescription, tempTxt); - }*/ - if ((strlen(d.protocolName) < 1) && (strlen(d.seriesDescription) > 1)) + } + d.dateTime = (atof(d.studyDate) * 1000000) + atof(d.studyTime); + //printMessage("slices in Acq %d %d %g %g\n",locationsInAcquisitionGE, d.locationsInAcquisition, d.xyzMM[3], d.zSpacing); + if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.locationsInAcquisition == 0)) + d.locationsInAcquisition = locationsInAcquisitionPhilips; + if ((d.manufacturer == kMANUFACTURER_GE) && (imagesInAcquisition > 0)) + d.locationsInAcquisition = imagesInAcquisition; //e.g. if 72 slices acquired but interpolated as 144 + if ((d.manufacturer == kMANUFACTURER_GE) && (d.locationsInAcquisition > 0) && (locationsInAcquisitionGE > 0) && (d.locationsInAcquisition != locationsInAcquisitionGE)) { + if (isVerbose) + printMessage("Check number of slices, discrepancy between tags (0020,1002; 0021,104F; 0054,0081) (%d vs %d) %s\n", locationsInAcquisitionGE, d.locationsInAcquisition, fname); + /* SAH.start: Fix for ZIP2 */ + int zipFactor = (int)roundf(d.xyzMM[3] / d.zSpacing); + if (zipFactor > 1) { + d.interp3D = zipFactor; + //printMessage("Issue 373: Check for ZIP2 Factor: %d SliceThickness+SliceGap: %f, SpacingBetweenSlices: %f \n", zipFactor, d.xyzMM[3], d.zSpacing); + locationsInAcquisitionGE *= zipFactor; // Multiply number of slices by ZIP factor. Do this prior to checking for conflict below (?). + } + if (isGEfieldMap) { //issue501 : to do check zip factor + //Volume 1) derived phase field map [Hz] and 2) magnitude volume. + d.isDerived = (d.imageNum <= locationsInAcquisitionGE); //first volume + d.isRealIsPhaseMapHz = d.isDerived; + d.isHasReal = d.isDerived; + } + /* SAH.end */ + if (locationsInAcquisitionGE < d.locationsInAcquisition) { + d.locationsInAcquisitionConflict = d.locationsInAcquisition; + d.locationsInAcquisition = locationsInAcquisitionGE; + } else + d.locationsInAcquisitionConflict = locationsInAcquisitionGE; + } + if ((d.manufacturer == kMANUFACTURER_GE) && (d.locationsInAcquisition == 0)) + d.locationsInAcquisition = locationsInAcquisitionGE; + if (d.zSpacing > 0.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])) { + d.CSA.numDti = d.xyzDim[3]; //issue506 + 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! + } + if ((d.imageStart > 144) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1)) + d.isValid = true; + //if ((d.imageStart > 144) && (d.xyzDim[1] >= 1) && (d.xyzDim[2] >= 1) && (d.xyzDim[4] > 1)) //Spectroscopy + // d.isValid = true; + if ((d.xyzMM[1] > FLT_EPSILON) && (d.xyzMM[2] < FLT_EPSILON)) { + printMessage("Please check voxel size\n"); + d.xyzMM[2] = d.xyzMM[1]; + } + if ((d.xyzMM[2] > FLT_EPSILON) && (d.xyzMM[1] < FLT_EPSILON)) { + printMessage("Please check voxel size\n"); + d.xyzMM[1] = d.xyzMM[2]; + } + if ((d.xyzMM[3] < FLT_EPSILON)) { + printMessage("Unable to determine slice thickness: please check voxel size\n"); + d.xyzMM[3] = 1.0; + } + //printMessage("Patient Position\t%g\t%g\t%g\tThick\t%g\n",d.patientPosition[1],d.patientPosition[2],d.patientPosition[3], d.xyzMM[3]); + //printMessage("Patient Position\t%g\t%g\t%g\tThick\t%g\tStart\t%d\n",d.patientPosition[1],d.patientPosition[2],d.patientPosition[3], d.xyzMM[3], d.imageStart); + // printMessage("ser %ld\n", d.seriesNum); + //int kEchoMult = 100; //For Siemens/GE Series 1,2,3... save 2nd echo as 201, 3rd as 301, etc + //if (d.seriesNum > 100) + // kEchoMult = 10; //For Philips data Saved as Series 101,201,301... save 2nd echo as 111, 3rd as 121, etc + //if (coilNum > 0) //segment images with multiple coils + // d.seriesNum = d.seriesNum + (100*coilNum); + //if (d.echoNum > 1) //segment images with multiple echoes + // d.seriesNum = d.seriesNum + (kEchoMult*d.echoNum); + if (isPaletteColor) { + d.isValid = false; + d.isDerived = true; //to my knowledge, palette images always derived + printWarning("Photometric Interpretation 'PALETTE COLOR' not supported\n"); + } + if ((d.compressionScheme == kCompress50) && (d.bitsAllocated > 8)) { + //dcmcjpg with +ee can create .51 syntax images that are 8,12,16,24-bit: we can only decode 8/24-bit + printError("Unable to decode %d-bit images with Transfer Syntax 1.2.840.10008.1.2.4.51, decompress with dcmdjpg or gdcmconv\n", d.bitsAllocated); + d.isValid = false; + } + if ((isMosaic) && (d.CSA.mosaicSlices < 1) && (numberOfImagesInMosaic < 1) && (!isInterpolated) && (d.phaseEncodingLines > 0) && (frequencyRows > 0) && ((d.xyzDim[1] % frequencyRows) == 0) && ((d.xyzDim[1] / frequencyRows) > 2) && ((d.xyzDim[2] % d.phaseEncodingLines) == 0) && ((d.xyzDim[2] / d.phaseEncodingLines) > 2)) { + //n.b. in future check if frequency is in row or column direction (and same with phase) + // >2 avoids detecting interpolated as mosaic, in future perhaps check "isInterpolated" + numberOfImagesInMosaic = (d.xyzDim[1] / frequencyRows) * (d.xyzDim[2] / d.phaseEncodingLines); + printWarning("Guessing this is a mosaic up to %d slices (issue 337).\n", numberOfImagesInMosaic); + } + if ((numberOfImagesInMosaic > 1) && (d.CSA.mosaicSlices < 1)) + d.CSA.mosaicSlices = numberOfImagesInMosaic; + if (d.isXA10A) + d.manufacturer = kMANUFACTURER_SIEMENS; //XA10A mosaics omit Manufacturer 0008,0070! + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (isMosaic) && (d.CSA.mosaicSlices < 1) && (d.phaseEncodingSteps > 0) && ((d.xyzDim[1] % d.phaseEncodingSteps) == 0) && ((d.xyzDim[2] % d.phaseEncodingSteps) == 0)) { + d.CSA.mosaicSlices = (d.xyzDim[1] / d.phaseEncodingSteps) * (d.xyzDim[2] / d.phaseEncodingSteps); + printWarning("Mosaic inferred without CSA header (check number of slices and spatial orientation)\n"); + } + if ((d.isXA10A) && (isMosaic) && (d.CSA.mosaicSlices < 1)) + d.CSA.mosaicSlices = -1; //mark as bogus DICOM + if ((!d.isXA10A) && (isMosaic) && (d.CSA.mosaicSlices < 1)) //See Erlangen Vida dataset - never reports "XA10" but mosaics have no attributes + printWarning("0008,0008=MOSAIC but number of slices not specified: %s\n", fname); + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.dtiV[1] < -1.0) && (d.CSA.dtiV[2] < -1.0) && (d.CSA.dtiV[3] < -1.0)) + d.CSA.dtiV[0] = 0; //SiemensTrio-Syngo2004A reports B=0 images as having impossible b-vectors. + if ((strlen(d.protocolName) < 1) && (strlen(d.seriesDescription) > 1)) strcpy(d.protocolName, d.seriesDescription); if ((strlen(d.protocolName) > 1) && (isMoCo)) - strcat (d.protocolName,"_MoCo"); //disambiguate MoCo https://github.com/neurolabusc/MRIcroGL/issues/31 - if ((strlen(d.protocolName) < 1) && (strlen(d.sequenceName) > 1) && (d.manufacturer != kMANUFACTURER_SIEMENS)) + strcat(d.protocolName, "_MoCo"); //disambiguate MoCo https://github.com/neurolabusc/MRIcroGL/issues/31 + if ((strlen(d.protocolName) < 1) && (strlen(d.sequenceName) > 1) && (d.manufacturer != kMANUFACTURER_SIEMENS)) strcpy(d.protocolName, d.sequenceName); //protocolName (0018,1030) optional, sequence name (0018,0024) is not a good substitute for Siemens as it can vary per volume: *ep_b0 *ep_b1000#1, *ep_b1000#2, etc https://www.nitrc.org/forum/forum.php?thread_id=8771&forum_id=4703 - - //d.isValid = false; - - // if (!isOrient) { - // if (d.isNonImage) - // printWarning("Spatial orientation ambiguous (tag 0020,0037 not found) [probably not important: derived image]: %s\n", fname); - // else if (((d.manufacturer == kMANUFACTURER_SIEMENS)) && (d.samplesPerPixel != 1)) - // printWarning("Spatial orientation ambiguous (tag 0020,0037 not found) [perhaps derived FA that is not required]: %s\n", fname); - // else - // printWarning("Spatial orientation ambiguous (tag 0020,0037 not found): %s\n", fname); - // } -/*if (d.isHasMagnitude) - printError("=====> mag %d %d\n", d.patientPositionRepeats, d.patientPositionSequentialRepeats ); -if (d.isHasPhase) - printError("=====> phase %d %d\n", d.patientPositionRepeats, d.patientPositionSequentialRepeats ); - - printError("=====> reps %d %d\n", d.patientPositionRepeats, d.patientPositionSequentialRepeats ); -*/ - /*if ((patientPositionSequentialRepeats > 1) && ( (d.xyzDim[3] % patientPositionSequentialRepeats) == 0 )) { - //will require Converting XYTZ to XYZT - //~ d.numberOfDynamicScans = d.xyzDim[3] / d.patientPositionSequentialRepeats; - //~ d.xyzDim[4] = d.xyzDim[3] / d.numberOfDynamicScans; - numberOfDynamicScans = d.xyzDim[3] / patientPositionSequentialRepeats; - d.xyzDim[4] = d.xyzDim[3] / numberOfDynamicScans; - - d.xyzDim[3] = d.numberOfDynamicScans; - }*/ - if (numberOfFrames == 0) numberOfFrames = d.xyzDim[3]; + if (numberOfFrames == 0) + numberOfFrames = d.xyzDim[3]; if ((locationsInAcquisitionPhilips > 0) && ((d.xyzDim[3] % locationsInAcquisitionPhilips) == 0)) { d.xyzDim[4] = d.xyzDim[3] / locationsInAcquisitionPhilips; d.xyzDim[3] = locationsInAcquisitionPhilips; @@ -7079,11 +6987,11 @@ if (d.isHasPhase) d.xyzDim[3] = maxInStackPositionNumber; } if ((!isnan(patientPositionStartPhilips[1])) && (!isnan(patientPositionEndPhilips[1]))) { - for (int k = 0; k < 4; k++) { - d.patientPosition[k] = patientPositionStartPhilips[k]; - d.patientPositionLast[k] = patientPositionEndPhilips[k]; - } - } + for (int k = 0; k < 4; k++) { + d.patientPosition[k] = patientPositionStartPhilips[k]; + d.patientPositionLast[k] = patientPositionEndPhilips[k]; + } + } if ((numberOfFrames > 1) && (locationsInAcquisitionPhilips > 0) && ((numberOfFrames % locationsInAcquisitionPhilips) != 0)) { //issue515 printWarning("Number of frames (%d) not divisible by locations in acquisition (2001,1018) %d (issue 515)\n", numberOfFrames, locationsInAcquisitionPhilips); d.xyzDim[4] = d.xyzDim[3] / locationsInAcquisitionPhilips; @@ -7092,69 +7000,68 @@ if (d.isHasPhase) } if ((B0Philips >= 0) && (d.CSA.numDti == 0)) { d.CSA.dtiV[0] = B0Philips; - d.CSA.numDti = 1; - } //issue409 Siemens XA saved as classic 2D not enhanced - if (!isnan(patientPositionStartPhilips[1])) //for Philips data without + d.CSA.numDti = 1; + } //issue409 Siemens XA saved as classic 2D not enhanced + if (!isnan(patientPositionStartPhilips[1])) //for Philips data without for (int k = 0; k < 4; k++) d.patientPosition[k] = patientPositionStartPhilips[k]; 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]); - if (!isnan(patientPositionEndPhilips[1])) - printMessage(" patient position end (0020,0032)\t%g\t%g\t%g\n", patientPositionEndPhilips[1],patientPositionEndPhilips[2],patientPositionEndPhilips[3]); - printMessage(" orient (0020,0037)\t%g\t%g\t%g\t%g\t%g\t%g\n", d.orient[1],d.orient[2],d.orient[3], d.orient[4],d.orient[5],d.orient[6]); - printMessage(" acq %d img %d ser %ld dim %dx%dx%dx%d mm %gx%gx%g offset %d loc %d valid %d ph %d mag %d nDTI %d 3d %d bits %d littleEndian %d echo %d coilCRC %d TE %g TR %g\n",d.acquNum,d.imageNum,d.seriesNum,d.xyzDim[1],d.xyzDim[2],d.xyzDim[3], d.xyzDim[4],d.xyzMM[1],d.xyzMM[2],d.xyzMM[3],d.imageStart, d.locationsInAcquisition, d.isValid, d.isHasPhase, d.isHasMagnitude, d.CSA.numDti, d.is3DAcq, d.bitsAllocated, d.isLittleEndian, d.echoNum, d.coilCrc, d.TE, d.TR); - if (d.CSA.dtiV[0] > 0) - printMessage(" DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); - } - if ((d.isValid) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1) && (d.imageStart < 132) && (!d.isRawDataStorage)) { - //20190524: Philips MR 55.1 creates non-image files that report kDim1/kDim2 - we can detect them since 0008,0016 reports "RawDataStorage" - //see https://neurostars.org/t/dcm2niix-error-from-philips-dicom-qsm-data-can-this-be-skipped/4883 - printError("Conversion aborted due to corrupt file: %s %dx%d %d\n", fname, d.xyzDim[1], d.xyzDim[2], d.imageStart); + 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]); + if (!isnan(patientPositionEndPhilips[1])) + printMessage(" patient position end (0020,0032)\t%g\t%g\t%g\n", patientPositionEndPhilips[1], patientPositionEndPhilips[2], patientPositionEndPhilips[3]); + printMessage(" orient (0020,0037)\t%g\t%g\t%g\t%g\t%g\t%g\n", d.orient[1], d.orient[2], d.orient[3], d.orient[4], d.orient[5], d.orient[6]); + printMessage(" acq %d img %d ser %ld dim %dx%dx%dx%d mm %gx%gx%g offset %d loc %d valid %d ph %d mag %d nDTI %d 3d %d bits %d littleEndian %d echo %d coilCRC %d TE %g TR %g\n", d.acquNum, d.imageNum, d.seriesNum, d.xyzDim[1], d.xyzDim[2], d.xyzDim[3], d.xyzDim[4], d.xyzMM[1], d.xyzMM[2], d.xyzMM[3], d.imageStart, d.locationsInAcquisition, d.isValid, d.isHasPhase, d.isHasMagnitude, d.CSA.numDti, d.is3DAcq, d.bitsAllocated, d.isLittleEndian, d.echoNum, d.coilCrc, d.TE, d.TR); + if (d.CSA.dtiV[0] > 0) + printMessage(" DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); + } + if ((d.isValid) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1) && (d.imageStart < 132) && (!d.isRawDataStorage)) { + //20190524: Philips MR 55.1 creates non-image files that report kDim1/kDim2 - we can detect them since 0008,0016 reports "RawDataStorage" + //see https://neurostars.org/t/dcm2niix-error-from-philips-dicom-qsm-data-can-this-be-skipped/4883 + printError("Conversion aborted due to corrupt file: %s %dx%d %d\n", fname, d.xyzDim[1], d.xyzDim[2], d.imageStart); #ifdef USING_R - Rf_error("Irrecoverable error during conversion"); + Rf_error("Irrecoverable error during conversion"); #else - exit (kEXIT_CORRUPT_FILE_FOUND); + exit(kEXIT_CORRUPT_FILE_FOUND); #endif - } - if ((numberOfFrames > 1) && (numDimensionIndexValues == 0) && (numberOfFrames == nSliceMM)) { //issue 372 - fidx* objects = (fidx*)malloc(sizeof(struct fidx) * numberOfFrames); - for (int i = 0; i < numberOfFrames; i++) { - objects[i].value = sliceMM[i]; - objects[i].index = i; - } - qsort(objects, numberOfFrames, sizeof(struct fidx), fcmp); - numDimensionIndexValues = numberOfFrames; - for (int i = 0; i < numberOfFrames; i++) { - // printf("%d > %g\n", objects[i].index, objects[i].value); - dcmDim[objects[i].index].dimIdx[0] = i; - } - for (int i = 0; i < 4; i++) { - d.patientPosition[i] = minPatientPosition[i]; - d.patientPositionLast[i] = maxPatientPosition[i]; - } - //printf("%g -> %g\n", objects[0].value, objects[numberOfFrames-1].value); - //printf("%g %g %g -> %g %g %g\n", d.patientPosition[1], d.patientPosition[2], d.patientPosition[3], d.patientPositionLast[1], d.patientPositionLast[2], d.patientPositionLast[3]); - free(objects); - } //issue 372 - if ((d.echoTrainLength == 0) && (echoTrainLengthPhil)) - d.echoTrainLength = echoTrainLengthPhil; //+1 ?? to convert "EPI factor" to echo train length, see issue 377 - if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.xyzDim[4] > 1) && (d.is3DAcq) && (d.echoTrainLength > 1) && (minDynamicScanBeginTime < maxDynamicScanBeginTime)) { //issue369 - float TR = 1000.0 * ((maxDynamicScanBeginTime-minDynamicScanBeginTime) / (d.xyzDim[4]-1)); //-1 : fence post problem - if (fabs(TR - d.TR) > 0.001) { - printWarning("Assuming TR = %gms, not 0018,0080 = %gms (see issue 369)\n", TR, d.TR); - d.TR = TR; - } - } - // printWarning("3D EPI with FrameAcquisitionDuration = %gs volumes = %d (see issue 369)\n", frameAcquisitionDuration/1000.0, d.xyzDim[4]); - - //printf("%g %g\n", minDynamicScanBeginTime, maxDynamicScanBeginTime); - //if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.xyzDim[4] > 1) && (d.is3DAcq) && (d.echoTrainLength > 1) && (frameAcquisitionDuration > 0.0)) //issue369 - // printWarning("3D EPI with FrameAcquisitionDuration = %gs volumes = %d (see issue 369)\n", frameAcquisitionDuration/1000.0, d.xyzDim[4]); - if (numDimensionIndexValues > 1) - strcpy(d.imageType, imageType1st); //for multi-frame datasets, return name of book, not name of last chapter + } + if ((numberOfFrames > 1) && (numDimensionIndexValues == 0) && (numberOfFrames == nSliceMM)) { //issue 372 + fidx *objects = (fidx *)malloc(sizeof(struct fidx) * numberOfFrames); + for (int i = 0; i < numberOfFrames; i++) { + objects[i].value = sliceMM[i]; + objects[i].index = i; + } + qsort(objects, numberOfFrames, sizeof(struct fidx), fcmp); + numDimensionIndexValues = numberOfFrames; + for (int i = 0; i < numberOfFrames; i++) { + // printf("%d > %g\n", objects[i].index, objects[i].value); + dcmDim[objects[i].index].dimIdx[0] = i; + } + for (int i = 0; i < 4; i++) { + d.patientPosition[i] = minPatientPosition[i]; + d.patientPositionLast[i] = maxPatientPosition[i]; + } + //printf("%g -> %g\n", objects[0].value, objects[numberOfFrames-1].value); + //printf("%g %g %g -> %g %g %g\n", d.patientPosition[1], d.patientPosition[2], d.patientPosition[3], d.patientPositionLast[1], d.patientPositionLast[2], d.patientPositionLast[3]); + free(objects); + } //issue 372 + if ((d.echoTrainLength == 0) && (echoTrainLengthPhil)) + d.echoTrainLength = echoTrainLengthPhil; //+1 ?? to convert "EPI factor" to echo train length, see issue 377 + if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.xyzDim[4] > 1) && (d.is3DAcq) && (d.echoTrainLength > 1) && (minDynamicScanBeginTime < maxDynamicScanBeginTime)) { //issue369 + float TR = 1000.0 * ((maxDynamicScanBeginTime - minDynamicScanBeginTime) / (d.xyzDim[4] - 1)); //-1 : fence post problem + if (fabs(TR - d.TR) > 0.001) { + printWarning("Assuming TR = %gms, not 0018,0080 = %gms (see issue 369)\n", TR, d.TR); + d.TR = TR; + } + } + // printWarning("3D EPI with FrameAcquisitionDuration = %gs volumes = %d (see issue 369)\n", frameAcquisitionDuration/1000.0, d.xyzDim[4]); + //printf("%g %g\n", minDynamicScanBeginTime, maxDynamicScanBeginTime); + //if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.xyzDim[4] > 1) && (d.is3DAcq) && (d.echoTrainLength > 1) && (frameAcquisitionDuration > 0.0)) //issue369 + // printWarning("3D EPI with FrameAcquisitionDuration = %gs volumes = %d (see issue 369)\n", frameAcquisitionDuration/1000.0, d.xyzDim[4]); + if (numDimensionIndexValues > 1) + strcpy(d.imageType, imageType1st); //for multi-frame datasets, return name of book, not name of last chapter if ((numDimensionIndexValues > 1) && (numDimensionIndexValues == numberOfFrames)) { - //Philips enhanced datasets can have custom slice orders and pack images with different TE, Phase/Magnitude/Etc. + //Philips enhanced datasets can have custom slice orders and pack images with different TE, Phase/Magnitude/Etc. int maxVariableItem = 0; int nVariableItems = 0; if (true) { // @@ -7164,39 +7071,43 @@ if (d.isHasPhase) mx[j] = dcmDim[0].dimIdx[j]; mn[j] = mx[j]; for (int i = 0; i < numDimensionIndexValues; i++) { - if (mx[j] < dcmDim[i].dimIdx[j]) mx[j] = dcmDim[i].dimIdx[j]; - if (mn[j] > dcmDim[i].dimIdx[j]) mn[j] = dcmDim[i].dimIdx[j]; + if (mx[j] < dcmDim[i].dimIdx[j]) + mx[j] = dcmDim[i].dimIdx[j]; + if (mn[j] > dcmDim[i].dimIdx[j]) + mn[j] = dcmDim[i].dimIdx[j]; } if (mx[j] != mn[j]) { maxVariableItem = j; - nVariableItems ++; + nVariableItems++; } } if (isVerbose > 1) { printMessage(" DimensionIndexValues (0020,9157), dimensions with variability:\n"); for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) - if (mn[i] != mx[i]) + if (mn[i] != mx[i]) printMessage(" Dimension %d Range: %d..%d\n", i, mn[i], mx[i]); - } - } //verbose > 1 + } + } //verbose > 1 //see http://dicom.nema.org/medical/Dicom/2018d/output/chtml/part03/sect_C.8.24.3.3.html //Philips puts spatial position as lower item than temporal position, the reverse is true for Bruker and Canon int stackPositionItem = 0; if (dimensionIndexPointerCounter > 0) - for(size_t i = 0; i < dimensionIndexPointerCounter; i++) - if (dimensionIndexPointer[i] == kInStackPositionNumber) stackPositionItem = i; + for (size_t i = 0; i < dimensionIndexPointerCounter; i++) + if (dimensionIndexPointer[i] == kInStackPositionNumber) + stackPositionItem = i; if ((d.manufacturer == kMANUFACTURER_CANON) && (nVariableItems == 1) && (d.xyzDim[4] > 1)) { //WARNING: Canon CANON V6.0SP2001* (0018,9005) = "AX fMRI" strangely sets TemporalPositionIndex(0020,9128) as 1 for all volumes: (0020,9157) and (0020,9128) are INCORRECT! printf("Invalid enhanced DICOM created by Canon: Only single dimension in DimensionIndexValues (0020,9157) varies, for 4D file (e.g. BOTH space and time should vary)\n"); printf("%d %d\n", stackPositionItem, maxVariableItem); int stackTimeItem = 0; if (stackPositionItem == 0) { - maxVariableItem ++; - stackTimeItem ++; //e.g. slot 0 = space, slot 1 = time + maxVariableItem++; + stackTimeItem++; //e.g. slot 0 = space, slot 1 = time } int vol = 0; for (int i = 0; i < numDimensionIndexValues; i++) { - if (1 == dcmDim[i].dimIdx[stackPositionItem]) vol ++; + if (1 == dcmDim[i].dimIdx[stackPositionItem]) + vol++; dcmDim[i].dimIdx[stackTimeItem] = vol; //printf("vol %d slice %d\n", dcmDim[i].dimIdx[stackTimeItem], dcmDim[i].dimIdx[stackPositionItem]); } @@ -7214,54 +7125,65 @@ if (d.isHasPhase) qsort(dcmDim, numberOfFrames, sizeof(struct TDCMdim), compareTDCMdimRev); #endif //for (int i = 0; i < numberOfFrames; i++) - // printf("i %d diskPos= %d dimIdx= %d %d %d %d TE= %g\n", i, dcmDim[i].diskPos, dcmDim[i].dimIdx[0], dcmDim[i].dimIdx[1], dcmDim[i].dimIdx[2], dcmDim[i].dimIdx[3], dti4D->TE[i]); - for (int i = 0; i < numberOfFrames; i++) { + // printf("i %d diskPos= %d dimIdx= %d %d %d %d TE= %g\n", i, dcmDim[i].diskPos, dcmDim[i].dimIdx[0], dcmDim[i].dimIdx[1], dcmDim[i].dimIdx[2], dcmDim[i].dimIdx[3], dti4D->TE[i]); + for (int i = 0; i < numberOfFrames; i++) { dti4D->sliceOrder[i] = dcmDim[i].diskPos; - dti4D->intenScale[i] = dcmDim[i].intenScale; - dti4D->intenIntercept[i] = dcmDim[i].intenIntercept; - dti4D->intenScalePhilips[i] = dcmDim[i].intenScalePhilips; - dti4D->RWVIntercept[i] = dcmDim[i].RWVIntercept; - dti4D->RWVScale[i] = dcmDim[i].RWVScale; - if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) d.isScaleVariesEnh = true; - if (dti4D->intenScale[i] != dti4D->intenScale[0]) d.isScaleVariesEnh = true; - if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) d.isScaleVariesEnh = true; - } - if ( !(d.manufacturer == kMANUFACTURER_BRUKER && d.isDiffusion) && (d.xyzDim[4] > 1) && (d.xyzDim[4] < kMaxDTI4D)) { //record variations in TE + dti4D->intenScale[i] = dcmDim[i].intenScale; + dti4D->intenIntercept[i] = dcmDim[i].intenIntercept; + dti4D->intenScalePhilips[i] = dcmDim[i].intenScalePhilips; + dti4D->RWVIntercept[i] = dcmDim[i].RWVIntercept; + dti4D->RWVScale[i] = dcmDim[i].RWVScale; + if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) + d.isScaleVariesEnh = true; + if (dti4D->intenScale[i] != dti4D->intenScale[0]) + d.isScaleVariesEnh = true; + if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) + d.isScaleVariesEnh = true; + } + if (!(d.manufacturer == kMANUFACTURER_BRUKER && d.isDiffusion) && (d.xyzDim[4] > 1) && (d.xyzDim[4] < kMaxDTI4D)) { //record variations in TE d.isScaleOrTEVaries = false; bool isTEvaries = false; bool isScaleVaries = false; //setting j = 1 in next few lines is a hack, just in case TE/scale/intercept listed AFTER dimensionIndexValues int j = 0; - if (d.xyzDim[3] > 1) j = 1; + if (d.xyzDim[3] > 1) + j = 1; for (int i = 0; i < d.xyzDim[4]; i++) { - int slice = j+(i * d.xyzDim[3]); + int slice = j + (i * d.xyzDim[3]); //dti4D->gradDynVol[i] = 0; //only PAR/REC - 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->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; + 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; /*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; + 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; + if ((isScaleVaries) || (isTEvaries)) + d.isScaleOrTEVaries = true; + if (isTEvaries) + d.isMultiEcho = true; //if echoVaries,count number of echoes /*int echoNum = 1; for (int i = 1; i < d.xyzDim[4]; i++) { @@ -7271,219 +7193,176 @@ if (d.isHasPhase) printMessage("Parameters vary across 3D volumes packed in single DICOM file:\n"); 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] ); + 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)) { - float dx = sqrt( pow(d.patientPosition[1]-d.patientPositionLast[1],2)+ - pow(d.patientPosition[2]-d.patientPositionLast[2],2)+ - pow(d.patientPosition[3]-d.patientPositionLast[3],2)); - dx = dx / (maxInStackPositionNumber - 1); - if ((dx > 0.0) && (!isSameFloatGE(dx, d.xyzMM[3])) ) //patientPosition has some rounding error + if ((d.xyzDim[3] == maxInStackPositionNumber) && (maxInStackPositionNumber > 1) && (d.zSpacing <= 0.0)) { + float dx = sqrt(pow(d.patientPosition[1] - d.patientPositionLast[1], 2) + + pow(d.patientPosition[2] - d.patientPositionLast[2], 2) + + pow(d.patientPosition[3] - d.patientPositionLast[3], 2)); + dx = dx / (maxInStackPositionNumber - 1); + if ((dx > 0.0) && (!isSameFloatGE(dx, d.xyzMM[3]))) //patientPosition has some rounding error d.xyzMM[3] = dx; } //d.zSpacing <= 0.0: Bruker does not populate 0018,0088 https://github.com/rordenlab/dcm2niix/issues/241 - } //if numDimensionIndexValues > 1 : enhanced DICOM - /* //Attempt to append ADC - printMessage("CXC grad %g %d %d\n", philDTI[0].V[0], maxGradNum, d.xyzDim[4]); - if ((maxGradNum > 1) && ((maxGradNum+1) == d.xyzDim[4]) ) { - //ADC map (non-zero b-value with zero vector length) - if (isVerbose) - printMessage("Final volume does not have an associated 0020,9157. Assuming final volume is an ADC/isotropic map\n", philDTI[0].V[0], maxGradNum, d.xyzDim[4]); - philDTI[maxGradNum].V[0] = 1000.0; - philDTI[maxGradNum].V[1] = 0.0; - philDTI[maxGradNum].V[2] = 0.0; - philDTI[maxGradNum].V[3] = 0.0; - maxGradNum++; - }*/ - /*if ((minGradNum >= 1) && ((maxGradNum-minGradNum+1) == d.xyzDim[4])) { - //see ADNI DWI data for 018_S_4868 - the gradient numbers are in the range 2..37 for 36 volumes - no gradient number 1! - if (philDTI[minGradNum -1].V[0] >= 0) { - if (isVerbose) - printMessage("Using %d diffusion data directions coded by DimensionIndexValues\n", maxGradNum); - int off = 0; - if (minGradNum > 1) { - off = minGradNum - 1; - printWarning("DimensionIndexValues (0020,9157) is not indexed from 1 (range %d..%d). Please validate results\n", minGradNum, maxGradNum); - } - for (int i = 0; i < d.xyzDim[4]; i++) { - dti4D->S[i].V[0] = philDTI[i+off].V[0]; - dti4D->S[i].V[1] = philDTI[i+off].V[1]; - dti4D->S[i].V[2] = philDTI[i+off].V[2]; - dti4D->S[i].V[3] = philDTI[i+off].V[3]; - if (isVerbose > 1) - printMessage(" grad %d b=%g vec=%gx%gx%g\n", i, dti4D->S[i].V[0], dti4D->S[i].V[1], dti4D->S[i].V[2], dti4D->S[i].V[3]); - } - d.CSA.numDti = maxGradNum - off; - } - }*/ + } //if numDimensionIndexValues > 1 : enhanced DICOM if (d.CSA.numDti >= kMaxDTI4D) { - printError("Unable to convert DTI [recompile with increased kMaxDTI4D] detected=%d, max = %d\n", d.CSA.numDti, kMaxDTI4D); - d.CSA.numDti = 0; - } - if ((hasDwiDirectionality) && (d.CSA.numDti < 1)) - d.CSA.numDti = 1; - if ((d.isValid) && (d.imageNum == 0)) { //Philips non-image DICOMs (PS_*, XX_*) are not valid images and do not include instance numbers - //TYPE 1 for MR image http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0020,0013) - // Only type 2 for some other DICOMs! Therefore, generate warning not error - printWarning("Instance number (0020,0013) not found: %s\n", fname); - d.imageNum = abs((int)d.instanceUidCrc) % 2147483647;//INT_MAX; - if (d.imageNum == 0) d.imageNum = 1; //https://github.com/rordenlab/dcm2niix/issues/341 - //d.imageNum = 1; //not set - } - if ((numDimensionIndexValues < 1) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (d.seriesNum > 99999) && (philMRImageDiffBValueNumber > 0)) { - //Ugly kludge to distinguish Philips classic DICOM dti - // images from a single sequence can have identical series number, instance number, gradient number - // the ONLY way to distinguish slices is using the private tag MRImageDiffBValueNumber - // confusingly, the same sequence can also generate MULTIPLE series numbers! - // for examples see https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging - d.seriesNum += (philMRImageDiffBValueNumber*1000); - } - //if (contentTime != 0.0) && (numDimensionIndexValues < (MAX_NUMBER_OF_DIMENSIONS - 1)){ - // uint_32t timeCRC = mz_crc32X((unsigned char*) &contentTime, sizeof(double)); - //} - //if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strcmp(d.sequenceName, "fldyn3d1")== 0)) { - if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strstr(d.sequenceName, "fldyn3d1") != NULL)) { - //combine DCE series https://github.com/rordenlab/dcm2niix/issues/252 - d.isStackableSeries = true; + printError("Unable to convert DTI [recompile with increased kMaxDTI4D] detected=%d, max = %d\n", d.CSA.numDti, kMaxDTI4D); + d.CSA.numDti = 0; + } + if ((hasDwiDirectionality) && (d.CSA.numDti < 1)) + d.CSA.numDti = 1; + if ((d.isValid) && (d.imageNum == 0)) { //Philips non-image DICOMs (PS_*, XX_*) are not valid images and do not include instance numbers + //TYPE 1 for MR image http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0020,0013) + // Only type 2 for some other DICOMs! Therefore, generate warning not error + printWarning("Instance number (0020,0013) not found: %s\n", fname); + d.imageNum = abs((int)d.instanceUidCrc) % 2147483647; //INT_MAX; + if (d.imageNum == 0) + d.imageNum = 1; //https://github.com/rordenlab/dcm2niix/issues/341 + //d.imageNum = 1; //not set + } + if ((numDimensionIndexValues < 1) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (d.seriesNum > 99999) && (philMRImageDiffBValueNumber > 0)) { + //Ugly kludge to distinguish Philips classic DICOM dti + // images from a single sequence can have identical series number, instance number, gradient number + // the ONLY way to distinguish slices is using the private tag MRImageDiffBValueNumber + // confusingly, the same sequence can also generate MULTIPLE series numbers! + // for examples see https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging + d.seriesNum += (philMRImageDiffBValueNumber * 1000); + } + //if (contentTime != 0.0) && (numDimensionIndexValues < (MAX_NUMBER_OF_DIMENSIONS - 1)){ + // uint_32t timeCRC = mz_crc32X((unsigned char*) &contentTime, sizeof(double)); + //} + //if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strcmp(d.sequenceName, "fldyn3d1")== 0)) { + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strstr(d.sequenceName, "fldyn3d1") != NULL)) { + //combine DCE series https://github.com/rordenlab/dcm2niix/issues/252 + d.isStackableSeries = true; d.imageNum += (d.seriesNum * 1000); strcpy(d.seriesInstanceUID, d.studyInstanceUID); - d.seriesUidCrc = mz_crc32X((unsigned char*) &d.protocolName, strlen(d.protocolName)); + d.seriesUidCrc = mz_crc32X((unsigned char *)&d.protocolName, strlen(d.protocolName)); } if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (!isTriggerSynced)) //issue408 d.triggerDelayTime = 0.0; if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime)) //issue395 d.triggerDelayTime = 0.0; //printf("%d\t%g\t%g\t%g\n", d.imageNum, d.acquisitionTime, d.triggerDelayTime, MRImageDynamicScanBeginTime); - if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strlen(seriesTimeTxt) > 1) && (d.isXA10A) && (d.xyzDim[3] == 1) && (d.xyzDim[4] < 2)) { - //printWarning(">>Ignoring series number of XA data saved as classic DICOM (issue 394)\n"); - d.isStackableSeries = true; + if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strlen(seriesTimeTxt) > 1) && (d.isXA10A) && (d.xyzDim[3] == 1) && (d.xyzDim[4] < 2)) { + //printWarning(">>Ignoring series number of XA data saved as classic DICOM (issue 394)\n"); + d.isStackableSeries = true; d.imageNum += (d.seriesNum * 1000); strcpy(d.seriesInstanceUID, seriesTimeTxt); // dest <- src - d.seriesUidCrc = mz_crc32X((unsigned char*) &seriesTimeTxt, strlen(seriesTimeTxt)); + d.seriesUidCrc = mz_crc32X((unsigned char *)&seriesTimeTxt, strlen(seriesTimeTxt)); } - if (((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON))&& (B0Philips > 0.0)) {//issue 388 + if (((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON)) && (B0Philips > 0.0)) { //issue 388 char txt[1024] = {""}; - sprintf(txt, "b=%d(", (int) round(B0Philips)); - if (strstr(d.imageComments, txt) != NULL) { - //printf("%s>>>%s %g\n", txt, d.imageComments, B0Philips); + sprintf(txt, "b=%d(", (int)round(B0Philips)); + if (strstr(d.imageComments, txt) != NULL) { + //printf("%s>>>%s %g\n", txt, d.imageComments, B0Philips); int len = strlen(txt); - strcpy(txt, (char*)&d.imageComments[len]); + strcpy(txt, (char *)&d.imageComments[len]); len = strlen(txt); for (int i = 0; i <= len; i++) { - if ((txt[i] >= '0') && (txt[i] <= '9')) continue; - if ((txt[i] == '.') || (txt[i] == '-')) continue; - txt[i] = ' '; - } - float v[4]; - dcmMultiFloat(len,(char*)&txt[0], 3, &v[0]); + if ((txt[i] >= '0') && (txt[i] <= '9')) + continue; + if ((txt[i] == '.') || (txt[i] == '-')) + continue; + txt[i] = ' '; + } + float v[4]; + dcmMultiFloat(len, (char *)&txt[0], 3, &v[0]); d.CSA.dtiV[0] = B0Philips; - #ifdef swizzleCanon //see issue422 and dcm_qa_canon - d.CSA.dtiV[1] = v[2]; - d.CSA.dtiV[2] = v[1]; - d.CSA.dtiV[3] = -v[3]; - #else - d.CSA.dtiV[1] = v[2]; - d.CSA.dtiV[2] = v[1]; - d.CSA.dtiV[3] = v[3]; - d.manufacturer = kMANUFACTURER_CANON; - #endif - //d.CSA.dtiV[1] = v[1]; - //d.CSA.dtiV[2] = v[2]; - //d.CSA.dtiV[3] = v[3]; - d.CSA.numDti = 1; - } - +#ifdef swizzleCanon //see issue422 and dcm_qa_canon + d.CSA.dtiV[1] = v[2]; + d.CSA.dtiV[2] = v[1]; + d.CSA.dtiV[3] = -v[3]; +#else + d.CSA.dtiV[1] = v[2]; + d.CSA.dtiV[2] = v[1]; + d.CSA.dtiV[3] = v[3]; + d.manufacturer = kMANUFACTURER_CANON; +#endif + //d.CSA.dtiV[1] = v[1]; + //d.CSA.dtiV[2] = v[2]; + //d.CSA.dtiV[3] = v[3]; + d.CSA.numDti = 1; + } } if ((isDICOMANON) && (isMATLAB)) { //issue 383 strcpy(d.seriesInstanceUID, d.studyDate); - // This check is unlikely to be important in practice, but it silences a warning from GCC with -Wrestrict - if (strlen(d.studyDate) < kDICOMStr) { - strncat(d.seriesInstanceUID, d.studyTime, kDICOMStr-strlen(d.studyDate)); - } - d.seriesUidCrc = mz_crc32X((unsigned char*) &d.seriesInstanceUID, strlen(d.seriesInstanceUID)); + // This check is unlikely to be important in practice, but it silences a warning from GCC with -Wrestrict + if (strlen(d.studyDate) < kDICOMStr) { + strncat(d.seriesInstanceUID, d.studyTime, kDICOMStr - strlen(d.studyDate)); + } + d.seriesUidCrc = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID)); } if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strstr(d.sequenceName, "fl2d1") != NULL)) { - d.isLocalizer = true; + d.isLocalizer = true; } //UIH 3D T1 scans report echo train length, which is interpreted as 3D EPI - if ((d.manufacturer == kMANUFACTURER_UIH) && (strstr(d.sequenceName, "gre_fsp") != NULL)) d.echoTrainLength = 0; + if ((d.manufacturer == kMANUFACTURER_UIH) && (strstr(d.sequenceName, "gre_fsp") != NULL)) + d.echoTrainLength = 0; //printf(">>%s\n", d.sequenceName); d.isValid = false; // Andrey Fedorov has requested keeping GE bvalues, see issue 264 //if ((d.CSA.numDti > 0) && (d.manufacturer == kMANUFACTURER_GE) && (d.numberOfDiffusionDirectionGE < 1)) // d.CSA.numDti = 0; //https://github.com/rordenlab/dcm2niix/issues/264 - if ((!d.isLocalizer) && (isInterpolated) && (d.imageNum <= 1)) - printWarning("interpolated protocol '%s' may be unsuitable for dwidenoise/mrdegibbs. %s\n", d.protocolName, fname); - if (((numDimensionIndexValues+3) < MAX_NUMBER_OF_DIMENSIONS) && (d.rawDataRunNumber > 0)) - d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS-4] = d.rawDataRunNumber; - if ((numDimensionIndexValues+2) < MAX_NUMBER_OF_DIMENSIONS) - d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS-3] = d.instanceUidCrc; - if ((numDimensionIndexValues+1) < MAX_NUMBER_OF_DIMENSIONS) - d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS-2] = d.echoNum; - if (numDimensionIndexValues < MAX_NUMBER_OF_DIMENSIONS) //https://github.com/rordenlab/dcm2niix/issues/221 - d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS-1] = mz_crc32X((unsigned char*) &d.seriesInstanceUID, strlen(d.seriesInstanceUID)); - if ((d.isValid) && (d.seriesUidCrc == 0)) { - if (d.seriesNum < 1) - d.seriesUidCrc = 1; //no series information - else - d.seriesUidCrc = d.seriesNum; //file does not have Series UID, use series number instead - } - if (d.seriesNum < 1) //https://github.com/rordenlab/dcm2niix/issues/218 - d.seriesNum = mz_crc32X((unsigned char*) &d.seriesInstanceUID, strlen(d.seriesInstanceUID)); - getFileName(d.imageBaseName, fname); - if (multiBandFactor > d.CSA.multiBandFactor) - d.CSA.multiBandFactor = multiBandFactor; //SMS reported in 0051,1011 but not CSA header - #ifndef myLoadWholeFileToReadHeader + if ((!d.isLocalizer) && (isInterpolated) && (d.imageNum <= 1)) + printWarning("interpolated protocol '%s' may be unsuitable for dwidenoise/mrdegibbs. %s\n", d.protocolName, fname); + if (((numDimensionIndexValues + 3) < MAX_NUMBER_OF_DIMENSIONS) && (d.rawDataRunNumber > 0)) + d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 4] = d.rawDataRunNumber; + if ((numDimensionIndexValues + 2) < MAX_NUMBER_OF_DIMENSIONS) + d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 3] = d.instanceUidCrc; + if ((numDimensionIndexValues + 1) < MAX_NUMBER_OF_DIMENSIONS) + d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 2] = d.echoNum; + if (numDimensionIndexValues < MAX_NUMBER_OF_DIMENSIONS) //https://github.com/rordenlab/dcm2niix/issues/221 + d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 1] = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID)); + if ((d.isValid) && (d.seriesUidCrc == 0)) { + if (d.seriesNum < 1) + d.seriesUidCrc = 1; //no series information + else + d.seriesUidCrc = d.seriesNum; //file does not have Series UID, use series number instead + } + if (d.seriesNum < 1) //https://github.com/rordenlab/dcm2niix/issues/218 + d.seriesNum = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID)); + getFileName(d.imageBaseName, fname); + if (multiBandFactor > d.CSA.multiBandFactor) + d.CSA.multiBandFactor = multiBandFactor; //SMS reported in 0051,1011 but not CSA header +#ifndef myLoadWholeFileToReadHeader fclose(file); - #endif - if ((temporalResolutionMS > 0.0) && (isSameFloatGE(d.TR,temporalResolutionMS)) ) { +#endif + if ((temporalResolutionMS > 0.0) && (isSameFloatGE(d.TR, temporalResolutionMS))) { //do something profound //in practice 0020,0110 not used //https://github.com/bids-standard/bep001/blob/repetitiontime/Proposal_RepetitionTime.md } - if (hasDwiDirectionality) d.isVectorFromBMatrix = false; //issue 265: Philips/Siemens have both directionality and bmatrix, Bruker only has bmatrix - /* - fixed 2/2019 by modifying to kDiffusionBFactor, kDiffusionDirectionRL, kDiffusionDirectionAP, kDiffusionDirectionFH - if ((d.xyzDim[3] == 1) && (numDimensionIndexValues < 1) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (B0Philips >= 0.0)) { - //Special case: old Philips Classic DWI storing vectors in 0019,10bb, 0019,10bc - //printf(">>>>%g %g %g %g\n",B0Philips, vRLPhilips, vAPPhilips, vFHPhilips); - d.CSA.dtiV[0] = B0Philips; - d.CSA.dtiV[1] = vRLPhilips; - d.CSA.dtiV[2] = vAPPhilips; - d.CSA.dtiV[3] = vFHPhilips; - d.CSA.numDti = 1; - } - */ + if (hasDwiDirectionality) + d.isVectorFromBMatrix = false; //issue 265: Philips/Siemens have both directionality and bmatrix, Bruker only has bmatrix //printf("%s\t%s\t%s\t%s\t%s_%s\n",d.patientBirthDate, d.procedureStepDescription,d.patientName, fname, d.studyDate, d.studyTime); //d.isValid = false; //printMessage(" patient position (0020,0032)\t%g\t%g\t%g\n", d.patientPosition[1],d.patientPosition[2],d.patientPosition[3]); - //printf("%d\t%g\t%g\t%g\t%g\n", d.imageNum, d.rtia_timerGE, d.patientPosition[1],d.patientPosition[2],d.patientPosition[3]); + //printf("%d\t%g\t%g\t%g\t%g\n", d.imageNum, d.rtia_timerGE, d.patientPosition[1],d.patientPosition[2],d.patientPosition[3]); //printf("%g\t\t%g\t%g\t%g\t%s\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3], fname); - //printMessage("buffer usage %d %d %d\n",d.imageStart, lPos+lFileOffset, MaxBufferSz); + //printMessage("buffer usage %d %d %d\n",d.imageStart, lPos+lFileOffset, MaxBufferSz); return d; } // readDICOM() -void setDefaultPrefs (struct TDCMprefs *prefs) { +void setDefaultPrefs(struct TDCMprefs *prefs) { prefs->isVerbose = false; - prefs->compressFlag = kCompressSupport; + prefs->compressFlag = kCompressSupport; prefs->isIgnoreTriggerTimes = false; } -struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) { +struct TDICOMdata readDICOMv(char *fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) { struct TDCMprefs prefs; setDefaultPrefs(&prefs); prefs.isVerbose = isVerbose; prefs.compressFlag = compressFlag; TDICOMdata ret = readDICOMx(fname, &prefs, dti4D); - return ret; + return ret; } - -struct TDICOMdata readDICOM(char * fname) { - struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); //unused - TDICOMdata ret = readDICOMv(fname, false, kCompressSupport, dti4D); +struct TDICOMdata readDICOM(char *fname) { + 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_batch.cpp b/console/nii_dicom_batch.cpp index 38bf59e8..ee4c4271 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1,34 +1,34 @@ //#define myNoSave //do not save images to disk #ifdef _MSC_VER - #include - #define getcwd _getcwd - #define chdir _chrdir - #include "io.h" - //#include - #define MiniZ +#include +#define getcwd _getcwd +#define chdir _chrdir +#include "io.h" +//#include +#define MiniZ #else - #include - #ifdef myDisableMiniZ - #undef MiniZ - #else - #define MiniZ - #endif +#include +#ifdef myDisableMiniZ +#undef MiniZ +#else +#define MiniZ +#endif #endif #if defined(__APPLE__) && defined(__MACH__) #endif #ifndef myDisableZLib - #ifdef MiniZ - #include "miniz.c" //single file clone of libz - #else - #include - #endif +#ifdef MiniZ +#include "miniz.c" //single file clone of libz #else - #undef MiniZ +#include +#endif +#else +#undef MiniZ #endif -#include "tinydir.h" -#include "print.h" #include "nifti1_io_core.h" +#include "print.h" +#include "tinydir.h" #ifndef USING_R #include "nifti1.h" #endif @@ -37,6 +37,7 @@ #include "nii_foreign.h" #endif #include "nii_dicom.h" +#include "nii_ortho.h" #include //toupper #include #include @@ -47,21 +48,20 @@ #include #include #include -#include // clock_t, clock, CLOCKS_PER_SEC -#include "nii_ortho.h" +#include // clock_t, clock, CLOCKS_PER_SEC #if defined(_WIN64) || defined(_WIN32) - #include //write to registry +#include //write to registry #endif #ifndef M_PI - #define M_PI 3.14159265358979323846 +#define M_PI 3.14159265358979323846 #endif #if defined(_WIN64) || defined(_WIN32) - const char kPathSeparator ='\\'; - const char kFileSep[2] = "\\"; +const char kPathSeparator = '\\'; +const char kFileSep[2] = "\\"; #else - const char kPathSeparator ='/'; - const char kFileSep[2] = "/"; +const char kPathSeparator = '/'; +const char kFileSep[2] = "/"; #endif #ifdef USING_R @@ -77,146 +77,151 @@ #define newTilt struct TDCMsort { - uint64_t indx, img; - uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; + uint64_t indx, img; + uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; }; - struct TSearchList { - unsigned long numItems, maxItems; - char **str; + unsigned long numItems, maxItems; + char **str; }; #ifndef PATH_MAX - #define PATH_MAX 4096 +#define PATH_MAX 4096 #endif -void dropFilenameFromPath(char *path) { // - const char *dirPath = strrchr(path, '/'); //UNIX - if (dirPath == 0) - dirPath = strrchr(path, '\\'); //Windows - if (dirPath == NULL) { - strcpy(path,""); - } else - path[dirPath - path] = 0; // please make sure there is enough space in TargetDirectory - 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[PATH_MAX]; - char* ok = getcwd(cwd, sizeof(cwd)); - if (ok !=NULL) - strcat (path,cwd); - } +void dropFilenameFromPath(char *path) { + const char *dirPath = strrchr(path, '/'); //UNIX + if (dirPath == 0) + dirPath = strrchr(path, '\\'); //Windows + if (dirPath == NULL) { + strcpy(path, ""); + } else + path[dirPath - path] = 0; // please make sure there is enough space in TargetDirectory + 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[PATH_MAX]; + char *ok = getcwd(cwd, sizeof(cwd)); + if (ok != NULL) + strcat(path, cwd); + } } void dropTrailingFileSep(char *path) { // - size_t len = strlen(path) - 1; - if (len <= 0) return; - if (path[len] == '/') - path[len] = '\0'; - else if (path[len] == '\\') - path[len] = '\0'; + size_t len = strlen(path) - 1; + if (len <= 0) + return; + if (path[len] == '/') + path[len] = '\0'; + else if (path[len] == '\\') + path[len] = '\0'; } -bool is_fileexists(const char * filename) { - FILE * fp = NULL; - if ((fp = fopen(filename, "r"))) { - fclose(fp); - return true; - } - return false; +bool is_fileexists(const char *filename) { + FILE *fp = NULL; + if ((fp = fopen(filename, "r"))) { + fclose(fp); + return true; + } + return false; } #ifndef S_ISDIR - #define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR) #endif #ifndef S_ISREG - #define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#define S_ISREG(mode) (((mode)&S_IFMT) == S_IFREG) #endif -bool is_fileNotDir(const char* path) { //returns false if path is a folder; requires #include - struct stat buf; - stat(path, &buf); - return S_ISREG(buf.st_mode); +bool is_fileNotDir(const char *path) { //returns false if path is a folder; requires #include + struct stat buf; + stat(path, &buf); + return S_ISREG(buf.st_mode); } //is_file() -bool is_exe(const char* path) { //requires #include - struct stat buf; - if (stat(path, &buf) != 0) return false; //file does not eist - if (!S_ISREG(buf.st_mode)) return false; //not regular file, e.g. '..' - return (buf.st_mode & 0111) ; - //return (S_ISREG(buf.st_mode) && (buf.st_mode & 0111) ); +bool is_exe(const char *path) { //requires #include + struct stat buf; + if (stat(path, &buf) != 0) + return false; //file does not eist + if (!S_ISREG(buf.st_mode)) + return false; //not regular file, e.g. '..' + return (buf.st_mode & 0111); + //return (S_ISREG(buf.st_mode) && (buf.st_mode & 0111) ); } //is_exe() #if defined(_WIN64) || defined(_WIN32) - //Windows does not support lstat - int is_dir(const char *pathname, int follow_link) { - struct stat s; - if ((NULL == pathname) || (0 == strlen(pathname))) - return 0; - int err = stat(pathname, &s); - if(-1 == err) - return 0; // does not exist - else { - if(S_ISDIR(s.st_mode)) { - return 1; // it's a dir - } else { - return 0;// exists but is no dir - } +//Windows does not support lstat +int is_dir(const char *pathname, int follow_link) { + struct stat s; + if ((NULL == pathname) || (0 == strlen(pathname))) + return 0; + int err = stat(pathname, &s); + if (-1 == err) + return 0; // does not exist + else { + if (S_ISDIR(s.st_mode)) { + return 1; // it's a dir + } else { + return 0; // exists but is no dir } - }// is_dir() + } +} // is_dir() #else //if windows else Unix - int is_dir(const char *pathname, int follow_link) - { - struct stat s; - int retval; - if ((NULL == pathname) || (0 == strlen(pathname))) - return 0; // does not exist - retval = follow_link ? stat(pathname, &s) : lstat(pathname, &s); - if ((-1 != retval) && (S_ISDIR(s.st_mode))) - return 1; // it's a dir - return 0; // exists but is no dir - }// is_dir() +int is_dir(const char *pathname, int follow_link) { + struct stat s; + int retval; + if ((NULL == pathname) || (0 == strlen(pathname))) + return 0; // does not exist + retval = follow_link ? stat(pathname, &s) : lstat(pathname, &s); + if ((-1 != retval) && (S_ISDIR(s.st_mode))) + return 1; // it's a dir + return 0; // exists but is no dir +} // is_dir() #endif -void opts2Prefs (struct TDCMopts* opts, struct TDCMprefs *prefs) { +void opts2Prefs(struct TDCMopts *opts, struct TDCMprefs *prefs) { setDefaultPrefs(prefs); prefs->isVerbose = opts->isVerbose; - prefs->compressFlag = opts->compressFlag; + prefs->compressFlag = opts->compressFlag; prefs->isIgnoreTriggerTimes = opts->isIgnoreTriggerTimes; } - -void geCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx, int isVerbose){ - //0018,1312 phase encoding is either in row or column direction - //0043,1039 (or 0043,a039). b value (as the first number in the string). - //0019,10bb (or 0019,a0bb). phase diffusion direction - //0019,10bc (or 0019,a0bc). frequency diffusion direction - //0019,10bd (or 0019,a0bd). slice diffusion direction - //These directions are relative to freq,phase,slice, so although no - //transformations are required, you need to check the direction of the - //phase encoding. This is in DICOM message 0018,1312. If this has value - //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->isBVecWorldCoordinates) return; //Canon classic DICOMs use image space, enhanced use world space! - 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 - else { - printMessage("GE DTI directions require head first supine acquisition\n"); + +void geCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx, int isVerbose) { + //0018,1312 phase encoding is either in row or column direction + //0043,1039 (or 0043,a039). b value (as the first number in the string). + //0019,10bb (or 0019,a0bb). phase diffusion direction + //0019,10bc (or 0019,a0bc). frequency diffusion direction + //0019,10bd (or 0019,a0bd). slice diffusion direction + //These directions are relative to freq,phase,slice, so although no + //transformations are required, you need to check the direction of the + //phase encoding. This is in DICOM message 0018,1312. If this has value + //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->isBVecWorldCoordinates) + return; //Canon classic DICOMs use image space, enhanced use world space! + 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 + else { + printMessage("GE DTI directions require head first supine acquisition\n"); + return; + } + bool col = false; + if (d->phaseEncodingRC == 'C') + col = true; + else if (d->phaseEncodingRC != 'R') { + printWarning("Unable to determine DTI gradients, 0018,1312 should be either R or C"); return; - } - bool col = false; - if (d->phaseEncodingRC == 'C') - col = true; - else if (d->phaseEncodingRC != 'R') { - printWarning("Unable to determine DTI gradients, 0018,1312 should be either R or C"); - return; - } - if (abs(sliceDir) != 3) - printWarning("Limited validation for non-Axial DTI: confirm gradient vector transformation.\n"); - //GE vectors from Xiangrui Li' dicm2nii, validated with datasets from https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging + } + if (abs(sliceDir) != 3) + printWarning("Limited validation for non-Axial DTI: confirm gradient vector transformation.\n"); + //GE vectors from Xiangrui Li' dicm2nii, validated with datasets from https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging ivec3 flp; if (abs(sliceDir) == 1) flp = setiVec3(1, 1, 0); //SAGITTAL @@ -229,175 +234,182 @@ void geCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx, int isV flp = setiVec3(0, 0, 1); //AXIAL??? } if (sliceDir < 0) - flp.v[2] = 1 - flp.v[2]; - if ((isVerbose) || (!col)) { - printMessage("Saving %d DTI gradients. GE Reorienting %s : please validate. isCol=%d sliceDir=%d flp=%d %d %d\n", d->CSA.numDti, d->protocolName, col, sliceDir, flp.v[0], flp.v[1],flp.v[2]); + flp.v[2] = 1 - flp.v[2]; + if ((isVerbose) || (!col)) { + printMessage("Saving %d DTI gradients. GE Reorienting %s : please validate. isCol=%d sliceDir=%d flp=%d %d %d\n", d->CSA.numDti, d->protocolName, col, sliceDir, flp.v[0], flp.v[1], flp.v[2]); if (!col) printWarning("Reorienting for ROW phase-encoding untested.\n"); } bool scaledBValWarning = false; - for (int i = 0; i < d->CSA.numDti; i++) { - float vLen = sqrt( (vx[i].V[1]*vx[i].V[1]) - + (vx[i].V[2]*vx[i].V[2]) - + (vx[i].V[3]*vx[i].V[3])); - if ((vx[i].V[0] <= FLT_EPSILON)|| (vLen <= FLT_EPSILON) ) { //bvalue=0 - for (int v= 1; v < 4; v++) - vx[i].V[v] = 0.0f; - continue; //do not normalize or reorient 0 vectors - } - if ((vLen > 0.03) && (vLen < 0.97)) { - //bVal scaled by norm(g)^2 issue163,245 - float bValtemp = 0, bVal = 0, bVecScale=0; - // rounding by 5 with mimimum of 5 if b-value > 0 - bValtemp = vx[i].V[0] * (vLen * vLen); - if (bValtemp > 0 && bValtemp < 5) { - bVal = 5; - } - else { - bVal = (int)((bValtemp + 2.5f)/5)*5; - } - if(bVal == 0) - bVecScale = 0; - else - { - bVecScale = sqrt((float)vx[i].V[0]/bVal); - } - if (!scaledBValWarning) { - printMessage("GE BVal scaling (e.g. %g -> %g s/mm^2)\n", vx[i].V[0], bVal); - scaledBValWarning = true; - } - vx[i].V[0] = bVal; - vx[i].V[1] = vx[i].V[1]*bVecScale; - vx[i].V[2] = vx[i].V[2]*bVecScale; - vx[i].V[3] = vx[i].V[3]*bVecScale; - } - if (!col) { //rows need to be swizzled - //see Stanford dataset Ax_DWI_Tetrahedral_7 unable to resolve between possible solutions - // http://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging - float swap = vx[i].V[1]; - vx[i].V[1] = vx[i].V[2]; - vx[i].V[2] = swap; - vx[i].V[2] = -vx[i].V[2]; //because of transpose? - } + for (int i = 0; i < d->CSA.numDti; i++) { + float vLen = sqrt((vx[i].V[1] * vx[i].V[1]) + (vx[i].V[2] * vx[i].V[2]) + (vx[i].V[3] * vx[i].V[3])); + if ((vx[i].V[0] <= FLT_EPSILON) || (vLen <= FLT_EPSILON)) { //bvalue=0 + for (int v = 1; v < 4; v++) + vx[i].V[v] = 0.0f; + continue; //do not normalize or reorient 0 vectors + } + if ((vLen > 0.03) && (vLen < 0.97)) { + //bVal scaled by norm(g)^2 issue163,245 + float bValtemp = 0, bVal = 0, bVecScale = 0; + // rounding by 5 with mimimum of 5 if b-value > 0 + bValtemp = vx[i].V[0] * (vLen * vLen); + if (bValtemp > 0 && bValtemp < 5) { + bVal = 5; + } else { + bVal = (int)((bValtemp + 2.5f) / 5) * 5; + } + if (bVal == 0) + bVecScale = 0; + else { + bVecScale = sqrt((float)vx[i].V[0] / bVal); + } + if (!scaledBValWarning) { + printMessage("GE BVal scaling (e.g. %g -> %g s/mm^2)\n", vx[i].V[0], bVal); + scaledBValWarning = true; + } + vx[i].V[0] = bVal; + vx[i].V[1] = vx[i].V[1] * bVecScale; + vx[i].V[2] = vx[i].V[2] * bVecScale; + vx[i].V[3] = vx[i].V[3] * bVecScale; + } + if (!col) { //rows need to be swizzled + //see Stanford dataset Ax_DWI_Tetrahedral_7 unable to resolve between possible solutions + // http://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging + float swap = vx[i].V[1]; + vx[i].V[1] = vx[i].V[2]; + vx[i].V[2] = swap; + vx[i].V[2] = -vx[i].V[2]; //because of transpose? + } for (int v = 0; v < 3; v++) if (flp.v[v] == 1) - vx[i].V[v+1] = -vx[i].V[v+1]; + vx[i].V[v + 1] = -vx[i].V[v + 1]; vx[i].V[2] = -vx[i].V[2]; //we read out Y-direction opposite order as dicm2nii, see also opts.isFlipY - } - //These next lines are only so files appear identical to old versions of dcm2niix: - // dicm2nii and dcm2niix generate polar opposite gradient directions. - // this does not matter, since intensity is the normal of the gradient vector. - for (int i = 0; i < d->CSA.numDti; i++) - for (int v = 1; v < 4; v++) - vx[i].V[v] = -vx[i].V[v]; - //These next lines convert any "-0" values to "0" - for (int i = 0; i < d->CSA.numDti; i++) - for (int v = 1; v < 4; v++) - if (isSameFloat(vx[i].V[v],-0)) - vx[i].V[v] = 0.0f; -}// geCorrectBvecs() - -void siemensPhilipsCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx, int isVerbose){ - //see Matthew Robson's http://users.fmrib.ox.ac.uk/~robson/internal/Dicom2Nifti111.m - //convert DTI vectors from scanner coordinates to image frame of reference - //Uses 6 orient values from ImageOrientationPatient (0020,0037) - // requires PatientPosition 0018,5100 is HFS (head first supine) - if ((!d->isBVecWorldCoordinates) && (d->manufacturer != kMANUFACTURER_BRUKER) && (d->manufacturer != kMANUFACTURER_TOSHIBA) && (d->manufacturer != kMANUFACTURER_HITACHI) && (d->manufacturer != kMANUFACTURER_UIH) && (d->manufacturer != kMANUFACTURER_SIEMENS) && (d->manufacturer != kMANUFACTURER_PHILIPS)) return; - if (d->CSA.numDti < 1) return; + } + //These next lines are only so files appear identical to old versions of dcm2niix: + // dicm2nii and dcm2niix generate polar opposite gradient directions. + // this does not matter, since intensity is the normal of the gradient vector. + for (int i = 0; i < d->CSA.numDti; i++) + for (int v = 1; v < 4; v++) + vx[i].V[v] = -vx[i].V[v]; + //These next lines convert any "-0" values to "0" + for (int i = 0; i < d->CSA.numDti; i++) + for (int v = 1; v < 4; v++) + if (isSameFloat(vx[i].V[v], -0)) + vx[i].V[v] = 0.0f; +} // geCorrectBvecs() + +void siemensPhilipsCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx, int isVerbose) { + //see Matthew Robson's http://users.fmrib.ox.ac.uk/~robson/internal/Dicom2Nifti111.m + //convert DTI vectors from scanner coordinates to image frame of reference + //Uses 6 orient values from ImageOrientationPatient (0020,0037) + // requires PatientPosition 0018,5100 is HFS (head first supine) + if ((!d->isBVecWorldCoordinates) && (d->manufacturer != kMANUFACTURER_BRUKER) && (d->manufacturer != kMANUFACTURER_TOSHIBA) && (d->manufacturer != kMANUFACTURER_HITACHI) && (d->manufacturer != kMANUFACTURER_UIH) && (d->manufacturer != kMANUFACTURER_SIEMENS) && (d->manufacturer != kMANUFACTURER_PHILIPS)) + return; + if (d->CSA.numDti < 1) + return; if (d->manufacturer == kMANUFACTURER_UIH) { - for (int i = 0; i < d->CSA.numDti; i++) { - vx[i].V[2] = -vx[i].V[2]; - for (int v= 0; v < 4; v++) - if (vx[i].V[v] == -0.0f) vx[i].V[v] = 0.0f; //remove sign from values that are virtually zero + for (int i = 0; i < d->CSA.numDti; i++) { + vx[i].V[2] = -vx[i].V[2]; + for (int v = 0; v < 4; v++) + if (vx[i].V[v] == -0.0f) + vx[i].V[v] = 0.0f; //remove sign from values that are virtually zero } #ifndef USING_R - for (int i = 0; i < 3; i++) - printf("%g = %g %g %g\n", vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); + for (int i = 0; i < 3; i++) + printf("%g = %g %g %g\n", vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); #endif - return; - } //https://github.com/rordenlab/dcm2niix/issues/225 - if ((toupper(d->patientOrient[0])== 'H') && (toupper(d->patientOrient[1])== 'F') && (toupper(d->patientOrient[2])== 'S')) - ; //participant was head first supine - else { - printMessage("Check Siemens/Philips bvecs: expected Patient Position (0018,5100) to be 'HFS' not '%s'\n", d->patientOrient); - //return; //see https://github.com/rordenlab/dcm2niix/issues/238 - } - vec3 read_vector = setVec3(d->orient[1],d->orient[2],d->orient[3]); - vec3 phase_vector = setVec3(d->orient[4],d->orient[5],d->orient[6]); - vec3 slice_vector = crossProduct(read_vector ,phase_vector); - read_vector = nifti_vect33_norm(read_vector); - phase_vector = nifti_vect33_norm(phase_vector); - slice_vector = nifti_vect33_norm(slice_vector); - for (int i = 0; i < d->CSA.numDti; i++) { - float vLen = sqrt( (vx[i].V[1]*vx[i].V[1]) - + (vx[i].V[2]*vx[i].V[2]) - + (vx[i].V[3]*vx[i].V[3])); - if ((vx[i].V[0] <= FLT_EPSILON)|| (vLen <= FLT_EPSILON) ) { //bvalue=0 - if (vx[i].V[0] > 5.0) //Philip stores n.b. UIH B=1.25126 Vec=0,0,0 while Philips stored isotropic images - printWarning("Volume %d appears to be derived image ADC/Isotropic (non-zero b-value with zero vector length)\n", i); - continue; //do not normalize or reorient b0 vectors - }//if bvalue=0 - vec3 bvecs_old =setVec3(vx[i].V[1],vx[i].V[2],vx[i].V[3]); - vec3 bvecs_new =setVec3(dotProduct(bvecs_old,read_vector),dotProduct(bvecs_old,phase_vector),dotProduct(bvecs_old,slice_vector) ); - bvecs_new = nifti_vect33_norm(bvecs_new); - vx[i].V[1] = bvecs_new.v[0]; - vx[i].V[2] = -bvecs_new.v[1]; - vx[i].V[3] = bvecs_new.v[2]; - if (abs(sliceDir) == kSliceOrientMosaicNegativeDeterminant) vx[i].V[2] = -vx[i].V[2]; - for (int v= 0; v < 4; v++) - if (vx[i].V[v] == -0.0f) vx[i].V[v] = 0.0f; //remove sign from values that are virtually zero - } //for each direction - if (d->isVectorFromBMatrix) { - printWarning("Saving %d DTI gradients. Eddy users: B-matrix does not encode b-vector polarity (issue 265).\n", d->CSA.numDti); - } else if (abs(sliceDir) == kSliceOrientMosaicNegativeDeterminant) { - printWarning("Saving %d DTI gradients. Validate vectors (matrix had a negative determinant).\n", d->CSA.numDti); //perhaps Siemens sagittal - } else if (( d->sliceOrient == kSliceOrientTra) || (d->manufacturer != kMANUFACTURER_PHILIPS)) { - if (isVerbose) - printMessage("Saving %d DTI gradients. Validate vectors.\n", d->CSA.numDti); - } else if ( d->sliceOrient == kSliceOrientUnknown) - printWarning("Saving %d DTI gradients. Validate vectors (image slice orientation not reported, e.g. 2001,100B).\n", d->CSA.numDti); + return; + } //https://github.com/rordenlab/dcm2niix/issues/225 + if ((toupper(d->patientOrient[0]) == 'H') && (toupper(d->patientOrient[1]) == 'F') && (toupper(d->patientOrient[2]) == 'S')) + ; //participant was head first supine + else { + printMessage("Check Siemens/Philips bvecs: expected Patient Position (0018,5100) to be 'HFS' not '%s'\n", d->patientOrient); + //return; //see https://github.com/rordenlab/dcm2niix/issues/238 + } + vec3 read_vector = setVec3(d->orient[1], d->orient[2], d->orient[3]); + vec3 phase_vector = setVec3(d->orient[4], d->orient[5], d->orient[6]); + vec3 slice_vector = crossProduct(read_vector, phase_vector); + read_vector = nifti_vect33_norm(read_vector); + phase_vector = nifti_vect33_norm(phase_vector); + slice_vector = nifti_vect33_norm(slice_vector); + for (int i = 0; i < d->CSA.numDti; i++) { + float vLen = sqrt((vx[i].V[1] * vx[i].V[1]) + (vx[i].V[2] * vx[i].V[2]) + (vx[i].V[3] * vx[i].V[3])); + if ((vx[i].V[0] <= FLT_EPSILON) || (vLen <= FLT_EPSILON)) { //bvalue=0 + if (vx[i].V[0] > 5.0) //Philip stores n.b. UIH B=1.25126 Vec=0,0,0 while Philips stored isotropic images + printWarning("Volume %d appears to be derived image ADC/Isotropic (non-zero b-value with zero vector length)\n", i); + continue; //do not normalize or reorient b0 vectors + } //if bvalue=0 + vec3 bvecs_old = setVec3(vx[i].V[1], vx[i].V[2], vx[i].V[3]); + vec3 bvecs_new = setVec3(dotProduct(bvecs_old, read_vector), dotProduct(bvecs_old, phase_vector), dotProduct(bvecs_old, slice_vector)); + bvecs_new = nifti_vect33_norm(bvecs_new); + vx[i].V[1] = bvecs_new.v[0]; + vx[i].V[2] = -bvecs_new.v[1]; + vx[i].V[3] = bvecs_new.v[2]; + if (abs(sliceDir) == kSliceOrientMosaicNegativeDeterminant) + vx[i].V[2] = -vx[i].V[2]; + for (int v = 0; v < 4; v++) + if (vx[i].V[v] == -0.0f) + vx[i].V[v] = 0.0f; //remove sign from values that are virtually zero + } //for each direction + if (d->isVectorFromBMatrix) { + printWarning("Saving %d DTI gradients. Eddy users: B-matrix does not encode b-vector polarity (issue 265).\n", d->CSA.numDti); + } else if (abs(sliceDir) == kSliceOrientMosaicNegativeDeterminant) { + printWarning("Saving %d DTI gradients. Validate vectors (matrix had a negative determinant).\n", d->CSA.numDti); //perhaps Siemens sagittal + } else if ((d->sliceOrient == kSliceOrientTra) || (d->manufacturer != kMANUFACTURER_PHILIPS)) { + if (isVerbose) + printMessage("Saving %d DTI gradients. Validate vectors.\n", d->CSA.numDti); + } else if (d->sliceOrient == kSliceOrientUnknown) + printWarning("Saving %d DTI gradients. Validate vectors (image slice orientation not reported, e.g. 2001,100B).\n", d->CSA.numDti); if (d->manufacturer == kMANUFACTURER_BRUKER) printWarning("Bruker DTI support experimental (issue 265).\n"); -}// siemensPhilipsCorrectBvecs() +} // siemensPhilipsCorrectBvecs() bool isNanPosition(struct TDICOMdata d) { //in 2007 some Siemens RGB DICOMs did not include the PatientPosition 0020,0032 tag - if (isnan(d.patientPosition[1])) return true; - if (isnan(d.patientPosition[2])) return true; - if (isnan(d.patientPosition[3])) return true; - return false; -}// isNanPosition() - -bool isSamePosition(struct TDICOMdata d, struct TDICOMdata d2){ - if ( isNanPosition(d) || isNanPosition(d2)) return false; - if (!isSameFloat(d.patientPosition[1],d2.patientPosition[1])) return false; - if (!isSameFloat(d.patientPosition[2],d2.patientPosition[2])) return false; - if (!isSameFloat(d.patientPosition[3],d2.patientPosition[3])) return false; - return true; -}// isSamePosition() - -void nii_saveText(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, char * dcmname) { - if (!opts.isCreateText) return; + if (isnan(d.patientPosition[1])) + return true; + if (isnan(d.patientPosition[2])) + return true; + if (isnan(d.patientPosition[3])) + return true; + return false; +} // isNanPosition() + +bool isSamePosition(struct TDICOMdata d, struct TDICOMdata d2) { + if (isNanPosition(d) || isNanPosition(d2)) + return false; + if (!isSameFloat(d.patientPosition[1], d2.patientPosition[1])) + return false; + if (!isSameFloat(d.patientPosition[2], d2.patientPosition[2])) + return false; + if (!isSameFloat(d.patientPosition[3], d2.patientPosition[3])) + return false; + return true; +} // isSamePosition() + +void nii_saveText(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, char *dcmname) { + if (!opts.isCreateText) + return; char txtname[2048] = {""}; - strcpy (txtname,pathoutname); - 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%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.patientBirthDate, d.patientSex, d.patientAge, h->dim[1], h->dim[2], h->dim[3], h->dim[4], - d.coilCrc,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); -}// nii_saveText() + strcpy(txtname, pathoutname); + 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%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.patientBirthDate, d.patientSex, d.patientAge, h->dim[1], h->dim[2], h->dim[3], h->dim[4], + d.coilCrc, 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); +} // nii_saveText() #define myReadAsciiCsa #ifdef myReadAsciiCsa //read from the ASCII portion of the Siemens CSA series header -// this is not recommended: poorly documented -// it is better to stick to the binary portion of the Siemens CSA image header +// this is not recommended: poorly documented +// it is better to stick to the binary portion of the Siemens CSA image header -#if defined(_WIN64) || defined(_WIN32) || defined(__sun) || (defined(__APPLE__) && defined(__POWERPC__)) +#if defined(_WIN64) || defined(_WIN32) || defined(__sun) || (defined(__APPLE__) && defined(__POWERPC__)) //https://opensource.apple.com/source/Libc/Libc-1044.1.2/string/FreeBSD/memmem.c /*- * Copyright (c) 2005 Pascal Gloor @@ -417,7 +429,7 @@ void nii_saveText(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) @@ -426,7 +438,7 @@ void nii_saveText(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ -const void * memmem(const char *l, size_t l_len, const char *s, size_t s_len) { +const void *memmem(const char *l, size_t l_len, const char *s, size_t s_len) { char *cur, *last; const char *cl = (const char *)l; const char *cs = (const char *)s; @@ -449,88 +461,96 @@ const void * memmem(const char *l, size_t l_len, const char *s, size_t s_len) { //n.b. memchr returns "const void *" not "void *" for Windows C++ https://msdn.microsoft.com/en-us/library/d7zdhf37.aspx #endif //for systems without memmem -int readKeyN1(const char * key, char * buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value +int readKeyN1(const char *key, char *buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); - if (!keyPos) return -1; + if (!keyPos) + return -1; int ret = 0; int i = (int)strlen(key); - while( ( i< remLength) && (keyPos[i] != 0x0A) ) { - if( keyPos[i] >= '0' && keyPos[i] <= '9' ) + while ((i < remLength) && (keyPos[i] != 0x0A)) { + if (keyPos[i] >= '0' && keyPos[i] <= '9') ret = (10 * ret) + keyPos[i] - '0'; i++; } return ret; } //readKeyN1() //return -1 if key not found -int readKey(const char * key, char * buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value +int readKey(const char *key, char *buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value int ret = 0; char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); - if (!keyPos) return ret; + if (!keyPos) + return ret; int i = (int)strlen(key); - while( ( i< remLength) && (keyPos[i] != 0x0A) ) { - if( keyPos[i] >= '0' && keyPos[i] <= '9' ) + while ((i < remLength) && (keyPos[i] != 0x0A)) { + if (keyPos[i] >= '0' && keyPos[i] <= '9') ret = (10 * ret) + keyPos[i] - '0'; i++; } return ret; } //readKey() //return 0 if key not found -float readKeyFloatNan(const char * key, char * buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value +float readKeyFloatNan(const char *key, char *buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); - if (!keyPos) return NAN; + if (!keyPos) + return NAN; char str[kDICOMStr]; strcpy(str, ""); char tmpstr[2]; - tmpstr[1] = 0; + tmpstr[1] = 0; int i = (int)strlen(key); - while( ( i< remLength) && (keyPos[i] != 0x0A) ) { - if( (keyPos[i] >= '0' && keyPos[i] <= '9') || (keyPos[i] == '.') || (keyPos[i] == '-') ) { + while ((i < remLength) && (keyPos[i] != 0x0A)) { + if ((keyPos[i] >= '0' && keyPos[i] <= '9') || (keyPos[i] == '.') || (keyPos[i] == '-')) { tmpstr[0] = keyPos[i]; - strcat (str, tmpstr); + strcat(str, tmpstr); } i++; } - if (strlen(str) < 1) return NAN; + if (strlen(str) < 1) + return NAN; return atof(str); } //readKeyFloatNan() -float readKeyFloat(const char * key, char * buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value +float readKeyFloat(const char *key, char *buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); - if (!keyPos) return 0.0; + if (!keyPos) + return 0.0; char str[kDICOMStr]; strcpy(str, ""); char tmpstr[2]; - tmpstr[1] = 0; + tmpstr[1] = 0; int i = (int)strlen(key); - while( ( i< remLength) && (keyPos[i] != 0x0A) ) { - if( (keyPos[i] >= '0' && keyPos[i] <= '9') || (keyPos[i] == '.') || (keyPos[i] == '-') ) { + while ((i < remLength) && (keyPos[i] != 0x0A)) { + if ((keyPos[i] >= '0' && keyPos[i] <= '9') || (keyPos[i] == '.') || (keyPos[i] == '-')) { tmpstr[0] = keyPos[i]; - strcat (str, tmpstr); + strcat(str, tmpstr); } i++; } - if (strlen(str) < 1) return 0.0; + if (strlen(str) < 1) + return 0.0; return atof(str); } //readKeyFloat() -void readKeyStr(const char * key, char * buffer, int remLength, char* outStr) { -//if key is CoilElementID.tCoilID the string 'CoilElementID.tCoilID = ""Head_32""' returns 'Head32' +void readKeyStr(const char *key, char *buffer, int remLength, char *outStr) { + //if key is CoilElementID.tCoilID the string 'CoilElementID.tCoilID = ""Head_32""' returns 'Head32' strcpy(outStr, ""); char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); - if (!keyPos) return; + if (!keyPos) + return; int i = (int)strlen(key); int outLen = 0; char tmpstr[2]; - tmpstr[1] = 0; - bool isQuote = false; - while( ( i < remLength) && (keyPos[i] != 0x0A) ) { + tmpstr[1] = 0; + bool isQuote = false; + while ((i < remLength) && (keyPos[i] != 0x0A)) { if ((isQuote) && (keyPos[i] != '"') && (outLen < kDICOMStrLarge)) { tmpstr[0] = keyPos[i]; - strcat (outStr, tmpstr); - outLen ++; + strcat(outStr, tmpstr); + outLen++; } if (keyPos[i] == '"') { - if (outLen > 0) break; + if (outLen > 0) + break; isQuote = true; } i++; @@ -538,113 +558,121 @@ void readKeyStr(const char * key, char * buffer, int remLength, char* outStr) { } //readKeyStr() int phoenixOffsetCSASeriesHeader(unsigned char *buff, int lLength) { - //returns offset to ASCII Phoenix data - if (lLength < 36) return 0; - if ((buff[0] != 'S') || (buff[1] != 'V') || (buff[2] != '1') || (buff[3] != '0') ) return EXIT_FAILURE; - int lPos = 8; //skip 8 bytes of data, 'SV10' plus 2 32-bit values unused1 and unused2 - int lnTag = buff[lPos]+(buff[lPos+1]<<8)+(buff[lPos+2]<<16)+(buff[lPos+3]<<24); - if ((buff[lPos+4] != 77) || (lnTag < 1)) return 0; - lPos += 8; //skip 8 bytes of data, 32-bit lnTag plus 77 00 00 0 - TCSAtag tagCSA; - TCSAitem itemCSA; - for (int lT = 1; lT <= lnTag; lT++) { - memcpy(&tagCSA, &buff[lPos], sizeof(tagCSA)); //read tag - if (!littleEndianPlatform()) - nifti_swap_4bytes(1, &tagCSA.nitems); - //printf("%d CSA of %s %d\n",lPos, tagCSA.name, tagCSA.nitems); - lPos +=sizeof(tagCSA); - if (strcmp(tagCSA.name, "MrPhoenixProtocol") == 0) - return lPos; - for (int lI = 1; lI <= tagCSA.nitems; lI++) { - memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); - lPos +=sizeof(itemCSA); - if (!littleEndianPlatform()) - nifti_swap_4bytes(1, &itemCSA.xx2_Len); - lPos += ((itemCSA.xx2_Len +3)/4)*4; - } - } - return 0; -} // phoeechoSpacingnixOffsetCSASeriesHeader() + //returns offset to ASCII Phoenix data + if (lLength < 36) + return 0; + if ((buff[0] != 'S') || (buff[1] != 'V') || (buff[2] != '1') || (buff[3] != '0')) + return EXIT_FAILURE; + int lPos = 8; //skip 8 bytes of data, 'SV10' plus 2 32-bit values unused1 and unused2 + int lnTag = buff[lPos] + (buff[lPos + 1] << 8) + (buff[lPos + 2] << 16) + (buff[lPos + 3] << 24); + if ((buff[lPos + 4] != 77) || (lnTag < 1)) + return 0; + lPos += 8; //skip 8 bytes of data, 32-bit lnTag plus 77 00 00 0 + TCSAtag tagCSA; + TCSAitem itemCSA; + for (int lT = 1; lT <= lnTag; lT++) { + memcpy(&tagCSA, &buff[lPos], sizeof(tagCSA)); //read tag + if (!littleEndianPlatform()) + nifti_swap_4bytes(1, &tagCSA.nitems); + //printf("%d CSA of %s %d\n",lPos, tagCSA.name, tagCSA.nitems); + lPos += sizeof(tagCSA); + if (strcmp(tagCSA.name, "MrPhoenixProtocol") == 0) + return lPos; + for (int lI = 1; lI <= tagCSA.nitems; lI++) { + memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); + lPos += sizeof(itemCSA); + if (!littleEndianPlatform()) + nifti_swap_4bytes(1, &itemCSA.xx2_Len); + lPos += ((itemCSA.xx2_Len + 3) / 4) * 4; + } + } + return 0; +} //phoenixOffsetCSASeriesHeader() #define kMaxWipFree 64 typedef struct { float TE0, TE1, delayTimeInTR, phaseOversampling, phaseResolution, txRefAmp; - int phaseEncodingLines, existUcImageNumb, ucMode, baseResolution, interp, partialFourier,echoSpacing, - difBipolar, parallelReductionFactorInPlane, refLinesPE, combineMode, patMode, ucMTC; - float alFree[kMaxWipFree] ; - float adFree[kMaxWipFree]; - float alTI[kMaxWipFree]; - float dThickness, ulShape, sPositionDTra, sNormalDTra; + int phaseEncodingLines, existUcImageNumb, ucMode, baseResolution, interp, partialFourier, echoSpacing, + difBipolar, parallelReductionFactorInPlane, refLinesPE, combineMode, patMode, ucMTC, accelFact3D; + float alFree[kMaxWipFree]; + float adFree[kMaxWipFree]; + float alTI[kMaxWipFree]; + float dAveragesDouble, dThickness, ulShape, sPositionDTra, sNormalDTra; } TCsaAscii; -void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, int csaLength, float* shimSetting, char* coilID, char* consistencyInfo, char* coilElements, char* pulseSequenceDetails, char* fmriExternalInfo, char* protocolName, char* wipMemBlock) { - //reads ASCII portion of CSASeriesHeaderInfo and returns lEchoTrainDuration or lEchoSpacing value - // returns 0 if no value found - csaAscii->TE0 = 0.0; - csaAscii->TE1 = 0.0; - csaAscii->delayTimeInTR = -0.001; - csaAscii->phaseOversampling = 0.0; - csaAscii->phaseResolution = 0.0; - csaAscii->txRefAmp = 0.0; - csaAscii->phaseEncodingLines = 0; - csaAscii->existUcImageNumb = 0; - csaAscii->ucMode = -1; - csaAscii->baseResolution = 0; - csaAscii->interp = 0; - csaAscii->partialFourier = 0; - csaAscii->echoSpacing = 0; - csaAscii->difBipolar = 0; //0=not assigned,1=bipolar,2=monopolar - csaAscii->parallelReductionFactorInPlane = 0; - csaAscii->refLinesPE = 0; - csaAscii->combineMode = 0; - csaAscii->patMode = 0; - csaAscii->ucMTC = 0; - for (int i = 0; i < 8; i++) - shimSetting[i] = 0.0; - strcpy(coilID, ""); - strcpy(consistencyInfo, ""); - strcpy(coilElements, ""); - strcpy(pulseSequenceDetails, ""); - strcpy(fmriExternalInfo, ""); - strcpy(wipMemBlock, ""); - strcpy(protocolName, ""); - if ((csaOffset < 0) || (csaLength < 8)) return; - FILE * pFile = fopen ( filename, "rb" ); - if(pFile==NULL) return; - fseek (pFile , 0 , SEEK_END); - long lSize = ftell (pFile); - if (lSize < (csaOffset+csaLength)) { - fclose (pFile); +void siemensCsaAscii(const char *filename, TCsaAscii *csaAscii, int csaOffset, int csaLength, float *shimSetting, char *coilID, char *consistencyInfo, char *coilElements, char *pulseSequenceDetails, char *fmriExternalInfo, char *protocolName, char *wipMemBlock) { + //reads ASCII portion of CSASeriesHeaderInfo and returns lEchoTrainDuration or lEchoSpacing value + // returns 0 if no value found + csaAscii->TE0 = 0.0; + csaAscii->TE1 = 0.0; + csaAscii->delayTimeInTR = -0.001; + csaAscii->phaseOversampling = 0.0; + csaAscii->phaseResolution = 0.0; + csaAscii->txRefAmp = 0.0; + csaAscii->phaseEncodingLines = 0; + csaAscii->existUcImageNumb = 0; + csaAscii->ucMode = -1; + csaAscii->baseResolution = 0; + csaAscii->interp = 0; + csaAscii->partialFourier = 0; + csaAscii->echoSpacing = 0; + csaAscii->difBipolar = 0; //0=not assigned,1=bipolar,2=monopolar + csaAscii->parallelReductionFactorInPlane = 0; + csaAscii->accelFact3D = 0;//lAccelFact3D + csaAscii->refLinesPE = 0; + csaAscii->combineMode = 0; + csaAscii->patMode = 0; + csaAscii->ucMTC = 0; + for (int i = 0; i < 8; i++) + shimSetting[i] = 0.0; + strcpy(coilID, ""); + strcpy(consistencyInfo, ""); + strcpy(coilElements, ""); + strcpy(pulseSequenceDetails, ""); + strcpy(fmriExternalInfo, ""); + strcpy(wipMemBlock, ""); + strcpy(protocolName, ""); + if ((csaOffset < 0) || (csaLength < 8)) + return; + FILE *pFile = fopen(filename, "rb"); + if (pFile == NULL) + return; + fseek(pFile, 0, SEEK_END); + long lSize = ftell(pFile); + if (lSize < (csaOffset + csaLength)) { + fclose(pFile); return; } fseek(pFile, csaOffset, SEEK_SET); - char * buffer = (char*) malloc (csaLength); - if(buffer == NULL) return; - size_t result = fread (buffer,1,csaLength,pFile); - if ((int)result != csaLength) return; + char *buffer = (char *)malloc(csaLength); + if (buffer == NULL) + return; + size_t result = fread(buffer, 1, csaLength, pFile); + if ((int)result != csaLength) + return; //next bit complicated: restrict to ASCII portion to avoid buffer overflow errors in BINARY portion int startAscii = phoenixOffsetCSASeriesHeader((unsigned char *)buffer, csaLength); int csaLengthTrim = csaLength; - char * bufferTrim = buffer; - if ((startAscii > 0) && (startAscii < csaLengthTrim) ) { //ignore binary data at start + char *bufferTrim = buffer; + if ((startAscii > 0) && (startAscii < csaLengthTrim)) { //ignore binary data at start bufferTrim += startAscii; csaLengthTrim -= startAscii; } char keyStr[] = "### ASCCONV BEGIN"; //skip to start of ASCII often "### ASCCONV BEGIN ###" but also "### ASCCONV BEGIN object=MrProtDataImpl@MrProtocolData" char *keyPos = (char *)memmem(bufferTrim, csaLengthTrim, keyStr, strlen(keyStr)); if (keyPos) { - //We could detect multi-echo MPRAGE here, e.g. "lContrasts = 4"- but ideally we want an earlier detection - csaLengthTrim -= (keyPos-bufferTrim); - //FmriExternalInfo listed AFTER AscConvEnd and uses different delimiter || - // char keyStrExt[] = "FmriExternalInfo"; - // readKeyStr(keyStrExt, keyPos, csaLengthTrim, fmriExternalInfo); - #define myCropAtAscConvEnd - #ifdef myCropAtAscConvEnd + //We could detect multi-echo MPRAGE here, e.g. "lContrasts = 4"- but ideally we want an earlier detection + csaLengthTrim -= (keyPos - bufferTrim); +//FmriExternalInfo listed AFTER AscConvEnd and uses different delimiter || +// char keyStrExt[] = "FmriExternalInfo"; +// readKeyStr(keyStrExt, keyPos, csaLengthTrim, fmriExternalInfo); +#define myCropAtAscConvEnd +#ifdef myCropAtAscConvEnd char keyStrEnd[] = "### ASCCONV END"; char *keyPosEnd = (char *)memmem(keyPos, csaLengthTrim, keyStrEnd, strlen(keyStrEnd)); if ((keyPosEnd) && ((keyPosEnd - keyPos) < csaLengthTrim)) //ignore binary data at end csaLengthTrim = (int)(keyPosEnd - keyPos); - #endif +#endif char keyStrLns[] = "sKSpace.lPhaseEncodingLines"; csaAscii->phaseEncodingLines = readKey(keyStrLns, keyPos, csaLengthTrim); char keyStrUcImg[] = "sSliceArray.ucImageNumb"; @@ -658,7 +686,7 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, char keyStrPF[] = "sKSpace.ucPhasePartialFourier"; csaAscii->partialFourier = readKey(keyStrPF, keyPos, csaLengthTrim); char keyStrES[] = "sFastImaging.lEchoSpacing"; - csaAscii->echoSpacing = readKey(keyStrES, keyPos, csaLengthTrim); + csaAscii->echoSpacing = readKey(keyStrES, keyPos, csaLengthTrim); char keyStrDS[] = "sDiffusion.dsScheme"; csaAscii->difBipolar = readKey(keyStrDS, keyPos, csaLengthTrim); if (csaAscii->difBipolar == 0) { @@ -670,6 +698,8 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, } char keyStrAF[] = "sPat.lAccelFactPE"; csaAscii->parallelReductionFactorInPlane = readKey(keyStrAF, keyPos, csaLengthTrim); + char keyStrAF3D[] = "sPat.lAccelFact3D"; + csaAscii->accelFact3D = readKey(keyStrAF3D, keyPos, csaLengthTrim); char keyStrRef[] = "sPat.lRefLinesPE"; csaAscii->refLinesPE = readKey(keyStrRef, keyPos, csaLengthTrim); char keyStrCombineMode[] = "ucCoilCombineMode"; @@ -686,17 +716,17 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, //char keyStrEF[] = "sFastImaging.lEPIFactor"; //ret = readKey(keyStrEF, keyPos, csaLengthTrim); char keyStrCoil[] = "sCoilElementID.tCoilID"; - readKeyStr(keyStrCoil, keyPos, csaLengthTrim, coilID); + readKeyStr(keyStrCoil, keyPos, csaLengthTrim, coilID); char keyStrCI[] = "sProtConsistencyInfo.tMeasuredBaselineString"; - readKeyStr(keyStrCI, keyPos, csaLengthTrim, consistencyInfo); + readKeyStr(keyStrCI, keyPos, csaLengthTrim, consistencyInfo); char keyStrCS[] = "sCoilSelectMeas.sCoilStringForConversion"; - readKeyStr(keyStrCS, keyPos, csaLengthTrim, coilElements); + readKeyStr(keyStrCS, keyPos, csaLengthTrim, coilElements); char keyStrSeq[] = "tSequenceFileName"; - readKeyStr(keyStrSeq, keyPos, csaLengthTrim, pulseSequenceDetails); + readKeyStr(keyStrSeq, keyPos, csaLengthTrim, pulseSequenceDetails); char keyStrWipMemBlock[] = "sWipMemBlock.tFree"; - readKeyStr(keyStrWipMemBlock, keyPos, csaLengthTrim, wipMemBlock); + readKeyStr(keyStrWipMemBlock, keyPos, csaLengthTrim, wipMemBlock); char keyStrPn[] = "tProtocolName"; - readKeyStr(keyStrPn, keyPos, csaLengthTrim, protocolName); + readKeyStr(keyStrPn, keyPos, csaLengthTrim, protocolName); char keyStrTE0[] = "alTE[0]"; csaAscii->TE0 = readKeyFloatNan(keyStrTE0, keyPos, csaLengthTrim); char keyStrTE1[] = "alTE[1]"; @@ -710,7 +740,7 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, if (keyPosTi) { for (int k = 0; k < kMaxWipFree; k++) { char txt[1024] = {""}; - sprintf(txt, "%s%d]", keyStrTiFree,k); + sprintf(txt, "%s%d]", keyStrTiFree, k); csaAscii->alTI[k] = readKeyFloatNan(txt, keyPos, csaLengthTrim); } } @@ -723,7 +753,7 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, if (keyPosFree) { for (int k = 0; k < kMaxWipFree; k++) { char txt[1024] = {""}; - sprintf(txt, "%s%d]", keyStrAlFree,k); + sprintf(txt, "%s%d]", keyStrAlFree, k); csaAscii->alFree[k] = readKeyFloat(txt, keyPos, csaLengthTrim); } } @@ -742,7 +772,7 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, if (keyPosFree) { for (int k = 0; k < kMaxWipFree; k++) { char txt[1024] = {""}; - sprintf(txt, "%s%d]", keyStrAdFree,k); + sprintf(txt, "%s%d]", keyStrAdFree, k); csaAscii->adFree[k] = readKeyFloatNan(txt, keyPos, csaLengthTrim); } } @@ -757,6 +787,9 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, char keyStrSNormalDTra[] = "sRSatArray.asElm[1].sNormal.dTra"; csaAscii->sNormalDTra = readKeyFloat(keyStrSNormalDTra, keyPos, csaLengthTrim); } + //Read NEX number of averages + char keyStrDAveragesDouble[] = "dAveragesDouble"; + csaAscii->dAveragesDouble = readKeyFloat(keyStrDAveragesDouble, keyPos, csaLengthTrim); //read delay time char keyStrDelay[] = "lDelayTimeInTR"; csaAscii->delayTimeInTR = readKeyFloat(keyStrDelay, keyPos, csaLengthTrim); @@ -775,11 +808,14 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, 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); + 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); + 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); + 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); @@ -792,119 +828,127 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, char keyStrSh7[] = "sGRADSPEC.alShimCurrent[4]"; shimSetting[7] = readKeyFloat(keyStrSh7, keyPos, csaLengthTrim); } - fclose (pFile); - free (buffer); + fclose(pFile); + free(buffer); return; } // siemensCsaAscii() #endif //myReadAsciiCsa() #ifndef myDisableZLib - //Uncomment next line to decode GE Protocol Data Block, for caveats see https://github.com/rordenlab/dcm2niix/issues/163 - #define myReadGeProtocolBlock +//Uncomment next line to decode GE Protocol Data Block, for caveats see https://github.com/rordenlab/dcm2niix/issues/163 +#define myReadGeProtocolBlock #endif #ifdef myReadGeProtocolBlock -int geProtocolBlock(const char * filename, int geOffset, int geLength, int isVerbose, int* sliceOrder, int* viewOrder, int* mbAccel, int* nSlices, float* groupDelay, char ioptGE[]) { +int geProtocolBlock(const char *filename, int geOffset, int geLength, int isVerbose, int *sliceOrder, int *viewOrder, int *mbAccel, int *nSlices, float *groupDelay, char ioptGE[]) { *sliceOrder = -1; *viewOrder = 0; *mbAccel = 0; *nSlices = 0; *groupDelay = 0.0; int ret = EXIT_FAILURE; - if ((geOffset < 0) || (geLength < 20)) return ret; - FILE * pFile = fopen ( filename, "rb" ); - if(pFile==NULL) return ret; - fseek (pFile , 0 , SEEK_END); - long lSize = ftell (pFile); - if (lSize < (geOffset+geLength)) { - fclose (pFile); + if ((geOffset < 0) || (geLength < 20)) + return ret; + FILE *pFile = fopen(filename, "rb"); + if (pFile == NULL) + return ret; + fseek(pFile, 0, SEEK_END); + long lSize = ftell(pFile); + if (lSize < (geOffset + geLength)) { + fclose(pFile); return ret; } fseek(pFile, geOffset, SEEK_SET); - uint8_t * pCmp = (uint8_t*) malloc (geLength); //uint8_t -> mz_uint8 - if(pCmp == NULL) return ret; - size_t result = fread (pCmp,1,geLength,pFile); - if ((int)result != geLength) return ret; + uint8_t *pCmp = (uint8_t *)malloc(geLength); //uint8_t -> mz_uint8 + if (pCmp == NULL) + return ret; + size_t result = fread(pCmp, 1, geLength, pFile); + if ((int)result != geLength) + return ret; int cmpSz = geLength; //http://www.forensicswiki.org/wiki/Gzip // always little endia! http://www.onicos.com/staff/iz/formats/gzip.html - if (cmpSz < 20) return ret; - if ((pCmp[0] != 31) || (pCmp[1] != 139) || (pCmp[2] != 8)) return ret; //check signature and deflate algorithm - uint8_t flags = pCmp[3]; + if (cmpSz < 20) + return ret; + if ((pCmp[0] != 31) || (pCmp[1] != 139) || (pCmp[2] != 8)) + return ret; //check signature and deflate algorithm + uint8_t flags = pCmp[3]; bool isFNAME = ((flags & 0x08) == 0x08); bool isFCOMMENT = ((flags & 0x10) == 0x10); uint32_t hdrSz = 10; - if (isFNAME) {//skip null-terminated string FNAME + if (isFNAME) { //skip null-terminated string FNAME for (; hdrSz < cmpSz; hdrSz++) - if (pCmp[hdrSz] == 0) break; + if (pCmp[hdrSz] == 0) + break; hdrSz++; } - if (isFCOMMENT) {//skip null-terminated string COMMENT + if (isFCOMMENT) { //skip null-terminated string COMMENT for (; hdrSz < cmpSz; hdrSz++) - if (pCmp[hdrSz] == 0) break; + if (pCmp[hdrSz] == 0) + break; hdrSz++; } - uint32_t unCmpSz = ((uint32_t)pCmp[cmpSz-4])+((uint32_t)pCmp[cmpSz-3] << 8)+((uint32_t)pCmp[cmpSz-2] << 16)+((uint32_t)pCmp[cmpSz-1] << 24); + uint32_t unCmpSz = ((uint32_t)pCmp[cmpSz - 4]) + ((uint32_t)pCmp[cmpSz - 3] << 8) + ((uint32_t)pCmp[cmpSz - 2] << 16) + ((uint32_t)pCmp[cmpSz - 1] << 24); //printf(">> %d %d %zu %zu %zu\n", isFNAME, isFCOMMENT, cmpSz, unCmpSz, hdrSz); z_stream s; - memset (&s, 0, sizeof (z_stream)); - #ifdef myDisableMiniZ - #define MZ_DEFAULT_WINDOW_BITS 15 // Window bits - #endif + memset(&s, 0, sizeof(z_stream)); +#ifdef myDisableMiniZ +#define MZ_DEFAULT_WINDOW_BITS 15 // Window bits +#endif inflateInit2(&s, -MZ_DEFAULT_WINDOW_BITS); uint8_t *pUnCmp = (uint8_t *)malloc((size_t)unCmpSz); s.avail_out = unCmpSz; - s.next_in = pCmp+ hdrSz; - s.avail_in = cmpSz-hdrSz-8; - s.next_out = (uint8_t *) pUnCmp; - #ifdef myDisableMiniZ + s.next_in = pCmp + hdrSz; + s.avail_in = cmpSz - hdrSz - 8; + s.next_out = (uint8_t *)pUnCmp; +#ifdef myDisableMiniZ ret = inflate(&s, Z_SYNC_FLUSH); if (ret != Z_STREAM_END) { free(pUnCmp); return EXIT_FAILURE; } - #else +#else ret = mz_inflate(&s, MZ_SYNC_FLUSH); if (ret != MZ_STREAM_END) { free(pUnCmp); return EXIT_FAILURE; } - #endif +#endif //https://groups.google.com/forum/#!msg/comp.protocols.dicom/mxnCkv8A-i4/W_uc6SxLwHQJ // DISCOVERY MR750 / 24\MX\MR Software release:DV24.0_R01_1344.a) are now storing an XML file - // - if ((pUnCmp[0] == '<') && (pUnCmp[1] == '?')) + // + if ((pUnCmp[0] == '<') && (pUnCmp[1] == '?')) printWarning("New XML-based GE Protocol Block is not yet supported: please report issue on dcm2niix Github page\n"); char keyStrSO[] = "SLICEORDER"; - *sliceOrder = readKeyN1(keyStrSO, (char *) pUnCmp, unCmpSz); - char keyStrVO[] = "VIEWORDER"; - *viewOrder = readKey(keyStrVO, (char *) pUnCmp, unCmpSz); + *sliceOrder = readKeyN1(keyStrSO, (char *)pUnCmp, unCmpSz); + char keyStrVO[] = "VIEWORDER"; + *viewOrder = readKey(keyStrVO, (char *)pUnCmp, unCmpSz); char keyStrMB[] = "MBACCEL"; - *mbAccel = readKey(keyStrMB, (char *) pUnCmp, unCmpSz); + *mbAccel = readKey(keyStrMB, (char *)pUnCmp, unCmpSz); char keyStrNS[] = "NOSLC"; - *nSlices = readKey(keyStrNS, (char *) pUnCmp, unCmpSz); - char keyStrDELACQ[] = "DELACQ"; - char DELACQ[100]; - readKeyStr(keyStrDELACQ, (char *) pUnCmp, unCmpSz, DELACQ); - char keyStrGD[] = "DELACQNOAV"; - *groupDelay = readKeyFloat(keyStrGD, (char *) pUnCmp, unCmpSz); - char keyStrIOPT[] = "IOPT"; - readKeyStr(keyStrIOPT, (char *) pUnCmp, unCmpSz, ioptGE); - char PHASEDELAYS1[10000]; - char keyStrPHASEDELAYS1[] = "PHASEDELAYS1"; - readKeyStr(keyStrPHASEDELAYS1, (char *) pUnCmp, unCmpSz, PHASEDELAYS1); - if (strstr(ioptGE,"MPh") != NULL) { - if (strcmp(DELACQ, "Minimum") == 0) { - *groupDelay = 0; - } - if (strstr(ioptGE,"MPhVar") != NULL) { - *groupDelay = -1; - // Multiphase EPI with Variable Delays - // TO-DO - // NEED TO rescue ALL_PHASES case (=Group delay) - // IF values in PHASEDELAYS1 are all same except 1st value (0), this case should be same as Group Delay - } - } + *nSlices = readKey(keyStrNS, (char *)pUnCmp, unCmpSz); + char keyStrDELACQ[] = "DELACQ"; + char DELACQ[100]; + readKeyStr(keyStrDELACQ, (char *)pUnCmp, unCmpSz, DELACQ); + char keyStrGD[] = "DELACQNOAV"; + *groupDelay = readKeyFloat(keyStrGD, (char *)pUnCmp, unCmpSz); + char keyStrIOPT[] = "IOPT"; + readKeyStr(keyStrIOPT, (char *)pUnCmp, unCmpSz, ioptGE); + char PHASEDELAYS1[10000]; + char keyStrPHASEDELAYS1[] = "PHASEDELAYS1"; + readKeyStr(keyStrPHASEDELAYS1, (char *)pUnCmp, unCmpSz, PHASEDELAYS1); + if (strstr(ioptGE, "MPh") != NULL) { + if (strcmp(DELACQ, "Minimum") == 0) { + *groupDelay = 0; + } + if (strstr(ioptGE, "MPhVar") != NULL) { + *groupDelay = -1; + // Multiphase EPI with Variable Delays + // TO-DO + // NEED TO rescue ALL_PHASES case (=Group delay) + // IF values in PHASEDELAYS1 are all same except 1st value (0), this case should be same as Group Delay + } + } if (isVerbose > 1) { printMessage("GE Protocol Block %s bytes %d compressed, %d uncompressed @ %d\n", filename, geLength, unCmpSz, geOffset); printMessage(" ViewOrder %d SliceOrder %d\n", *viewOrder, *sliceOrder); @@ -916,17 +960,19 @@ int geProtocolBlock(const char * filename, int geOffset, int geLength, int isV #endif //myReadGeProtocolBlock() void json_Str(FILE *fp, const char *sLabel, char *sVal) { // issue131,425 - if (strlen(sVal) < 1) return; + if (strlen(sVal) < 1) + return; unsigned char sValEsc[2048] = {""}; - unsigned char *iVal = (unsigned char *) sVal; + unsigned char *iVal = (unsigned char *)sVal; int o = 0; - for (int i = 0; i < strlen(sVal); i ++) { + for (int i = 0; i < strlen(sVal); i++) { //escape double quote (") and Backslash if ((sVal[i] == '"') || (sVal[i] == '\\')) { //escape double quotes and back slash - sValEsc[o] = '\\'; - o++; + sValEsc[o] = '\\'; + o++; } - if ((sVal[i] >= 0x01) && (sVal[i] <= 0x07)) continue; //control characters like "bell" + if ((sVal[i] >= 0x01) && (sVal[i] <= 0x07)) + continue; //control characters like "bell" //http://dicom.nema.org/medical/dicom/current/output/html/part05.html //0x08 Backspace is replaced with \b //0x09 Tab is replaced with \t @@ -935,38 +981,46 @@ void json_Str(FILE *fp, const char *sLabel, char *sVal) { // issue131,425 //0x0C Form feed is replaced with \f //0x0D Carriage return is replaced with \r if ((sVal[i] >= 0x08) && (sVal[i] <= 0x0D)) { - sValEsc[o] = '\\'; - o++; - if (sVal[i] == 0x08) sValEsc[o] = 'b'; - if (sVal[i] == 0x09) sValEsc[o] = '9'; - if (sVal[i] == 0x0A) sValEsc[o] = 'n'; - if (sVal[i] == 0x0B) sValEsc[o] = '\\'; - if (sVal[i] == 0x0C) sValEsc[o] = 'f'; - if (sVal[i] == 0x0D) sValEsc[o] = 'r'; - o++; - continue; - } - //https://stackoverflow.com/questions/4059775/convert-iso-8859-1-strings-to-utf-8-in-c-c - if (iVal[i] >= 128) { - sValEsc[o]=0xc2+(iVal[i]>0xbf); - o++; - sValEsc[o]=(iVal[i]&0x3f)+0x80; - } else { - sValEsc[o] = sVal[i]; - } - o++; - } - sValEsc[o] = '\0'; - fprintf(fp, sLabel, sValEsc ); + sValEsc[o] = '\\'; + o++; + if (sVal[i] == 0x08) + sValEsc[o] = 'b'; + if (sVal[i] == 0x09) + sValEsc[o] = '9'; + if (sVal[i] == 0x0A) + sValEsc[o] = 'n'; + if (sVal[i] == 0x0B) + sValEsc[o] = '\\'; + if (sVal[i] == 0x0C) + sValEsc[o] = 'f'; + if (sVal[i] == 0x0D) + sValEsc[o] = 'r'; + o++; + continue; + } + //https://stackoverflow.com/questions/4059775/convert-iso-8859-1-strings-to-utf-8-in-c-c + if (iVal[i] >= 128) { + sValEsc[o] = 0xc2 + (iVal[i] > 0xbf); + o++; + sValEsc[o] = (iVal[i] & 0x3f) + 0x80; + } else { + sValEsc[o] = sVal[i]; + } + o++; + } + sValEsc[o] = '\0'; + fprintf(fp, sLabel, sValEsc); } //json_Str void json_FloatNotNan(FILE *fp, const char *sLabel, float sVal) { - if (isnan(sVal)) return; - fprintf(fp, sLabel, sVal ); + if (isnan(sVal)) + return; + fprintf(fp, sLabel, sVal); } //json_Float void print_FloatNotNan(const char *sLabel, int iVal, float sVal) { - if (isnan(sVal)) return; + if (isnan(sVal)) + return; printMessage(sLabel, iVal, sVal); } //json_Float @@ -976,69 +1030,73 @@ void json_Float(FILE *fp, const char *sLabel, float sVal) { printWarning(sLabel, sVal); return; } - if (sVal <= 0.0) return; - fprintf(fp, sLabel, sVal ); + if (sVal <= 0.0) + return; + fprintf(fp, sLabel, sVal); } //json_Float void json_Bool(FILE *fp, const char *sLabel, int sVal) { // json_Str(fp, "\t\"MTState\"", d.mtState); - //n.b. in JSON, true and false are lower case, whereas in Python they are capitalized + //n.b. in JSON, true and false are lower case, whereas in Python they are capitalized // only print 0 and >=1 for false and true, ignore negative values - if (sVal == 0) fprintf(fp, sLabel, "false"); - if (sVal > 0) fprintf(fp, sLabel, "true"); - - //if (sVal = 0) fprintf(" : false,\n" ); - //if (sVal = 1) fprintf(" : true,\n" ); + if (sVal == 0) + fprintf(fp, sLabel, "false"); + if (sVal > 0) + fprintf(fp, sLabel, "true"); } //json_Bool -void rescueProtocolName(struct TDICOMdata *d, const char * filename) { +void rescueProtocolName(struct TDICOMdata *d, const char *filename) { //tools like gdcmanon strip protocol name (0018,1030) but for Siemens we can recover it from CSASeriesHeaderInfo (0029,1020) - if ((d->manufacturer != kMANUFACTURER_SIEMENS) || (d->CSA.SeriesHeader_offset < 1) || (d->CSA.SeriesHeader_length < 1)) return; - if (strlen(d->protocolName) > 0) return; + if ((d->manufacturer != kMANUFACTURER_SIEMENS) || (d->CSA.SeriesHeader_offset < 1) || (d->CSA.SeriesHeader_length < 1)) + return; + if (strlen(d->protocolName) > 0) + return; #ifdef myReadAsciiCsa float shimSetting[8]; 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; + protocolName[kDICOMStr - 1] = 0; strcpy(d->protocolName, protocolName); #endif } -void nii_SaveBIDSX(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char * filename, struct TDTI4D *dti4D) { -//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) && (opts.isOnlyBIDS)) printMessage("Input-only mode: no BIDS/NIfTI output generated for '%s'\n", pathoutname); - if (!opts.isCreateBIDS) return; +void nii_SaveBIDSX(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char *filename, struct TDTI4D *dti4D) { + //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) && (opts.isOnlyBIDS)) + printMessage("Input-only mode: no BIDS/NIfTI output generated for '%s'\n", pathoutname); + if (!opts.isCreateBIDS) + return; char txtname[2048] = {""}; - strcpy (txtname,pathoutname); - strcat (txtname,".json"); + 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; + 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; }; //attempt to determine BIDS sequence type -/*(0018,0024) SequenceName + /*(0018,0024) SequenceName ep_b: dwi epfid2d: perf epfid2d: bold @@ -1063,13 +1121,13 @@ tse2d: T2 tse3d: T2*/ /* if (d.manufacturer == kMANUFACTURER_SIEMENS) { - #define kLabel_UNKNOWN 0 - #define kLabel_T1w 1 - #define kLabel_T2w 2 - #define kLabel_bold 3 - #define kLabel_perf 4 - #define kLabel_dwi 5 - #define kLabel_fieldmap 6 + #define kLabel_UNKNOWN 0 + #define kLabel_T1w 1 + #define kLabel_T2w 2 + #define kLabel_bold 3 + #define kLabel_perf 4 + #define kLabel_dwi 5 + #define kLabel_fieldmap 6 int iLabel = kLabel_UNKNOWN; if (d.CSA.numDti > 1) iLabel = kLabel_dwi; //if ((iLabel == kLabel_UNKNOWN) && (d.is2DAcq)) @@ -1081,47 +1139,52 @@ tse3d: T2*/ } }*/ //report vendor - if (d.fieldStrength > 0.0) fprintf(fp, "\t\"MagneticFieldStrength\": %g,\n", d.fieldStrength ); + if (d.fieldStrength > 0.0) + fprintf(fp, "\t\"MagneticFieldStrength\": %g,\n", d.fieldStrength); //Imaging Frequency (0018,0084) can be useful https://support.brainvoyager.com/brainvoyager/functional-analysis-preparation/29-pre-processing/78-epi-distortion-correction-echo-spacing-and-bandwidth // however, UIH stores 128176031 not 128.176031 https://github.com/rordenlab/dcm2niix/issues/225 if (d.imagingFrequency < 9000000) json_Float(fp, "\t\"ImagingFrequency\": %g,\n", d.imagingFrequency); switch (d.manufacturer) { - case kMANUFACTURER_BRUKER: - fprintf(fp, "\t\"Manufacturer\": \"Bruker\",\n" ); - break; - 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; - case kMANUFACTURER_CANON: - fprintf(fp, "\t\"Manufacturer\": \"Canon\",\n" ); - break; - case kMANUFACTURER_HITACHI: - fprintf(fp, "\t\"Manufacturer\": \"Hitachi\",\n" ); - break; - case kMANUFACTURER_UIH: - fprintf(fp, "\t\"Manufacturer\": \"UIH\",\n" ); - break; + case kMANUFACTURER_BRUKER: + fprintf(fp, "\t\"Manufacturer\": \"Bruker\",\n"); + break; + 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; + case kMANUFACTURER_CANON: + fprintf(fp, "\t\"Manufacturer\": \"Canon\",\n"); + break; + case kMANUFACTURER_HITACHI: + fprintf(fp, "\t\"Manufacturer\": \"Hitachi\",\n"); + break; + case kMANUFACTURER_UIH: + fprintf(fp, "\t\"Manufacturer\": \"UIH\",\n"); + break; }; - if (d.epiVersionGE == 0) fprintf(fp, "\t\"PulseSequenceName\": \"epi\",\n"); - if (d.epiVersionGE == 1) fprintf(fp, "\t\"PulseSequenceName\": \"epiRT\",\n"); - if (d.internalepiVersionGE == 1) fprintf(fp, "\t\"InternalPulseSequenceName\": \"EPI\",\n"); - if (d.internalepiVersionGE == 2) fprintf(fp, "\t\"InternalPulseSequenceName\": \"EPI2\",\n"); + if (d.epiVersionGE == 0) + fprintf(fp, "\t\"PulseSequenceName\": \"epi\",\n"); + if (d.epiVersionGE == 1) + fprintf(fp, "\t\"PulseSequenceName\": \"epiRT\",\n"); + if (d.internalepiVersionGE == 1) + fprintf(fp, "\t\"InternalPulseSequenceName\": \"EPI\",\n"); + if (d.internalepiVersionGE == 2) + fprintf(fp, "\t\"InternalPulseSequenceName\": \"EPI2\",\n"); 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 ); + json_Str(fp, "\t\"DeviceSerialNumber\": \"%s\",\n", d.deviceSerialNumber); + json_Str(fp, "\t\"StationName\": \"%s\",\n", d.stationName); if (!opts.isAnonymizeBIDS) { json_Str(fp, "\t\"SeriesInstanceUID\": \"%s\",\n", d.seriesInstanceUID); json_Str(fp, "\t\"StudyInstanceUID\": \"%s\",\n", d.studyInstanceUID); @@ -1132,24 +1195,27 @@ tse3d: T2*/ json_Str(fp, "\t\"PatientID\": \"%s\",\n", d.patientID); json_Str(fp, "\t\"AccessionNumber\": \"%s\",\n", d.accessionNumber); if (strlen(d.patientBirthDate) == 8) { //DICOM DA YYYYMMDD -> ISO 8601 "YYYY-MM-DD" - int ayear,amonth,aday; + int ayear, amonth, aday; sscanf(d.patientBirthDate, "%4d%2d%2d", &ayear, &amonth, &aday); fprintf(fp, "\t\"PatientBirthDate\": "); fprintf(fp, (ayear >= 0 && ayear <= 9999) ? "\"%4d" : "\"%+4d", ayear); fprintf(fp, "-%02d-%02d\",\n", amonth, aday); } - if (d.patientSex != '?') fprintf(fp, "\t\"PatientSex\": \"%c\",\n", d.patientSex); + 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.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\"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"); + 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); @@ -1162,42 +1228,43 @@ tse3d: T2*/ for (size_t i = 0; i < strlen(d.imageType); i++) { if (d.imageType[i] != '_') { if (isSep) - fprintf(fp, "\", \""); + fprintf(fp, "\", \""); isSep = false; fprintf(fp, "%c", d.imageType[i]); } else isSep = true; } - if ((d.isHasPhase) && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_PHASE_") == NULL)) ) - fprintf(fp,"\", \"PHASE"); //"_IMAGINARY_" - if ((d.isHasReal) && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_REAL_") == NULL)) ) - 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.isHasPhase) && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_PHASE_") == NULL))) + fprintf(fp, "\", \"PHASE"); //"_IMAGINARY_" + if ((d.isHasReal) && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_REAL_") == NULL))) + 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) fprintf(fp, "\t\"RawImage\": false,\n"); - if (d.seriesNum > 0) fprintf(fp, "\t\"SeriesNumber\": %ld,\n", d.seriesNum); + 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){ + 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; + 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 + 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); + fprintf(fp, "\t\"AcquisitionTime\": \"%02d:%02d:%02.6f\",\n", ahour, amin, asec); //report date and time together if (!opts.isAnonymizeBIDS) { fprintf(fp, "\t\"AcquisitionDateTime\": "); @@ -1213,15 +1280,15 @@ tse3d: T2*/ json_Str(fp, "\t\"ImageComments\": \"%s\",\n", d.imageComments); json_Str(fp, "\t\"ConversionComments\": \"%s\",\n", opts.imageComments); //if conditionals: the following values are required for DICOM MRI, but not available for CT - json_Float(fp, "\t\"TriggerDelayTime\": %g,\n", d.triggerDelayTime ); + json_Float(fp, "\t\"TriggerDelayTime\": %g,\n", d.triggerDelayTime); if (d.RWVScale != 0) { - fprintf(fp, "\t\"PhilipsRWVSlope\": %g,\n", d.RWVScale ); - fprintf(fp, "\t\"PhilipsRWVIntercept\": %g,\n", d.RWVIntercept ); + fprintf(fp, "\t\"PhilipsRWVSlope\": %g,\n", d.RWVScale); + fprintf(fp, "\t\"PhilipsRWVIntercept\": %g,\n", d.RWVIntercept); } if ((!d.isScaleVariesEnh) && (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\"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); } //https://bids-specification--622.org.readthedocs.build/en/622/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#case-3-direct-field-mapping @@ -1229,13 +1296,13 @@ tse3d: T2*/ fprintf(fp, "\t\"Units\": \"Hz\",\n"); // //PET ISOTOPE MODULE ATTRIBUTES json_Str(fp, "\t\"Radiopharmaceutical\": \"%s\",\n", d.radiopharmaceutical); - json_Float(fp, "\t\"RadionuclidePositronFraction\": %g,\n", d.radionuclidePositronFraction ); - json_Float(fp, "\t\"RadionuclideTotalDose\": %g,\n", d.radionuclideTotalDose ); - json_Float(fp, "\t\"RadionuclideHalfLife\": %g,\n", d.radionuclideHalfLife ); - json_Float(fp, "\t\"DoseCalibrationFactor\": %g,\n", d.doseCalibrationFactor ); - json_Float(fp, "\t\"IsotopeHalfLife\": %g,\n", d.ecat_isotope_halflife); - json_Float(fp, "\t\"Dosage\": %g,\n", d.ecat_dosage); - json_Str(fp, "\t\"ConvolutionKernel\": \"%s\",\n", d.convolutionKernel); + json_Float(fp, "\t\"RadionuclidePositronFraction\": %g,\n", d.radionuclidePositronFraction); + json_Float(fp, "\t\"RadionuclideTotalDose\": %g,\n", d.radionuclideTotalDose); + json_Float(fp, "\t\"RadionuclideHalfLife\": %g,\n", d.radionuclideHalfLife); + json_Float(fp, "\t\"DoseCalibrationFactor\": %g,\n", d.doseCalibrationFactor); + json_Float(fp, "\t\"IsotopeHalfLife\": %g,\n", d.ecat_isotope_halflife); + json_Float(fp, "\t\"Dosage\": %g,\n", d.ecat_dosage); + json_Str(fp, "\t\"ConvolutionKernel\": \"%s\",\n", d.convolutionKernel); json_Str(fp, "\t\"Units\": \"%s\",\n", d.unitsPT); //https://github.com/bids-standard/bids-specification/pull/773 json_Str(fp, "\t\"DecayCorrection\": \"%s\",\n", d.decayCorrection); json_Str(fp, "\t\"AttenuationCorrectionMethod\": \"%s\",\n", d.attenuationCorrectionMethod); @@ -1246,8 +1313,9 @@ tse3d: T2*/ for (int i = 0; i < h->dim[4]; i++) { if (i != 0) fprintf(fp, ",\n"); - if (dti4D->decayFactor[i] < 0) break; - fprintf(fp, "\t\t%g", dti4D->decayFactor[i] ); + if (dti4D->decayFactor[i] < 0) + break; + fprintf(fp, "\t\t%g", dti4D->decayFactor[i]); } fprintf(fp, "\t],\n"); } @@ -1256,8 +1324,9 @@ tse3d: T2*/ for (int i = 0; i < h->dim[4]; i++) { if (i != 0) fprintf(fp, ",\n"); - if (dti4D->volumeOnsetTime[i] < 0) break; - fprintf(fp, "\t\t%g", dti4D->volumeOnsetTime[i] ); + if (dti4D->volumeOnsetTime[i] < 0) + break; + fprintf(fp, "\t\t%g", dti4D->volumeOnsetTime[i]); } fprintf(fp, "\t],\n"); } @@ -1266,48 +1335,59 @@ tse3d: T2*/ for (int i = 0; i < h->dim[4]; i++) { if (i != 0) fprintf(fp, ",\n"); - if (dti4D->frameDuration[i] < 0) break; - fprintf(fp, "\t\t%g", dti4D->frameDuration[i] / 1000.0 ); // from 0018,1242 ms -> sec + if (dti4D->frameDuration[i] < 0) + break; + fprintf(fp, "\t\t%g", dti4D->frameDuration[i] / 1000.0); // from 0018,1242 ms -> sec } fprintf(fp, "\t],\n"); } //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.numberOfAverages > 1.0) json_Float(fp, "\t\"NumberOfAverages\": %g,\n", d.numberOfAverages ); - if ((d.echoNum > 1) || (d.isMultiEcho)) fprintf(fp, "\t\"EchoNumber\": %d,\n", d.echoNum); - if ((d.TE > 0.0) && (!d.isXRay)) fprintf(fp, "\t\"EchoTime\": %g,\n", d.TE / 1000.0 ); + if ((d.TE > 0.0) && (d.isXRay)) + 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.numberOfAverages > 1.0) + json_Float(fp, "\t\"NumberOfAverages\": %g,\n", d.numberOfAverages); + if ((d.echoNum > 1) || (d.isMultiEcho)) + fprintf(fp, "\t\"EchoNumber\": %d,\n", d.echoNum); + if ((d.TE > 0.0) && (!d.isXRay)) + fprintf(fp, "\t\"EchoTime\": %g,\n", d.TE / 1000.0); //if ((d.TE2 > 0.0) && (!d.isXRay)) fprintf(fp, "\t\"EchoTime2\": %g,\n", d.TE2 / 1000.0 ); - json_Float(fp, "\t\"RepetitionTime\": %g,\n", d.TR / 1000.0 ); + json_Float(fp, "\t\"RepetitionTime\": %g,\n", d.TR / 1000.0); json_Float(fp, "\t\"RepetitionTimeExcitation\": %g,\n", dti4D->repetitionTimeExcitation); json_Float(fp, "\t\"RepetitionTimeInversion\": %g,\n", dti4D->repetitionTimeInversion); - json_Bool(fp, "\t\"MTState\": %s,\n", d.mtState); - json_Bool(fp, "\t\"SpoilingState\": %s,\n", d.spoiling); + json_Bool(fp, "\t\"MTState\": %s,\n", d.mtState); // BIDS suggests 0018,9020 but Siemens V-series do not populate this, alternatives are CSA or (0018,0021) CS [SK\MTC\SP] + //SpoilingState + bool isSpoiled = (d.spoiling > kSPOILING_NONE); + if ((d.spoiling == kSPOILING_UNKOWN) && (strstr(d.sequenceVariant, "\\SP") != NULL)) //BIDS suggests 0018,9016 Siemens V-series do not populate this, (0018,0021) CS [SK\MTC\SP] + isSpoiled = true; + if (isSpoiled) + json_Bool(fp, "\t\"SpoilingState\": %s,\n", true); //Siemens reports SpoilingState but not SpoilingType if (d.spoiling == kSPOILING_RF) - fprintf(fp, "\t\"SpoilingType\": \"RF\",\n" ); + fprintf(fp, "\t\"SpoilingType\": \"RF\",\n"); if (d.spoiling == kSPOILING_GRADIENT) - fprintf(fp, "\t\"SpoilingType\": \"GRADIENT\",\n" ); + fprintf(fp, "\t\"SpoilingType\": \"GRADIENT\",\n"); if (d.spoiling == kSPOILING_RF_AND_GRADIENT) - fprintf(fp, "\t\"SpoilingType\": \"COMBINED\",\n" ); - - json_Float(fp, "\t\"InversionTime\": %g,\n", d.TI / 1000.0 ); - json_Float(fp, "\t\"FlipAngle\": %g,\n", d.flipAngle ); + fprintf(fp, "\t\"SpoilingType\": \"COMBINED\",\n"); + json_Float(fp, "\t\"InversionTime\": %g,\n", d.TI / 1000.0); + json_Float(fp, "\t\"FlipAngle\": %g,\n", d.flipAngle); bool interp = false; //2D interpolation float phaseOversampling = 0.0; //n.b. https://neurostars.org/t/getting-missing-ge-information-required-by-bids-for-common-preprocessing/1357/7 json_Str(fp, "\t\"PhaseEncodingDirectionDisplayed\": \"%s\",\n", d.phaseEncodingDirectionDisplayedUIH); if ((d.manufacturer == kMANUFACTURER_GE) && (d.phaseEncodingGE != kGE_PHASE_ENCODING_POLARITY_UNKNOWN)) { //only set for GE - if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) fprintf(fp, "\t\"PhaseEncodingPolarityGE\": \"Unflipped\",\n" ); - if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED) fprintf(fp, "\t\"PhaseEncodingPolarityGE\": \"Flipped\",\n" ); + if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) + fprintf(fp, "\t\"PhaseEncodingPolarityGE\": \"Unflipped\",\n"); + if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED) + fprintf(fp, "\t\"PhaseEncodingPolarityGE\": \"Flipped\",\n"); } float delayTimeInTR = -0.01; - #ifdef myReadAsciiCsa +#ifdef myReadAsciiCsa if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.SeriesHeader_offset > 0) && (d.CSA.SeriesHeader_length > 0)) { float pf = 1.0f; //partial fourier float shimSetting[8]; @@ -1321,9 +1401,11 @@ tse3d: T2*/ delayTimeInTR = csaAscii.delayTimeInTR; if ((d.isHasPhase) && (csaAscii.TE0 > 0.0) && (csaAscii.TE1 > 0.0)) { //issue400 //https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html - json_Float(fp, "\t\"EchoTime1\": %g,\n", csaAscii.TE0 / 1000000.0 ); - json_Float(fp, "\t\"EchoTime2\": %g,\n", csaAscii.TE1 / 1000000.0 ); - } + json_Float(fp, "\t\"EchoTime1\": %g,\n", csaAscii.TE0 / 1000000.0); + json_Float(fp, "\t\"EchoTime2\": %g,\n", csaAscii.TE1 / 1000000.0); + } + if (csaAscii.dAveragesDouble > 1.0) //*spcR_44ns fractional and independent of (0018,0083) DS NumberOfAverages, e.g. 0018,0083=2, dAveragesDouble = 1.4? + json_Float(fp, "\t\"AveragesDouble\": %g,\n", csaAscii.dAveragesDouble); phaseOversampling = csaAscii.phaseOversampling; if (csaAscii.existUcImageNumb > 0) { if (d.CSA.protocolSliceNumber1 < 2) { @@ -1344,66 +1426,64 @@ tse3d: T2*/ } //verbose */ //ASL specific tags - 2D pCASL Danny J.J. Wang http://www.loft-lab.org - if ((strstr(pulseSequenceDetails,"ep2d_pcasl")) || (strstr(pulseSequenceDetails,"ep2d_pcasl_UI_PHC"))) { + if ((strstr(pulseSequenceDetails, "ep2d_pcasl")) || (strstr(pulseSequenceDetails, "ep2d_pcasl_UI_PHC"))) { json_FloatNotNan(fp, "\t\"LabelOffset\": %g,\n", csaAscii.adFree[1]); //mm - json_FloatNotNan(fp, "\t\"PostLabelDelay\": %g,\n", csaAscii.adFree[2] * (1.0/1000000.0)); //usec -> sec + json_FloatNotNan(fp, "\t\"PostLabelDelay\": %g,\n", csaAscii.adFree[2] * (1.0 / 1000000.0)); //usec -> sec json_FloatNotNan(fp, "\t\"NumRFBlocks\": %g,\n", csaAscii.adFree[3]); - json_FloatNotNan(fp, "\t\"RFGap\": %g,\n", csaAscii.adFree[4] * (1.0/1000000.0)); //usec -> sec - json_FloatNotNan(fp, "\t\"MeanGzx10\": %g,\n", csaAscii.adFree[10]); // mT/m - json_FloatNotNan(fp, "\t\"PhiAdjust\": %g,\n", csaAscii.adFree[11]); // percent + json_FloatNotNan(fp, "\t\"RFGap\": %g,\n", csaAscii.adFree[4] * (1.0 / 1000000.0)); //usec -> sec + json_FloatNotNan(fp, "\t\"MeanGzx10\": %g,\n", csaAscii.adFree[10]); // mT/m + json_FloatNotNan(fp, "\t\"PhiAdjust\": %g,\n", csaAscii.adFree[11]); // percent } //ASL specific tags - 3D pCASL Danny J.J. Wang http://www.loft-lab.org - if (strstr(pulseSequenceDetails,"tgse_pcasl")) { - json_FloatNotNan(fp, "\t\"RFGap\": %g,\n", csaAscii.adFree[4] * (1.0/1000000.0)); //usec -> sec - json_FloatNotNan(fp, "\t\"MeanGzx10\": %g,\n", csaAscii.adFree[10]); // mT/m - json_FloatNotNan(fp, "\t\"T1\": %g,\n", csaAscii.adFree[12] * (1.0/1000000.0)); //usec -> sec + if (strstr(pulseSequenceDetails, "tgse_pcasl")) { + json_FloatNotNan(fp, "\t\"RFGap\": %g,\n", csaAscii.adFree[4] * (1.0 / 1000000.0)); //usec -> sec + json_FloatNotNan(fp, "\t\"MeanGzx10\": %g,\n", csaAscii.adFree[10]); // mT/m + json_FloatNotNan(fp, "\t\"T1\": %g,\n", csaAscii.adFree[12] * (1.0 / 1000000.0)); //usec -> sec } //ASL specific tags - 2D PASL Siemens Product - if (strstr(pulseSequenceDetails,"ep2d_pasl")) { - json_FloatNotNan(fp, "\t\"InversionTime\": %g,\n", csaAscii.alTI[0] * (1.0/1000000.0)); //us -> sec - json_FloatNotNan(fp, "\t\"SaturationStopTime\": %g,\n", csaAscii.alTI[2] * (1.0/1000000.0)); //us -> sec + if (strstr(pulseSequenceDetails, "ep2d_pasl")) { + json_FloatNotNan(fp, "\t\"InversionTime\": %g,\n", csaAscii.alTI[0] * (1.0 / 1000000.0)); //us -> sec + json_FloatNotNan(fp, "\t\"SaturationStopTime\": %g,\n", csaAscii.alTI[2] * (1.0 / 1000000.0)); //us -> sec } //ASL specific tags - 3D PASL Siemens Product http://adni.loni.usc.edu/wp-content/uploads/2010/05/ADNI3_Basic_Siemens_Skyra_E11.pdf - if (strstr(pulseSequenceDetails,"tgse_pasl")) { - json_FloatNotNan(fp, "\t\"BolusDuration\": %g,\n", csaAscii.alTI[0] * (1.0/1000000.0)); //usec->sec - json_FloatNotNan(fp, "\t\"InversionTime\": %g,\n", csaAscii.alTI[2] * (1.0/1000000.0)); //usec -> sec + if (strstr(pulseSequenceDetails, "tgse_pasl")) { + json_FloatNotNan(fp, "\t\"BolusDuration\": %g,\n", csaAscii.alTI[0] * (1.0 / 1000000.0)); //usec->sec + json_FloatNotNan(fp, "\t\"InversionTime\": %g,\n", csaAscii.alTI[2] * (1.0 / 1000000.0)); //usec -> sec //json_FloatNotNan(fp, "\t\"SaturationStopTime\": %g,\n", csaAscii.alTI[2] * (1.0/1000.0)); } //PASL http://www.pubmed.com/11746944 http://www.pubmed.com/21606572 - if (strstr(pulseSequenceDetails,"ep2d_fairest")) { - json_FloatNotNan(fp, "\t\"PostInversionDelay\": %g,\n", csaAscii.adFree[2] * (1.0/1000.0)); //usec->sec - json_FloatNotNan(fp, "\t\"PostLabelDelay\": %g,\n", csaAscii.adFree[4] * (1.0/1000.0)); //usec -> sec + if (strstr(pulseSequenceDetails, "ep2d_fairest")) { + json_FloatNotNan(fp, "\t\"PostInversionDelay\": %g,\n", csaAscii.adFree[2] * (1.0 / 1000.0)); //usec->sec + json_FloatNotNan(fp, "\t\"PostLabelDelay\": %g,\n", csaAscii.adFree[4] * (1.0 / 1000.0)); //usec -> sec } //ASL specific tags - Oxford (Thomas OKell) bool isOxfordASL = false; - if (strstr(pulseSequenceDetails,"to_ep2d_VEPCASL")) { //Oxford 2D pCASL + if (strstr(pulseSequenceDetails, "to_ep2d_VEPCASL")) { //Oxford 2D pCASL isOxfordASL = true; - json_FloatNotNan(fp, "\t\"InversionTime\": %g,\n", csaAscii.alTI[2] * (1.0/1000000.0)); //ms->sec - json_FloatNotNan(fp, "\t\"BolusDuration\": %g,\n", csaAscii.alTI[0] * (1.0/1000000.0)); //usec -> sec - //alTI[0] = 700000 - //alTI[2] = 1800000 - + json_FloatNotNan(fp, "\t\"InversionTime\": %g,\n", csaAscii.alTI[2] * (1.0 / 1000000.0)); //ms->sec + json_FloatNotNan(fp, "\t\"BolusDuration\": %g,\n", csaAscii.alTI[0] * (1.0 / 1000000.0)); //usec -> sec json_Float(fp, "\t\"TagRFFlipAngle\": %g,\n", csaAscii.alFree[4]); - json_Float(fp, "\t\"TagRFDuration\": %g,\n", csaAscii.alFree[5]/1000000.0); //usec -> sec - json_Float(fp, "\t\"TagRFSeparation\": %g,\n", csaAscii.alFree[6]/1000000.0); //usec -> sec - json_FloatNotNan(fp, "\t\"MeanTagGradient\": %g,\n", csaAscii.adFree[0]); //mTm - json_FloatNotNan(fp, "\t\"TagGradientAmplitude\": %g,\n", csaAscii.adFree[1]); //mTm - json_Float(fp, "\t\"TagDuration\": %g,\n", csaAscii.alFree[9]/ 1000.0); //ms -> sec - json_Float(fp, "\t\"MaximumT1Opt\": %g,\n", csaAscii.alFree[10]/ 1000.0); //ms -> sec + json_Float(fp, "\t\"TagRFDuration\": %g,\n", csaAscii.alFree[5] / 1000000.0); //usec -> sec + json_Float(fp, "\t\"TagRFSeparation\": %g,\n", csaAscii.alFree[6] / 1000000.0); //usec -> sec + json_FloatNotNan(fp, "\t\"MeanTagGradient\": %g,\n", csaAscii.adFree[0]); //mTm + json_FloatNotNan(fp, "\t\"TagGradientAmplitude\": %g,\n", csaAscii.adFree[1]); //mTm + json_Float(fp, "\t\"TagDuration\": %g,\n", csaAscii.alFree[9] / 1000.0); //ms -> sec + json_Float(fp, "\t\"MaximumT1Opt\": %g,\n", csaAscii.alFree[10] / 1000.0); //ms -> sec //report post label delay - int nPLD = 0; bool isValid = true; //detect gaps in PLD array: If user sets PLD1=250, PLD2=0 PLD3=375 only PLD1 was acquired for (int k = 11; k < 31; k++) { - if ((isnan(csaAscii.alFree[k])) || (csaAscii.alFree[k] <= 0.0)) isValid = false; - if (isValid) nPLD ++; + if ((isnan(csaAscii.alFree[k])) || (csaAscii.alFree[k] <= 0.0)) + isValid = false; + if (isValid) + nPLD++; } //for k if (nPLD > 0) { // record PostLabelDelays, these are listed as "PLD0","PLD1",etc in PDF fprintf(fp, "\t\"InitialPostLabelDelay\": [\n"); //https://docs.google.com/document/d/15tnn5F10KpgHypaQJNNGiNKsni9035GtDqJzWqkkP6c/edit# for (int i = 0; i < nPLD; i++) { if (i != 0) fprintf(fp, ",\n"); - fprintf(fp, "\t\t%g", csaAscii.alFree[i+11]/ 1000.0); //ms -> sec + fprintf(fp, "\t\t%g", csaAscii.alFree[i + 11] / 1000.0); //ms -> sec } fprintf(fp, "\t],\n"); } @@ -1422,29 +1502,30 @@ tse3d: T2*/ json_FloatNotNan(fp, newstr, csaAscii.adFree[k]); } } - if (strstr(pulseSequenceDetails,"jw_tgse_VEPCASL")) { //Oxford 3D pCASL + if (strstr(pulseSequenceDetails, "jw_tgse_VEPCASL")) { //Oxford 3D pCASL isOxfordASL = true; json_Float(fp, "\t\"TagRFFlipAngle\": %g,\n", csaAscii.alFree[6]); - json_Float(fp, "\t\"TagRFDuration\": %g,\n", csaAscii.alFree[7]/1000000.0); //usec -> sec - json_Float(fp, "\t\"TagRFSeparation\": %g,\n", csaAscii.alFree[8]/1000000.0); //usec -> sec - json_Float(fp, "\t\"MaximumT1Opt\": %g,\n", csaAscii.alFree[9]/1000.0); //ms -> sec - json_Float(fp, "\t\"Tag0\": %g,\n", csaAscii.alFree[10]/1000.0); //DelayTimeInTR usec -> sec - json_Float(fp, "\t\"Tag1\": %g,\n", csaAscii.alFree[11]/1000.0); //DelayTimeInTR usec -> sec - json_Float(fp, "\t\"Tag2\": %g,\n", csaAscii.alFree[12]/1000.0); //DelayTimeInTR usec -> sec - json_Float(fp, "\t\"Tag3\": %g,\n", csaAscii.alFree[13]/1000.0); //DelayTimeInTR usec -> sec - + json_Float(fp, "\t\"TagRFDuration\": %g,\n", csaAscii.alFree[7] / 1000000.0); //usec -> sec + json_Float(fp, "\t\"TagRFSeparation\": %g,\n", csaAscii.alFree[8] / 1000000.0); //usec -> sec + json_Float(fp, "\t\"MaximumT1Opt\": %g,\n", csaAscii.alFree[9] / 1000.0); //ms -> sec + json_Float(fp, "\t\"Tag0\": %g,\n", csaAscii.alFree[10] / 1000.0); //DelayTimeInTR usec -> sec + json_Float(fp, "\t\"Tag1\": %g,\n", csaAscii.alFree[11] / 1000.0); //DelayTimeInTR usec -> sec + json_Float(fp, "\t\"Tag2\": %g,\n", csaAscii.alFree[12] / 1000.0); //DelayTimeInTR usec -> sec + json_Float(fp, "\t\"Tag3\": %g,\n", csaAscii.alFree[13] / 1000.0); //DelayTimeInTR usec -> sec int nPLD = 0; bool isValid = true; //detect gaps in PLD array: If user sets PLD1=250, PLD2=0 PLD3=375 only PLD1 was acquired for (int k = 30; k < 38; k++) { - if ((isnan(csaAscii.alFree[k])) || (csaAscii.alFree[k] <= 0.0)) isValid = false; - if (isValid) nPLD ++; + if ((isnan(csaAscii.alFree[k])) || (csaAscii.alFree[k] <= 0.0)) + isValid = false; + if (isValid) + nPLD++; } //for k if (nPLD > 0) { // record PostLabelDelays, these are listed as "PLD0","PLD1",etc in PDF fprintf(fp, "\t\"InitialPostLabelDelay\": [\n"); //https://docs.google.com/document/d/15tnn5F10KpgHypaQJNNGiNKsni9035GtDqJzWqkkP6c/edit# for (int i = 0; i < nPLD; i++) { if (i != 0) fprintf(fp, ",\n"); - fprintf(fp, "\t\t%g", csaAscii.alFree[i+30]/ 1000.0); //ms -> sec + fprintf(fp, "\t\t%g", csaAscii.alFree[i + 30] / 1000.0); //ms -> sec } fprintf(fp, "\t],\n"); } @@ -1467,10 +1548,14 @@ tse3d: T2*/ //general properties if ((csaAscii.partialFourier > 0) && ((d.modality == kMODALITY_MR))) { //check MR, e.g. do not report for Siemens PET //https://github.com/ismrmrd/siemens_to_ismrmrd/blob/master/parameter_maps/IsmrmrdParameterMap_Siemens_EPI_FLASHREF.xsl - if (csaAscii.partialFourier == 1) pf = 0.5; // 4/8 - if (csaAscii.partialFourier == 2) pf = 0.625; // 5/8 - if (csaAscii.partialFourier == 4) pf = 0.75; - if (csaAscii.partialFourier == 8) pf = 0.875; + if (csaAscii.partialFourier == 1) + pf = 0.5; // 4/8 + if (csaAscii.partialFourier == 2) + pf = 0.625; // 5/8 + if (csaAscii.partialFourier == 4) + pf = 0.75; + if (csaAscii.partialFourier == 8) + pf = 0.875; fprintf(fp, "\t\"PartialFourier\": %g,\n", pf); } if (csaAscii.interp > 0) { //in-plane interpolation @@ -1479,7 +1564,8 @@ tse3d: T2*/ } if (d.interp3D > 1) //through-plane interpolation e.g. GE ZIP2 through-plane http://mriquestions.com/zip.html fprintf(fp, "\t\"Interpolation3D\": %d,\n", d.interp3D); - if (csaAscii.baseResolution > 0) fprintf(fp, "\t\"BaseResolution\": %d,\n", csaAscii.baseResolution ); + if (csaAscii.baseResolution > 0) + fprintf(fp, "\t\"BaseResolution\": %d,\n", csaAscii.baseResolution); if (shimSetting[0] != 0.0) { fprintf(fp, "\t\"ShimSetting\": [\n"); for (int i = 0; i < 8; i++) { @@ -1490,81 +1576,85 @@ tse3d: T2*/ fprintf(fp, "\t],\n"); } if (d.CSA.numDti > 0) { // - if (csaAscii.difBipolar == 1) fprintf(fp, "\t\"DiffusionScheme\": \"Bipolar\",\n" ); - if (csaAscii.difBipolar == 2) fprintf(fp, "\t\"DiffusionScheme\": \"Monopolar\",\n" ); + if (csaAscii.difBipolar == 1) + fprintf(fp, "\t\"DiffusionScheme\": \"Bipolar\",\n"); + if (csaAscii.difBipolar == 2) + fprintf(fp, "\t\"DiffusionScheme\": \"Monopolar\",\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 - if (d.modality == kMODALITY_MR) json_Float(fp, "\t\"TxRefAmp\": %g,\n", csaAscii.txRefAmp); - if (d.modality == kMODALITY_MR) json_Float(fp, "\t\"PhaseResolution\": %g,\n", csaAscii.phaseResolution); + json_Float(fp, "\t\"DelayTime\": %g,\n", delayTimeInTR / 1000000.0); //DelayTimeInTR usec -> sec + if (d.modality == kMODALITY_MR) + json_Float(fp, "\t\"TxRefAmp\": %g,\n", csaAscii.txRefAmp); + if (d.modality == kMODALITY_MR) + json_Float(fp, "\t\"PhaseResolution\": %g,\n", csaAscii.phaseResolution); json_Float(fp, "\t\"PhaseOversampling\": %g,\n", phaseOversampling); json_Float(fp, "\t\"VendorReportedEchoSpacing\": %g,\n", csaAscii.echoSpacing / 1000000.0); //usec -> sec //ETD and epiFactor not useful/reliable https://github.com/rordenlab/dcm2niix/issues/127 //if (echoTrainDuration > 0) fprintf(fp, "\t\"EchoTrainDuration\": %g,\n", echoTrainDuration / 1000000.0); //usec -> sec //if (epiFactor > 0) fprintf(fp, "\t\"EPIFactor\": %d,\n", epiFactor); json_Str(fp, "\t\"ReceiveCoilName\": \"%s\",\n", coilID); - if (d.modality == kMODALITY_MR) json_Str(fp, "\t\"ReceiveCoilActiveElements\": \"%s\",\n", coilElements); - if (strcmp(coilElements,d.coilName) != 0) + if (d.modality == kMODALITY_MR) + json_Str(fp, "\t\"ReceiveCoilActiveElements\": \"%s\",\n", coilElements); + if (strcmp(coilElements, d.coilName) != 0) json_Str(fp, "\t\"CoilString\": \"%s\",\n", d.coilName); strcpy(d.coilName, ""); json_Str(fp, "\t\"PulseSequenceDetails\": \"%s\",\n", pulseSequenceDetails); json_Str(fp, "\t\"FmriExternalInfo\": \"%s\",\n", fmriExternalInfo); json_Str(fp, "\t\"WipMemBlock\": \"%s\",\n", wipMemBlock); - if (strlen(d.protocolName) < 1) //insert protocol name if it exists in CSA but not DICOM header: https://github.com/nipy/heudiconv/issues/80 + 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); if (csaAscii.refLinesPE > 0) fprintf(fp, "\t\"RefLinesPE\": %d,\n", csaAscii.refLinesPE); //https://github.com/bids-standard/bids-specification/pull/681#issuecomment-861767213 if (csaAscii.combineMode == 1) - fprintf(fp, "\t\"CoilCombinationMethod\": \"Sum of Squares\",\n" ); + fprintf(fp, "\t\"CoilCombinationMethod\": \"Sum of Squares\",\n"); if (csaAscii.combineMode == 2) - fprintf(fp, "\t\"CoilCombinationMethod\": \"Adaptive Combine\",\n" ); + fprintf(fp, "\t\"CoilCombinationMethod\": \"Adaptive Combine\",\n"); if ((csaAscii.ucMTC == 1) && (d.mtState < 0)) //precedence for 0018,9020 over CSA json_Bool(fp, "\t\"MTState\": %s,\n", 1); json_Str(fp, "\t\"ConsistencyInfo\": \"%s\",\n", consistencyInfo); - if (csaAscii.parallelReductionFactorInPlane > 0) {//AccelFactorPE -> phase encoding + if (csaAscii.accelFact3D > 1.01) json_Float(fp, "\t\"AccelFact3D\": %g,\n", csaAscii.accelFact3D); //see *spcR_44ns where "sPat.lAccelFactPE = 1", "sPat.lAccelFact3D = 2" (0051,1011) LO [p2], perhaps ParallelReductionFactorInPlane should be 1? + if (csaAscii.parallelReductionFactorInPlane > 0) { //AccelFactorPE -> phase encoding if (csaAscii.patMode == 1) - fprintf(fp, "\t\"MatrixCoilMode\": \"SENSE\",\n" ); + fprintf(fp, "\t\"MatrixCoilMode\": \"SENSE\",\n"); if (csaAscii.patMode == 2) - fprintf(fp, "\t\"MatrixCoilMode\": \"GRAPPA\",\n" ); + fprintf(fp, "\t\"MatrixCoilMode\": \"GRAPPA\",\n"); if (d.accelFactPE < 1.0) { //value not found in DICOM header, but WAS found in CSA ascii d.accelFactPE = csaAscii.parallelReductionFactorInPlane; //value found in ASCII but not in DICOM (0051,1011) - //fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); } - if (csaAscii.parallelReductionFactorInPlane != (int)(d.accelFactPE)) + if ((csaAscii.accelFact3D < 1.01) && (csaAscii.parallelReductionFactorInPlane != (int)(d.accelFactPE))) printWarning("ParallelReductionFactorInPlane reported in DICOM [0051,1011] (%d) does not match CSA series value %d\n", (int)(d.accelFactPE), csaAscii.parallelReductionFactorInPlane); } } else { //e.g. Siemens Vida does not have CSA header, but has many attributes json_Str(fp, "\t\"ReceiveCoilActiveElements\": \"%s\",\n", d.coilElements); - if (strcmp(d.coilElements,d.coilName) != 0) + if (strcmp(d.coilElements, d.coilName) != 0) json_Str(fp, "\t\"CoilString\": \"%s\",\n", d.coilName); if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (!d.is3DAcq) && (d.phaseEncodingLines > d.echoTrainLength) && (d.echoTrainLength > 1)) { - //ETL is > 1, as some GE files list 1, as an example see series mr_0005 in dcm_qa_nih - float pf = (float)d.phaseEncodingLines; - if (d.accelFactPE > 1) - pf = (float)pf / (float)d.accelFactPE; //estimate: not sure if we round up or down - - pf = (float)d.echoTrainLength / (float)pf; - if (pf < 1.0) //e.g. if difference between lines and echo length not all explained by iPAT (SENSE/GRAPPA) - fprintf(fp, "\t\"PartialFourier\": %g,\n", pf); + //ETL is > 1, as some GE files list 1, as an example see series mr_0005 in dcm_qa_nih + float pf = (float)d.phaseEncodingLines; + if (d.accelFactPE > 1) + pf = (float)pf / (float)d.accelFactPE; //estimate: not sure if we round up or down + pf = (float)d.echoTrainLength / (float)pf; + if (pf < 1.0) //e.g. if difference between lines and echo length not all explained by iPAT (SENSE/GRAPPA) + fprintf(fp, "\t\"PartialFourier\": %g,\n", pf); } //compute partial Fourier: not reported in XA10, so infer - //printf("PhaseLines=%d EchoTrainLength=%d SENSE=%g\n", d.phaseEncodingLines, d.echoTrainLength, d.accelFactPE); //n.b. we can not distinguish pF from SENSE/GRAPPA for UIH + //printf("PhaseLines=%d EchoTrainLength=%d SENSE=%g\n", d.phaseEncodingLines, d.echoTrainLength, d.accelFactPE); //n.b. we can not distinguish pF from SENSE/GRAPPA for UIH } - #endif +#endif //GE ASL specific tags - if (d.aslFlagsGE & kASL_FLAG_GE_CONTINUOUS) - fprintf(fp, "\t\"ASLContrastTechnique\": \"CONTINUOUS\",\n" ); - if (d.aslFlagsGE & kASL_FLAG_GE_PSEUDOCONTINUOUS) - fprintf(fp, "\t\"ASLContrastTechnique\": \"PSEUDOCONTINUOUS\",\n" ); - if (d.aslFlagsGE & kASL_FLAG_GE_3DPCASL) - fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D pulsed continuous ASL technique\",\n" ); - if (d.aslFlagsGE & kASL_FLAG_GE_3DCASL) - fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D continuous ASL technique\",\n" ); + if (d.aslFlagsGE & kASL_FLAG_GE_CONTINUOUS) + fprintf(fp, "\t\"ASLContrastTechnique\": \"CONTINUOUS\",\n"); + if (d.aslFlagsGE & kASL_FLAG_GE_PSEUDOCONTINUOUS) + fprintf(fp, "\t\"ASLContrastTechnique\": \"PSEUDOCONTINUOUS\",\n"); + if (d.aslFlagsGE & kASL_FLAG_GE_3DPCASL) + fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D pulsed continuous ASL technique\",\n"); + if (d.aslFlagsGE & kASL_FLAG_GE_3DCASL) + fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D continuous ASL technique\",\n"); if (d.durationLabelPulseGE > 0) { - json_Float(fp, "\t\"LabelingDuration\": %g,\n", d.durationLabelPulseGE / 1000.0); - json_Float(fp, "\t\"PostLabelingDelay\": %g,\n", d.TI / 1000.0); //For GE ASL: InversionTime -> Post-label delay + json_Float(fp, "\t\"LabelingDuration\": %g,\n", d.durationLabelPulseGE / 1000.0); + json_Float(fp, "\t\"PostLabelingDelay\": %g,\n", d.TI / 1000.0); //For GE ASL: InversionTime -> Post-label delay } json_Float(fp, "\t\"NumberOfPointsPerArm\": %g,\n", d.numberOfPointsPerArm); json_Float(fp, "\t\"NumberOfArms\": %g,\n", d.numberOfArms); @@ -1573,66 +1663,69 @@ tse3d: T2*/ fprintf(fp, "\t\"MultibandAccelerationFactor\": %d,\n", d.CSA.multiBandFactor); json_Float(fp, "\t\"PercentPhaseFOV\": %g,\n", d.phaseFieldofView); json_Float(fp, "\t\"PercentSampling\": %g,\n", d.percentSampling); - if (d.echoTrainLength > 1) //>1 as for Siemens EPI this is 1, Siemens uses EPI factor http://mriquestions.com/echo-planar-imaging.html + 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); //0018,0091 Combination of partial fourier and in-plane parallel imaging - if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_PHASE) - fprintf(fp, "\t\"PartialFourierDirection\": \"PHASE\",\n" ); - if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_FREQUENCY) - fprintf(fp, "\t\"PartialFourierDirection\": \"FREQUENCY\",\n" ); - if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT) - fprintf(fp, "\t\"PartialFourierDirection\": \"SLICE_SELECT\",\n" ); - if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_COMBINATION) - fprintf(fp, "\t\"PartialFourierDirection\": \"COMBINATION\",\n" ); - if ((d.phaseEncodingSteps > 0) && (d.isPartialFourier) && (d.manufacturer == kMANUFACTURER_PHILIPS)) { - //issue 377 - fprintf(fp, "\t\"PartialFourierEnabled\": \"YES\",\n" ); - fprintf(fp, "\t\"PhaseEncodingStepsNoPartialFourier\": %d,\n", d.phaseEncodingSteps ); - } else if (d.phaseEncodingSteps > 0) - fprintf(fp, "\t\"PhaseEncodingSteps\": %d,\n", d.phaseEncodingSteps ); - if ((d.phaseEncodingLines > 0) && (d.modality == kMODALITY_MR)) fprintf(fp, "\t\"AcquisitionMatrixPE\": %d,\n", d.phaseEncodingLines ); - + if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_PHASE) + fprintf(fp, "\t\"PartialFourierDirection\": \"PHASE\",\n"); + if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_FREQUENCY) + fprintf(fp, "\t\"PartialFourierDirection\": \"FREQUENCY\",\n"); + if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT) + fprintf(fp, "\t\"PartialFourierDirection\": \"SLICE_SELECT\",\n"); + if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_COMBINATION) + fprintf(fp, "\t\"PartialFourierDirection\": \"COMBINATION\",\n"); + if ((d.phaseEncodingSteps > 0) && (d.isPartialFourier) && (d.manufacturer == kMANUFACTURER_PHILIPS)) { + //issue 377 + fprintf(fp, "\t\"PartialFourierEnabled\": \"YES\",\n"); + fprintf(fp, "\t\"PhaseEncodingStepsNoPartialFourier\": %d,\n", d.phaseEncodingSteps); + } else if (d.phaseEncodingSteps > 0) + fprintf(fp, "\t\"PhaseEncodingSteps\": %d,\n", d.phaseEncodingSteps); + if ((d.phaseEncodingLines > 0) && (d.modality == kMODALITY_MR)) + fprintf(fp, "\t\"AcquisitionMatrixPE\": %d,\n", d.phaseEncodingLines); //Compute ReconMatrixPE // Actual size of the *reconstructed* data in the PE dimension, which does NOT match // phaseEncodingLines in the case of interpolation or phaseResolution < 100% // We'll need this for generating a value for effectiveEchoSpacing that is consistent // with the *reconstructed* data. int reconMatrixPE = d.phaseEncodingLines; - if ((h->dim[2] > 0) && (h->dim[1] > 0)) { - if (h->dim[1] == h->dim[2]) //phase encoding does not matter + if ((h->dim[2] > 0) && (h->dim[1] > 0)) { + if (h->dim[1] == h->dim[2]) //phase encoding does not matter reconMatrixPE = h->dim[2]; - else if (d.phaseEncodingRC =='C') + else if (d.phaseEncodingRC == 'C') reconMatrixPE = h->dim[2]; //see dcm_qa: NOPF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_200PFOV_AP_0034 - else if (d.phaseEncodingRC =='R') + else if (d.phaseEncodingRC == 'R') reconMatrixPE = h->dim[1]; - } - if ((d.modality == kMODALITY_MR) && (reconMatrixPE > 0)) fprintf(fp, "\t\"ReconMatrixPE\": %d,\n", reconMatrixPE ); - double bandwidthPerPixelPhaseEncode = d.bandwidthPerPixelPhaseEncode; - if (bandwidthPerPixelPhaseEncode == 0.0) - bandwidthPerPixelPhaseEncode = d.CSA.bandwidthPerPixelPhaseEncode; - json_Float(fp, "\t\"BandwidthPerPixelPhaseEncode\": %g,\n", bandwidthPerPixelPhaseEncode ); - //if ((!d.is3DAcq) && (d.accelFactPE > 1.0)) fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); - if (d.accelFactPE > 1.0) fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); + } + if ((d.modality == kMODALITY_MR) && (reconMatrixPE > 0)) + fprintf(fp, "\t\"ReconMatrixPE\": %d,\n", reconMatrixPE); + double bandwidthPerPixelPhaseEncode = d.bandwidthPerPixelPhaseEncode; + if (bandwidthPerPixelPhaseEncode == 0.0) + bandwidthPerPixelPhaseEncode = d.CSA.bandwidthPerPixelPhaseEncode; + json_Float(fp, "\t\"BandwidthPerPixelPhaseEncode\": %g,\n", bandwidthPerPixelPhaseEncode); + //if ((!d.is3DAcq) && (d.accelFactPE > 1.0)) fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); + if (d.accelFactPE > 1.0) + fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); json_Str(fp, "\t\"ParallelAcquisitionTechnique\": \"%s\",\n", d.parallelAcquisitionTechnique); - //https://github.com/rordenlab/dcm2niix/issues/314 - if (d.accelFactOOP > 1.0) fprintf(fp, "\t\"ParallelReductionOutOfPlane\": %g,\n", d.accelFactOOP); + //https://github.com/rordenlab/dcm2niix/issues/314 + if (d.accelFactOOP > 1.0) + fprintf(fp, "\t\"ParallelReductionOutOfPlane\": %g,\n", d.accelFactOOP); //EffectiveEchoSpacing // Siemens bandwidthPerPixelPhaseEncode already accounts for the effects of parallel imaging, // interpolation, phaseOversampling, and phaseResolution, in the context of the size of the // *reconstructed* data in the PE dimension - double effectiveEchoSpacing = 0.0; - //next: dicm2nii's method for determining effectiveEchoSpacing if bandwidthPerPixelPhaseEncode is unknown, see issue 315 + double effectiveEchoSpacing = 0.0; + //next: dicm2nii's method for determining effectiveEchoSpacing if bandwidthPerPixelPhaseEncode is unknown, see issue 315 //if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode <= 0.0) && (d.CSA.sliceMeasurementDuration >= 0)) - // effectiveEchoSpacing = d.CSA.sliceMeasurementDuration / (reconMatrixPE * 1000.0); + // effectiveEchoSpacing = d.CSA.sliceMeasurementDuration / (reconMatrixPE * 1000.0); if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode > 0.0)) - effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * reconMatrixPE); + effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * reconMatrixPE); json_Float(fp, "\t\"WaterFatShift\": %g,\n", d.waterFatShift); - if ((effectiveEchoSpacing == 0.0) && (d.imagingFrequency > 0.0) && (d.waterFatShift != 0.0) && (d.echoTrainLength > 0) && (reconMatrixPE > 1)) { - //in theory we could use either fieldStrength or imagingFrequency, but the former is typically provided with low precision + if ((effectiveEchoSpacing == 0.0) && (d.imagingFrequency > 0.0) && (d.waterFatShift != 0.0) && (d.echoTrainLength > 0) && (reconMatrixPE > 1)) { + //in theory we could use either fieldStrength or imagingFrequency, but the former is typically provided with low precision //https://github.com/rordenlab/dcm2niix/issues/377 - // EchoSpacing 1/BW/EPI_factor https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=ind1308&L=FSL&D=0&P=113520 - // this formula from https://support.brainvoyager.com/brainvoyager/functional-analysis-preparation/29-pre-processing/78-epi-distortion-correction-echo-spacing-and-bandwidth - // https://neurostars.org/t/consolidating-epi-echo-spacing-and-readout-time-for-philips-scanner/4406 - /* + // EchoSpacing 1/BW/EPI_factor https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=ind1308&L=FSL&D=0&P=113520 + // this formula from https://support.brainvoyager.com/brainvoyager/functional-analysis-preparation/29-pre-processing/78-epi-distortion-correction-echo-spacing-and-bandwidth + // https://neurostars.org/t/consolidating-epi-echo-spacing-and-readout-time-for-philips-scanner/4406 + /* ActualEchoSpacing = WaterFatShift / (ImagingFrequency * 3.4 * (EPI_Factor + 1)) TotalReadoutTIme = ActualEchoSpacing * EPI_Factor EffectiveEchoSpacing = TotalReadoutTime / (ReconMatrixPE - 1) @@ -1640,75 +1733,79 @@ tse3d: T2*/ ImagingFrequency = 0018,0084 EPI_Factor = 0018,0091 or 2001,1013 ReconMatrixPE = 0028,0010 or 0028,0011 depending on 0018,1312 - - */ + */ float actualEchoSpacing = d.waterFatShift / (d.imagingFrequency * 3.4 * (d.echoTrainLength + 1)); - float totalReadoutTime = actualEchoSpacing * d.echoTrainLength; + float totalReadoutTime = actualEchoSpacing * d.echoTrainLength; float effectiveEchoSpacingPhil = totalReadoutTime / (reconMatrixPE - 1); json_Float(fp, "\t\"EstimatedEffectiveEchoSpacing\": %g,\n", effectiveEchoSpacingPhil); - fprintf(fp, "\t\"EstimatedTotalReadoutTime\": %g,\n", totalReadoutTime); - } + } if (d.effectiveEchoSpacingGE > 0.0) { //TotalReadoutTime = [ ceil (PE_AcquisitionMatrix / Asset_R_factor) - 1] * ESP - float roundFactor = 2.0; - if (d.isPartialFourier) roundFactor = 4.0; - float totalReadoutTime = ((ceil (1/roundFactor * d.phaseEncodingLines / d.accelFactPE) * roundFactor) - 1.0) * d.effectiveEchoSpacingGE * 0.000001; + float roundFactor = 2.0; + if (d.isPartialFourier) + roundFactor = 4.0; + float totalReadoutTime = ((ceil(1 / roundFactor * d.phaseEncodingLines / d.accelFactPE) * roundFactor) - 1.0) * d.effectiveEchoSpacingGE * 0.000001; //printf("ASSET= %g PE_AcquisitionMatrix= %d ESP= %d TotalReadoutTime= %g\n", d.accelFactPE, d.phaseEncodingLines, d.effectiveEchoSpacingGE, totalReadoutTime); //json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", totalReadoutTime); - effectiveEchoSpacing = totalReadoutTime / (reconMatrixPE - 1); + effectiveEchoSpacing = totalReadoutTime / (reconMatrixPE - 1); } - json_Float(fp, "\t\"EffectiveEchoSpacing\": %g,\n", effectiveEchoSpacing); + json_Float(fp, "\t\"EffectiveEchoSpacing\": %g,\n", effectiveEchoSpacing); // Calculate true echo spacing (should match what Siemens reports on the console) // i.e., should match "echoSpacing" extracted from the ASCII CSA header, when that exists - double trueESfactor = 1.0; - if (d.accelFactPE > 1.0) trueESfactor /= d.accelFactPE; + double trueESfactor = 1.0; + if (d.accelFactPE > 1.0) + trueESfactor /= d.accelFactPE; if (phaseOversampling > 0.0) - trueESfactor *= (1.0 + phaseOversampling); + trueESfactor *= (1.0 + phaseOversampling); float derivedEchoSpacing = 0.0; derivedEchoSpacing = bandwidthPerPixelPhaseEncode * trueESfactor * reconMatrixPE; - if (derivedEchoSpacing != 0) derivedEchoSpacing = 1/derivedEchoSpacing; + if (derivedEchoSpacing != 0) + derivedEchoSpacing = 1 / derivedEchoSpacing; json_Float(fp, "\t\"DerivedVendorReportedEchoSpacing\": %g,\n", derivedEchoSpacing); - //TotalReadOutTime: Really should be called "EffectiveReadOutTime", by analogy with "EffectiveEchoSpacing". + //TotalReadOutTime: Really should be called "EffectiveReadOutTime", by analogy with "EffectiveEchoSpacing". // But BIDS spec calls it "TotalReadOutTime". // So, we DO NOT USE EchoTrainLength, because not trying to compute the actual (physical) readout time. // Rather, the point of computing "EffectiveEchoSpacing" properly is so that this // "Total(Effective)ReadOutTime" can be computed straightforwardly as the product of the // EffectiveEchoSpacing and the size of the *reconstructed* matrix in the PE direction. - // see https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup/TopupUsersGuide#A--datain + // see https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup/TopupUsersGuide#A--datain // FSL definition is start of first line until start of last line. // Other than the use of (n-1), the value is basically just 1.0/bandwidthPerPixelPhaseEncode. - // https://github.com/rordenlab/dcm2niix/issues/130 - if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0) && (d.manufacturer != kMANUFACTURER_UIH)) - json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", effectiveEchoSpacing * (reconMatrixPE - 1.0)); //do not store INF issue 512 - if (d.manufacturer == kMANUFACTURER_UIH) //https://github.com/rordenlab/dcm2niix/issues/225 - json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", d.acquisitionDuration / 1000.0); - json_Float(fp, "\t\"PixelBandwidth\": %g,\n", d.pixelBandwidth ); + // https://github.com/rordenlab/dcm2niix/issues/130 + if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0) && (d.manufacturer != kMANUFACTURER_UIH)) + json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", effectiveEchoSpacing * (reconMatrixPE - 1.0)); //do not store INF issue 512 + if (d.manufacturer == kMANUFACTURER_UIH) //https://github.com/rordenlab/dcm2niix/issues/225 + json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", d.acquisitionDuration / 1000.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 int phPos = d.CSA.phaseEncodingDirectionPositive; //next two conditionals updated: make GE match Siemens - if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) phPos = 1; - if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED) phPos = 0; - //if ((phPos >= 0) && (d.phaseEncodingRC == 'R') && (d.manufacturer == kMANUFACTURER_UIH)) phPos = 1 - phPos; //issue410 - bool isSkipPhaseEncodingAxis = d.is3DAcq; - if (d.echoTrainLength > 1) isSkipPhaseEncodingAxis = false; //issue 371: ignore phaseEncoding for 3D MP-RAGE/SPACE, but report for 3D EPI - if (((d.phaseEncodingRC == 'R') || (d.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && (phPos < 0)) { + if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) + phPos = 1; + if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED) + phPos = 0; + //if ((phPos >= 0) && (d.phaseEncodingRC == 'R') && (d.manufacturer == kMANUFACTURER_UIH)) phPos = 1 - phPos; //issue410 + bool isSkipPhaseEncodingAxis = d.is3DAcq; + if (d.echoTrainLength > 1) + isSkipPhaseEncodingAxis = false; //issue 371: ignore phaseEncoding for 3D MP-RAGE/SPACE, but report for 3D EPI + if (((d.phaseEncodingRC == 'R') || (d.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && (phPos < 0)) { //when phase encoding axis is known but we do not know phase encoding polarity // https://github.com/rordenlab/dcm2niix/issues/163 // This will typically correspond with InPlanePhaseEncodingDirectionDICOM if (d.phaseEncodingRC == 'C') //Values should be "R"ow, "C"olumn or "?"Unknown fprintf(fp, "\t\"PhaseEncodingAxis\": \"j\",\n"); else if (d.phaseEncodingRC == 'R') - fprintf(fp, "\t\"PhaseEncodingAxis\": \"i\",\n"); + fprintf(fp, "\t\"PhaseEncodingAxis\": \"i\",\n"); } - if (((d.phaseEncodingRC == 'R') || (d.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && (phPos >= 0)) { + if (((d.phaseEncodingRC == 'R') || (d.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && (phPos >= 0)) { //printf("%ld %d %d %c %d\n", d.seriesNum, d.echoTrainLength, isSkipPhaseEncodingAxis, d.phaseEncodingRC, phPos); //test issue 371 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"); + fprintf(fp, "\t\"PhaseEncodingDirection\": \"i"); else fprintf(fp, "\t\"PhaseEncodingDirection\": \"?"); //phaseEncodingDirectionPositive has one of three values: UNKNOWN (-1), NEGATIVE (0), POSITIVE (1) @@ -1728,11 +1825,11 @@ tse3d: T2*/ //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_CT) && (d.modality != kMODALITY_PT) && (!d.is3DAcq) && (d.CSA.sliceTiming[0] >= 0.0)) { - fprintf(fp, "\t\"SliceTiming\": [\n"); + fprintf(fp, "\t\"SliceTiming\": [\n"); for (int i = 0; i < h->dim[3]; i++) { if (i != 0) fprintf(fp, ",\n"); - fprintf(fp, "\t\t%g", d.CSA.sliceTiming[i] / 1000.0 ); + fprintf(fp, "\t\t%g", d.CSA.sliceTiming[i] / 1000.0); } fprintf(fp, "\t],\n"); } @@ -1745,41 +1842,44 @@ tse3d: T2*/ } fprintf(fp, "\t],\n"); json_Str(fp, "\t\"ImageOrientationText\": \"%s\",\n", d.imageOrientationText); - if (d.phaseEncodingRC == 'C') fprintf(fp, "\t\"InPlanePhaseEncodingDirectionDICOM\": \"COL\",\n" ); - if (d.phaseEncodingRC == 'R') fprintf(fp, "\t\"InPlanePhaseEncodingDirectionDICOM\": \"ROW\",\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", kDCMdate ); + fprintf(fp, "\t\"ConversionSoftwareVersion\": \"%s\"\n", kDCMdate); //fprintf(fp, "\t\"ConversionSoftwareVersion\": \"%s\"\n", kDCMvers );kDCMdate fprintf(fp, "}\n"); - fclose(fp); -}// nii_SaveBIDSX() + fclose(fp); +} // nii_SaveBIDSX() #ifndef USING_R -void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char * filename) { -struct TDTI4D *dti4D; -dti4D->sliceOrder[0] = -1; -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() +void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char *filename) { + struct TDTI4D *dti4D; + dti4D->sliceOrder[0] = -1; + 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() #endif bool isADCnotDTI(TDTI bvec) { //returns true if bval!=0 but all bvecs == 0 (Philips code for derived ADC image) - return ((!isSameFloat(bvec.V[0],0.0f)) && //not a B-0 image - ((isSameFloat(bvec.V[1],0.0f)) && (isSameFloat(bvec.V[2],0.0f)) && (isSameFloat(bvec.V[3],0.0f)) ) ); + return ((!isSameFloat(bvec.V[0], 0.0f)) && //not a B-0 image + ((isSameFloat(bvec.V[1], 0.0f)) && (isSameFloat(bvec.V[2], 0.0f)) && (isSameFloat(bvec.V[3], 0.0f)))); } -unsigned char * removeADC(struct nifti_1_header *hdr, unsigned char *inImg, int numADC) { -//for speed we just clip the number of volumes, the realloc routine would be nice -// we do not want to copy input to a new smaller array since 4D DTI datasets can be huge -// and that would require almost twice as much RAM - if (numADC < 1) return inImg; +unsigned char *removeADC(struct nifti_1_header *hdr, unsigned char *inImg, int numADC) { + //for speed we just clip the number of volumes, the realloc routine would be nice + // we do not want to copy input to a new smaller array since 4D DTI datasets can be huge + // and that would require almost twice as much RAM + if (numADC < 1) + return inImg; hdr->dim[4] = hdr->dim[4] - numADC; if (hdr->dim[4] < 2) hdr->dim[0] = 3; //e.g. 4D 2-volume DWI+ADC becomes 3D DWI if ADC is removed @@ -1788,45 +1888,47 @@ unsigned char * removeADC(struct nifti_1_header *hdr, unsigned char *inImg, int //#define naive_reorder_vols //for simple, fast re-ordering that consumes a lot of RAM #ifdef naive_reorder_vols -unsigned char * reorderVolumes(struct nifti_1_header *hdr, unsigned char *inImg, int * volOrderIndex) { -//reorder volumes to place ADC at end and (optionally) B=0 at start -// volOrderIndex[0] reports location of desired first volume -// naive solution creates an output buffer that doubles RAM usage (2 *numVol) +unsigned char *reorderVolumes(struct nifti_1_header *hdr, unsigned char *inImg, int *volOrderIndex) { + //reorder volumes to place ADC at end and (optionally) B=0 at start + // volOrderIndex[0] reports location of desired first volume + // naive solution creates an output buffer that doubles RAM usage (2 *numVol) int numVol = hdr->dim[4]; - int numVolBytes = hdr->dim[1]*hdr->dim[2]*hdr->dim[3]*(hdr->bitpix/8); - if ((!volOrderIndex) || (numVol < 1) || (numVolBytes < 1)) return inImg; + int numVolBytes = hdr->dim[1] * hdr->dim[2] * hdr->dim[3] * (hdr->bitpix / 8); + if ((!volOrderIndex) || (numVol < 1) || (numVolBytes < 1)) + return inImg; unsigned char *outImg = (unsigned char *)malloc(numVolBytes * numVol); int outPos = 0; for (int i = 0; i < numVol; i++) { memcpy(&outImg[outPos], &inImg[volOrderIndex[i] * numVolBytes], numVolBytes); // dest, src, bytes - outPos += numVolBytes; + outPos += numVolBytes; } //for each volume free(volOrderIndex); free(inImg); return outImg; } //reorderVolumes() #else // naive_reorder_vols -unsigned char * reorderVolumes(struct nifti_1_header *hdr, unsigned char *inImg, int * volOrderIndex) { -//reorder volumes to place ADC at end and (optionally) B=0 at start -// volOrderIndex[0] reports location of desired first volume -// complicated by fact that 4D DTI data is often huge -// simple solutions would create an output buffer that would double RAM usage (2 *numVol) -// here we bubble-sort volumes in place to use numVols+1 memory +unsigned char *reorderVolumes(struct nifti_1_header *hdr, unsigned char *inImg, int *volOrderIndex) { + //reorder volumes to place ADC at end and (optionally) B=0 at start + // volOrderIndex[0] reports location of desired first volume + // complicated by fact that 4D DTI data is often huge + // simple solutions would create an output buffer that would double RAM usage (2 *numVol) + // here we bubble-sort volumes in place to use numVols+1 memory int numVol = hdr->dim[4]; - int numVolBytes = hdr->dim[1]*hdr->dim[2]*hdr->dim[3]*(hdr->bitpix/8); - int * inPos = (int *) malloc(numVol * sizeof(int)); + int numVolBytes = hdr->dim[1] * hdr->dim[2] * hdr->dim[3] * (hdr->bitpix / 8); + int *inPos = (int *)malloc(numVol * sizeof(int)); for (int i = 0; i < numVol; i++) - inPos[i] = i; + inPos[i] = i; unsigned char *tempVol = (unsigned char *)malloc(numVolBytes); int outPos = 0; for (int o = 0; o < numVol; o++) { int i = inPos[volOrderIndex[o]]; //input volume - if (i == o) continue; //volume in correct order + if (i == o) + continue; //volume in correct order memcpy(&tempVol[0], &inImg[o * numVolBytes], numVolBytes); //make temp - memcpy(&inImg[o * numVolBytes], &inImg[i * numVolBytes], numVolBytes); //copy volume to desire location dest, src, bytes - memcpy(&inImg[i * numVolBytes], &tempVol[0], numVolBytes); //copy unsorted volume - inPos[o] = i; - outPos += numVolBytes; + memcpy(&inImg[o * numVolBytes], &inImg[i * numVolBytes], numVolBytes); //copy volume to desire location dest, src, bytes + memcpy(&inImg[i * numVolBytes], &tempVol[0], numVolBytes); //copy unsorted volume + inPos[o] = i; + outPos += numVolBytes; } //for each volume free(inPos); free(volOrderIndex); @@ -1835,60 +1937,69 @@ unsigned char * reorderVolumes(struct nifti_1_header *hdr, unsigned char *inImg, } //reorderVolumes() #endif // naive_reorder_vols -float * bvals; //global variable for cmp_bvals -int cmp_bvals(const void *a, const void *b){ - int ia = *(int *)a; - int ib = *(int *)b; - //return bvals[ia] > bvals[ib] ? -1 : bvals[ia] < bvals[ib]; - return bvals[ia] < bvals[ib] ? -1 : bvals[ia] > bvals[ib]; +float *bvals; //global variable for cmp_bvals +int cmp_bvals(const void *a, const void *b) { + int ia = *(int *)a; + int ib = *(int *)b; + //return bvals[ia] > bvals[ib] ? -1 : bvals[ia] < bvals[ib]; + return bvals[ia] < bvals[ib] ? -1 : bvals[ia] > bvals[ib]; } // cmp_bvals() bool isAllZeroFloat(float v1, float v2, float v3) { - if (!isSameFloatGE(v1, 0.0)) return false; - if (!isSameFloatGE(v2, 0.0)) return false; - if (!isSameFloatGE(v3, 0.0)) return false; + if (!isSameFloatGE(v1, 0.0)) + return false; + if (!isSameFloatGE(v2, 0.0)) + return false; + if (!isSameFloatGE(v3, 0.0)) + return false; return true; } -int * nii_saveDTI(char pathoutname[],int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmList[], struct TDCMopts opts, int sliceDir, struct TDTI4D *dti4D, int * numADC, int numVol) { - //reports non-zero if any volumes should be excluded (e.g. philip stores an ADC maps) - *numADC = 0; - if (opts.isOnlyBIDS) return NULL; - uint64_t indx0 = dcmSort[0].indx; //first volume - int numDti = dcmList[indx0].CSA.numDti; +int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TDCMopts opts, int sliceDir, struct TDTI4D *dti4D, int *numADC, int numVol) { + //reports non-zero if any volumes should be excluded (e.g. philip stores an ADC maps) + *numADC = 0; + if (opts.isOnlyBIDS) + return NULL; + uint64_t indx0 = dcmSort[0].indx; //first volume + int numDti = dcmList[indx0].CSA.numDti; #ifdef USING_R - ImageList *images = (ImageList *) opts.imageList; + ImageList *images = (ImageList *)opts.imageList; #endif - //https://github.com/rordenlab/dcm2niix/issues/352 - bool allB0 = dcmList[indx0].isDiffusion; - if (dcmList[indx0].isDerived) allB0 = false; //e.g. FA map - if ((numDti == numVol) && (numDti > 1)) allB0 = false; - if (numDti > 1) allB0 = false; - if (nConvert > 1) allB0 = false; - if ((numDti == 1) && (dti4D->S[0].V[0] > 50.0)) allB0 = false; - if (allB0) { - if (opts.isVerbose) + //https://github.com/rordenlab/dcm2niix/issues/352 + bool allB0 = dcmList[indx0].isDiffusion; + if (dcmList[indx0].isDerived) + allB0 = false; //e.g. FA map + if ((numDti == numVol) && (numDti > 1)) + allB0 = false; + if (numDti > 1) + allB0 = false; + if (nConvert > 1) + allB0 = false; + if ((numDti == 1) && (dti4D->S[0].V[0] > 50.0)) + allB0 = false; + if (allB0) { + if (opts.isVerbose) printMessage("Diffusion image without gradients: assuming %d volume B=0 series\n", numVol); #ifdef USING_R // The image hasn't been created yet, so the attributes must be deferred - images->addDeferredAttribute("bValues", std::vector(numVol,0.0)); - images->addDeferredAttribute("bVectors", std::vector(numVol*3,0.0), numVol, 3); + images->addDeferredAttribute("bValues", std::vector(numVol, 0.0)); + images->addDeferredAttribute("bVectors", std::vector(numVol * 3, 0.0), numVol, 3); #else - char sep = '\t'; + char sep = '\t'; if (opts.isCreateBIDS) - sep = ' '; - //save bval - char txtname[2048] = {""}; - strcpy (txtname,pathoutname); - strcat (txtname,".bval"); + sep = ' '; + //save bval + char txtname[2048] = {""}; + strcpy(txtname, pathoutname); + strcat(txtname, ".bval"); FILE *fp = fopen(txtname, "w"); for (int i = 0; i < (numVol); i++) fprintf(fp, "%d%c", 0, sep); fprintf(fp, "\n"); fclose(fp); //save bvec - strcpy (txtname,pathoutname); - strcat (txtname,".bvec"); + strcpy(txtname, pathoutname); + strcat(txtname, ".bvec"); fp = fopen(txtname, "w"); for (int v = 0; v < (3); v++) { for (int i = 0; i < (numVol); i++) @@ -1897,93 +2008,102 @@ int * nii_saveDTI(char pathoutname[],int nConvert, struct TDCMsort dcmSort[],str } fclose(fp); #endif - } - if (numDti < 1) return NULL; - if ((numDti < 3) && (nConvert < 3)) return NULL; - TDTI * vx = NULL; - if (numDti > 2) { - vx = (TDTI *)malloc(numDti * sizeof(TDTI)); - for (int i = 0; i < numDti; i++) //for each direction - for (int v = 0; v < 4; v++) //for each vector+B-value - vx[i].V[v] = dti4D->S[i].V[v]; - } else { //if (numDti == 1) {//extract DTI from different slices - vx = (TDTI *)malloc(nConvert * sizeof(TDTI)); - numDti = 0; - for (int i = 0; i < nConvert; i++) { //for each image - if ((dcmList[indx0].CSA.mosaicSlices > 1) || (isSamePosition(dcmList[indx0],dcmList[dcmSort[i].indx]))) { - //if (numDti < kMaxDTIv) - for (int v = 0; v < 4; v++) //for each vector+B-value - vx[numDti].V[v] = dcmList[dcmSort[i].indx].CSA.dtiV[v]; //dcmList[indx0].CSA.dtiV[numDti][v] = dcmList[dcmSort[i].indx].CSA.dtiV[0][v]; - numDti++; - } //for slices with repeats - }//for each file - dcmList[indx0].CSA.numDti = numDti; //warning structure not changed outside scope! - } - bool bValueVaries = false; - for (int i = 1; i < numDti; i++) //check if all bvalues match first volume - if (vx[i].V[0] != vx[0].V[0]) bValueVaries = true; - //optional: record b-values even without variability - float minBval = vx[0].V[0]; - for (int i = 1; i < numDti; i++) //check if all bvalues match first volume - if (vx[i].V[0] < minBval) minBval = vx[i].V[0]; - if (minBval > 50.0) bValueVaries = true; - //start issue394: experimental, single volume per series, Siemens XA - if (!isAllZeroFloat(vx[0].V[1], vx[0].V[2], vx[0].V[3])) - bValueVaries = true; - //end issue394 - if (!bValueVaries) { - bool bVecVaries = false; - for (int i = 1; i < numDti; i++) {//check if all bvalues match first volume - if (vx[i].V[1] != vx[0].V[1]) bVecVaries = true; - if (vx[i].V[2] != vx[0].V[2]) bVecVaries = true; - if (vx[i].V[3] != vx[0].V[3]) bVecVaries = true; - } - if (!bVecVaries) { + } + if (numDti < 1) + return NULL; + if ((numDti < 3) && (nConvert < 3)) + return NULL; + TDTI *vx = NULL; + if (numDti > 2) { + vx = (TDTI *)malloc(numDti * sizeof(TDTI)); + for (int i = 0; i < numDti; i++) //for each direction + for (int v = 0; v < 4; v++) //for each vector+B-value + vx[i].V[v] = dti4D->S[i].V[v]; + } else { //if (numDti == 1) {//extract DTI from different slices + vx = (TDTI *)malloc(nConvert * sizeof(TDTI)); + numDti = 0; + for (int i = 0; i < nConvert; i++) { //for each image + if ((dcmList[indx0].CSA.mosaicSlices > 1) || (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx]))) { + //if (numDti < kMaxDTIv) + for (int v = 0; v < 4; v++) //for each vector+B-value + vx[numDti].V[v] = dcmList[dcmSort[i].indx].CSA.dtiV[v]; //dcmList[indx0].CSA.dtiV[numDti][v] = dcmList[dcmSort[i].indx].CSA.dtiV[0][v]; + numDti++; + } //for slices with repeats + } //for each file + dcmList[indx0].CSA.numDti = numDti; //warning structure not changed outside scope! + } + bool bValueVaries = false; + for (int i = 1; i < numDti; i++) //check if all bvalues match first volume + if (vx[i].V[0] != vx[0].V[0]) + bValueVaries = true; + //optional: record b-values even without variability + float minBval = vx[0].V[0]; + for (int i = 1; i < numDti; i++) //check if all bvalues match first volume + if (vx[i].V[0] < minBval) + minBval = vx[i].V[0]; + if (minBval > 50.0) + bValueVaries = true; + //start issue394: experimental, single volume per series, Siemens XA + if (!isAllZeroFloat(vx[0].V[1], vx[0].V[2], vx[0].V[3])) + bValueVaries = true; + //end issue394 + if (!bValueVaries) { + bool bVecVaries = false; + for (int i = 1; i < numDti; i++) { //check if all bvalues match first volume + if (vx[i].V[1] != vx[0].V[1]) + bVecVaries = true; + if (vx[i].V[2] != vx[0].V[2]) + bVecVaries = true; + if (vx[i].V[3] != vx[0].V[3]) + bVecVaries = true; + } + if (!bVecVaries) { + free(vx); + return NULL; + } + if (opts.isVerbose) { + for (int i = 0; i < numDti; i++) + printMessage("bxyz %g %g %g %g\n", vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); + } + //Stutters XINAPSE7 seem to save B=0 as B=2000, but these are not derived? https://github.com/rordenlab/dcm2niix/issues/182 + bool bZeroBvec = false; + for (int i = 0; i < numDti; i++) { //check if all bvalues match first volume + if (isAllZeroFloat(vx[i].V[1], vx[i].V[2], vx[i].V[3])) { + vx[i].V[0] = 0; + //printWarning("volume %d might be B=0\n", i); + bZeroBvec = true; + } + } + if (bZeroBvec) + printWarning("Assuming volumes without gradients are actually B=0\n"); + else { + printWarning("No bvec/bval files created. Only one B-value reported for all volumes: %g\n", vx[0].V[0]); free(vx); return NULL; - } - if (opts.isVerbose) { - for (int i = 0; i < numDti; i++) - printMessage("bxyz %g %g %g %g\n",vx[i].V[0],vx[i].V[1],vx[i].V[2],vx[i].V[3]); - } - //Stutters XINAPSE7 seem to save B=0 as B=2000, but these are not derived? https://github.com/rordenlab/dcm2niix/issues/182 - bool bZeroBvec = false; - for (int i = 0; i < numDti; i++) {//check if all bvalues match first volume - if (isAllZeroFloat(vx[i].V[1], vx[i].V[2], vx[i].V[3])) { - vx[i].V[0] = 0; - //printWarning("volume %d might be B=0\n", i); - bZeroBvec = true; - } - } - if (bZeroBvec) - printWarning("Assuming volumes without gradients are actually B=0\n"); - else { - printWarning("No bvec/bval files created. Only one B-value reported for all volumes: %g\n",vx[0].V[0]); - free(vx); - return NULL; - } - } - //report values: - //for (int i = 1; i < numDti; i++) //check if all bvalues match first volume - // printMessage("%d bval= %g bvec= %g %g %g\n",i, vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); + } + } + //report values: + //for (int i = 1; i < numDti; i++) //check if all bvalues match first volume + // printMessage("%d bval= %g bvec= %g %g %g\n",i, vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); int minB0idx = 0; - float minB0 = vx[0].V[0]; - for (int i = 0; i < numDti; i++) - if (vx[i].V[0] < minB0) { - minB0 = vx[i].V[0]; - minB0idx = i; - } - float maxB0 = vx[0].V[0]; - for (int i = 0; i < numDti; i++) - if (vx[i].V[0] > maxB0) - maxB0 = vx[i].V[0]; - //for CMRR sequences unweighted volumes are not actually B=0 but they have B near zero - if (minB0 > 50) printWarning("This diffusion series does not have a B0 (reference) volume\n"); + float minB0 = vx[0].V[0]; + for (int i = 0; i < numDti; i++) + if (vx[i].V[0] < minB0) { + minB0 = vx[i].V[0]; + minB0idx = i; + } + float maxB0 = vx[0].V[0]; + for (int i = 0; i < numDti; i++) + if (vx[i].V[0] > maxB0) + maxB0 = vx[i].V[0]; + //for CMRR sequences unweighted volumes are not actually B=0 but they have B near zero + if (minB0 > 50) + printWarning("This diffusion series does not have a B0 (reference) volume\n"); if ((!opts.isSortDTIbyBVal) && (minB0idx > 0)) printMessage("Note: B0 not the first volume in the series (FSL eddy reference volume is %d)\n", minB0idx); float kADCval = maxB0 + 1; //mark as unusual - *numADC = 0; - bvals = (float *) malloc(numDti * sizeof(float)); + *numADC = 0; + bvals = (float *)malloc(numDti * sizeof(float)); int numGEwarn = 0; bool isGEADC = (dcmList[indx0].numberOfDiffusionDirectionGE == 0); for (int i = 0; i < numDti; i++) { @@ -1991,25 +2111,25 @@ int * nii_saveDTI(char pathoutname[],int nConvert, struct TDCMsort dcmSort[],str //printMessage("---bxyz %g %g %g %g\n",vx[i].V[0],vx[i].V[1],vx[i].V[2],vx[i].V[3]); //Philips includes derived isotropic images //if (((dcmList[indx0].manufacturer == kMANUFACTURER_GE) || (dcmList[indx0].manufacturer == kMANUFACTURER_PHILIPS)) && (isADCnotDTI(vx[i]))) { - if (((dcmList[indx0].manufacturer == kMANUFACTURER_GE)) && (isADCnotDTI(vx[i]))) { - numGEwarn += 1; - if (isGEADC) { //e.g. GE Trace where bval=900, bvec=0,0,0 - *numADC = *numADC + 1; - //printWarning("GE ADC volume %d\n", i+1); - bvals[i] = kADCval; - } else - vx[i].V[0] = 0; //e.g. GE raw B=0 where bval=900, bvec=0,0,0 - } //see issue 245 - if (((dcmList[indx0].manufacturer == kMANUFACTURER_PHILIPS)) && (isADCnotDTI(vx[i]))) { - *numADC = *numADC + 1; - bvals[i] = kADCval; - //printMessage("+++bxyz %d\n",i); - } - bvals[i] = bvals[i] + (0.5 * i/numDti); //add a small bias so ties are kept in sequential order + if (((dcmList[indx0].manufacturer == kMANUFACTURER_GE)) && (isADCnotDTI(vx[i]))) { + numGEwarn += 1; + if (isGEADC) { //e.g. GE Trace where bval=900, bvec=0,0,0 + *numADC = *numADC + 1; + //printWarning("GE ADC volume %d\n", i+1); + bvals[i] = kADCval; + } else + vx[i].V[0] = 0; //e.g. GE raw B=0 where bval=900, bvec=0,0,0 + } //see issue 245 + if (((dcmList[indx0].manufacturer == kMANUFACTURER_PHILIPS)) && (isADCnotDTI(vx[i]))) { + *numADC = *numADC + 1; + bvals[i] = kADCval; + //printMessage("+++bxyz %d\n",i); + } + bvals[i] = bvals[i] + (0.5 * i / numDti); //add a small bias so ties are kept in sequential order } if (numGEwarn > 0) printWarning("Some images had bval>0 but bvec=0 (either Trace or b=0, see issue 245)\n"); - /*if ((*numADC == numDti) || (numGEwarn == numDti)) { //issue 405: we now save bvals file for isotropic series + /*if ((*numADC == numDti) || (numGEwarn == numDti)) { //issue 405: we now save bvals file for isotropic series //all isotropic/ADC images - no valid bvecs *numADC = 0; free(bvals); @@ -2017,8 +2137,8 @@ int * nii_saveDTI(char pathoutname[],int nConvert, struct TDCMsort dcmSort[],str return NULL; }*/ bool isIsotropic = false; - if ((*numADC == numDti) || (numGEwarn == numDti)) //issue 405: we now save bvals file for isotropic series - isIsotropic = true; + if ((*numADC == numDti) || (numGEwarn == numDti)) //issue 405: we now save bvals file for isotropic series + isIsotropic = true; if (*numADC > 0) { // DWIs (i.e. short diffusion scans with too few directions to // calculate tensors...they typically acquire b=0 + 3 b > 0 so @@ -2033,187 +2153,182 @@ int * nii_saveDTI(char pathoutname[],int nConvert, struct TDCMsort dcmSort[],str // One hackish way to accomplish that is to set *numADC = 0 // when *numADC == 1 && numDti == 2. // - Rob Reid, 2017-11-29. - if ((*numADC == 1) && ((numDti - *numADC) < 2)){ + if ((*numADC == 1) && ((numDti - *numADC) < 2)) { *numADC = 0; printMessage("Note: this appears to be a b=0+trace DWI; ADC/trace removal has been disabled.\n"); - } - else{ - if ((numDti - *numADC) < 2) { + } else { + if ((numDti - *numADC) < 2) { /*if (!dcmList[indx0].isDerived) //no need to warn if images are derived Trace/ND pair - printWarning("No bvec/bval files created: only single value after ADC excluded\n"); + printWarning("No bvec/bval files created: only single value after ADC excluded\n"); *numADC = 0; free(bvals); free(vx); return NULL;*/ - printMessage("Warning: Isotropic DWI series, all bvecs are zero (issue 405)\n"); - *numADC = 0; - } else - printMessage("Note: %d volumes appear to be ADC or trace images that will be removed to allow processing\n", - *numADC); + printMessage("Warning: Isotropic DWI series, all bvecs are zero (issue 405)\n"); + *numADC = 0; + } else + printMessage("Note: %d volumes appear to be ADC or trace images that will be removed to allow processing\n", *numADC); } } //sort ALL including ADC - int * volOrderIndex = (int *) malloc(numDti * sizeof(int)); + int *volOrderIndex = (int *)malloc(numDti * sizeof(int)); for (int i = 0; i < numDti; i++) - volOrderIndex[i] = i; + volOrderIndex[i] = i; if (opts.isSortDTIbyBVal) qsort(volOrderIndex, numDti, sizeof(*volOrderIndex), cmp_bvals); else if (*numADC > 0) { int o = 0; for (int i = 0; i < numDti; i++) { if (bvals[i] < kADCval) { - volOrderIndex[o] = i; - o++; - } //if not ADC - } //for each volume + volOrderIndex[o] = i; + o++; + } //if not ADC + } //for each volume } //if sort else if has ADC free(bvals); //save VX as sorted - TDTI * vxOrig = (TDTI *)malloc(numDti * sizeof(TDTI)); + TDTI *vxOrig = (TDTI *)malloc(numDti * sizeof(TDTI)); for (int i = 0; i < numDti; i++) - vxOrig[i] = vx[i]; - //remove ADC + vxOrig[i] = vx[i]; + //remove ADC numDti = numDti - *numADC; free(vx); vx = (TDTI *)malloc(numDti * sizeof(TDTI)); - for (int i = 0; i < numDti; i++) - vx[i] = vxOrig[volOrderIndex[i]]; - free(vxOrig); - //if no ADC or sequential, the is no need to re-order volumes + for (int i = 0; i < numDti; i++) + vx[i] = vxOrig[volOrderIndex[i]]; + free(vxOrig); + //if no ADC or sequential, the is no need to re-order volumes bool isSequential = true; for (int i = 1; i < (numDti + *numADC); i++) - if (volOrderIndex[i] <= volOrderIndex[i-1]) + if (volOrderIndex[i] <= volOrderIndex[i - 1]) isSequential = false; if (isSequential) { free(volOrderIndex); volOrderIndex = NULL; } if (!isSequential) - printMessage("DTI volumes re-ordered by ascending b-value\n"); - #ifdef RAW_BVEC //save raw bvecs as reported by DICOM, not rotated to image space - char txtname[2048] = {""}; - strcpy(txtname,pathoutname); - strcat (txtname,".rvec"); - FILE *fp = fopen(txtname, "w"); - for (int i = 0; i < numDti; i++) - fprintf(fp, "%g\t",vx[i].V[1]); - fprintf(fp, "\n"); - for (int i = 0; i < numDti; i++) - fprintf(fp, "%g\t",vx[i].V[2]); - fprintf(fp, "\n"); - for (int i = 0; i < numDti; i++) - fprintf(fp, "%g\t",vx[i].V[3]); - fprintf(fp, "\n"); - fclose(fp); - #endif + printMessage("DTI volumes re-ordered by ascending b-value\n"); +#ifdef RAW_BVEC //save raw bvecs as reported by DICOM, not rotated to image space + char txtname[2048] = {""}; + strcpy(txtname, pathoutname); + strcat(txtname, ".rvec"); + FILE *fp = fopen(txtname, "w"); + for (int i = 0; i < numDti; i++) + fprintf(fp, "%g\t", vx[i].V[1]); + fprintf(fp, "\n"); + for (int i = 0; i < numDti; i++) + fprintf(fp, "%g\t", vx[i].V[2]); + fprintf(fp, "\n"); + for (int i = 0; i < numDti; i++) + fprintf(fp, "%g\t", vx[i].V[3]); + fprintf(fp, "\n"); + fclose(fp); +#endif 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) - vx[i].V[2] = -vx[i].V[2]; - } //for each direction - } //if not a mosaic - if (opts.isVerbose) { - for (int i = 0; i < (numDti); i++) { - printMessage("%d\tB=\t%g\tVec=\t%g\t%g\t%g\n",i, vx[i].V[0], - vx[i].V[1],vx[i].V[2],vx[i].V[3]); - - } //for each direction - } - //printMessage("%f\t%f\t%f",dcmList[indx0].CSA.dtiV[1][1],dcmList[indx0].CSA.dtiV[1][2],dcmList[indx0].CSA.dtiV[1][3]); + 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) + vx[i].V[2] = -vx[i].V[2]; + } //for each direction + } //if not a mosaic + if (opts.isVerbose) { + for (int i = 0; i < (numDti); i++) { + printMessage("%d\tB=\t%g\tVec=\t%g\t%g\t%g\n", i, vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); + } //for each direction + } + //printMessage("%f\t%f\t%f",dcmList[indx0].CSA.dtiV[1][1],dcmList[indx0].CSA.dtiV[1][2],dcmList[indx0].CSA.dtiV[1][3]); #ifdef USING_R - std::vector bValues(numDti); - std::vector bVectors(numDti*3); - for (int i = 0; i < numDti; i++) - { - bValues[i] = vx[i].V[0]; - for (int j = 0; j < 3; j++) - bVectors[i+j*numDti] = vx[i].V[j+1]; - } - // The image hasn't been created yet, so the attributes must be deferred - images->addDeferredAttribute("bValues", bValues); - images->addDeferredAttribute("bVectors", bVectors, numDti, 3); + std::vector bValues(numDti); + std::vector bVectors(numDti * 3); + for (int i = 0; i < numDti; i++) { + bValues[i] = vx[i].V[0]; + for (int j = 0; j < 3; j++) + bVectors[i + j * numDti] = vx[i].V[j + 1]; + } + // The image hasn't been created yet, so the attributes must be deferred + images->addDeferredAttribute("bValues", bValues); + images->addDeferredAttribute("bVectors", bVectors, numDti, 3); #else - if (opts.isSaveNRRD) { - if (numDti < kMaxDTI4D) { - dcmList[indx0].CSA.numDti = numDti; - for (int i = 0; i < numDti; i++) //for each direction - for (int v = 0; v < 4; v++) //for each vector+B-value - dti4D->S[i].V[v] = vx[i].V[v]; - } - free(vx); - return volOrderIndex; - } - char txtname[2048] = {""}; - strcpy (txtname,pathoutname); - strcat (txtname,".bval"); - //printMessage("Saving DTI %s\n",txtname); - FILE *fp = fopen(txtname, "w"); - if (fp == NULL) { - free(vx); - return volOrderIndex; - } - for (int i = 0; i < (numDti-1); i++) { - if (opts.isCreateBIDS) { - fprintf(fp, "%g ", vx[i].V[0]); - } else { - fprintf(fp, "%g\t", vx[i].V[0]); - } - } - fprintf(fp, "%g\n", vx[numDti-1].V[0]); - fclose(fp); - if (isIsotropic) { //issue 405: ISOTROPIC images have bval but not bvec - free(vx); - return volOrderIndex; - } - strcpy(txtname,pathoutname); - if (dcmList[indx0].isVectorFromBMatrix) - strcat (txtname,".mvec"); - else - strcat (txtname,".bvec"); - //printMessage("Saving DTI %s\n",txtname); - fp = fopen(txtname, "w"); - if (fp == NULL) { - free(vx); - return volOrderIndex; - } - for (int v = 1; v < 4; v++) { - for (int i = 0; i < (numDti-1); i++) { - if (opts.isCreateBIDS) { - fprintf(fp, "%g ", vx[i].V[v]); - } else { - fprintf(fp, "%g\t", vx[i].V[v]); - } - } - fprintf(fp, "%g\n", vx[numDti-1].V[v]); - } - fclose(fp); + if (opts.isSaveNRRD) { + if (numDti < kMaxDTI4D) { + dcmList[indx0].CSA.numDti = numDti; + for (int i = 0; i < numDti; i++) //for each direction + for (int v = 0; v < 4; v++) //for each vector+B-value + dti4D->S[i].V[v] = vx[i].V[v]; + } + free(vx); + return volOrderIndex; + } + char txtname[2048] = {""}; + strcpy(txtname, pathoutname); + strcat(txtname, ".bval"); + //printMessage("Saving DTI %s\n",txtname); + FILE *fp = fopen(txtname, "w"); + if (fp == NULL) { + free(vx); + return volOrderIndex; + } + for (int i = 0; i < (numDti - 1); i++) { + if (opts.isCreateBIDS) { + fprintf(fp, "%g ", vx[i].V[0]); + } else { + fprintf(fp, "%g\t", vx[i].V[0]); + } + } + fprintf(fp, "%g\n", vx[numDti - 1].V[0]); + fclose(fp); + if (isIsotropic) { //issue 405: ISOTROPIC images have bval but not bvec + free(vx); + return volOrderIndex; + } + strcpy(txtname, pathoutname); + if (dcmList[indx0].isVectorFromBMatrix) + strcat(txtname, ".mvec"); + else + strcat(txtname, ".bvec"); + //printMessage("Saving DTI %s\n",txtname); + fp = fopen(txtname, "w"); + if (fp == NULL) { + free(vx); + return volOrderIndex; + } + for (int v = 1; v < 4; v++) { + for (int i = 0; i < (numDti - 1); i++) { + if (opts.isCreateBIDS) { + fprintf(fp, "%g ", vx[i].V[v]); + } else { + fprintf(fp, "%g\t", vx[i].V[v]); + } + } + fprintf(fp, "%g\n", vx[numDti - 1].V[v]); + } + fclose(fp); #endif - free(vx); - return volOrderIndex; -}// nii_saveDTI() + free(vx); + return volOrderIndex; +} // nii_saveDTI() -float sqr(float v){ - return v*v; -}// sqr() +float sqr(float v) { + return v * v; +} // sqr() #ifdef newTilt //see issue 254 -float vec3Length (vec3 v) { //normalize vector length - return sqrt( (v.v[0]*v.v[0]) - + (v.v[1]*v.v[1]) - + (v.v[2]*v.v[2])); +float vec3Length(vec3 v) { //normalize vector length + return sqrt((v.v[0] * v.v[0]) + (v.v[1] * v.v[1]) + (v.v[2] * v.v[2])); } -float vec3maxMag (vec3 v) { //return signed vector with maximum magnitude +float vec3maxMag(vec3 v) { //return signed vector with maximum magnitude float mx = v.v[0]; - if (fabs(v.v[1]) > fabs(mx)) mx = v.v[1]; - if (fabs(v.v[2]) > fabs(mx)) mx = v.v[2]; + if (fabs(v.v[1]) > fabs(mx)) + mx = v.v[1]; + if (fabs(v.v[2]) > fabs(mx)) + mx = v.v[2]; return mx; } @@ -2221,83 +2336,95 @@ vec3 makePositive(vec3 v) { //we do not no order of cross product or order of instance number (e.g. head->foot, foot->head) // this function matches the polarity of slice direction inferred from patient position and image orient vec3 ret = v; - if (vec3maxMag(v) >= 0.0) return ret; + if (vec3maxMag(v) >= 0.0) + return ret; ret.v[0] = -ret.v[0]; ret.v[1] = -ret.v[1]; ret.v[2] = -ret.v[2]; return ret; } -void vecRep (vec3 v) { //normalize vector length - printMessage("[%g %g %g]\n", v.v[0], v.v[1], v.v[2]); +void vecRep(vec3 v) { //normalize vector length + printMessage("[%g %g %g]\n", v.v[0], v.v[1], v.v[2]); } //Precise method for determining gantry tilt // rationale: -// gantry tilt (0018,1120) is optional -// some tools may correct gantry tilt but not reset 0018,1120 -// 0018,1120 might be saved at low precision (though patientPosition, orient might be as well) +// gantry tilt (0018,1120) is optional +// some tools may correct gantry tilt but not reset 0018,1120 +// 0018,1120 might be saved at low precision (though patientPosition, orient might be as well) //https://github.com/rordenlab/dcm2niix/issues/253 float computeGantryTiltPrecise(struct TDICOMdata d1, struct TDICOMdata d2, int isVerbose) { float ret = 0.0; - if (isNanPosition(d1)) return ret; + if (isNanPosition(d1)) + return ret; vec3 slice_vector = setVec3(d2.patientPosition[1] - d1.patientPosition[1], - d2.patientPosition[2] - d1.patientPosition[2], - d2.patientPosition[3] - d1.patientPosition[3]); - float len = vec3Length(slice_vector); + d2.patientPosition[2] - d1.patientPosition[2], + d2.patientPosition[3] - d1.patientPosition[3]); + float len = vec3Length(slice_vector); if (isSameFloat(len, 0.0)) { slice_vector = setVec3(d1.patientPositionLast[1] - d1.patientPosition[1], - d1.patientPositionLast[2] - d1.patientPosition[2], - d1.patientPositionLast[3] - d1.patientPosition[3]); - len = vec3Length(slice_vector); - if (isSameFloat(len, 0.0)) return ret; + d1.patientPositionLast[2] - d1.patientPosition[2], + d1.patientPositionLast[3] - d1.patientPosition[3]); + len = vec3Length(slice_vector); + if (isSameFloat(len, 0.0)) + return ret; } - if (isnan(slice_vector.v[0])) return ret; + if (isnan(slice_vector.v[0])) + return ret; slice_vector = makePositive(slice_vector); - vec3 read_vector = setVec3(d1.orient[1],d1.orient[2],d1.orient[3]); - vec3 phase_vector = setVec3(d1.orient[4],d1.orient[5],d1.orient[6]); - vec3 slice_vector90 = crossProduct(read_vector ,phase_vector); //perpendicular - slice_vector90 = makePositive(slice_vector90); + vec3 read_vector = setVec3(d1.orient[1], d1.orient[2], d1.orient[3]); + vec3 phase_vector = setVec3(d1.orient[4], d1.orient[5], d1.orient[6]); + vec3 slice_vector90 = crossProduct(read_vector, phase_vector); //perpendicular + slice_vector90 = makePositive(slice_vector90); float len90 = vec3Length(slice_vector90); - if (isSameFloat(len90, 0.0)) return ret; - float dotX = dotProduct(slice_vector90, slice_vector); - float cosX = dotX / (len * len90); - float degX = acos(cosX) * (180.0 / M_PI); //arccos, radian -> degrees - if (!isSameFloat(cosX, 1.0)) + if (isSameFloat(len90, 0.0)) + return ret; + float dotX = dotProduct(slice_vector90, slice_vector); + float cosX = dotX / (len * len90); + float degX = acos(cosX) * (180.0 / M_PI); //arccos, radian -> degrees + if (!isSameFloat(cosX, 1.0)) ret = degX; - if ((isSameFloat(ret, 0.0)) && (isSameFloat(ret, d1.gantryTilt)) ) return 0.0; - //determine if gantry tilt is positive or negative - vec3 signv = crossProduct(slice_vector,slice_vector90); + if ((isSameFloat(ret, 0.0)) && (isSameFloat(ret, d1.gantryTilt))) + return 0.0; + //determine if gantry tilt is positive or negative + vec3 signv = crossProduct(slice_vector, slice_vector90); float sign = vec3maxMag(signv); - if (isSameFloatGE(ret, 0.0)) return 0.0; //parallel vectors - if (sign > 0.0) ret = -ret; //the length of len90 was negative, negative gantry tilt - //while (ret >= 89.99) ret -= 90; - //while (ret <= -89.99) ret += 90; - if (isSameFloatGE(ret, 0.0)) return 0.0; - if ((isVerbose) || (isnan(ret))) { - printMessage("Gantry Tilt Parameters (see issue 253)\n"); - printMessage(" Read ="); vecRep(read_vector); - printMessage(" Phase ="); vecRep(phase_vector); - printMessage(" CrossReadPhase ="); vecRep(slice_vector90); - printMessage(" Slice ="); vecRep(slice_vector); - } - printMessage("Gantry Tilt based on 0018,1120 %g, estimated from slice vector %g\n", d1.gantryTilt, ret); + if (isSameFloatGE(ret, 0.0)) + return 0.0; //parallel vectors + if (sign > 0.0) + ret = -ret; //the length of len90 was negative, negative gantry tilt + //while (ret >= 89.99) ret -= 90; + //while (ret <= -89.99) ret += 90; + if (isSameFloatGE(ret, 0.0)) + return 0.0; + if ((isVerbose) || (isnan(ret))) { + printMessage("Gantry Tilt Parameters (see issue 253)\n"); + printMessage(" Read ="); + vecRep(read_vector); + printMessage(" Phase ="); + vecRep(phase_vector); + printMessage(" CrossReadPhase ="); + vecRep(slice_vector90); + printMessage(" Slice ="); + vecRep(slice_vector); + } + printMessage("Gantry Tilt based on 0018,1120 %g, estimated from slice vector %g\n", d1.gantryTilt, ret); return ret; } #endif //newTilt //see issue 254 - float intersliceDistance(struct TDICOMdata d1, struct TDICOMdata d2) { - //some MRI scans have gaps between slices, some CT have overlapping slices. Comparing adjacent slices provides measure for dx between slices - if ( isNanPosition(d1) || isNanPosition(d2)) - return d1.xyzMM[3]; - float tilt = 1.0; - //printMessage("0020,0032 %g %g %g -> %g %g %g\n",d1.patientPosition[1],d1.patientPosition[2],d1.patientPosition[3],d2.patientPosition[1],d2.patientPosition[2],d2.patientPosition[3]); - if (d1.gantryTilt != 0) - tilt = (float) cos(d1.gantryTilt * M_PI/180); //for CT scans with gantry tilt, we need to compute distance between slices, not distance along bed - return tilt * sqrt( sqr(d1.patientPosition[1]-d2.patientPosition[1])+ - sqr(d1.patientPosition[2]-d2.patientPosition[2])+ - sqr(d1.patientPosition[3]-d2.patientPosition[3])); + //some MRI scans have gaps between slices, some CT have overlapping slices. Comparing adjacent slices provides measure for dx between slices + if (isNanPosition(d1) || isNanPosition(d2)) + return d1.xyzMM[3]; + float tilt = 1.0; + //printMessage("0020,0032 %g %g %g -> %g %g %g\n",d1.patientPosition[1],d1.patientPosition[2],d1.patientPosition[3],d2.patientPosition[1],d2.patientPosition[2],d2.patientPosition[3]); + if (d1.gantryTilt != 0) + tilt = (float)cos(d1.gantryTilt * M_PI / 180); //for CT scans with gantry tilt, we need to compute distance between slices, not distance along bed + return tilt * sqrt(sqr(d1.patientPosition[1] - d2.patientPosition[1]) + + sqr(d1.patientPosition[2] - d2.patientPosition[2]) + + sqr(d1.patientPosition[3] - d2.patientPosition[3])); } //intersliceDistance() //#define myInstanceNumberOrderIsNotSpatial @@ -2307,8 +2434,8 @@ float intersliceDistance(struct TDICOMdata d1, struct TDICOMdata d2) { // this situation is exceptionally rare, and there is a performance penalty // further, there may be unintended consequences. // Therefore, use of myInstanceNumberOrderIsNotSpatial is NOT recommended -// a better solution is to fix the sequences that generated those files -// as such images will probably disrupt most tools. +// a better solution is to fix the sequences that generated those files +// as such images will probably disrupt most tools. // This option is only to salvage borked data. // This code has also not been tested on data stored in TXYZ rather than XYZT order //#ifdef myInstanceNumberOrderIsNotSpatial @@ -2316,66 +2443,75 @@ float intersliceDistance(struct TDICOMdata d1, struct TDICOMdata d2) { float intersliceDistanceSigned(struct TDICOMdata d1, struct TDICOMdata d2) { //reports distance between two slices, signed as 2nd slice can be in front or behind 1st vec3 slice_vector = setVec3(d2.patientPosition[1] - d1.patientPosition[1], - d2.patientPosition[2] - d1.patientPosition[2], - d2.patientPosition[3] - d1.patientPosition[3]); - float len = vec3Length(slice_vector); - if (isSameFloat(len, 0.0)) return len; - if (d1.gantryTilt != 0) - len = len * cos(d1.gantryTilt * M_PI/180); - vec3 read_vector = setVec3(d1.orient[1],d1.orient[2],d1.orient[3]); - vec3 phase_vector = setVec3(d1.orient[4],d1.orient[5],d1.orient[6]); - vec3 slice_vector90 = crossProduct(read_vector ,phase_vector); //perpendicular + d2.patientPosition[2] - d1.patientPosition[2], + d2.patientPosition[3] - d1.patientPosition[3]); + float len = vec3Length(slice_vector); + if (isSameFloat(len, 0.0)) + return len; + if (d1.gantryTilt != 0) + len = len * cos(d1.gantryTilt * M_PI / 180); + vec3 read_vector = setVec3(d1.orient[1], d1.orient[2], d1.orient[3]); + vec3 phase_vector = setVec3(d1.orient[4], d1.orient[5], d1.orient[6]); + vec3 slice_vector90 = crossProduct(read_vector, phase_vector); //perpendicular float dot = dotProduct(slice_vector90, slice_vector); - if (dot < 0.0) return -len; + if (dot < 0.0) + return -len; return len; } //https://stackoverflow.com/questions/36714030/c-sort-float-array-while-keeping-track-of-indices/36714204 -struct TFloatSort{ - float value; - int index; +struct TFloatSort { + float value; + int index; }; - int compareTFloatSort(const void *a,const void *b){ - struct TFloatSort *a1 = (struct TFloatSort *)a; - struct TFloatSort *a2 = (struct TFloatSort*)b; - if((*a1).value > (*a2).value) return 1; - if((*a1).value < (*a2).value) return -1; - //if value is tied, retain index order (useful for TXYZ images?) - if((*a1).index > (*a2).index) return 1; - if((*a1).index < (*a2).index) return -1; - return 0; +int compareTFloatSort(const void *a, const void *b) { + struct TFloatSort *a1 = (struct TFloatSort *)a; + struct TFloatSort *a2 = (struct TFloatSort *)b; + if ((*a1).value > (*a2).value) + return 1; + if ((*a1).value < (*a2).value) + return -1; + //if value is tied, retain index order (useful for TXYZ images?) + if ((*a1).index > (*a2).index) + return 1; + if ((*a1).index < (*a2).index) + return -1; + return 0; } // compareTFloatSort() bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[]) { - //ensure slice position is sequential: either ascending [1 2 3] or descending [3 2 1], not [1 3 2], [3 1 2] etc. - //n.b. as currently designed, this will force swapDim3Dim4() for 4D data - int nConvert = d3 * d4; - if (d3 < 3) return true; //always consistent - float dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx],dcmList[dcmSort[1].indx]); + //ensure slice position is sequential: either ascending [1 2 3] or descending [3 2 1], not [1 3 2], [3 1 2] etc. + //n.b. as currently designed, this will force swapDim3Dim4() for 4D data + int nConvert = d3 * d4; + if (d3 < 3) + return true; //always consistent + float dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); bool isAscending1 = (dx > 0); - bool isConsistent = true; - for(int i=1; i < d3; i++) { - dx = intersliceDistanceSigned(dcmList[dcmSort[i-1].indx],dcmList[dcmSort[i].indx]); + bool isConsistent = true; + for (int i = 1; i < d3; i++) { + dx = intersliceDistanceSigned(dcmList[dcmSort[i - 1].indx], dcmList[dcmSort[i].indx]); bool isAscending = (dx > 0); - if (isAscending != isAscending1) isConsistent = false; //direction reverses + if (isAscending != isAscending1) + isConsistent = false; //direction reverses } - if (isConsistent) return true; + if (isConsistent) + return true; printWarning("Order specified by DICOM instance number is not spatial (reordering).\n"); - TFloatSort * floatSort = (TFloatSort *)malloc(d3 * sizeof(TFloatSort)); - for(int i=0; i < d3; i++) { - dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx],dcmList[dcmSort[i].indx]); + TFloatSort *floatSort = (TFloatSort *)malloc(d3 * sizeof(TFloatSort)); + for (int i = 0; i < d3; i++) { + dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); floatSort[i].value = dx; - floatSort[i].index=i; + floatSort[i].index = i; } - TDCMsort* dcmSortIn = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); - for(int i=0; i < nConvert; i++) + TDCMsort *dcmSortIn = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); + for (int i = 0; i < nConvert; i++) dcmSortIn[i] = dcmSort[i]; qsort(floatSort, d3, sizeof(struct TFloatSort), compareTFloatSort); //sort based on series and image numbers.... - for(int vol=0; vol < d4; vol++) { + for (int vol = 0; vol < d4; vol++) { int volInc = vol * d3; - for(int i=0; i < d3; i++) - dcmSort[volInc+i] = dcmSortIn[volInc+floatSort[i].index]; + for (int i = 0; i < d3; i++) + dcmSort[volInc + i] = dcmSortIn[volInc + floatSort[i].index]; } free(floatSort); free(dcmSortIn); @@ -2384,41 +2520,46 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s //#endif //myInstanceNumberOrderIsNotSpatial void swapDim3Dim4(int d3, int d4, struct TDCMsort dcmSort[]) { - //swap space and time: input A0,A1...An,B0,B1...Bn output A0,B0,A1,B1,... - int nConvert = d3 * d4; - TDCMsort * dcmSortIn = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); - for (int i = 0; i < nConvert; i++) dcmSortIn[i] = dcmSort[i]; - int i = 0; - for (int b = 0; b < d3; b++) - for (int a = 0; a < d4; a++) { - int k = (a *d3) + b; - //printMessage("%d -> %d %d ->%d\n",i,a, b, k); - dcmSort[k] = dcmSortIn[i]; - i++; - } + //swap space and time: input A0,A1...An,B0,B1...Bn output A0,B0,A1,B1,... + int nConvert = d3 * d4; + TDCMsort *dcmSortIn = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); + for (int i = 0; i < nConvert; i++) + dcmSortIn[i] = dcmSort[i]; + int i = 0; + for (int b = 0; b < d3; b++) + for (int a = 0; a < d4; a++) { + int k = (a * d3) + b; + //printMessage("%d -> %d %d ->%d\n",i,a, b, k); + dcmSort[k] = dcmSortIn[i]; + i++; + } free(dcmSortIn); } //swapDim3Dim4() -bool intensityScaleVaries(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmList[]){ - //detect whether some DICOM images report different intensity scaling - //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; - 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 (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 false; +bool intensityScaleVaries(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[]) { + //detect whether some DICOM images report different intensity scaling + //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; + 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 (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 false; } //intensityScaleVaries() /*unsigned char * nii_bgr2rgb(unsigned char* bImg, struct nifti_1_header *hdr) { //DICOM planarappears to be BBB..B,GGG..G,RRR..R, NIfTI RGB saved in planes RRR..RGGG..GBBBB..B - // see http://www.barre.nom.fr/medical/samples/index.html US-RGB-8-epicard + // see http://www.barre.nom.fr/medical/samples/index.html US-RGB-8-epicard if (hdr->datatype != DT_RGB24) return bImg; int dim3to7 = 1; for (int i = 3; i < 8; i++) @@ -2441,42 +2582,46 @@ bool intensityScaleVaries(int nConvert, struct TDCMsort dcmSort[],struct TDICOMd return bImg; } */ -void niiDeleteFnm(const char* outname, const char* ext) { - char niiname[2048] = {""}; - strcat (niiname,outname); - strcat (niiname,ext); - if (is_fileexists(niiname)) - remove(niiname); +void niiDeleteFnm(const char *outname, const char *ext) { + char niiname[2048] = {""}; + strcat(niiname, outname); + strcat(niiname, ext); + if (is_fileexists(niiname)) + remove(niiname); } -void niiDelete(const char*niiname) { +void niiDelete(const char *niiname) { //for niiname "~/d/img" delete img.nii, img.bvec, img.bval, img.json - niiDeleteFnm(niiname,".nii"); - niiDeleteFnm(niiname,".nii.gz"); - niiDeleteFnm(niiname,".nrrd"); - niiDeleteFnm(niiname,".nhdr"); - niiDeleteFnm(niiname,".raw.gz"); - niiDeleteFnm(niiname,".json"); - niiDeleteFnm(niiname,".bval"); - niiDeleteFnm(niiname,".bvec"); + niiDeleteFnm(niiname, ".nii"); + niiDeleteFnm(niiname, ".nii.gz"); + niiDeleteFnm(niiname, ".nrrd"); + niiDeleteFnm(niiname, ".nhdr"); + niiDeleteFnm(niiname, ".raw.gz"); + niiDeleteFnm(niiname, ".json"); + niiDeleteFnm(niiname, ".bval"); + niiDeleteFnm(niiname, ".bvec"); } -bool niiExists(const char*pathoutname) { - char niiname[2048] = {""}; - strcat (niiname,pathoutname); - strcat (niiname,".nii"); - if (is_fileexists(niiname)) return true; - char gzname[2048] = {""}; - strcat (gzname,pathoutname); - strcat (gzname,".nii.gz"); - if (is_fileexists(gzname)) return true; - strcpy (niiname,pathoutname); - strcat (niiname,".nrrd"); - if (is_fileexists(niiname)) return true; - strcpy (niiname,pathoutname); - strcat (niiname,".nhdr"); - if (is_fileexists(niiname)) return true; - return false; +bool niiExists(const char *pathoutname) { + char niiname[2048] = {""}; + strcat(niiname, pathoutname); + strcat(niiname, ".nii"); + if (is_fileexists(niiname)) + return true; + char gzname[2048] = {""}; + strcat(gzname, pathoutname); + strcat(gzname, ".nii.gz"); + if (is_fileexists(gzname)) + return true; + strcpy(niiname, pathoutname); + strcat(niiname, ".nrrd"); + if (is_fileexists(niiname)) + return true; + strcpy(niiname, pathoutname); + strcat(niiname, ".nhdr"); + if (is_fileexists(niiname)) + return true; + return false; } //niiExists() #ifndef W_OK @@ -2485,449 +2630,477 @@ bool niiExists(const char*pathoutname) { int strcicmp(char const *a, char const *b) //case insensitive compare { - for (;; a++, b++) { - int d = tolower(*a) - tolower(*b); - if (d != 0 || !*a) - return d; - } -}// strcicmp() - -bool isExt (char *file_name, const char* ext) { - char *p_extension; - if((p_extension = strrchr(file_name,'.')) != NULL ) - if(strcicmp(p_extension,ext) == 0) return true; - //if(strcmp(p_extension,ext) == 0) return true; - return false; -}// isExt() - -void cleanISO8859(char * cString) { + for (;; a++, b++) { + int d = tolower(*a) - tolower(*b); + if (d != 0 || !*a) + return d; + } +} // strcicmp() + +bool isExt(char *file_name, const char *ext) { + char *p_extension; + if ((p_extension = strrchr(file_name, '.')) != NULL) + if (strcicmp(p_extension, ext) == 0) + return true; + //if(strcmp(p_extension,ext) == 0) return true; + return false; +} // isExt() + +void cleanISO8859(char *cString) { int len = strlen(cString); - if (len < 1) return; - for (int i = 0; i < len; i++) - //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1 - if (cString[i]< 1) { - unsigned char c = (unsigned char)cString[i]; - if ((c >= 192) && (c <= 198)) cString[i] = 'A'; - if (c == 199) cString[i] = 'C'; - if ((c >= 200) && (c <= 203)) cString[i] = 'E'; - if ((c >= 204) && (c <= 207)) cString[i] = 'I'; - if (c == 208) cString[i] = 'D'; - if (c == 209) cString[i] = 'N'; - if ((c >= 210) && (c <= 214)) cString[i] = 'O'; - if (c == 215) cString[i] = 'x'; - if (c == 216) cString[i] = 'O'; - if ((c >= 217) && (c <= 220)) cString[i] = 'O'; - if (c == 221) cString[i] = 'Y'; - if ((c >= 224) && (c <= 230)) cString[i] = 'a'; - if (c == 231) cString[i] = 'c'; - if ((c >= 232) && (c <= 235)) cString[i] = 'e'; - if ((c >= 236) && (c <= 239)) cString[i] = 'i'; - if (c == 240) cString[i] = 'o'; - if (c == 241) cString[i] = 'n'; - if ((c >= 242) && (c <= 246)) cString[i] = 'o'; - if (c == 248) cString[i] = 'o'; - if ((c >= 249) && (c <= 252)) cString[i] = 'u'; - if (c == 253) cString[i] = 'y'; - if (c == 255) cString[i] = 'y'; - } + if (len < 1) + return; + for (int i = 0; i < len; i++) + //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1 + if (cString[i] < 1) { + unsigned char c = (unsigned char)cString[i]; + if ((c >= 192) && (c <= 198)) + cString[i] = 'A'; + if (c == 199) + cString[i] = 'C'; + if ((c >= 200) && (c <= 203)) + cString[i] = 'E'; + if ((c >= 204) && (c <= 207)) + cString[i] = 'I'; + if (c == 208) + cString[i] = 'D'; + if (c == 209) + cString[i] = 'N'; + if ((c >= 210) && (c <= 214)) + cString[i] = 'O'; + if (c == 215) + cString[i] = 'x'; + if (c == 216) + cString[i] = 'O'; + if ((c >= 217) && (c <= 220)) + cString[i] = 'O'; + if (c == 221) + cString[i] = 'Y'; + if ((c >= 224) && (c <= 230)) + cString[i] = 'a'; + if (c == 231) + cString[i] = 'c'; + if ((c >= 232) && (c <= 235)) + cString[i] = 'e'; + if ((c >= 236) && (c <= 239)) + cString[i] = 'i'; + if (c == 240) + cString[i] = 'o'; + if (c == 241) + cString[i] = 'n'; + if ((c >= 242) && (c <= 246)) + cString[i] = 'o'; + if (c == 248) + cString[i] = 'o'; + if ((c >= 249) && (c <= 252)) + cString[i] = 'u'; + if (c == 253) + cString[i] = 'y'; + if (c == 255) + cString[i] = 'y'; + } } -int nii_createFilename(struct TDICOMdata dcm, char * niiFilename, struct TDCMopts opts) { - char pth[PATH_MAX] = {""}; - if (strlen(opts.outdir) > 0) { - strcpy(pth, opts.outdir); - int w =access(pth,W_OK); - if (w != 0) { - //should never happen except with "-b i": see kEXIT_OUTPUT_FOLDER_READ_ONLY for early termination - // with "-b i" the code below generates a warning but no files are created +int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts opts) { + char pth[PATH_MAX] = {""}; + if (strlen(opts.outdir) > 0) { + strcpy(pth, opts.outdir); + int w = access(pth, W_OK); + if (w != 0) { + //should never happen except with "-b i": see kEXIT_OUTPUT_FOLDER_READ_ONLY for early termination + // with "-b i" the code below generates a warning but no files are created if (getcwd(pth, sizeof(pth)) != NULL) { - #ifdef USE_CWD_IF_OUTDIR_NO_WRITE //optional: fall back to current working directory - w =access(pth,W_OK); +#ifdef USE_CWD_IF_OUTDIR_NO_WRITE //optional: fall back to current working directory + w = access(pth, W_OK); if (w != 0) { - printError("You do not have write permissions for the directory %s\n",opts.outdir); + printError("You do not have write permissions for the directory %s\n", opts.outdir); return EXIT_FAILURE; } printWarning("%s write permission denied. Saving to working directory %s \n", opts.outdir, pth); - #else - printError("You do not have write permissions for the directory %s\n",opts.outdir); +#else + printError("You do not have write permissions for the directory %s\n", opts.outdir); return EXIT_FAILURE; - #endif +#endif } - } - } - char inname[PATH_MAX] = {""};//{"test%t_%av"}; //% a = acquisition, %n patient name, %t time - strcpy(inname, opts.filename); - bool isDcmExt = isExt(inname, ".dcm"); // "%r.dcm" with multi-echo should generate "1.dcm", "1e2.dcm" + } + } + char inname[PATH_MAX] = {""}; //{"test%t_%av"}; //% a = acquisition, %n patient name, %t time + strcpy(inname, opts.filename); + bool isDcmExt = isExt(inname, ".dcm"); // "%r.dcm" with multi-echo should generate "1.dcm", "1e2.dcm" if (isDcmExt) { inname[strlen(inname) - 4] = '\0'; } - char outname[PATH_MAX] = {""}; - char newstr[256]; - if (strlen(inname) < 1) { - strcpy(inname, "T%t_N%n_S%s"); - } - const char kTempPathSeparator ='\a'; - for (size_t pos = 0; pos start) { - strncpy(&newstr[0], &inname[0] + start, pos - start); - newstr[pos - start] = '\0'; - strcat (outname,newstr); - } - pos++; //extra increment: skip both % and following character - char f = 'P'; - if (pos < strlen(inname)) f = toupper(inname[pos]); - if (f == 'A') { - isCoilReported = true; - strcat (outname,dcm.coilName); - } - if (f == 'B') strcat (outname,dcm.imageBaseName); - if (f == 'C') strcat (outname,dcm.imageComments); - if (f == 'D') strcat (outname,dcm.seriesDescription); - if (f == 'E') { - isEchoReported = true; - sprintf(newstr, "%d", dcm.echoNum); - strcat (outname,newstr); - } - if (f == 'F') - strcat (outname,opts.indirParent); - if (f == 'G') - strcat(outname, dcm.accessionNumber); - if (f == 'I') - strcat (outname,dcm.patientID); - if (f == 'J') - strcat (outname,dcm.seriesInstanceUID); - if (f == 'K') - strcat (outname,dcm.studyInstanceUID); - if (f == 'L') //"L"ocal Institution-generated description or classification of the Procedure Step that was performed. - strcat (outname,dcm.procedureStepDescription); - if (f == 'M') { - if (dcm.manufacturer == kMANUFACTURER_BRUKER) - strcat (outname,"Br"); - else if (dcm.manufacturer == kMANUFACTURER_GE) - strcat (outname,"GE"); - else if (dcm.manufacturer == kMANUFACTURER_TOSHIBA) - strcat (outname,"To"); - else if (dcm.manufacturer == kMANUFACTURER_CANON) - strcat (outname,"Ca"); - else if (dcm.manufacturer == kMANUFACTURER_UIH) - strcat (outname,"UI"); - else if (dcm.manufacturer == kMANUFACTURER_PHILIPS) - strcat (outname,"Ph"); - else if (dcm.manufacturer == kMANUFACTURER_SIEMENS) - strcat (outname,"Si"); - else - strcat (outname,"NA"); //manufacturer name not available - } - if (f == 'N') - strcat (outname,dcm.patientName); - if (f == 'O') { - strcat (outname,dcm.instanceUID); - if (strlen(dcm.instanceUID) > 0) + bool isCoilReported = false; + bool isEchoReported = false; + bool isSeriesReported = false; + //bool isAcquisitionReported = false; + bool isImageNumReported = false; + while (pos < strlen(inname)) { + if (inname[pos] == '%') { + if (pos > start) { + strncpy(&newstr[0], &inname[0] + start, pos - start); + newstr[pos - start] = '\0'; + strcat(outname, newstr); + } + pos++; //extra increment: skip both % and following character + char f = 'P'; + if (pos < strlen(inname)) + f = toupper(inname[pos]); + if (f == 'A') { + isCoilReported = true; + strcat(outname, dcm.coilName); + } + if (f == 'B') + strcat(outname, dcm.imageBaseName); + if (f == 'C') + strcat(outname, dcm.imageComments); + if (f == 'D') + strcat(outname, dcm.seriesDescription); + if (f == 'E') { + isEchoReported = true; + sprintf(newstr, "%d", dcm.echoNum); + strcat(outname, newstr); + } + if (f == 'F') + strcat(outname, opts.indirParent); + if (f == 'G') + strcat(outname, dcm.accessionNumber); + if (f == 'I') + strcat(outname, dcm.patientID); + if (f == 'J') + strcat(outname, dcm.seriesInstanceUID); + if (f == 'K') + strcat(outname, dcm.studyInstanceUID); + if (f == 'L') //"L"ocal Institution-generated description or classification of the Procedure Step that was performed. + strcat(outname, dcm.procedureStepDescription); + if (f == 'M') { + if (dcm.manufacturer == kMANUFACTURER_BRUKER) + strcat(outname, "Br"); + else if (dcm.manufacturer == kMANUFACTURER_GE) + strcat(outname, "GE"); + else if (dcm.manufacturer == kMANUFACTURER_TOSHIBA) + strcat(outname, "To"); + else if (dcm.manufacturer == kMANUFACTURER_CANON) + strcat(outname, "Ca"); + else if (dcm.manufacturer == kMANUFACTURER_UIH) + strcat(outname, "UI"); + else if (dcm.manufacturer == kMANUFACTURER_PHILIPS) + strcat(outname, "Ph"); + else if (dcm.manufacturer == kMANUFACTURER_SIEMENS) + strcat(outname, "Si"); + else + strcat(outname, "NA"); //manufacturer name not available + } + if (f == 'N') + strcat(outname, dcm.patientName); + if (f == 'O') { + strcat(outname, dcm.instanceUID); + if (strlen(dcm.instanceUID) > 0) isAddNamePostFixes = false; //should be unique, so no need to post-fix } - if (f == 'P') { - strcat (outname,dcm.protocolName); - if (strlen(dcm.protocolName) < 1) - printWarning("Unable to append protocol name (0018,1030) to filename (it is empty).\n"); - } - if (f == 'R') { - sprintf(newstr, "%d", dcm.imageNum); - strcat (outname,newstr); - isImageNumReported = true; - } - if (f == 'Q') - strcat (outname,dcm.scanningSequence); - if (f == 'S') { - sprintf(newstr, "%ld", dcm.seriesNum); - strcat (outname,newstr); - isSeriesReported = true; - } - if (f == 'T') { - sprintf(newstr, "%0.0f", dcm.dateTime); - strcat (outname,newstr); - } + if (f == 'P') { + strcat(outname, dcm.protocolName); + if (strlen(dcm.protocolName) < 1) + printWarning("Unable to append protocol name (0018,1030) to filename (it is empty).\n"); + } + if (f == 'R') { + sprintf(newstr, "%d", dcm.imageNum); + strcat(outname, newstr); + isImageNumReported = true; + } + if (f == 'Q') + strcat(outname, dcm.scanningSequence); + if (f == 'S') { + sprintf(newstr, "%ld", dcm.seriesNum); + strcat(outname, newstr); + isSeriesReported = true; + } + if (f == 'T') { + sprintf(newstr, "%0.0f", dcm.dateTime); + strcat(outname, newstr); + } if (f == 'U') { if (opts.isRenameNotConvert) { sprintf(newstr, "%d", dcm.acquNum); - strcat (outname,newstr); + strcat(outname, newstr); //isAcquisitionReported = true; } else { - #ifdef mySegmentByAcq +#ifdef mySegmentByAcq sprintf(newstr, "%d", dcm.acquNum); - strcat (outname,newstr); - //isAcquisitionReported = true; - #else + strcat(outname, newstr); +//isAcquisitionReported = true; +#else printWarning("Ignoring '%%u' in output filename (recompile to segment by acquisition)\n"); - #endif - } +#endif + } } if (f == 'V') { if (dcm.manufacturer == kMANUFACTURER_BRUKER) - strcat (outname,"Bruker"); + strcat(outname, "Bruker"); else if (dcm.manufacturer == kMANUFACTURER_GE) - strcat (outname,"GE"); + strcat(outname, "GE"); else if (dcm.manufacturer == kMANUFACTURER_PHILIPS) - strcat (outname,"Philips"); + strcat(outname, "Philips"); else if (dcm.manufacturer == kMANUFACTURER_SIEMENS) - strcat (outname,"Siemens"); + strcat(outname, "Siemens"); else if (dcm.manufacturer == kMANUFACTURER_TOSHIBA) - strcat (outname,"Toshiba"); + strcat(outname, "Toshiba"); else if (dcm.manufacturer == kMANUFACTURER_CANON) - strcat (outname,"Canon"); + strcat(outname, "Canon"); else if (dcm.manufacturer == kMANUFACTURER_UIH) - strcat (outname,"UIH"); + strcat(outname, "UIH"); else - strcat (outname,"NA"); + strcat(outname, "NA"); } if (f == 'X') - strcat (outname,dcm.studyID); + strcat(outname, dcm.studyID); if ((f == 'Y') && (dcm.rawDataRunNumber >= 0)) { - sprintf(newstr, "%d", dcm.rawDataRunNumber); //GE (0019,10A2) else (0020,0100) - strcat (outname,newstr); - } - if (f == 'Z') - strcat (outname,dcm.sequenceName); - if ((f >= '0') && (f <= '9')) { - if ((pos= 0)) { - char zeroPad[12] = {""}; - sprintf(zeroPad,"%%0%dd",f - '0'); - sprintf(newstr, zeroPad, dcm.rawDataRunNumber); //GE (0019,10A2) else (0020,0100) - strcat (outname,newstr); - pos++; // e.g. %3f requires extra increment: skip both number and following character - } - } - start = pos + 1; - } //found a % character - pos++; - } //for each character in input - if (pos > start) { //append any trailing characters - strncpy(&newstr[0], &inname[0] + start, pos - start); - newstr[pos - start] = '\0'; - strcat (outname,newstr); - } - if ((isAddNamePostFixes) && (!isCoilReported) && (dcm.isCoilVaries)) { - //sprintf(newstr, "_c%d", dcm.coilNum); - //strcat (outname,newstr); - strcat (outname, "_c"); - strcat (outname,dcm.coilName); - } - // myMultiEchoFilenameSkipEcho1 https://github.com/rordenlab/dcm2niix/issues/237 - #ifdef myMultiEchoFilenameSkipEcho1 - if ((isAddNamePostFixes) && (!isEchoReported) && (dcm.isMultiEcho) && (dcm.echoNum >= 1)) { //multiple echoes saved as same series - #else - if ((isAddNamePostFixes) && (!isEchoReported) && ((dcm.isMultiEcho) || (dcm.echoNum > 1))) { //multiple echoes saved as same series - #endif - sprintf(newstr, "_e%d", dcm.echoNum); - strcat (outname,newstr); - isEchoReported = true; - } - if ((isAddNamePostFixes) && (!isSeriesReported) && (!isEchoReported) && (dcm.echoNum > 1)) { //last resort: user provided no method to disambiguate echo number in filename - sprintf(newstr, "_e%d", dcm.echoNum); - strcat (outname,newstr); - isEchoReported = true; - } + sprintf(newstr, "%d", dcm.rawDataRunNumber); //GE (0019,10A2) else (0020,0100) + strcat(outname, newstr); + } + if (f == 'Z') + strcat(outname, dcm.sequenceName); + if ((f >= '0') && (f <= '9')) { + if ((pos < strlen(inname)) && (toupper(inname[pos + 1]) == 'S')) { + char zeroPad[12] = {""}; + sprintf(zeroPad, "%%0%dd", f - '0'); + sprintf(newstr, zeroPad, dcm.seriesNum); + strcat(outname, newstr); + pos++; // e.g. %3f requires extra increment: skip both number and following character + } + if ((pos < strlen(inname)) && (toupper(inname[pos + 1]) == 'R')) { + char zeroPad[12] = {""}; + sprintf(zeroPad, "%%0%dd", f - '0'); + sprintf(newstr, zeroPad, dcm.imageNum); + isImageNumReported = true; + strcat(outname, newstr); + pos++; // e.g. %3f requires extra increment: skip both number and following character + } + if ((pos < strlen(inname)) && (toupper(inname[pos + 1]) == 'Y') && (dcm.rawDataRunNumber >= 0)) { + char zeroPad[12] = {""}; + sprintf(zeroPad, "%%0%dd", f - '0'); + sprintf(newstr, zeroPad, dcm.rawDataRunNumber); //GE (0019,10A2) else (0020,0100) + strcat(outname, newstr); + pos++; // e.g. %3f requires extra increment: skip both number and following character + } + } + start = pos + 1; + } //found a % character + pos++; + } //for each character in input + if (pos > start) { //append any trailing characters + strncpy(&newstr[0], &inname[0] + start, pos - start); + newstr[pos - start] = '\0'; + strcat(outname, newstr); + } + if ((isAddNamePostFixes) && (!isCoilReported) && (dcm.isCoilVaries)) { + //sprintf(newstr, "_c%d", dcm.coilNum); + //strcat (outname,newstr); + strcat(outname, "_c"); + strcat(outname, dcm.coilName); + } +// myMultiEchoFilenameSkipEcho1 https://github.com/rordenlab/dcm2niix/issues/237 +#ifdef myMultiEchoFilenameSkipEcho1 + if ((isAddNamePostFixes) && (!isEchoReported) && (dcm.isMultiEcho) && (dcm.echoNum >= 1)) { //multiple echoes saved as same series +#else + if ((isAddNamePostFixes) && (!isEchoReported) && ((dcm.isMultiEcho) || (dcm.echoNum > 1))) { //multiple echoes saved as same series +#endif + sprintf(newstr, "_e%d", dcm.echoNum); + strcat(outname, newstr); + isEchoReported = true; + } + if ((isAddNamePostFixes) && (!isSeriesReported) && (!isEchoReported) && (dcm.echoNum > 1)) { //last resort: user provided no method to disambiguate echo number in filename + sprintf(newstr, "_e%d", dcm.echoNum); + strcat(outname, newstr); + isEchoReported = true; + } if ((dcm.isNonParallelSlices) && (!isImageNumReported)) { sprintf(newstr, "_i%05d", dcm.imageNum); - strcat (outname,newstr); - } - /*if (dcm.maxGradDynVol > 0) { //Philips segmented - sprintf(newstr, "_v%04d", dcm.gradDynVol+1); //+1 as indexed from zero - strcat (outname,newstr); - }*/ - if ((isAddNamePostFixes) && (dcm.isHasImaginary)) { - strcat (outname,"_imaginary"); //has phase map - } - 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)) { - strcat (outname,"_ph"); //has phase map - if (dcm.isHasMagnitude) - strcat (outname,"Mag"); //Philips enhanced with BOTH phase and Magnitude in single file - } - if ((isAddNamePostFixes) && (dcm.triggerDelayTime >= 1) && (dcm.manufacturer != kMANUFACTURER_GE)){ //issue 336 GE uses this for slice timing - sprintf(newstr, "_t%d", (int)roundf(dcm.triggerDelayTime)); - strcat (outname,newstr); - } - //could add (isAddNamePostFixes) to these next two, but consequences could be catastrophic - if (dcm.isRawDataStorage) //avoid name clash for Philips XX_ files - strcat (outname,"_Raw"); - if (dcm.isGrayscaleSoftcopyPresentationState) //avoid name clash for Philips PS_ files - strcat (outname,"_PS"); - if (isDcmExt) - strcat (outname,".dcm"); - if (strlen(outname) < 1) strcpy(outname, "dcm2nii_invalidName"); - if (outname[0] == '.') outname[0] = '_'; //make sure not a hidden file - //eliminate illegal characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx - // https://github.com/rordenlab/dcm2niix/issues/237 - #ifdef myOsSpecificFilenameMask - #define kMASK_WINDOWS_SPECIAL_CHARACTERS 0 - #else - #define kMASK_WINDOWS_SPECIAL_CHARACTERS 1 - #endif - #if defined(_WIN64) || defined(_WIN32) || defined(kMASK_WINDOWS_SPECIAL_CHARACTERS)//https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names - for (size_t pos = 0; pos') || (outname[pos] == ':') || (outname[pos] == ';') - || (outname[pos] == '"') // || (outname[pos] == '/') || (outname[pos] == '\\') - //|| (outname[pos] == '^') issue398 - || (outname[pos] == '*') || (outname[pos] == '|') || (outname[pos] == '?')) - outname[pos] = '_'; - #else - for (size_t pos = 0; pos 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); -#if defined(USING_R) && (defined(_WIN64) || defined(_WIN32)) - // R also uses forward slash on Windows, so allow it here - if (!sep) - sep = strchr(outname, '/'); -#endif - if (sep) { - char newdir[2048] = {""}; - strcat (newdir,baseoutname); - //struct stat st = {0}; - for (size_t pos = 0; pos< strlen(outname); pos ++) { - if (outname[pos] == kPathSeparator) { - //if (stat(newdir, &st) == -1) - if (!is_dir(newdir,true)) - #if defined(_WIN64) || defined(_WIN32) - mkdir(newdir); - #else - mkdir(newdir, 0700); - #endif - } - char ch[12] = {""}; - sprintf(ch,"%c",outname[pos]); - strcat (newdir,ch); - } - } - //printMessage("path='%s' name='%s'\n", pathoutname, outname); - //make sure outname is unique - strcat (baseoutname,outname); - char pathoutname[2048] = {""}; - strcat (pathoutname,baseoutname); - if ((niiExists(pathoutname)) && (opts.nameConflictBehavior == kNAME_CONFLICT_SKIP)) { - printWarning("Skipping existing file named %s\n", pathoutname); - return EXIT_FAILURE; - } - if ((niiExists(pathoutname)) && (opts.nameConflictBehavior == kNAME_CONFLICT_OVERWRITE)) { - printWarning("Overwriting existing file with the name %s\n", pathoutname); - niiDelete(pathoutname); - strcpy(niiFilename,pathoutname); - return EXIT_SUCCESS; - } - int i = 0; - while (niiExists(pathoutname) && (i < 26)) { - strcpy(pathoutname,baseoutname); - appendChar[0] = 'a'+i; - strcat (pathoutname,appendChar); - i++; - } - if (i >= 26) { - printError("Too many NIFTI images with the name %s\n", baseoutname); - return EXIT_FAILURE; - } - //printMessage("-->%s\n",pathoutname); return EXIT_SUCCESS; - //printMessage("outname=%s\n", pathoutname); - strcpy(niiFilename,pathoutname); - return EXIT_SUCCESS; -} //nii_createFilename() - -void nii_createDummyFilename(char * niiFilename, struct TDCMopts opts) { - //generate string that illustrating sample of filename - struct TDICOMdata d = clear_dicom_data(); - strcpy(d.patientName, "John_Doe"); - strcpy(d.patientID, "ID123"); - strcpy(d.accessionNumber, "ID123"); - strcpy(d.imageType,"ORIGINAL"); - strcpy(d.imageComments, "imgComments"); - strcpy(d.studyDate, "1/1/1977"); - strcpy(d.studyTime, "11:11:11"); - strcpy(d.protocolName, "MPRAGE"); - strcpy(d.seriesDescription, "T1_mprage"); - strcpy(d.sequenceName, "T1"); - strcpy(d.scanningSequence, "tfl3d1_ns"); - strcpy(d.sequenceVariant, "tfl3d1_ns"); - strcpy(d.manufacturersModelName, "N/A"); - strcpy(d.procedureStepDescription, ""); - strcpy(d.seriesInstanceUID, ""); - strcpy(d.studyInstanceUID, ""); - strcpy(d.bodyPartExamined,""); - strcpy(opts.indirParent,"myFolder"); - char niiFilenameBase[PATH_MAX] = {"/usr/myFolder/dicom.dcm"}; - nii_createFilename(d, niiFilenameBase, opts) ; - strcpy(niiFilename,"Example output filename: '"); - strcat(niiFilename,niiFilenameBase); - if (opts.isSaveNRRD) { + strcat(outname, newstr); + } + /*if (dcm.maxGradDynVol > 0) { //Philips segmented + sprintf(newstr, "_v%04d", dcm.gradDynVol+1); //+1 as indexed from zero + strcat (outname,newstr); + }*/ + if ((isAddNamePostFixes) && (dcm.isHasImaginary)) { + strcat(outname, "_imaginary"); //has phase map + } + 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)) { + strcat(outname, "_ph"); //has phase map + if (dcm.isHasMagnitude) + strcat(outname, "Mag"); //Philips enhanced with BOTH phase and Magnitude in single file + } + if ((isAddNamePostFixes) && (dcm.triggerDelayTime >= 1) && (dcm.manufacturer != kMANUFACTURER_GE)) { //issue 336 GE uses this for slice timing + sprintf(newstr, "_t%d", (int)roundf(dcm.triggerDelayTime)); + strcat(outname, newstr); + } + //could add (isAddNamePostFixes) to these next two, but consequences could be catastrophic + if (dcm.isRawDataStorage) //avoid name clash for Philips XX_ files + strcat(outname, "_Raw"); + if (dcm.isGrayscaleSoftcopyPresentationState) //avoid name clash for Philips PS_ files + strcat(outname, "_PS"); + if (isDcmExt) + strcat(outname, ".dcm"); + if (strlen(outname) < 1) + strcpy(outname, "dcm2nii_invalidName"); + if (outname[0] == '.') + outname[0] = '_'; //make sure not a hidden file +//eliminate illegal characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx +// https://github.com/rordenlab/dcm2niix/issues/237 +#ifdef myOsSpecificFilenameMask +#define kMASK_WINDOWS_SPECIAL_CHARACTERS 0 +#else +#define kMASK_WINDOWS_SPECIAL_CHARACTERS 1 +#endif +#if defined(_WIN64) || defined(_WIN32) || defined(kMASK_WINDOWS_SPECIAL_CHARACTERS) //https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names + for (size_t pos = 0; pos < strlen(outname); pos++) + if ((outname[pos] == '\\') || (outname[pos] == '/') || (outname[pos] == ' ') || (outname[pos] == '<') || (outname[pos] == '>') || (outname[pos] == ':') || (outname[pos] == ';') || (outname[pos] == '"') // || (outname[pos] == '/') || (outname[pos] == '\\') + //|| (outname[pos] == '^') issue398 + || (outname[pos] == '*') || (outname[pos] == '|') || (outname[pos] == '?')) + outname[pos] = '_'; +#else + for (size_t pos = 0; pos < strlen(outname); pos++) + if (outname[pos] == ':') //not allowed by MacOS + outname[pos] = '_'; +#endif + cleanISO8859(outname); + //re-insert explicit path separators: -f %t/%s_%p will have folder for time, but will not segment a protocol named "fMRI\bold" + for (int pos = 0; pos < strlen(outname); pos++) { + if (outname[pos] == kTempPathSeparator) + outname[pos] = kPathSeparator; //e.g. for Windows, convert "/" to "\" + if (outname[pos] < 32) //https://en.wikipedia.org/wiki/ASCII#Control_characters + outname[pos] = '_'; + } + char baseoutname[2048] = {""}; + strcat(baseoutname, pth); + char appendChar[2] = {"a"}; + 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); +#if defined(USING_R) && (defined(_WIN64) || defined(_WIN32)) + // R also uses forward slash on Windows, so allow it here + if (!sep) + sep = strchr(outname, '/'); +#endif + if (sep) { + char newdir[2048] = {""}; + strcat(newdir, baseoutname); + //struct stat st = {0}; + for (size_t pos = 0; pos < strlen(outname); pos++) { + if (outname[pos] == kPathSeparator) { + //if (stat(newdir, &st) == -1) + if (!is_dir(newdir, true)) +#if defined(_WIN64) || defined(_WIN32) + mkdir(newdir); +#else + mkdir(newdir, 0700); +#endif + } + char ch[12] = {""}; + sprintf(ch, "%c", outname[pos]); + strcat(newdir, ch); + } + } + //printMessage("path='%s' name='%s'\n", pathoutname, outname); + //make sure outname is unique + strcat(baseoutname, outname); + char pathoutname[2048] = {""}; + strcat(pathoutname, baseoutname); + if ((niiExists(pathoutname)) && (opts.nameConflictBehavior == kNAME_CONFLICT_SKIP)) { + printWarning("Skipping existing file named %s\n", pathoutname); + return EXIT_FAILURE; + } + if ((niiExists(pathoutname)) && (opts.nameConflictBehavior == kNAME_CONFLICT_OVERWRITE)) { + printWarning("Overwriting existing file with the name %s\n", pathoutname); + niiDelete(pathoutname); + strcpy(niiFilename, pathoutname); + return EXIT_SUCCESS; + } + int i = 0; + while (niiExists(pathoutname) && (i < 26)) { + strcpy(pathoutname, baseoutname); + appendChar[0] = 'a' + i; + strcat(pathoutname, appendChar); + i++; + } + if (i >= 26) { + printError("Too many NIFTI images with the name %s\n", baseoutname); + return EXIT_FAILURE; + } + //printMessage("-->%s\n",pathoutname); return EXIT_SUCCESS; + //printMessage("outname=%s\n", pathoutname); + strcpy(niiFilename, pathoutname); + return EXIT_SUCCESS; +} //nii_createFilename() + +void nii_createDummyFilename(char *niiFilename, struct TDCMopts opts) { + //generate string that illustrating sample of filename + struct TDICOMdata d = clear_dicom_data(); + strcpy(d.patientName, "John_Doe"); + strcpy(d.patientID, "ID123"); + strcpy(d.accessionNumber, "ID123"); + strcpy(d.imageType, "ORIGINAL"); + strcpy(d.imageComments, "imgComments"); + strcpy(d.studyDate, "1/1/1977"); + strcpy(d.studyTime, "11:11:11"); + strcpy(d.protocolName, "MPRAGE"); + strcpy(d.seriesDescription, "T1_mprage"); + strcpy(d.sequenceName, "T1"); + strcpy(d.scanningSequence, "tfl3d1_ns"); + strcpy(d.sequenceVariant, "tfl3d1_ns"); + strcpy(d.manufacturersModelName, "N/A"); + strcpy(d.procedureStepDescription, ""); + strcpy(d.seriesInstanceUID, ""); + strcpy(d.studyInstanceUID, ""); + strcpy(d.bodyPartExamined, ""); + strcpy(opts.indirParent, "myFolder"); + char niiFilenameBase[PATH_MAX] = {"/usr/myFolder/dicom.dcm"}; + nii_createFilename(d, niiFilenameBase, opts); + strcpy(niiFilename, "Example output filename: '"); + strcat(niiFilename, niiFilenameBase); + if (opts.isSaveNRRD) { if (opts.isGz) - strcat(niiFilename,".nhdr'"); + strcat(niiFilename, ".nhdr'"); else - strcat(niiFilename,".nrrd'"); - } else { + strcat(niiFilename, ".nrrd'"); + } else { if (opts.isGz) - strcat(niiFilename,".nii.gz'"); + strcat(niiFilename, ".nii.gz'"); else - strcat(niiFilename,".nii'"); - } -}// nii_createDummyFilename() + strcat(niiFilename, ".nii'"); + } +} // nii_createDummyFilename() #ifndef myDisableZLib @@ -2937,259 +3110,284 @@ unsigned long mz_compressBound(unsigned long source_len) { } unsigned long mz_crc32(unsigned long crc, const unsigned char *ptr, size_t buf_len) { - return crc32(crc, ptr, (uInt) buf_len); + return crc32(crc, ptr, (uInt)buf_len); } #endif #ifndef MZ_UBER_COMPRESSION //defined in miniz, not defined in zlib - #define MZ_UBER_COMPRESSION 9 +#define MZ_UBER_COMPRESSION 9 #endif #ifndef MZ_DEFAULT_LEVEL - #define MZ_DEFAULT_LEVEL 6 +#define MZ_DEFAULT_LEVEL 6 #endif -void writeNiiGz (char * baseName, struct nifti_1_header hdr, unsigned char* src_buffer, unsigned long src_len, int gzLevel, bool isSkipHeader) { - //create gz file in RAM, save to disk http://www.zlib.net/zlib_how.html - // in general this single-threaded approach is slower than PIGZ but is useful for slow (network attached) disk drives - char fname[2048] = {""}; - strcpy (fname,baseName); - if (!isSkipHeader) strcat (fname,".nii.gz"); - unsigned long hdrPadBytes = sizeof(hdr) + 4; //348 byte header + 4 byte pad - if (isSkipHeader) hdrPadBytes = 0; - unsigned long cmp_len = mz_compressBound(src_len+hdrPadBytes); - unsigned char *pCmp = (unsigned char *)malloc(cmp_len); - z_stream strm; - strm.total_in = 0; - strm.total_out = 0; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.next_out = pCmp; // output char array - strm.avail_out = (unsigned int)cmp_len; // size of output - int zLevel = MZ_DEFAULT_LEVEL;//Z_DEFAULT_COMPRESSION; - if ((gzLevel > 0) && (gzLevel < 11)) - zLevel = gzLevel; - if (zLevel > MZ_UBER_COMPRESSION) - zLevel = MZ_UBER_COMPRESSION; - if (deflateInit(&strm, zLevel)!= Z_OK) { - free(pCmp); - return; - } - //unsigned char *pHdr = (unsigned char *)malloc(hdrPadBytes); - unsigned char *pHdr; - if (!isSkipHeader) { +void writeNiiGz(char *baseName, struct nifti_1_header hdr, unsigned char *src_buffer, unsigned long src_len, int gzLevel, bool isSkipHeader) { + //create gz file in RAM, save to disk http://www.zlib.net/zlib_how.html + // in general this single-threaded approach is slower than PIGZ but is useful for slow (network attached) disk drives + char fname[2048] = {""}; + strcpy(fname, baseName); + if (!isSkipHeader) + strcat(fname, ".nii.gz"); + unsigned long hdrPadBytes = sizeof(hdr) + 4; //348 byte header + 4 byte pad + if (isSkipHeader) + hdrPadBytes = 0; + unsigned long cmp_len = mz_compressBound(src_len + hdrPadBytes); + unsigned char *pCmp = (unsigned char *)malloc(cmp_len); + z_stream strm; + strm.total_in = 0; + strm.total_out = 0; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.next_out = pCmp; // output char array + strm.avail_out = (unsigned int)cmp_len; // size of output + int zLevel = MZ_DEFAULT_LEVEL; //Z_DEFAULT_COMPRESSION; + if ((gzLevel > 0) && (gzLevel < 11)) + zLevel = gzLevel; + if (zLevel > MZ_UBER_COMPRESSION) + zLevel = MZ_UBER_COMPRESSION; + if (deflateInit(&strm, zLevel) != Z_OK) { + free(pCmp); + return; + } + //unsigned char *pHdr = (unsigned char *)malloc(hdrPadBytes); + unsigned char *pHdr; + if (!isSkipHeader) { //add header pHdr = (unsigned char *)malloc(hdrPadBytes); - pHdr[hdrPadBytes-1] = 0; pHdr[hdrPadBytes-2] = 0; pHdr[hdrPadBytes-3] = 0; pHdr[hdrPadBytes-4] = 0; - memcpy(pHdr,&hdr, sizeof(hdr)); + pHdr[hdrPadBytes - 1] = 0; + pHdr[hdrPadBytes - 2] = 0; + pHdr[hdrPadBytes - 3] = 0; + pHdr[hdrPadBytes - 4] = 0; + memcpy(pHdr, &hdr, sizeof(hdr)); strm.avail_in = (unsigned int)hdrPadBytes; // size of input - strm.next_in = (uint8_t *)pHdr; // input header -- TPX strm.next_in = (Bytef *)pHdr; uint32_t + strm.next_in = (uint8_t *)pHdr; // input header -- TPX strm.next_in = (Bytef *)pHdr; uint32_t deflate(&strm, Z_NO_FLUSH); - } - //add image - strm.avail_in = (unsigned int)src_len; // size of input + } + //add image + strm.avail_in = (unsigned int)src_len; // size of input strm.next_in = (uint8_t *)src_buffer; // input image -- TPX strm.next_in = (Bytef *)src_buffer; - deflate(&strm, Z_FINISH); //Z_NO_FLUSH; - //finish up - deflateEnd(&strm); - unsigned long file_crc32 = mz_crc32(0L, Z_NULL, 0); - if (!isSkipHeader) file_crc32 = mz_crc32(file_crc32, pHdr, (unsigned int)hdrPadBytes); - file_crc32 = mz_crc32(file_crc32, src_buffer, (unsigned int)src_len); - cmp_len = strm.total_out; - if (cmp_len <= 0) { - free(pCmp); - free(src_buffer); - return; - } - FILE *fileGz = fopen(fname, "wb"); - if (!fileGz) { - free(pCmp); - free(src_buffer); - return; - } - //write header http://www.gzip.org/zlib/rfc-gzip.html - fputc((char)0x1f, fileGz); //ID1 - fputc((char)0x8b, fileGz); //ID2 - fputc((char)0x08, fileGz); //CM - use deflate compression method - fputc((char)0x00, fileGz); //FLG - no addition fields - fputc((char)0x00, fileGz); //MTIME0 - fputc((char)0x00, fileGz); //MTIME1 - fputc((char)0x00, fileGz); //MTIME2 - fputc((char)0x00, fileGz); //MTIME2 - fputc((char)0x00, fileGz); //XFL - fputc((char)0xff, fileGz); //OS - //write Z-compressed data - fwrite (&pCmp[2] , sizeof(char), cmp_len-6, fileGz); //-6 as LZ78 format has 2 bytes header (typically 0x789C) and 4 bytes tail (ADLER 32) - //write tail: write redundancy check and uncompressed size as bytes to ensure LITTLE-ENDIAN order - fputc((unsigned char)(file_crc32), fileGz); - fputc((unsigned char)(file_crc32 >> 8), fileGz); - fputc((unsigned char)(file_crc32 >> 16), fileGz); - fputc((unsigned char)(file_crc32 >> 24), fileGz); - fputc((unsigned char)(strm.total_in), fileGz); - fputc((unsigned char)(strm.total_in >> 8), fileGz); - fputc((unsigned char)(strm.total_in >> 16), fileGz); - fputc((unsigned char)(strm.total_in >> 24), fileGz); - fclose(fileGz); - free(pCmp); - if (!isSkipHeader) free(pHdr); + deflate(&strm, Z_FINISH); //Z_NO_FLUSH; + //finish up + deflateEnd(&strm); + unsigned long file_crc32 = mz_crc32(0L, Z_NULL, 0); + if (!isSkipHeader) + file_crc32 = mz_crc32(file_crc32, pHdr, (unsigned int)hdrPadBytes); + file_crc32 = mz_crc32(file_crc32, src_buffer, (unsigned int)src_len); + cmp_len = strm.total_out; + if (cmp_len <= 0) { + free(pCmp); + free(src_buffer); + return; + } + FILE *fileGz = fopen(fname, "wb"); + if (!fileGz) { + free(pCmp); + free(src_buffer); + return; + } + //write header http://www.gzip.org/zlib/rfc-gzip.html + fputc((char)0x1f, fileGz); //ID1 + fputc((char)0x8b, fileGz); //ID2 + fputc((char)0x08, fileGz); //CM - use deflate compression method + fputc((char)0x00, fileGz); //FLG - no addition fields + fputc((char)0x00, fileGz); //MTIME0 + fputc((char)0x00, fileGz); //MTIME1 + fputc((char)0x00, fileGz); //MTIME2 + fputc((char)0x00, fileGz); //MTIME2 + fputc((char)0x00, fileGz); //XFL + fputc((char)0xff, fileGz); //OS + //write Z-compressed data + fwrite(&pCmp[2], sizeof(char), cmp_len - 6, fileGz); //-6 as LZ78 format has 2 bytes header (typically 0x789C) and 4 bytes tail (ADLER 32) + //write tail: write redundancy check and uncompressed size as bytes to ensure LITTLE-ENDIAN order + fputc((unsigned char)(file_crc32), fileGz); + fputc((unsigned char)(file_crc32 >> 8), fileGz); + fputc((unsigned char)(file_crc32 >> 16), fileGz); + fputc((unsigned char)(file_crc32 >> 24), fileGz); + fputc((unsigned char)(strm.total_in), fileGz); + fputc((unsigned char)(strm.total_in >> 8), fileGz); + fputc((unsigned char)(strm.total_in >> 16), fileGz); + fputc((unsigned char)(strm.total_in >> 24), fileGz); + fclose(fileGz); + free(pCmp); + if (!isSkipHeader) + free(pHdr); } //writeNiiGz() #endif #ifdef USING_R // Version of nii_saveNII() for R/divest: create nifti_image pointer and push onto stack -int nii_saveNII (char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) -{ - hdr.vox_offset = 352; - // Extract the basename from the full file path - char *start = niiFilename + strlen(niiFilename); - while (start >= niiFilename && *start != '/' && *start != kPathSeparator) - start--; - std::string name(++start); - nifti_image *image = nifti_convert_nhdr2nim(hdr, niiFilename); - if (image == NULL) - return EXIT_FAILURE; - image->data = (void *) im; - ImageList *images = (ImageList *) opts.imageList; - images->append(image, name); - free(image); - return EXIT_SUCCESS; +int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) { + hdr.vox_offset = 352; + // Extract the basename from the full file path + char *start = niiFilename + strlen(niiFilename); + while (start >= niiFilename && *start != '/' && *start != kPathSeparator) + start--; + std::string name(++start); + nifti_image *image = nifti_convert_nhdr2nim(hdr, niiFilename); + if (image == NULL) + return EXIT_FAILURE; + image->data = (void *)im; + ImageList *images = (ImageList *)opts.imageList; + images->append(image, name); + free(image); + return EXIT_SUCCESS; } -void nii_saveAttributes (struct TDICOMdata &data, struct nifti_1_header &header, struct TDCMopts &opts, const char *filename) -{ - ImageList *images = (ImageList *) opts.imageList; - switch (data.modality) { - case kMODALITY_CR: images->addAttribute("modality", "CR"); break; - case kMODALITY_CT: images->addAttribute("modality", "CT"); break; - case kMODALITY_MR: images->addAttribute("modality", "MR"); break; - case kMODALITY_PT: images->addAttribute("modality", "PT"); break; - case kMODALITY_US: images->addAttribute("modality", "US"); break; - } - switch (data.manufacturer) { - case kMANUFACTURER_SIEMENS: images->addAttribute("manufacturer", "Siemens"); break; - case kMANUFACTURER_GE: images->addAttribute("manufacturer", "GE"); break; - case kMANUFACTURER_PHILIPS: images->addAttribute("manufacturer", "Philips"); break; - case kMANUFACTURER_TOSHIBA: images->addAttribute("manufacturer", "Toshiba"); break; - case kMANUFACTURER_UIH: images->addAttribute("manufacturer", "UIH"); break; - case kMANUFACTURER_BRUKER: images->addAttribute("manufacturer", "Bruker"); break; - case kMANUFACTURER_HITACHI: images->addAttribute("manufacturer", "Hitachi"); break; - case kMANUFACTURER_CANON: images->addAttribute("manufacturer", "Canon"); break; - } - images->addAttribute("scannerModelName", data.manufacturersModelName); - images->addAttribute("imageType", data.imageType); - if (data.seriesNum > 0) - images->addAttribute("seriesNumber", int(data.seriesNum)); - images->addAttribute("seriesDescription", data.seriesDescription); - images->addAttribute("sequenceName", data.sequenceName); - images->addAttribute("protocolName", data.protocolName); - images->addDateAttribute("studyDate", data.studyDate); - images->addTimeAttribute("studyTime", data.studyTime); - images->addAttribute("fieldStrength", data.fieldStrength); - images->addAttribute("flipAngle", data.flipAngle); - images->addAttribute("echoTime", data.TE); - images->addAttribute("repetitionTime", data.TR); - images->addAttribute("inversionTime", data.TI); - if (!data.isXRay) { - images->addAttribute("sliceThickness", data.zThick); - images->addAttribute("sliceSpacing", data.zSpacing); - } - if (data.CSA.multiBandFactor > 1) - images->addAttribute("multibandFactor", data.CSA.multiBandFactor); - if (data.phaseEncodingSteps > 0) - images->addAttribute("phaseEncodingSteps", data.phaseEncodingSteps); - if (data.phaseEncodingLines > 0) - images->addAttribute("phaseEncodingLines", data.phaseEncodingLines); - - // Calculations relating to the reconstruction in the phase encode direction, - // which are needed to derive effective echo spacing and readout time below. - // See the nii_SaveBIDS() function for details - int reconMatrixPE = data.phaseEncodingLines; - if ((header.dim[2] > 0) && (header.dim[1] > 0)) { - if (header.dim[1] == header.dim[2]) //phase encoding does not matter - reconMatrixPE = header.dim[2]; - else if (data.phaseEncodingRC =='C') - reconMatrixPE = header.dim[2]; - else if (data.phaseEncodingRC =='R') - reconMatrixPE = header.dim[1]; - } - - double bandwidthPerPixelPhaseEncode = data.bandwidthPerPixelPhaseEncode; - if (bandwidthPerPixelPhaseEncode == 0.0) - bandwidthPerPixelPhaseEncode = data.CSA.bandwidthPerPixelPhaseEncode; - double effectiveEchoSpacing = 0.0; - if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode > 0.0)) - effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * reconMatrixPE); - if (data.effectiveEchoSpacingGE > 0.0) { - double roundFactor = data.isPartialFourier ? 4.0 : 2.0; - double totalReadoutTime = ((ceil(1.0/roundFactor * data.phaseEncodingLines / data.accelFactPE) * roundFactor) - 1.0) * data.effectiveEchoSpacingGE * 0.000001; - effectiveEchoSpacing = totalReadoutTime / (reconMatrixPE - 1); - } - - images->addAttribute("effectiveEchoSpacing", effectiveEchoSpacing); - if (data.manufacturer == kMANUFACTURER_UIH) - images->addAttribute("effectiveReadoutTime", data.acquisitionDuration / 1000.0); - else if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0)) - images->addAttribute("effectiveReadoutTime", effectiveEchoSpacing * (reconMatrixPE - 1.0)); - images->addAttribute("pixelBandwidth", data.pixelBandwidth); - if ((data.manufacturer == kMANUFACTURER_SIEMENS) && (data.dwellTime > 0)) - images->addAttribute("dwellTime", data.dwellTime * 1e-9); - - // Phase encoding polarity - // We only save these attributes if both direction and polarity are known - bool isSkipPhaseEncodingAxis = data.is3DAcq; - if (data.echoTrainLength > 1) isSkipPhaseEncodingAxis = false; //issue 371: ignore phaseEncoding for 3D MP-RAGE/SPACE, but report for 3D EPI - - if (((data.phaseEncodingRC == 'R') || (data.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && ((data.CSA.phaseEncodingDirectionPositive == 1) || (data.CSA.phaseEncodingDirectionPositive == 0))) { - if (data.phaseEncodingRC == 'C') { - images->addAttribute("phaseEncodingDirection", "j"); - // Notice the XOR (^): the sense of phaseEncodingDirectionPositive - // is reversed if we are flipping the y-axis - images->addAttribute("phaseEncodingSign", ((data.CSA.phaseEncodingDirectionPositive == 0) ^ opts.isFlipY) ? -1 : 1); - } - else if (data.phaseEncodingRC == 'R') { - images->addAttribute("phaseEncodingDirection", "i"); - images->addAttribute("phaseEncodingSign", data.CSA.phaseEncodingDirectionPositive == 0 ? -1 : 1); - } - } - - // Slice timing (stored in seconds) - if (data.CSA.sliceTiming[0] >= 0.0 && (data.manufacturer == kMANUFACTURER_UIH || data.manufacturer == kMANUFACTURER_GE || (data.manufacturer == kMANUFACTURER_SIEMENS && !data.isXA10A))) { - std::vector sliceTimes; - for (int i=0; iaddAttribute("sliceTiming", sliceTimes); - } - - images->addAttribute("patientIdentifier", data.patientID); - images->addAttribute("patientName", data.patientName); - images->addDateAttribute("patientBirthDate", data.patientBirthDate); - if (strlen(data.patientAge) > 0 && strcmp(data.patientAge,"000Y") != 0) - images->addAttribute("patientAge", data.patientAge); - if (data.patientSex == 'F') - images->addAttribute("patientSex", "F"); - else if (data.patientSex == 'M') - images->addAttribute("patientSex", "M"); - images->addAttribute("patientWeight", data.patientWeight); - images->addAttribute("comments", data.imageComments); +void nii_saveAttributes(struct TDICOMdata &data, struct nifti_1_header &header, struct TDCMopts &opts, const char *filename) { + ImageList *images = (ImageList *)opts.imageList; + switch (data.modality) { + case kMODALITY_CR: + images->addAttribute("modality", "CR"); + break; + case kMODALITY_CT: + images->addAttribute("modality", "CT"); + break; + case kMODALITY_MR: + images->addAttribute("modality", "MR"); + break; + case kMODALITY_PT: + images->addAttribute("modality", "PT"); + break; + case kMODALITY_US: + images->addAttribute("modality", "US"); + break; + } + switch (data.manufacturer) { + case kMANUFACTURER_SIEMENS: + images->addAttribute("manufacturer", "Siemens"); + break; + case kMANUFACTURER_GE: + images->addAttribute("manufacturer", "GE"); + break; + case kMANUFACTURER_PHILIPS: + images->addAttribute("manufacturer", "Philips"); + break; + case kMANUFACTURER_TOSHIBA: + images->addAttribute("manufacturer", "Toshiba"); + break; + case kMANUFACTURER_UIH: + images->addAttribute("manufacturer", "UIH"); + break; + case kMANUFACTURER_BRUKER: + images->addAttribute("manufacturer", "Bruker"); + break; + case kMANUFACTURER_HITACHI: + images->addAttribute("manufacturer", "Hitachi"); + break; + case kMANUFACTURER_CANON: + images->addAttribute("manufacturer", "Canon"); + break; + } + images->addAttribute("scannerModelName", data.manufacturersModelName); + images->addAttribute("imageType", data.imageType); + if (data.seriesNum > 0) + images->addAttribute("seriesNumber", int(data.seriesNum)); + images->addAttribute("seriesDescription", data.seriesDescription); + images->addAttribute("sequenceName", data.sequenceName); + images->addAttribute("protocolName", data.protocolName); + images->addDateAttribute("studyDate", data.studyDate); + images->addTimeAttribute("studyTime", data.studyTime); + images->addAttribute("fieldStrength", data.fieldStrength); + images->addAttribute("flipAngle", data.flipAngle); + images->addAttribute("echoTime", data.TE); + images->addAttribute("repetitionTime", data.TR); + images->addAttribute("inversionTime", data.TI); + if (!data.isXRay) { + images->addAttribute("sliceThickness", data.zThick); + images->addAttribute("sliceSpacing", data.zSpacing); + } + if (data.CSA.multiBandFactor > 1) + images->addAttribute("multibandFactor", data.CSA.multiBandFactor); + if (data.phaseEncodingSteps > 0) + images->addAttribute("phaseEncodingSteps", data.phaseEncodingSteps); + if (data.phaseEncodingLines > 0) + images->addAttribute("phaseEncodingLines", data.phaseEncodingLines); + // Calculations relating to the reconstruction in the phase encode direction, + // which are needed to derive effective echo spacing and readout time below. + // See the nii_SaveBIDS() function for details + int reconMatrixPE = data.phaseEncodingLines; + if ((header.dim[2] > 0) && (header.dim[1] > 0)) { + if (header.dim[1] == header.dim[2]) //phase encoding does not matter + reconMatrixPE = header.dim[2]; + else if (data.phaseEncodingRC == 'C') + reconMatrixPE = header.dim[2]; + else if (data.phaseEncodingRC == 'R') + reconMatrixPE = header.dim[1]; + } + double bandwidthPerPixelPhaseEncode = data.bandwidthPerPixelPhaseEncode; + if (bandwidthPerPixelPhaseEncode == 0.0) + bandwidthPerPixelPhaseEncode = data.CSA.bandwidthPerPixelPhaseEncode; + double effectiveEchoSpacing = 0.0; + if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode > 0.0)) + effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * reconMatrixPE); + if (data.effectiveEchoSpacingGE > 0.0) { + double roundFactor = data.isPartialFourier ? 4.0 : 2.0; + double totalReadoutTime = ((ceil(1.0 / roundFactor * data.phaseEncodingLines / data.accelFactPE) * roundFactor) - 1.0) * data.effectiveEchoSpacingGE * 0.000001; + effectiveEchoSpacing = totalReadoutTime / (reconMatrixPE - 1); + } + images->addAttribute("effectiveEchoSpacing", effectiveEchoSpacing); + if (data.manufacturer == kMANUFACTURER_UIH) + images->addAttribute("effectiveReadoutTime", data.acquisitionDuration / 1000.0); + else if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0)) + images->addAttribute("effectiveReadoutTime", effectiveEchoSpacing * (reconMatrixPE - 1.0)); + images->addAttribute("pixelBandwidth", data.pixelBandwidth); + if ((data.manufacturer == kMANUFACTURER_SIEMENS) && (data.dwellTime > 0)) + images->addAttribute("dwellTime", data.dwellTime * 1e-9); + // Phase encoding polarity + // We only save these attributes if both direction and polarity are known + bool isSkipPhaseEncodingAxis = data.is3DAcq; + if (data.echoTrainLength > 1) + isSkipPhaseEncodingAxis = false; //issue 371: ignore phaseEncoding for 3D MP-RAGE/SPACE, but report for 3D EPI + if (((data.phaseEncodingRC == 'R') || (data.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && ((data.CSA.phaseEncodingDirectionPositive == 1) || (data.CSA.phaseEncodingDirectionPositive == 0))) { + if (data.phaseEncodingRC == 'C') { + images->addAttribute("phaseEncodingDirection", "j"); + // Notice the XOR (^): the sense of phaseEncodingDirectionPositive + // is reversed if we are flipping the y-axis + images->addAttribute("phaseEncodingSign", ((data.CSA.phaseEncodingDirectionPositive == 0) ^ opts.isFlipY) ? -1 : 1); + } else if (data.phaseEncodingRC == 'R') { + images->addAttribute("phaseEncodingDirection", "i"); + images->addAttribute("phaseEncodingSign", data.CSA.phaseEncodingDirectionPositive == 0 ? -1 : 1); + } + } + // Slice timing (stored in seconds) + if (data.CSA.sliceTiming[0] >= 0.0 && (data.manufacturer == kMANUFACTURER_UIH || data.manufacturer == kMANUFACTURER_GE || (data.manufacturer == kMANUFACTURER_SIEMENS && !data.isXA10A))) { + std::vector sliceTimes; + for (int i = 0; i < header.dim[3]; i++) { + if (data.CSA.sliceTiming[i] < 0.0) + break; + sliceTimes.push_back(data.CSA.sliceTiming[i] / 1000.0); + } + images->addAttribute("sliceTiming", sliceTimes); + } + images->addAttribute("patientIdentifier", data.patientID); + images->addAttribute("patientName", data.patientName); + images->addDateAttribute("patientBirthDate", data.patientBirthDate); + if (strlen(data.patientAge) > 0 && strcmp(data.patientAge, "000Y") != 0) + images->addAttribute("patientAge", data.patientAge); + if (data.patientSex == 'F') + images->addAttribute("patientSex", "F"); + else if (data.patientSex == 'M') + images->addAttribute("patientSex", "M"); + images->addAttribute("patientWeight", data.patientWeight); + images->addAttribute("comments", data.imageComments); } #else -int pigz_File(char * fname, struct TDCMopts opts, size_t imgsz) { +int pigz_File(char *fname, struct TDCMopts opts, size_t imgsz) { //given "/dir/file.nii" creates "/dir/file.nii.gz" char blockSize[768]; strcpy(blockSize, ""); //-b 960 increases block size from 128 to 960: each block has 32kb lead in... so less redundancy - if (imgsz > 1000000) strcpy(blockSize, " -b 960"); + if (imgsz > 1000000) + strcpy(blockSize, " -b 960"); char command[768]; - strcpy(command, "\"" ); - strcat(command, opts.pigzname ); - if ((opts.gzLevel > 0) && (opts.gzLevel < 12)) { + strcpy(command, "\""); + strcat(command, opts.pigzname); + if ((opts.gzLevel > 0) && (opts.gzLevel < 12)) { char newstr[256]; sprintf(newstr, "\"%s -n -f -%d \"", blockSize, opts.gzLevel); strcat(command, newstr); @@ -3200,185 +3398,208 @@ int pigz_File(char * fname, struct TDCMopts opts, size_t imgsz) { } strcat(command, fname); strcat(command, "\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' - #if defined(_WIN64) || defined(_WIN32) //using CreateProcess instead of system to run in background (avoids screen flicker) - DWORD exitCode; +#if defined(_WIN64) || defined(_WIN32) //using CreateProcess instead of system to run in background (avoids screen flicker) + DWORD exitCode; PROCESS_INFORMATION ProcessInfo = {0}; - STARTUPINFO startupInfo= {0}; + STARTUPINFO startupInfo = {0}; startupInfo.cb = sizeof(startupInfo); - //StartupInfo.cb = sizeof StartupInfo ; //Only compulsory field - if(CreateProcess(NULL, command, NULL,NULL,FALSE,NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,NULL, NULL,&startupInfo,&ProcessInfo)) { - //printMessage("compression --- %s\n",command); - WaitForSingleObject(ProcessInfo.hProcess,INFINITE); - CloseHandle(ProcessInfo.hThread); - CloseHandle(ProcessInfo.hProcess); - } else - printMessage("Compression failed %s\n",command); - #else //if win else linux + //StartupInfo.cb = sizeof StartupInfo ; //Only compulsory field + if (CreateProcess(NULL, command, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, NULL, &startupInfo, &ProcessInfo)) { + //printMessage("compression --- %s\n",command); + WaitForSingleObject(ProcessInfo.hProcess, INFINITE); + CloseHandle(ProcessInfo.hThread); + CloseHandle(ProcessInfo.hProcess); + } else + printMessage("Compression failed %s\n", command); +#else //if win else linux int ret = system(command); if (ret == -1) - printWarning("Failed to execute: %s\n",command); - #endif //else linux - printMessage("Compress: %s\n",command); - return EXIT_SUCCESS; + printWarning("Failed to execute: %s\n", command); +#endif //else linux + printMessage("Compress: %s\n", command); + return EXIT_SUCCESS; } // pigz_File() - -int nii_saveNRRD(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts, struct TDICOMdata d, struct TDTI4D *dti4D, int numDTI) { +int nii_saveNRRD(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, struct TDTI4D *dti4D, int numDTI) { int n, nDim = hdr.dim[0]; - //printMessage("NRRD writer is experimental\n"); - if (nDim < 1) return EXIT_FAILURE; - bool isGz = opts.isGz; + //printMessage("NRRD writer is experimental\n"); + if (nDim < 1) + return EXIT_FAILURE; + bool isGz = opts.isGz; size_t imgsz = nii_ImgBytes(hdr); - if ((isGz) && (imgsz >= 2147483647)) { + if ((isGz) && (imgsz >= 2147483647)) { printWarning("Saving huge image uncompressed (many GZip tools have 2 Gb limit).\n"); isGz = false; } char fname[2048] = {""}; - strcpy (fname, niiFilename); - if (isGz) - strcat (fname,".nhdr"); //nrrd or nhdr - else - strcat (fname,".nrrd"); //nrrd or nhdr + strcpy(fname, niiFilename); + if (isGz) + strcat(fname, ".nhdr"); //nrrd or nhdr + else + strcat(fname, ".nrrd"); //nrrd or nhdr FILE *fp = fopen(fname, "w"); - fprintf(fp,"NRRD0005\n"); - fprintf(fp,"# Complete NRRD file format specification at:\n"); - fprintf(fp,"# http://teem.sourceforge.net/nrrd/format.html\n"); - fprintf(fp,"# dcm2niix %s NRRD export transforms by Tashrif Billah\n", kDCMdate); - char rgbNoneStr[10] = {""}; + fprintf(fp, "NRRD0005\n"); + fprintf(fp, "# Complete NRRD file format specification at:\n"); + fprintf(fp, "# http://teem.sourceforge.net/nrrd/format.html\n"); + fprintf(fp, "# dcm2niix %s NRRD export transforms by Tashrif Billah\n", kDCMdate); + char rgbNoneStr[10] = {""}; //type tag - switch (hdr.datatype) { - case DT_RGB24: - fprintf(fp,"type: uint8\n"); - strcpy (rgbNoneStr, " none"); - break; - case DT_UINT8: - fprintf(fp,"type: uint8\n"); - break; - case DT_INT16: - fprintf(fp,"type: int16\n"); - break; - case DT_UINT16: - fprintf(fp,"type: uint16\n"); - break; - case DT_FLOAT32: - fprintf(fp,"type: float\n"); - break; - case DT_INT32: - fprintf(fp,"type: int32\n"); - break; - case DT_FLOAT64: - fprintf(fp,"type: double\n"); - break; - default: - printError("Unknown NRRD datatype %d\n", hdr.datatype); - fclose(fp); - return EXIT_FAILURE; - } - //dimension tag - if (hdr.datatype == DT_RGB24) - fprintf(fp,"dimension: %d\n", nDim+1); //RGB is first dimension - else - fprintf(fp,"dimension: %d\n", nDim); - //space tag - fprintf(fp,"space: right-anterior-superior\n"); + switch (hdr.datatype) { + case DT_RGB24: + fprintf(fp, "type: uint8\n"); + strcpy(rgbNoneStr, " none"); + break; + case DT_UINT8: + fprintf(fp, "type: uint8\n"); + break; + case DT_INT16: + fprintf(fp, "type: int16\n"); + break; + case DT_UINT16: + fprintf(fp, "type: uint16\n"); + break; + case DT_FLOAT32: + fprintf(fp, "type: float\n"); + break; + case DT_INT32: + fprintf(fp, "type: int32\n"); + break; + case DT_FLOAT64: + fprintf(fp, "type: double\n"); + break; + default: + printError("Unknown NRRD datatype %d\n", hdr.datatype); + fclose(fp); + return EXIT_FAILURE; + } + //dimension tag + if (hdr.datatype == DT_RGB24) + fprintf(fp, "dimension: %d\n", nDim + 1); //RGB is first dimension + else + fprintf(fp, "dimension: %d\n", nDim); + //space tag + fprintf(fp, "space: right-anterior-superior\n"); //sizes tag - fprintf(fp,"sizes:"); - if (hdr.datatype == DT_RGB24) fprintf(fp," 3"); + fprintf(fp, "sizes:"); + if (hdr.datatype == DT_RGB24) + fprintf(fp, " 3"); for (int i = 1; i <= hdr.dim[0]; i++) - fprintf(fp," %d", hdr.dim[i]); - fprintf(fp,"\n"); - //thicknesses - if ((d.zThick > 0.0) && (nDim >= 3)) { - fprintf(fp,"thicknesses: NaN NaN %g", d.zThick); - int n = 3; - while (n < nDim ) { - fprintf(fp," NaN"); - n ++; + fprintf(fp, " %d", hdr.dim[i]); + fprintf(fp, "\n"); + //thicknesses + if ((d.zThick > 0.0) && (nDim >= 3)) { + fprintf(fp, "thicknesses: NaN NaN %g", d.zThick); + int n = 3; + while (n < nDim) { + fprintf(fp, " NaN"); + n++; } - fprintf(fp,"\n"); - } - //byteskip only for .nhdr, not .nrrd + fprintf(fp, "\n"); + } + //byteskip only for .nhdr, not .nrrd if (littleEndianPlatform()) //raw data in native format - fprintf(fp,"endian: little\n"); + fprintf(fp, "endian: little\n"); else - fprintf(fp,"endian: big\n"); - if (isGz) { - fprintf(fp,"encoding: gzip\n"); - strcpy (fname, niiFilename); - strcat (fname,".raw.gz"); - char basefname[2048] = {""}; - getFileNameX(basefname, fname, 2048); - fprintf(fp,"data file: %s\n", basefname); - } else - fprintf(fp,"encoding: raw\n"); - fprintf(fp,"space units: \"mm\" \"mm\" \"mm\"\n"); + fprintf(fp, "endian: big\n"); + if (isGz) { + fprintf(fp, "encoding: gzip\n"); + strcpy(fname, niiFilename); + strcat(fname, ".raw.gz"); + char basefname[2048] = {""}; + getFileNameX(basefname, fname, 2048); + fprintf(fp, "data file: %s\n", basefname); + } else + fprintf(fp, "encoding: raw\n"); + fprintf(fp, "space units: \"mm\" \"mm\" \"mm\"\n"); //origin - fprintf(fp,"space origin: (%g,%g,%g)\n", hdr.srow_x[3],hdr.srow_y[3],hdr.srow_z[3]); + fprintf(fp, "space origin: (%g,%g,%g)\n", hdr.srow_x[3], hdr.srow_y[3], hdr.srow_z[3]); //space directions: - fprintf(fp,"space directions:%s (%g,%g,%g) (%g,%g,%g) (%g,%g,%g)", rgbNoneStr, hdr.srow_x[0],hdr.srow_y[0],hdr.srow_z[0], - hdr.srow_x[1],hdr.srow_y[1],hdr.srow_z[1], hdr.srow_x[2],hdr.srow_y[2],hdr.srow_z[2] ); + fprintf(fp, "space directions:%s (%g,%g,%g) (%g,%g,%g) (%g,%g,%g)", rgbNoneStr, hdr.srow_x[0], hdr.srow_y[0], hdr.srow_z[0], + hdr.srow_x[1], hdr.srow_y[1], hdr.srow_z[1], hdr.srow_x[2], hdr.srow_y[2], hdr.srow_z[2]); n = 3; - while (n < nDim ) { - fprintf(fp," none"); - n ++; + while (n < nDim) { + fprintf(fp, " none"); + n++; } - fprintf(fp,"\n"); + fprintf(fp, "\n"); //centerings tag if (hdr.dim[0] < 4) //*check RGB, more dims - fprintf(fp,"centerings:%s cell cell cell\n", rgbNoneStr); + fprintf(fp, "centerings:%s cell cell cell\n", rgbNoneStr); else - fprintf(fp,"centerings:%s cell cell cell ???\n", rgbNoneStr); + fprintf(fp, "centerings:%s cell cell cell ???\n", rgbNoneStr); //kinds tag - fprintf(fp,"kinds:"); - if (hdr.datatype == DT_RGB24) fprintf(fp," RGB-color"); + fprintf(fp, "kinds:"); + if (hdr.datatype == DT_RGB24) + fprintf(fp, " RGB-color"); n = 0; - while ((n < nDim ) && (n < 3)) { - fprintf(fp," space"); //dims 1..3 - n ++; + while ((n < nDim) && (n < 3)) { + fprintf(fp, " space"); //dims 1..3 + n++; } - while (n < nDim ) { - fprintf(fp," list"); //dims 4..7 - n ++; + while (n < nDim) { + fprintf(fp, " list"); //dims 4..7 + n++; } - fprintf(fp,"\n"); + fprintf(fp, "\n"); //http://teem.sourceforge.net/nrrd/format.html bool isFloat = (hdr.datatype == DT_FLOAT64) || (hdr.datatype == DT_FLOAT32); if (((!isSameFloat(hdr.scl_inter, 0.0)) || (!isSameFloat(hdr.scl_slope, 1.0))) && (!isFloat)) { //http://teem.sourceforge.net/nrrd/format.html double dtMin = 0.0; //DT_UINT8, DT_RGB24, DT_UINT16 - if (hdr.datatype == DT_INT16) dtMin = -32768.0; - if (hdr.datatype == DT_INT32) dtMin = -2147483648.0; - fprintf(fp,"oldmin: %8.8f\n", (dtMin * hdr.scl_slope) + hdr.scl_inter); + if (hdr.datatype == DT_INT16) + dtMin = -32768.0; + if (hdr.datatype == DT_INT32) + dtMin = -2147483648.0; + fprintf(fp, "oldmin: %8.8f\n", (dtMin * hdr.scl_slope) + hdr.scl_inter); double dtMax = 255.00; //DT_UINT8, DT_RGB24 - if (hdr.datatype == DT_INT16) dtMax = 32767.0; - if (hdr.datatype == DT_UINT16) dtMax = 65535.0; - if (hdr.datatype == DT_INT32) dtMax = 2147483647.0; - fprintf(fp,"oldmax: %8.8f\n", (dtMax * hdr.scl_slope) + hdr.scl_inter); + if (hdr.datatype == DT_INT16) + dtMax = 32767.0; + if (hdr.datatype == DT_UINT16) + dtMax = 65535.0; + if (hdr.datatype == DT_INT32) + dtMax = 2147483647.0; + fprintf(fp, "oldmax: %8.8f\n", (dtMax * hdr.scl_slope) + hdr.scl_inter); } //Slicer DWIconvert values - if (d.modality == kMODALITY_MR) fprintf(fp,"DICOM_0008_0060_Modality:=MR\n"); - if (d.modality == kMODALITY_CT) fprintf(fp,"DICOM_0008_0060_Modality:=CT\n"); - if (d.manufacturer == kMANUFACTURER_SIEMENS) fprintf(fp,"DICOM_0008_0070_Manufacturer:=SIEMENS\n"); - if (d.manufacturer == kMANUFACTURER_PHILIPS) fprintf(fp,"DICOM_0008_0070_Manufacturer:=Philips Medical Systems\n"); - if (d.manufacturer == kMANUFACTURER_GE) fprintf(fp,"DICOM_0008_0070_Manufacturer:=GE MEDICAL SYSTEMS\n"); - if (strlen(d.manufacturersModelName) > 0) fprintf(fp,"DICOM_0008_1090_ManufacturerModelName:=%s\n",d.manufacturersModelName); - if (strlen(d.scanOptions) > 0) fprintf(fp,"DICOM_0018_0022_ScanOptions:=%s\n",d.scanOptions); - if (d.is2DAcq) fprintf(fp,"DICOM_0018_0023_MRAcquisitionType:=2D\n"); - if (d.is3DAcq) fprintf(fp,"DICOM_0018_0023_MRAcquisitionType:=3D\n"); - //if (strlen(d.mrAcquisitionType) > 0) fprintf(fp,"DICOM_0018_0023_MRAcquisitionType:=%s\n",d.mrAcquisitionType); - if (d.TR > 0.0) fprintf(fp,"DICOM_0018_0080_RepetitionTime:=%g\n",d.TR); - if ((d.TE > 0.0) && (!d.isXRay)) fprintf(fp,"DICOM_0018_0081_EchoTime:=%g\n",d.TE); - if ((d.TE > 0.0) && (d.isXRay)) fprintf(fp,"DICOM_0018_1152_XRayExposure:=%g\n",d.TE); - if (d.numberOfAverages > 0.0) fprintf(fp,"DICOM_0018_0083_NumberOfAverages:=%g\n",d.numberOfAverages); - if (d.fieldStrength > 0.0) fprintf(fp,"DICOM_0018_0087_MagneticFieldStrength:=%g\n",d.fieldStrength); - if (strlen(d.softwareVersions) > 0) fprintf(fp,"DICOM_0018_1020_SoftwareVersions:=%s\n",d.softwareVersions); - if (d.flipAngle > 0.0) fprintf(fp,"DICOM_0018_1314_FlipAngle:=%g\n",d.flipAngle); + if (d.modality == kMODALITY_MR) + fprintf(fp, "DICOM_0008_0060_Modality:=MR\n"); + if (d.modality == kMODALITY_CT) + fprintf(fp, "DICOM_0008_0060_Modality:=CT\n"); + if (d.manufacturer == kMANUFACTURER_SIEMENS) + fprintf(fp, "DICOM_0008_0070_Manufacturer:=SIEMENS\n"); + if (d.manufacturer == kMANUFACTURER_PHILIPS) + fprintf(fp, "DICOM_0008_0070_Manufacturer:=Philips Medical Systems\n"); + if (d.manufacturer == kMANUFACTURER_GE) + fprintf(fp, "DICOM_0008_0070_Manufacturer:=GE MEDICAL SYSTEMS\n"); + if (strlen(d.manufacturersModelName) > 0) + fprintf(fp, "DICOM_0008_1090_ManufacturerModelName:=%s\n", d.manufacturersModelName); + if (strlen(d.scanOptions) > 0) + fprintf(fp, "DICOM_0018_0022_ScanOptions:=%s\n", d.scanOptions); + if (d.is2DAcq) + fprintf(fp, "DICOM_0018_0023_MRAcquisitionType:=2D\n"); + if (d.is3DAcq) + fprintf(fp, "DICOM_0018_0023_MRAcquisitionType:=3D\n"); + //if (strlen(d.mrAcquisitionType) > 0) fprintf(fp,"DICOM_0018_0023_MRAcquisitionType:=%s\n",d.mrAcquisitionType); + if (d.TR > 0.0) + fprintf(fp, "DICOM_0018_0080_RepetitionTime:=%g\n", d.TR); + if ((d.TE > 0.0) && (!d.isXRay)) + fprintf(fp, "DICOM_0018_0081_EchoTime:=%g\n", d.TE); + if ((d.TE > 0.0) && (d.isXRay)) + fprintf(fp, "DICOM_0018_1152_XRayExposure:=%g\n", d.TE); + if (d.numberOfAverages > 0.0) + fprintf(fp, "DICOM_0018_0083_NumberOfAverages:=%g\n", d.numberOfAverages); + if (d.fieldStrength > 0.0) + fprintf(fp, "DICOM_0018_0087_MagneticFieldStrength:=%g\n", d.fieldStrength); + if (strlen(d.softwareVersions) > 0) + fprintf(fp, "DICOM_0018_1020_SoftwareVersions:=%s\n", d.softwareVersions); + if (d.flipAngle > 0.0) + fprintf(fp, "DICOM_0018_1314_FlipAngle:=%g\n", d.flipAngle); //multivolume but NOT DTI, e.g. fMRI/DCE see https://www.slicer.org/wiki/Documentation/4.4/Modules/MultiVolumeExplorer - // https://github.com/QIICR/PkModeling/blob/master/PkSolver/IO/MultiVolumeMetaDictReader.cxx#L34-L58 - // for "MultiVolume.FrameLabels:=" - // https://www.slicer.org/wiki/Documentation/4.4/Modules/MultiVolumeExplorer - // for "axis 0 index values:=" - // https://github.com/mhe/pynrrd/issues/71 + // https://github.com/QIICR/PkModeling/blob/master/PkSolver/IO/MultiVolumeMetaDictReader.cxx#L34-L58 + // for "MultiVolume.FrameLabels:=" + // https://www.slicer.org/wiki/Documentation/4.4/Modules/MultiVolumeExplorer + // for "axis 0 index values:=" + // https://github.com/mhe/pynrrd/issues/71 // "I don't know if it is a good idea for dcm2niix to mimic Slicer converter tags" Andrey Fedorov /* if ((nDim > 3) && (hdr.dim[4] > 1) && (numDTI < 1)) { @@ -3393,71 +3614,74 @@ int nii_saveNRRD(char * niiFilename, struct nifti_1_header hdr, unsigned char* i frameTime = d.triggerDelayTime / (hdr.dim[4] - 1); //GE dce data for (int i = 0; i < (hdr.dim[4]-1); i++) fprintf(fp,"%g,", i * frameTime); - fprintf(fp,"%g\n", (hdr.dim[4]-1) * frameTime); + fprintf(fp,"%g\n", (hdr.dim[4]-1) * frameTime); fprintf(fp,"MultiVolume.NumberOfFrames:=%d\n",hdr.dim[4]); } */ //DWI values if ((nDim > 3) && (numDTI > 0) && (numDTI < kMaxDTI4D)) { mat33 inv; - LOAD_MAT33(inv, hdr.pixdim[1],0.0,0.0, 0.0,hdr.pixdim[2],0.0, 0.0, 0.0,hdr.pixdim[3]); + LOAD_MAT33(inv, hdr.pixdim[1], 0.0, 0.0, 0.0, hdr.pixdim[2], 0.0, 0.0, 0.0, hdr.pixdim[3]); inv = nifti_mat33_inverse(inv); mat33 s; - LOAD_MAT33(s,hdr.srow_x[0],hdr.srow_x[1],hdr.srow_x[2], - hdr.srow_y[0],hdr.srow_y[1],hdr.srow_y[2], - hdr.srow_z[0],hdr.srow_z[1],hdr.srow_z[2]); + LOAD_MAT33(s, hdr.srow_x[0], hdr.srow_x[1], hdr.srow_x[2], + hdr.srow_y[0], hdr.srow_y[1], hdr.srow_y[2], + hdr.srow_z[0], hdr.srow_z[1], hdr.srow_z[2]); mat33 mf = nifti_mat33_mul(inv, s); - fprintf(fp,"measurement frame: (%g,%g,%g) (%g,%g,%g) (%g,%g,%g)\n", - mf.m[0][0],mf.m[1][0],mf.m[2][0], - mf.m[0][1],mf.m[1][1],mf.m[2][1], - mf.m[0][2],mf.m[1][2],mf.m[2][2]); + fprintf(fp, "measurement frame: (%g,%g,%g) (%g,%g,%g) (%g,%g,%g)\n", + mf.m[0][0], mf.m[1][0], mf.m[2][0], + mf.m[0][1], mf.m[1][1], mf.m[2][1], + mf.m[0][2], mf.m[1][2], mf.m[2][2]); //modality tag - fprintf(fp,"modality:=DWMRI\n"); + fprintf(fp, "modality:=DWMRI\n"); float b_max = 0.0; for (int i = 0; i < numDTI; i++) if (dti4D->S[i].V[0] > b_max) b_max = dti4D->S[i].V[0]; - fprintf(fp,"DWMRI_b-value:=%g\n", b_max); - //gradient tag, e.g. DWMRI_gradient_0000:=0.0 0.0 0.0 + fprintf(fp, "DWMRI_b-value:=%g\n", b_max); + //gradient tag, e.g. DWMRI_gradient_0000:=0.0 0.0 0.0 for (int i = 0; i < numDTI; i++) { float factor = 0.0; - if (b_max > 0) factor = sqrt(dti4D->S[i].V[0]/b_max); - if ( (dti4D->S[i].V[0] > 50.0) && (isSameFloatGE(0.0, dti4D->S[i].V[1])) && (isSameFloatGE(0.0, dti4D->S[i].V[2])) && (isSameFloatGE(0.0, dti4D->S[i].V[3])) ) { + if (b_max > 0) + factor = sqrt(dti4D->S[i].V[0] / b_max); + if ((dti4D->S[i].V[0] > 50.0) && (isSameFloatGE(0.0, dti4D->S[i].V[1])) && (isSameFloatGE(0.0, dti4D->S[i].V[2])) && (isSameFloatGE(0.0, dti4D->S[i].V[3]))) { //On May 2, 2019, at 10:47 AM, Gordon L. Kindlmann <> wrote: - //(assuming b_max 2000, we write "isotropic" for the b=2000 isotropic image, and specify the b-value if it is an isotropic image but not b-bax + //(assuming b_max 2000, we write "isotropic" for the b=2000 isotropic image, and specify the b-value if it is an isotropic image but not b-bax // DWMRI_gradient_0003:=isotropic b=1000 // DWMRI_gradient_0004:=isotropic if (isSameFloatGE(b_max, dti4D->S[i].V[0])) - fprintf(fp,"DWMRI_gradient_%04d:=isotropic\n", i); + fprintf(fp, "DWMRI_gradient_%04d:=isotropic\n", i); else - fprintf(fp,"DWMRI_gradient_%04d:=isotropic b=%g\n", i, dti4D->S[i].V[0]); + fprintf(fp, "DWMRI_gradient_%04d:=isotropic b=%g\n", i, dti4D->S[i].V[0]); } else - fprintf(fp,"DWMRI_gradient_%04d:=%.17g %.17g %.17g\n", i, factor*dti4D->S[i].V[1], factor*dti4D->S[i].V[2], factor*dti4D->S[i].V[3]); - //printf("%g = %g %g %g>>>>\n",dti4D->S[i].V[0], dti4D->S[i].V[1],dti4D->S[i].V[2],dti4D->S[i].V[3]); + fprintf(fp, "DWMRI_gradient_%04d:=%.17g %.17g %.17g\n", i, factor * dti4D->S[i].V[1], factor * dti4D->S[i].V[2], factor * dti4D->S[i].V[3]); + //printf("%g = %g %g %g>>>>\n",dti4D->S[i].V[0], dti4D->S[i].V[1],dti4D->S[i].V[2],dti4D->S[i].V[3]); } } - fprintf(fp,"\n"); //blank line: end of NRRD header - if (!isGz) fwrite(&im[0], imgsz, 1, fp); + fprintf(fp, "\n"); //blank line: end of NRRD header + if (!isGz) + fwrite(&im[0], imgsz, 1, fp); fclose(fp); - if (!isGz) return EXIT_SUCCESS; - //below: gzip file - #ifdef myDisableZLib - if (strlen(opts.pigzname) < 1) { //internal compression - printError("Compiled without gz support, unable to compress %s\n", fname); - return EXIT_FAILURE; - } - #else - if (strlen(opts.pigzname) < 1) { //internal compression - writeNiiGz (fname, hdr, im, imgsz, opts.gzLevel, true); - return EXIT_SUCCESS; - } - #endif + if (!isGz) + return EXIT_SUCCESS; +//below: gzip file +#ifdef myDisableZLib + if (strlen(opts.pigzname) < 1) { //internal compression + printError("Compiled without gz support, unable to compress %s\n", fname); + return EXIT_FAILURE; + } +#else + if (strlen(opts.pigzname) < 1) { //internal compression + writeNiiGz(fname, hdr, im, imgsz, opts.gzLevel, true); + return EXIT_SUCCESS; + } +#endif //below pigz - strcpy (fname, niiFilename); //without gz - strcat (fname,".raw"); - fp = fopen(fname, "wb"); - fwrite(&im[0], imgsz, 1, fp); - fclose(fp); - return pigz_File(fname, opts, imgsz); + strcpy(fname, niiFilename); //without gz + strcat(fname, ".raw"); + fp = fopen(fname, "wb"); + fwrite(&im[0], imgsz, 1, fp); + fclose(fp); + return pigz_File(fname, opts, imgsz); } // nii_saveNRRD() #endif @@ -3465,386 +3689,382 @@ int nii_saveNRRD(char * niiFilename, struct nifti_1_header hdr, unsigned char* i #ifdef USING_R #ifndef max - #define max(a,b) std::max(a,b) +#define max(a, b) std::max(a, b) #endif #ifndef min - #define min(a,b) std::min(a,b) +#define min(a, b) std::min(a, b) #endif #else #ifndef max - #define max(a,b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a > _b ? _a : _b; }) +#define max(a, b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) #endif #ifndef min - #define min(a,b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a < _b ? _a : _b; }) +#define min(a, b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) #endif #endif -void removeSclSlopeInter(struct nifti_1_header* hdr, unsigned char* img) { +void removeSclSlopeInter(struct nifti_1_header *hdr, unsigned char *img) { //NRRD does not have scl_slope scl_inter. Adjust data if possible // https://discourse.slicer.org/t/preserve-image-rescale-and-slope-when-saving-in-nrrd-file/13357 - if (isSameFloat(hdr->scl_inter,0.0) && isSameFloat(hdr->scl_slope,1.0)) return; - if ((!isSameFloat(fmod(hdr->scl_inter, 1.0f),0.0)) || (!isSameFloat(fmod(hdr->scl_slope, 1.0f),0.0))) return; + if (isSameFloat(hdr->scl_inter, 0.0) && isSameFloat(hdr->scl_slope, 1.0)) + return; + if ((!isSameFloat(fmod(hdr->scl_inter, 1.0f), 0.0)) || (!isSameFloat(fmod(hdr->scl_slope, 1.0f), 0.0))) + return; int nVox = 1; for (int i = 1; i < 8; i++) - if (hdr->dim[i] > 1) nVox = nVox * hdr->dim[i]; + if (hdr->dim[i] > 1) + nVox = nVox * hdr->dim[i]; if (hdr->datatype == DT_INT16) { - int16_t * img16 = (int16_t*) img; + int16_t *img16 = (int16_t *)img; int16_t mn, mx; mn = img16[0]; mx = mn; - for (int i=0; i < nVox; i++) { + for (int i = 0; i < nVox; i++) { mn = min(mn, img16[i]); mx = max(mx, img16[i]); } float v = (mn * hdr->scl_slope) + hdr->scl_inter; - if ((v < -32768) || (v > 32767)) return; + if ((v < -32768) || (v > 32767)) + return; v = (mx * hdr->scl_slope) + hdr->scl_inter; - if ((v < -32768) || (v > 32767)) return; - for (int i=0; i < nVox; i++) + if ((v < -32768) || (v > 32767)) + return; + for (int i = 0; i < nVox; i++) img16[i] = round((img16[i] * hdr->scl_slope) + hdr->scl_inter); - hdr->scl_slope = 1.0; + hdr->scl_slope = 1.0; hdr->scl_inter = 0.0; - return; + return; } if (hdr->datatype == DT_UINT16) { - uint16_t * img16 = (uint16_t*) img; - uint16_t mn, mx; - mn = img16[0]; - mx = mn; - for (int i=0; i < nVox; i++) { - mn = min(mn, img16[i]); - mx = max(mx, img16[i]); - } - float v = (mn * hdr->scl_slope) + hdr->scl_inter; - if ((v < 0) || (v > 65535)) return; - v = (mx * hdr->scl_slope) + hdr->scl_inter; - if ((v < 0) || (v > 65535)) return; - for (int i=0; i < nVox; i++) - img16[i] = round((img16[i] * hdr->scl_slope) + hdr->scl_inter); - hdr->scl_slope = 1.0; - hdr->scl_inter = 0.0; - return; - } - //printWarning("NRRD unable to record scl_slope/scl_inter %g/%g\n", hdr->scl_slope, hdr->scl_inter); + uint16_t *img16 = (uint16_t *)img; + uint16_t mn, mx; + mn = img16[0]; + mx = mn; + for (int i = 0; i < nVox; i++) { + mn = min(mn, img16[i]); + mx = max(mx, img16[i]); + } + float v = (mn * hdr->scl_slope) + hdr->scl_inter; + if ((v < 0) || (v > 65535)) + return; + v = (mx * hdr->scl_slope) + hdr->scl_inter; + if ((v < 0) || (v > 65535)) + return; + for (int i = 0; i < nVox; i++) + img16[i] = round((img16[i] * hdr->scl_slope) + hdr->scl_inter); + hdr->scl_slope = 1.0; + hdr->scl_inter = 0.0; + return; + } + //printWarning("NRRD unable to record scl_slope/scl_inter %g/%g\n", hdr->scl_slope, hdr->scl_inter); } #ifndef USING_R -void swapEndian(struct nifti_1_header* hdr, unsigned char* im, bool isNative) { +void swapEndian(struct nifti_1_header *hdr, unsigned char *im, bool isNative) { //swap endian from big->little or little->big // must be told which is native to detect datatype and number of voxels // one could also auto-detect: hdr->sizeof_hdr==348 - if (!isNative) swap_nifti_header(hdr); + if (!isNative) + swap_nifti_header(hdr); int nVox = 1; - for (int i = 1; i < 8; i++) - if (hdr->dim[i] > 1) nVox = nVox * hdr->dim[i]; + for (int i = 1; i < 8; i++) + if (hdr->dim[i] > 1) + nVox = nVox * hdr->dim[i]; int bitpix = hdr->bitpix; int datatype = hdr->datatype; - if (isNative) swap_nifti_header(hdr); - if (datatype == DT_RGBA32) return; + if (isNative) + swap_nifti_header(hdr); + if (datatype == DT_RGBA32) + return; //n.b. do not swap 8-bit, 24-bit RGB, and 32-bit RGBA - if (bitpix == 16) nifti_swap_2bytes(nVox, im); - if (bitpix == 32) nifti_swap_4bytes(nVox, im); - if (bitpix == 64) nifti_swap_8bytes(nVox, im); + if (bitpix == 16) + nifti_swap_2bytes(nVox, im); + if (bitpix == 32) + nifti_swap_4bytes(nVox, im); + if (bitpix == 64) + nifti_swap_8bytes(nVox, im); } -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 = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); +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 = (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); - if (imgsz < 1) { - printMessage("Error: Image size is zero bytes %s\n", niiFilename); - return EXIT_FAILURE; - } - #ifndef myDisableGzSizeLimits - //see https://github.com/rordenlab/dcm2niix/issues/124 - uint64_t kMaxPigz = 4294967264; - //https://stackoverflow.com/questions/5272825/detecting-64bit-compile-in-c - #ifndef UINTPTR_MAX - uint64_t kMaxGz = 2147483647; - #elif UINTPTR_MAX == 0xffffffff - uint64_t kMaxGz = 2147483647; - #elif UINTPTR_MAX == 0xffffffffffffffff - uint64_t kMaxGz = kMaxPigz; - #else - compiler error: unable to determine is 32 or 64 bit - #endif - #ifndef myDisableZLib - if ((opts.isGz) && (strlen(opts.pigzname) < 1) && ((imgsz+hdr.vox_offset) >= kMaxGz) ) { //use internal compressor - printWarning("Saving uncompressed data: internal compressor unable to process such large files.\n"); - if ((imgsz+hdr.vox_offset) < kMaxPigz) - printWarning(" Hint: using external compressor (pigz) should help.\n"); - } else if ((opts.isGz) && (strlen(opts.pigzname) < 1) && ((imgsz+hdr.vox_offset) < kMaxGz) ) { //use internal compressor - if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) - writeNiiGz (niiFilename, hdr, im, imgsz, opts.gzLevel, false); - if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) - return EXIT_SUCCESS; - } - #endif - #endif - char fname[2048] = {""}; - strcpy (fname,niiFilename); - strcat (fname,".nii"); - #if defined(_WIN64) || defined(_WIN32) + } + hdr.vox_offset = 352; + size_t imgsz = nii_ImgBytes(hdr); + if (imgsz < 1) { + printMessage("Error: Image size is zero bytes %s\n", niiFilename); + return EXIT_FAILURE; + } +#ifndef myDisableGzSizeLimits + //see https://github.com/rordenlab/dcm2niix/issues/124 + uint64_t kMaxPigz = 4294967264; +//https://stackoverflow.com/questions/5272825/detecting-64bit-compile-in-c +#ifndef UINTPTR_MAX + uint64_t kMaxGz = 2147483647; +#elif UINTPTR_MAX == 0xffffffff + uint64_t kMaxGz = 2147483647; +#elif UINTPTR_MAX == 0xffffffffffffffff + uint64_t kMaxGz = kMaxPigz; +#else + compiler error : unable to determine is 32 or 64 bit +#endif +#ifndef myDisableZLib + if ((opts.isGz) && (strlen(opts.pigzname) < 1) && ((imgsz + hdr.vox_offset) >= kMaxGz)) { //use internal compressor + printWarning("Saving uncompressed data: internal compressor unable to process such large files.\n"); + if ((imgsz + hdr.vox_offset) < kMaxPigz) + printWarning(" Hint: using external compressor (pigz) should help.\n"); + } else if ((opts.isGz) && (strlen(opts.pigzname) < 1) && ((imgsz + hdr.vox_offset) < kMaxGz)) { //use internal compressor + if (!opts.isSaveNativeEndian) + swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) + writeNiiGz(niiFilename, hdr, im, imgsz, opts.gzLevel, false); + if (!opts.isSaveNativeEndian) + swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) + return EXIT_SUCCESS; + } +#endif +#endif + char fname[2048] = {""}; + strcpy(fname, niiFilename); + strcat(fname, ".nii"); +#if defined(_WIN64) || defined(_WIN32) if ((opts.isGz) && (opts.isPipedGz)) - printWarning("The 'optimal' piped gz is only available for Unix\n"); - #else //if windows else Unix - if ((opts.isGz) && (opts.isPipedGz) && (strlen(opts.pigzname) > 0) ) { + printWarning("The 'optimal' piped gz is only available for Unix\n"); +#else //if windows else Unix + if ((opts.isGz) && (opts.isPipedGz) && (strlen(opts.pigzname) > 0)) { //piped gz if (opts.isVerbose) - printMessage(" Optimal piped gz will fail if pigz version < 2.3.4.\n"); - char command[768]; - strcpy(command, "\"" ); - strcat(command, opts.pigzname ); - if ((opts.gzLevel > 0) && (opts.gzLevel < 12)) { - char newstr[256]; - sprintf(newstr, "\" -n -f -%d > \"", opts.gzLevel); - strcat(command, newstr); - } else - strcat(command, "\" -n -f > \""); //current versions of pigz (2.3) built on Windows can hang if the filename is included, presumably because it is not finding the path characters ':\' - strcat(command, fname); - strcat(command, ".gz\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' + printMessage(" Optimal piped gz will fail if pigz version < 2.3.4.\n"); + char command[768]; + strcpy(command, "\""); + strcat(command, opts.pigzname); + if ((opts.gzLevel > 0) && (opts.gzLevel < 12)) { + char newstr[256]; + sprintf(newstr, "\" -n -f -%d > \"", opts.gzLevel); + strcat(command, newstr); + } else + strcat(command, "\" -n -f > \""); //current versions of pigz (2.3) built on Windows can hang if the filename is included, presumably because it is not finding the path characters ':\' + strcat(command, fname); + strcat(command, ".gz\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' //strcat(command, "x.gz\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' - if (opts.isVerbose) - printMessage("Compress: %s\n",command); - FILE *pigzPipe; - if (( pigzPipe = popen(command, "w")) == NULL) { - printError("Unable to open pigz pipe\n"); - return EXIT_FAILURE; - } - if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) - fwrite(&hdr, sizeof(hdr), 1, pigzPipe); - uint32_t pad = 0; - fwrite(&pad, sizeof( pad), 1, pigzPipe); - fwrite(&im[0], imgsz, 1, pigzPipe); - pclose(pigzPipe); - if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) + if (opts.isVerbose) + printMessage("Compress: %s\n", command); + FILE *pigzPipe; + if ((pigzPipe = popen(command, "w")) == NULL) { + printError("Unable to open pigz pipe\n"); + return EXIT_FAILURE; + } + if (!opts.isSaveNativeEndian) + swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) + fwrite(&hdr, sizeof(hdr), 1, pigzPipe); + uint32_t pad = 0; + fwrite(&pad, sizeof(pad), 1, pigzPipe); + fwrite(&im[0], imgsz, 1, pigzPipe); + pclose(pigzPipe); + if (!opts.isSaveNativeEndian) + swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) return EXIT_SUCCESS; - } - #endif - FILE *fp = fopen(fname, "wb"); - if (!fp) return EXIT_FAILURE; - if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) - fwrite(&hdr, sizeof(hdr), 1, fp); - uint32_t pad = 0; - fwrite(&pad, sizeof( pad), 1, fp); - fwrite(&im[0], imgsz, 1, fp); - fclose(fp); - if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) - if ((opts.isGz) && (strlen(opts.pigzname) > 0) ) { - #ifndef myDisableGzSizeLimits - if ((imgsz+hdr.vox_offset) > kMaxPigz) { - printWarning("Saving uncompressed data: image too large for pigz.\n"); - return EXIT_SUCCESS; - } - #endif - return pigz_File(fname, opts, imgsz); - } - return EXIT_SUCCESS; -}// nii_saveNII() + } +#endif + FILE *fp = fopen(fname, "wb"); + if (!fp) + return EXIT_FAILURE; + if (!opts.isSaveNativeEndian) + swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) + fwrite(&hdr, sizeof(hdr), 1, fp); + uint32_t pad = 0; + fwrite(&pad, sizeof(pad), 1, fp); + fwrite(&im[0], imgsz, 1, fp); + fclose(fp); + if (!opts.isSaveNativeEndian) + swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) + if ((opts.isGz) && (strlen(opts.pigzname) > 0)) { +#ifndef myDisableGzSizeLimits + if ((imgsz + hdr.vox_offset) > kMaxPigz) { + printWarning("Saving uncompressed data: image too large for pigz.\n"); + return EXIT_SUCCESS; + } +#endif + return pigz_File(fname, opts, imgsz); + } + return EXIT_SUCCESS; +} // nii_saveNII() #endif -int nii_saveNIIx(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts) { +int nii_saveNIIx(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts) { struct TDICOMdata dcm = clear_dicom_data(); return nii_saveNII(niiFilename, hdr, im, opts, dcm); } -int nii_saveNII3D(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts, struct TDICOMdata d) { - //save 4D series as sequence of 3D volumes - struct nifti_1_header hdr1 = hdr; - int nVol = 1; - for (int i = 4; i < 8; i++) { - if (hdr.dim[i] > 1) nVol = nVol * hdr.dim[i]; - hdr1.dim[i] = 0; - } - hdr1.dim[0] = 3; //save as 3D file - size_t imgsz = nii_ImgBytes(hdr1); - size_t pos = 0; - char fname[2048] = {""}; - char zeroPad[PATH_MAX] = {""}; +int nii_saveNII3D(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) { + //save 4D series as sequence of 3D volumes + struct nifti_1_header hdr1 = hdr; + int nVol = 1; + for (int i = 4; i < 8; i++) { + if (hdr.dim[i] > 1) + nVol = nVol * hdr.dim[i]; + hdr1.dim[i] = 0; + } + hdr1.dim[0] = 3; //save as 3D file + size_t imgsz = nii_ImgBytes(hdr1); + size_t pos = 0; + char fname[2048] = {""}; + char zeroPad[PATH_MAX] = {""}; double fnVol = nVol; - int zeroPadLen = (1 + log10( fnVol)); - sprintf(zeroPad,"%%s_%%0%dd",zeroPadLen); - for (int i = 1; i <= nVol; i++) { - sprintf(fname,zeroPad,niiFilename,i); - if (nii_saveNII(fname, hdr1, (unsigned char*)&im[pos], opts, d) == EXIT_FAILURE) return EXIT_FAILURE; - pos += imgsz; - } - return EXIT_SUCCESS; -}// nii_saveNII3D() - -/* -//this version can convert INT16->UINT16 -// some were concerned about this https://github.com/rordenlab/dcm2niix/issues/198 -void nii_scale16bitSigned(unsigned char *img, struct nifti_1_header *hdr){ - if (hdr->datatype != DT_INT16) return; - int dim3to7 = 1; - for (int i = 3; i < 8; i++) - if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; - int nVox = hdr->dim[1]*hdr->dim[2]* dim3to7; - if (nVox < 1) return; - int16_t * img16 = (int16_t*) img; - int16_t max16 = img16[0]; - int16_t min16 = max16; - //clock_t start = clock(); - for (int i=0; i < nVox; i++) { - if (img16[i] < min16) - min16 = img16[i]; - if (img16[i] > max16) - max16 = img16[i]; - } - int kMx = 32000; //actually 32767 - maybe a bit of padding for interpolation ringing - bool isConvertToUint16 = true; //if false output is always same as input: INT16, if true and no negative values output will be UINT16 - if ((isConvertToUint16) && (min16 >= 0)) - kMx = 64000; - int scale = kMx / (int)max16; - if (abs(min16) > max16) - scale = kMx / (int)abs(min16); - if (scale < 2) return; //already uses dynamic range - hdr->scl_slope = hdr->scl_slope/ scale; - if ((isConvertToUint16) && (min16 >= 0)) { //only positive values: save as UINT16 0..65535 - hdr->datatype = DT_UINT16; - uint16_t * uimg16 = (uint16_t*) img; - for (int i=0; i < nVox; i++) - uimg16[i] = (int)img16[i] * scale; - } else {//includes negative values: save as INT16 -32768..32768 - for (int i=0; i < nVox; i++) - img16[i] = img16[i] * scale; - } - printMessage("Maximizing 16-bit range: raw %d..%d\n", min16, max16); -}*/ + int zeroPadLen = (1 + log10(fnVol)); + sprintf(zeroPad, "%%s_%%0%dd", zeroPadLen); + for (int i = 1; i <= nVol; i++) { + sprintf(fname, zeroPad, niiFilename, i); + if (nii_saveNII(fname, hdr1, (unsigned char *)&im[pos], opts, d) == EXIT_FAILURE) + return EXIT_FAILURE; + pos += imgsz; + } + return EXIT_SUCCESS; +} // nii_saveNII3D() void nii_storeIntegerScaleFactor(int scale, struct nifti_1_header *hdr) { -//appends NIfTI header description field with " isN" where N is integer scaling + //appends NIfTI header description field with " isN" where N is integer scaling char newstr[256]; sprintf(newstr, " is%d", scale); - if ((strlen(newstr)+strlen(hdr->descrip)) < 80) - strcat (hdr->descrip,newstr); + if ((strlen(newstr) + strlen(hdr->descrip)) < 80) + strcat(hdr->descrip, newstr); } void nii_mask12bit(unsigned char *img, struct nifti_1_header *hdr) { -//https://github.com/rordenlab/dcm2niix/issues/251 - if (hdr->datatype != DT_INT16) return; - int dim3to7 = 1; - for (int i = 3; i < 8; i++) - if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; - int nVox = hdr->dim[1]*hdr->dim[2]* dim3to7; - if (nVox < 1) return; - int16_t * img16 = (int16_t*) img; - for (int i=0; i < nVox; i++) - img16[i] = img16[i] & 4095; //12 bit data ranges from 0..4095, any other values are overflow + //https://github.com/rordenlab/dcm2niix/issues/251 + if (hdr->datatype != DT_INT16) + return; + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; + if (nVox < 1) + return; + int16_t *img16 = (int16_t *)img; + for (int i = 0; i < nVox; i++) + img16[i] = img16[i] & 4095; //12 bit data ranges from 0..4095, any other values are overflow } void nii_scale16bitSigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { -//lossless scaling of INT16 data: e.g. input with range -100...3200 and scl_slope=1 -// will be stored as -1000...32000 with scl_slope 0.1 - if (hdr->datatype != DT_INT16) return; - int dim3to7 = 1; - for (int i = 3; i < 8; i++) - if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; - int nVox = hdr->dim[1]*hdr->dim[2]* dim3to7; - if (nVox < 1) return; - int16_t * img16 = (int16_t*) img; - int16_t max16 = img16[0]; - int16_t min16 = max16; - for (int i=0; i < nVox; i++) { - if (img16[i] < min16) - min16 = img16[i]; - if (img16[i] > max16) - max16 = img16[i]; - } - int kMx = 32000; //actually 32767 - maybe a bit of padding for interpolation ringing - int scale = kMx / (int)max16; + //lossless scaling of INT16 data: e.g. input with range -100...3200 and scl_slope=1 + // will be stored as -1000...32000 with scl_slope 0.1 + if (hdr->datatype != DT_INT16) + return; + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; + if (nVox < 1) + return; + int16_t *img16 = (int16_t *)img; + int16_t max16 = img16[0]; + int16_t min16 = max16; + for (int i = 0; i < nVox; i++) { + if (img16[i] < min16) + min16 = img16[i]; + if (img16[i] > max16) + max16 = img16[i]; + } + int kMx = 32000; //actually 32767 - maybe a bit of padding for interpolation ringing + int scale = kMx / (int)max16; if (abs(min16) > max16) scale = kMx / (int)abs(min16); if (scale < 2) { - if (isVerbose) - printMessage("Sufficient 16-bit range: raw %d..%d\n", min16, max16); + if (isVerbose) + printMessage("Sufficient 16-bit range: raw %d..%d\n", min16, max16); return; //already uses dynamic range } - hdr->scl_slope = hdr->scl_slope/ scale; - for (int i=0; i < nVox; i++) - img16[i] = img16[i] * scale; - printMessage("Maximizing 16-bit range: raw %d..%d is%d\n", min16, max16, scale); - nii_storeIntegerScaleFactor(scale, hdr); + hdr->scl_slope = hdr->scl_slope / scale; + for (int i = 0; i < nVox; i++) + img16[i] = img16[i] * scale; + printMessage("Maximizing 16-bit range: raw %d..%d is%d\n", min16, max16, scale); + nii_storeIntegerScaleFactor(scale, hdr); } -void nii_scale16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose){ -//lossless scaling of UINT16 data: e.g. input with range 0...3200 and scl_slope=1 -// will be stored as 0...64000 with scl_slope 0.05 - if (hdr->datatype != DT_UINT16) return; - int dim3to7 = 1; - for (int i = 3; i < 8; i++) - if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; - int nVox = hdr->dim[1]*hdr->dim[2]* dim3to7; - if (nVox < 1) return; - uint16_t * img16 = (uint16_t*) img; - uint16_t max16 = img16[0]; - for (int i=0; i < nVox; i++) - if (img16[i] > max16) - max16 = img16[i]; - int kMx = 64000; //actually 65535 - maybe a bit of padding for interpolation ringing - int scale = kMx / (int)max16; - if (scale < 2) { - if (isVerbose > 0) - printMessage("Sufficient unsigned 16-bit range: raw max %d\n", max16); +void nii_scale16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { + //lossless scaling of UINT16 data: e.g. input with range 0...3200 and scl_slope=1 + // will be stored as 0...64000 with scl_slope 0.05 + if (hdr->datatype != DT_UINT16) + return; + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; + if (nVox < 1) + return; + uint16_t *img16 = (uint16_t *)img; + uint16_t max16 = img16[0]; + for (int i = 0; i < nVox; i++) + if (img16[i] > max16) + max16 = img16[i]; + int kMx = 64000; //actually 65535 - maybe a bit of padding for interpolation ringing + int scale = kMx / (int)max16; + if (scale < 2) { + if (isVerbose > 0) + printMessage("Sufficient unsigned 16-bit range: raw max %d\n", max16); return; //already uses dynamic range } - hdr->scl_slope = hdr->scl_slope/ scale; - for (int i=0; i < nVox; i++) - img16[i] = img16[i] * scale; - printMessage("Maximizing 16-bit range: raw max %d is%d\n", max16, scale); - nii_storeIntegerScaleFactor(scale, hdr); + hdr->scl_slope = hdr->scl_slope / scale; + for (int i = 0; i < nVox; i++) + img16[i] = img16[i] * scale; + printMessage("Maximizing 16-bit range: raw max %d is%d\n", max16, scale); + nii_storeIntegerScaleFactor(scale, hdr); } #define UINT16_TO_INT16_IF_LOSSLESS #ifdef UINT16_TO_INT16_IF_LOSSLESS -void nii_check16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose){ - //default NIfTI 16-bit is signed, set to unusual 16-bit unsigned if required... - if (hdr->datatype != DT_UINT16) return; - int dim3to7 = 1; - for (int i = 3; i < 8; i++) - if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; - int nVox = hdr->dim[1]*hdr->dim[2]* dim3to7; - if (nVox < 1) return; - unsigned short * img16 = (unsigned short*) img; - unsigned short max16 = img16[0]; - //clock_t start = clock(); - for (int i=0; i < nVox; i++) - if (img16[i] > max16) - max16 = img16[i]; - //printMessage("max16= %d vox=%d %fms\n",max16, nVox, ((double)(clock()-start))/1000); - if (max16 > 32767) { - if (isVerbose > 0) - printMessage("Note: 16-bit UNSIGNED integer image. Some tools will convert to 32-bit.\n"); - } - else { - hdr->datatype = DT_INT16; - printMessage("UINT16->INT16 Future release will change default. github.com/rordenlab/dcm2niix/issues/338\n"); - } +void nii_check16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { + //default NIfTI 16-bit is signed, set to unusual 16-bit unsigned if required... + if (hdr->datatype != DT_UINT16) + return; + int dim3to7 = 1; + for (int i = 3; i < 8; i++) + if (hdr->dim[i] > 1) + dim3to7 = dim3to7 * hdr->dim[i]; + int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; + if (nVox < 1) + return; + unsigned short *img16 = (unsigned short *)img; + unsigned short max16 = img16[0]; + //clock_t start = clock(); + for (int i = 0; i < nVox; i++) + if (img16[i] > max16) + max16 = img16[i]; + //printMessage("max16= %d vox=%d %fms\n",max16, nVox, ((double)(clock()-start))/1000); + if (max16 > 32767) { + if (isVerbose > 0) + printMessage("Note: 16-bit UNSIGNED integer image. Some tools will convert to 32-bit.\n"); + } else { + hdr->datatype = DT_INT16; + printMessage("UINT16->INT16 Future release will change default. github.com/rordenlab/dcm2niix/issues/338\n"); + } } //nii_check16bitUnsigned() #else -void nii_check16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose){ - if (hdr->datatype != DT_UINT16) return; - if (isVerbose < 1) return; +void nii_check16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { + if (hdr->datatype != DT_UINT16) + return; + if (isVerbose < 1) + return; printMessage("Note: 16-bit UNSIGNED integer image. Some tools will convert to 32-bit.\n"); } #endif @@ -3853,203 +4073,211 @@ void nii_check16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int // printMessage("Instance\t%d\t0020,0032\t%g\t%g\t%g\n", d1.imageNum, d1.patientPosition[1],d1.patientPosition[2],d1.patientPosition[3]); //} -int siemensCtKludge(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmList[]) { - //Siemens CT bug: when a user draws an open object graphics object onto a 2D slice this is appended as an additional image, - //regardless of slice position. These images do not report number of positions in the volume, so we need tedious leg work to detect - uint64_t indx0 = dcmSort[0].indx; - if ((nConvert < 2) ||(dcmList[indx0].manufacturer != kMANUFACTURER_SIEMENS) || (!isSameFloat(dcmList[indx0].TR ,0.0f))) return nConvert; - float prevDx = 0.0; - for (int i = 1; i < nConvert; i++) { - float dx = intersliceDistance(dcmList[indx0],dcmList[dcmSort[i].indx]); - if ((!isSameFloat(dx,0.0f)) && (dx < prevDx)) { - //for (int j = 1; j < nConvert; j++) - // reportPos(dcmList[dcmSort[j].indx]); - printMessage("Slices skipped: image position not sequential, admonish your vendor (Siemens OOG?)\n"); - return i; - } - prevDx = dx; - } - return nConvert; //all images in sequential order -}// siemensCtKludge() - -int isSameFloatT (float a, float b, float tolerance) { - return (fabs (a - b) <= tolerance); +int siemensCtKludge(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[]) { + //Siemens CT bug: when a user draws an open object graphics object onto a 2D slice this is appended as an additional image, + //regardless of slice position. These images do not report number of positions in the volume, so we need tedious leg work to detect + uint64_t indx0 = dcmSort[0].indx; + if ((nConvert < 2) || (dcmList[indx0].manufacturer != kMANUFACTURER_SIEMENS) || (!isSameFloat(dcmList[indx0].TR, 0.0f))) + return nConvert; + float prevDx = 0.0; + for (int i = 1; i < nConvert; i++) { + float dx = intersliceDistance(dcmList[indx0], dcmList[dcmSort[i].indx]); + if ((!isSameFloat(dx, 0.0f)) && (dx < prevDx)) { + //for (int j = 1; j < nConvert; j++) + // reportPos(dcmList[dcmSort[j].indx]); + printMessage("Slices skipped: image position not sequential, admonish your vendor (Siemens OOG?)\n"); + return i; + } + prevDx = dx; + } + return nConvert; //all images in sequential order +} // siemensCtKludge() + +int isSameFloatT(float a, float b, float tolerance) { + return (fabs(a - b) <= tolerance); } -void adjustOriginForNegativeTilt(struct nifti_1_header * hdr, float shiftPxY) { - if (hdr->sform_code > 0) { - // Adjust the srow_* offsets using srow_y - hdr->srow_x[3] -= shiftPxY * hdr->srow_y[0]; - hdr->srow_y[3] -= shiftPxY * hdr->srow_y[1]; - hdr->srow_z[3] -= shiftPxY * hdr->srow_y[2]; - } - if (hdr->qform_code > 0) { - // Adjust the quaternion offsets using quatern_* and pixdim - mat44 mat = nifti_quatern_to_mat44(hdr->quatern_b, hdr->quatern_c, hdr->quatern_d, - hdr->qoffset_x, hdr->qoffset_y, hdr->qoffset_z, - hdr->pixdim[1], hdr->pixdim[2], hdr->pixdim[3], hdr->pixdim[0]); - hdr->qoffset_x -= shiftPxY * mat.m[1][0]; - hdr->qoffset_y -= shiftPxY * mat.m[1][1]; - hdr->qoffset_z -= shiftPxY * mat.m[1][2]; - } +void adjustOriginForNegativeTilt(struct nifti_1_header *hdr, float shiftPxY) { + if (hdr->sform_code > 0) { + // Adjust the srow_* offsets using srow_y + hdr->srow_x[3] -= shiftPxY * hdr->srow_y[0]; + hdr->srow_y[3] -= shiftPxY * hdr->srow_y[1]; + hdr->srow_z[3] -= shiftPxY * hdr->srow_y[2]; + } + if (hdr->qform_code > 0) { + // Adjust the quaternion offsets using quatern_* and pixdim + mat44 mat = nifti_quatern_to_mat44(hdr->quatern_b, hdr->quatern_c, hdr->quatern_d, + hdr->qoffset_x, hdr->qoffset_y, hdr->qoffset_z, + hdr->pixdim[1], hdr->pixdim[2], hdr->pixdim[3], hdr->pixdim[0]); + hdr->qoffset_x -= shiftPxY * mat.m[1][0]; + hdr->qoffset_y -= shiftPxY * mat.m[1][1]; + hdr->qoffset_z -= shiftPxY * mat.m[1][2]; + } } -unsigned char * nii_saveNII3DtiltFloat32(char * niiFilename, struct nifti_1_header * hdr, unsigned char* im, struct TDCMopts opts, struct TDICOMdata d, float * sliceMMarray, float gantryTiltDeg, int manufacturer ) { - //correct for gantry tilt - http://www.mathworks.com/matlabcentral/fileexchange/24458-dicom-gantry-tilt-correction - if (opts.isOnlyBIDS) return im; - if (gantryTiltDeg == 0.0) return im; - struct nifti_1_header hdrIn = *hdr; - int nVox2DIn = hdrIn.dim[1]*hdrIn.dim[2]; - if ((nVox2DIn < 1) || (hdrIn.dim[0] != 3) || (hdrIn.dim[3] < 3)) return im; - if (hdrIn.datatype != DT_FLOAT32) { - printMessage("Only able to correct gantry tilt for 16-bit integer or 32-bit float data with at least 3 slices."); - return im; - } - printMessage("Gantry Tilt Correction is new: please validate conversions\n"); - float GNTtanPx = tan(gantryTiltDeg / (180/M_PI))/hdrIn.pixdim[2]; //tangent(degrees->radian) - //unintuitive step: reverse sign for negative gantry tilt, therefore -27deg == +27deg (why @!?#) - // seen in http://www.mathworks.com/matlabcentral/fileexchange/28141-gantry-detector-tilt-correction/content/gantry2.m - // also validated with actual data... - #ifndef newTilt - if (manufacturer == kMANUFACTURER_PHILIPS) //see 'Manix' example from Osirix - GNTtanPx = - GNTtanPx; - else if ((manufacturer == kMANUFACTURER_SIEMENS) && (gantryTiltDeg > 0.0)) - GNTtanPx = - GNTtanPx; - else if (manufacturer == kMANUFACTURER_GE) - ; //do nothing - else - if (gantryTiltDeg < 0.0) GNTtanPx = - GNTtanPx; //see Toshiba examples from John Muschelli - #endif //newTilt - float * imIn32 = ( float*) im; +unsigned char *nii_saveNII3DtiltFloat32(char *niiFilename, struct nifti_1_header *hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, float *sliceMMarray, float gantryTiltDeg, int manufacturer) { + //correct for gantry tilt - http://www.mathworks.com/matlabcentral/fileexchange/24458-dicom-gantry-tilt-correction + if (opts.isOnlyBIDS) + return im; + if (gantryTiltDeg == 0.0) + return im; + struct nifti_1_header hdrIn = *hdr; + int nVox2DIn = hdrIn.dim[1] * hdrIn.dim[2]; + if ((nVox2DIn < 1) || (hdrIn.dim[0] != 3) || (hdrIn.dim[3] < 3)) + return im; + if (hdrIn.datatype != DT_FLOAT32) { + printMessage("Only able to correct gantry tilt for 16-bit integer or 32-bit float data with at least 3 slices."); + return im; + } + printMessage("Gantry Tilt Correction is new: please validate conversions\n"); + float GNTtanPx = tan(gantryTiltDeg / (180 / M_PI)) / hdrIn.pixdim[2]; //tangent(degrees->radian) +//unintuitive step: reverse sign for negative gantry tilt, therefore -27deg == +27deg (why @!?#) +// seen in http://www.mathworks.com/matlabcentral/fileexchange/28141-gantry-detector-tilt-correction/content/gantry2.m +// also validated with actual data... +#ifndef newTilt + if (manufacturer == kMANUFACTURER_PHILIPS) //see 'Manix' example from Osirix + GNTtanPx = -GNTtanPx; + else if ((manufacturer == kMANUFACTURER_SIEMENS) && (gantryTiltDeg > 0.0)) + GNTtanPx = -GNTtanPx; + else if (manufacturer == kMANUFACTURER_GE) + ; //do nothing + else if (gantryTiltDeg < 0.0) + GNTtanPx = -GNTtanPx; //see Toshiba examples from John Muschelli +#endif //newTilt + float *imIn32 = (float *)im; //create new output image: larger due to skew // compute how many pixels slice must be extended due to skew - int s = hdrIn.dim[3] - 1; //top slice - float maxSliceMM = fabs(s * hdrIn.pixdim[3]); - if (sliceMMarray != NULL) maxSliceMM = fabs(sliceMMarray[s]); - int pxOffset = ceil(fabs(GNTtanPx*maxSliceMM)); - // printMessage("Tilt extends slice by %d pixels", pxOffset); + int s = hdrIn.dim[3] - 1; //top slice + float maxSliceMM = fabs(s * hdrIn.pixdim[3]); + if (sliceMMarray != NULL) + maxSliceMM = fabs(sliceMMarray[s]); + int pxOffset = ceil(fabs(GNTtanPx * maxSliceMM)); + // printMessage("Tilt extends slice by %d pixels", pxOffset); hdr->dim[2] = hdr->dim[2] + pxOffset; - - // When there is negative tilt, the image origin must be adjusted for the padding that will be added. - if (GNTtanPx < 0) { - // printMessage("Adjusting origin for %d pixels padding (float)\n", pxOffset); - adjustOriginForNegativeTilt(hdr, pxOffset); - } - int nVox2D = hdr->dim[1]*hdr->dim[2]; - unsigned char * imOut = (unsigned char *)malloc(nVox2D * hdrIn.dim[3] * 4);// *4 as 32-bits per voxel, sizeof(float) ); - float * imOut32 = ( float*) imOut; + // When there is negative tilt, the image origin must be adjusted for the padding that will be added. + if (GNTtanPx < 0) { + // printMessage("Adjusting origin for %d pixels padding (float)\n", pxOffset); + adjustOriginForNegativeTilt(hdr, pxOffset); + } + int nVox2D = hdr->dim[1] * hdr->dim[2]; + unsigned char *imOut = (unsigned char *)malloc(nVox2D * hdrIn.dim[3] * 4); // *4 as 32-bits per voxel, sizeof(float) ); + float *imOut32 = (float *)imOut; //set surrounding voxels to padding (if present) or darkest observed value bool hasPixelPaddingValue = !isnan(d.pixelPaddingValue); float pixelPaddingValue; if (hasPixelPaddingValue) { pixelPaddingValue = d.pixelPaddingValue; - } - else { + } else { // Find darkest pixel value. Note that `hasPixelPaddingValue` remains false so that the darkest value // will not trigger nearest neighbor interpolation below when this value is found in the image. pixelPaddingValue = imIn32[0]; for (int v = 0; v < (nVox2DIn * hdrIn.dim[3]); v++) if (imIn32[v] < pixelPaddingValue) - pixelPaddingValue = imIn32[v]; + pixelPaddingValue = imIn32[v]; } for (int v = 0; v < (nVox2D * hdrIn.dim[3]); v++) imOut32[v] = pixelPaddingValue; //copy skewed voxels for (int s = 0; s < hdrIn.dim[3]; s++) { //for each slice float sliceMM = s * hdrIn.pixdim[3]; - if (sliceMMarray != NULL) sliceMM = sliceMMarray[s]; //variable slice thicknesses + if (sliceMMarray != NULL) + sliceMM = sliceMMarray[s]; //variable slice thicknesses //sliceMM -= mmMidZ; //adjust so tilt relative to middle slice if (GNTtanPx < 0) sliceMM -= maxSliceMM; - float Offset = GNTtanPx*sliceMM; - float fracHi = ceil(Offset) - Offset; //ceil not floor since rI=r-Offset not rI=r+Offset + float Offset = GNTtanPx * sliceMM; + float fracHi = ceil(Offset) - Offset; //ceil not floor since rI=r-Offset not rI=r+Offset float fracLo = 1.0f - fracHi; for (int r = 0; r < hdr->dim[2]; r++) { //for each row of output - float rI = (float)r - Offset; //input row + float rI = (float)r - Offset; //input row if ((rI >= 0.0) && (rI < hdrIn.dim[2])) { int rLo = floor(rI); int rHi = rLo + 1; - if (rHi >= hdrIn.dim[2]) rHi = rLo; + if (rHi >= hdrIn.dim[2]) + rHi = rLo; rLo = (rLo * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row below rHi = (rHi * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row above int rOut = (r * hdrIn.dim[1]) + (s * nVox2D); //offset to output row for (int c = 0; c < hdrIn.dim[1]; c++) { //for each column - float valLo = (float) imIn32[rLo+c]; - float valHi = (float) imIn32[rHi+c]; + float valLo = (float)imIn32[rLo + c]; + float valHi = (float)imIn32[rHi + c]; if (hasPixelPaddingValue && (valLo == pixelPaddingValue || valHi == pixelPaddingValue)) { // https://github.com/rordenlab/dcm2niix/issues/262 - Use nearest neighbor interpolation // when at least one of the values is padding. - imOut32[rOut+c] = fracHi >= 0.5 ? valHi : valLo; - } - else { - imOut32[rOut+c] = round(valLo*fracLo + valHi*fracHi); + imOut32[rOut + c] = fracHi >= 0.5 ? valHi : valLo; + } else { + imOut32[rOut + c] = round(valLo * fracLo + valHi * fracHi); } } //for c (each column) } //rI (input row) in range } //for r (each row) } //for s (each slice)*/ free(im); - if (sliceMMarray != NULL) return imOut; //we will save after correcting for variable slice thicknesses - char niiFilenameTilt[2048] = {""}; - strcat(niiFilenameTilt,niiFilename); - strcat(niiFilenameTilt,"_Tilt"); - nii_saveNII3D(niiFilenameTilt, *hdr, imOut, opts, d); - return imOut; -}// nii_saveNII3DtiltFloat32() - -unsigned char * nii_saveNII3Dtilt(char * niiFilename, struct nifti_1_header * hdr, unsigned char* im, struct TDCMopts opts, struct TDICOMdata d, float * sliceMMarray, float gantryTiltDeg, int manufacturer ) { - //correct for gantry tilt - http://www.mathworks.com/matlabcentral/fileexchange/24458-dicom-gantry-tilt-correction - if (opts.isOnlyBIDS) return im; - if (gantryTiltDeg == 0.0) return im; - struct nifti_1_header hdrIn = *hdr; - int nVox2DIn = hdrIn.dim[1]*hdrIn.dim[2]; - if ((nVox2DIn < 1) || (hdrIn.dim[0] != 3) || (hdrIn.dim[3] < 3)) return im; - if (hdrIn.datatype == DT_FLOAT32) - return nii_saveNII3DtiltFloat32(niiFilename, hdr, im, opts, d, sliceMMarray, gantryTiltDeg, manufacturer); - if (hdrIn.datatype != DT_INT16) { - printMessage("Only able to correct gantry tilt for 16-bit integer data with at least 3 slices."); - return im; - } - printMessage("Gantry Tilt Correction is new: please validate conversions\n"); - float GNTtanPx = tan(gantryTiltDeg / (180/M_PI))/hdrIn.pixdim[2]; //tangent(degrees->radian) - //unintuitive step: reverse sign for negative gantry tilt, therefore -27deg == +27deg (why @!?#) - // seen in http://www.mathworks.com/matlabcentral/fileexchange/28141-gantry-detector-tilt-correction/content/gantry2.m - // also validated with actual data... - #ifndef newTilt - if (manufacturer == kMANUFACTURER_PHILIPS) //see 'Manix' example from Osirix - GNTtanPx = - GNTtanPx; - else if ((manufacturer == kMANUFACTURER_SIEMENS) && (gantryTiltDeg > 0.0)) - GNTtanPx = - GNTtanPx; - else if (manufacturer == kMANUFACTURER_GE) - ; //do nothing - else - if (gantryTiltDeg < 0.0) GNTtanPx = - GNTtanPx; //see Toshiba examples from John Muschelli - #endif //newTilt - short * imIn16 = ( short*) im; + if (sliceMMarray != NULL) + return imOut; //we will save after correcting for variable slice thicknesses + char niiFilenameTilt[2048] = {""}; + strcat(niiFilenameTilt, niiFilename); + strcat(niiFilenameTilt, "_Tilt"); + nii_saveNII3D(niiFilenameTilt, *hdr, imOut, opts, d); + return imOut; +} // nii_saveNII3DtiltFloat32() + +unsigned char *nii_saveNII3Dtilt(char *niiFilename, struct nifti_1_header *hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, float *sliceMMarray, float gantryTiltDeg, int manufacturer) { + //correct for gantry tilt - http://www.mathworks.com/matlabcentral/fileexchange/24458-dicom-gantry-tilt-correction + if (opts.isOnlyBIDS) + return im; + if (gantryTiltDeg == 0.0) + return im; + struct nifti_1_header hdrIn = *hdr; + int nVox2DIn = hdrIn.dim[1] * hdrIn.dim[2]; + if ((nVox2DIn < 1) || (hdrIn.dim[0] != 3) || (hdrIn.dim[3] < 3)) + return im; + if (hdrIn.datatype == DT_FLOAT32) + return nii_saveNII3DtiltFloat32(niiFilename, hdr, im, opts, d, sliceMMarray, gantryTiltDeg, manufacturer); + if (hdrIn.datatype != DT_INT16) { + printMessage("Only able to correct gantry tilt for 16-bit integer data with at least 3 slices."); + return im; + } + printMessage("Gantry Tilt Correction is new: please validate conversions\n"); + float GNTtanPx = tan(gantryTiltDeg / (180 / M_PI)) / hdrIn.pixdim[2]; //tangent(degrees->radian) +//unintuitive step: reverse sign for negative gantry tilt, therefore -27deg == +27deg (why @!?#) +// seen in http://www.mathworks.com/matlabcentral/fileexchange/28141-gantry-detector-tilt-correction/content/gantry2.m +// also validated with actual data... +#ifndef newTilt + if (manufacturer == kMANUFACTURER_PHILIPS) //see 'Manix' example from Osirix + GNTtanPx = -GNTtanPx; + else if ((manufacturer == kMANUFACTURER_SIEMENS) && (gantryTiltDeg > 0.0)) + GNTtanPx = -GNTtanPx; + else if (manufacturer == kMANUFACTURER_GE) + ; //do nothing + else if (gantryTiltDeg < 0.0) + GNTtanPx = -GNTtanPx; //see Toshiba examples from John Muschelli +#endif //newTilt + short *imIn16 = (short *)im; //create new output image: larger due to skew // compute how many pixels slice must be extended due to skew - int s = hdrIn.dim[3] - 1; //top slice - float maxSliceMM = fabs(s * hdrIn.pixdim[3]); - if (sliceMMarray != NULL) maxSliceMM = fabs(sliceMMarray[s]); - int pxOffset = ceil(fabs(GNTtanPx*maxSliceMM)); - // printMessage("Tilt extends slice by %d pixels", pxOffset); + int s = hdrIn.dim[3] - 1; //top slice + float maxSliceMM = fabs(s * hdrIn.pixdim[3]); + if (sliceMMarray != NULL) + maxSliceMM = fabs(sliceMMarray[s]); + int pxOffset = ceil(fabs(GNTtanPx * maxSliceMM)); + // printMessage("Tilt extends slice by %d pixels", pxOffset); hdr->dim[2] = hdr->dim[2] + pxOffset; - // When there is negative tilt, the image origin must be adjusted for the padding that will be added. - if (GNTtanPx < 0) { - // printMessage("Adjusting origin for %d pixels padding (short)\n", pxOffset); - adjustOriginForNegativeTilt(hdr, pxOffset); - } - int nVox2D = hdr->dim[1]*hdr->dim[2]; - unsigned char * imOut = (unsigned char *)malloc(nVox2D * hdrIn.dim[3] * 2);// *2 as 16-bits per voxel, sizeof( short) ); - short * imOut16 = ( short*) imOut; + // When there is negative tilt, the image origin must be adjusted for the padding that will be added. + if (GNTtanPx < 0) { + // printMessage("Adjusting origin for %d pixels padding (short)\n", pxOffset); + adjustOriginForNegativeTilt(hdr, pxOffset); + } + int nVox2D = hdr->dim[1] * hdr->dim[2]; + unsigned char *imOut = (unsigned char *)malloc(nVox2D * hdrIn.dim[3] * 2); // *2 as 16-bits per voxel, sizeof( short) ); + short *imOut16 = (short *)imOut; //set surrounding voxels to padding (if present) or darkest observed value bool hasPixelPaddingValue = !isnan(d.pixelPaddingValue); short pixelPaddingValue; if (hasPixelPaddingValue) { - pixelPaddingValue = (short) round(d.pixelPaddingValue); - } - else { + pixelPaddingValue = (short)round(d.pixelPaddingValue); + } else { // Find darkest pixel value. Note that `hasPixelPaddingValue` remains false so that the darkest value // will not trigger nearest neighbor interpolation below when this value is found in the image. pixelPaddingValue = imIn16[0]; @@ -4062,532 +4290,418 @@ unsigned char * nii_saveNII3Dtilt(char * niiFilename, struct nifti_1_header * hd //copy skewed voxels for (int s = 0; s < hdrIn.dim[3]; s++) { //for each slice float sliceMM = s * hdrIn.pixdim[3]; - if (sliceMMarray != NULL) sliceMM = sliceMMarray[s]; //variable slice thicknesses + if (sliceMMarray != NULL) + sliceMM = sliceMMarray[s]; //variable slice thicknesses //sliceMM -= mmMidZ; //adjust so tilt relative to middle slice if (GNTtanPx < 0) sliceMM -= maxSliceMM; - float Offset = GNTtanPx*sliceMM; - float fracHi = ceil(Offset) - Offset; //ceil not floor since rI=r-Offset not rI=r+Offset + float Offset = GNTtanPx * sliceMM; + float fracHi = ceil(Offset) - Offset; //ceil not floor since rI=r-Offset not rI=r+Offset float fracLo = 1.0f - fracHi; for (int r = 0; r < hdr->dim[2]; r++) { //for each row of output - float rI = (float)r - Offset; //input row + float rI = (float)r - Offset; //input row if ((rI >= 0.0) && (rI < hdrIn.dim[2])) { int rLo = floor(rI); int rHi = rLo + 1; - if (rHi >= hdrIn.dim[2]) rHi = rLo; + if (rHi >= hdrIn.dim[2]) + rHi = rLo; rLo = (rLo * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row below rHi = (rHi * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row above int rOut = (r * hdrIn.dim[1]) + (s * nVox2D); //offset to output row for (int c = 0; c < hdrIn.dim[1]; c++) { //for each row - short valLo = imIn16[rLo+c]; - short valHi = imIn16[rHi+c]; + short valLo = imIn16[rLo + c]; + short valHi = imIn16[rHi + c]; if (hasPixelPaddingValue && (valLo == pixelPaddingValue || valHi == pixelPaddingValue)) { // https://github.com/rordenlab/dcm2niix/issues/262 - Use nearest neighbor interpolation // when at least one of the values is padding. - imOut16[rOut+c] = fracHi >= 0.5 ? valHi : valLo; - } - else { - imOut16[rOut+c] = round((((float) valLo)*fracLo) + ((float) valHi)*fracHi); + imOut16[rOut + c] = fracHi >= 0.5 ? valHi : valLo; + } else { + imOut16[rOut + c] = round((((float)valLo) * fracLo) + ((float)valHi) * fracHi); } } //for c (each column) } //rI (input row) in range } //for r (each row) } //for s (each slice)*/ free(im); - if (sliceMMarray != NULL) return imOut; //we will save after correcting for variable slice thicknesses - char niiFilenameTilt[2048] = {""}; - strcat(niiFilenameTilt,niiFilename); - strcat(niiFilenameTilt,"_Tilt"); - nii_saveNII3D(niiFilenameTilt, *hdr, imOut, opts, d); - return imOut; -}// nii_saveNII3Dtilt() - -int nii_saveNII3Deq(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts, struct TDICOMdata d, float * sliceMMarray ) { - //convert image with unequal slice distances to equal slice distances - //sliceMMarray = 0.0 3.0 6.0 12.0 22.0 <- ascending distance from first slice - if (opts.isOnlyBIDS) return EXIT_SUCCESS; - int nVox2D = hdr.dim[1]*hdr.dim[2]; - if ((nVox2D < 1) || (hdr.dim[0] != 3) || (hdr.dim[3] < 3)) return EXIT_FAILURE; - if ((hdr.datatype != DT_FLOAT32) && (hdr.datatype != DT_UINT8) && (hdr.datatype != DT_RGB24) && (hdr.datatype != DT_INT16)) { - printMessage("Only able to make equidistant slices from 8,16,24-bit integer or 32-bit float image data."); - return EXIT_FAILURE; - } - float mn = sliceMMarray[1] - sliceMMarray[0]; - for (int i = 1; i < hdr.dim[3]; i++) { - float dx = sliceMMarray[i] - sliceMMarray[i-1]; - if ((dx < mn) && (!isSameFloat(dx, 0.0))) // <- allow slice direction to reverse - mn = sliceMMarray[i] - sliceMMarray[i-1]; - } - if (mn <= 0.0f) { - printMessage("Unable to equalize slice distances: slice order not consistently ascending:\n"); - printMessage("dx=[0"); + if (sliceMMarray != NULL) + return imOut; //we will save after correcting for variable slice thicknesses + char niiFilenameTilt[2048] = {""}; + strcat(niiFilenameTilt, niiFilename); + strcat(niiFilenameTilt, "_Tilt"); + nii_saveNII3D(niiFilenameTilt, *hdr, imOut, opts, d); + return imOut; +} // nii_saveNII3Dtilt() + +int nii_saveNII3Deq(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, float *sliceMMarray) { + //convert image with unequal slice distances to equal slice distances + //sliceMMarray = 0.0 3.0 6.0 12.0 22.0 <- ascending distance from first slice + if (opts.isOnlyBIDS) + return EXIT_SUCCESS; + int nVox2D = hdr.dim[1] * hdr.dim[2]; + if ((nVox2D < 1) || (hdr.dim[0] != 3) || (hdr.dim[3] < 3)) + return EXIT_FAILURE; + if ((hdr.datatype != DT_FLOAT32) && (hdr.datatype != DT_UINT8) && (hdr.datatype != DT_RGB24) && (hdr.datatype != DT_INT16)) { + printMessage("Only able to make equidistant slices from 8,16,24-bit integer or 32-bit float image data."); + return EXIT_FAILURE; + } + float mn = sliceMMarray[1] - sliceMMarray[0]; + for (int i = 1; i < hdr.dim[3]; i++) { + float dx = sliceMMarray[i] - sliceMMarray[i - 1]; + if ((dx < mn) && (!isSameFloat(dx, 0.0))) // <- allow slice direction to reverse + mn = sliceMMarray[i] - sliceMMarray[i - 1]; + } + if (mn <= 0.0f) { + printMessage("Unable to equalize slice distances: slice order not consistently ascending:\n"); + printMessage("dx=[0"); for (int i = 1; i < hdr.dim[3]; i++) - printMessage(" %g", sliceMMarray[i-1]); + printMessage(" %g", sliceMMarray[i - 1]); printMessage("]\n"); printMessage(" Recompiling with '-DmyInstanceNumberOrderIsNotSpatial' might help.\n"); - return EXIT_FAILURE; - } - int slices = hdr.dim[3]; - slices = (int)ceil((sliceMMarray[slices-1]-0.5*(sliceMMarray[slices-1]-sliceMMarray[slices-2]))/mn); //-0.5: fence post - if (slices > (hdr.dim[3] * 2)) { - slices = 2 * hdr.dim[3]; - mn = (sliceMMarray[hdr.dim[3]-1]) / (slices-1); - } - //printMessage("-->%g mn slices %d orig %d\n", mn, slices, hdr.dim[3]); - if (slices < 3) return EXIT_FAILURE; - struct nifti_1_header hdrX = hdr; - hdrX.dim[3] = slices; - hdrX.pixdim[3] = mn; - if ((hdr.pixdim[3] != 0.0) && (hdr.pixdim[3] != hdrX.pixdim[3])) { - float Scale = hdrX.pixdim[3] / hdr.pixdim[3]; - //to do: do I change srow_z or srow_x[2], srow_y[2], srow_z[2], - hdrX.srow_z[0] = hdr.srow_z[0] * Scale; - hdrX.srow_z[1] = hdr.srow_z[1] * Scale; - hdrX.srow_z[2] = hdr.srow_z[2] * Scale; - } - unsigned char *imX; - if (hdr.datatype == DT_FLOAT32) { - float * im32 = ( float*) im; - imX = (unsigned char *)malloc( (nVox2D * slices) * 4);//sizeof(float) - float * imX32 = ( float*) imX; - for (int s=0; s < slices; s++) { - float sliceXmm = s * mn; //distance from first slice - int sliceXi = (s * nVox2D);//offset for this slice - int sHi = 0; - while ((sHi < (hdr.dim[3] - 1) ) && (sliceMMarray[sHi] < sliceXmm)) - sHi += 1; - int sLo = sHi - 1; - if (sLo < 0) sLo = 0; - float mmHi = sliceMMarray[sHi]; - float mmLo = sliceMMarray[sLo]; - sLo = sLo * nVox2D; - sHi = sHi * nVox2D; - if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX - //for (int v=0; v < nVox2D; v++) - // imX16[sliceXi+v] = im16[sHi+v]; - memcpy(&imX32[sliceXi], &im32[sHi], nVox2D* sizeof(float)); //memcpy( dest, src, bytes) - } else { - float fracHi = (sliceXmm-mmLo)/ (mmHi-mmLo); - float fracLo = 1.0 - fracHi; - //weight between two slices - for (int v=0; v < nVox2D; v++) - imX32[sliceXi+v] = round( ( (float)im32[sLo+v]*fracLo) + (float)im32[sHi+v]*fracHi); - } - } - } else if (hdr.datatype == DT_INT16) { - short * im16 = ( short*) im; - imX = (unsigned char *)malloc( (nVox2D * slices) * 2);//sizeof( short) ); - short * imX16 = ( short*) imX; - for (int s=0; s < slices; s++) { - float sliceXmm = s * mn; //distance from first slice - int sliceXi = (s * nVox2D);//offset for this slice - int sHi = 0; - while ((sHi < (hdr.dim[3] - 1) ) && (sliceMMarray[sHi] < sliceXmm)) - sHi += 1; - int sLo = sHi - 1; - if (sLo < 0) sLo = 0; - float mmHi = sliceMMarray[sHi]; - float mmLo = sliceMMarray[sLo]; - sLo = sLo * nVox2D; - sHi = sHi * nVox2D; - if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX - //for (int v=0; v < nVox2D; v++) - // imX16[sliceXi+v] = im16[sHi+v]; - memcpy(&imX16[sliceXi], &im16[sHi], nVox2D* sizeof(unsigned short)); //memcpy( dest, src, bytes) - - } else { - float fracHi = (sliceXmm-mmLo)/ (mmHi-mmLo); - float fracLo = 1.0 - fracHi; - //weight between two slices - for (int v=0; v < nVox2D; v++) - imX16[sliceXi+v] = round( ( (float)im16[sLo+v]*fracLo) + (float)im16[sHi+v]*fracHi); - } - } - } else { - if (hdr.datatype == DT_RGB24) nVox2D = nVox2D * 3; - imX = (unsigned char *)malloc( (nVox2D * slices) * 2);//sizeof( short) ); - for (int s=0; s < slices; s++) { - float sliceXmm = s * mn; //distance from first slice - int sliceXi = (s * nVox2D);//offset for this slice - int sHi = 0; - while ((sHi < (hdr.dim[3] - 1) ) && (sliceMMarray[sHi] < sliceXmm)) - sHi += 1; - int sLo = sHi - 1; - if (sLo < 0) sLo = 0; - float mmHi = sliceMMarray[sHi]; - float mmLo = sliceMMarray[sLo]; - sLo = sLo * nVox2D; - sHi = sHi * nVox2D; - if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX - memcpy(&imX[sliceXi], &im[sHi], nVox2D); //memcpy( dest, src, bytes) - } else { - float fracHi = (sliceXmm-mmLo)/ (mmHi-mmLo); - float fracLo = 1.0 - fracHi; //weight between two slices - for (int v=0; v < nVox2D; v++) - imX[sliceXi+v] = round( ( (float)im[sLo+v]*fracLo) + (float)im[sHi+v]*fracHi); - } - } - } - char niiFilenameEq[2048] = {""}; - strcat(niiFilenameEq,niiFilename); - strcat(niiFilenameEq,"_Eq"); - nii_saveNII3D(niiFilenameEq, hdrX, imX, opts, d); - free(imX); - return EXIT_SUCCESS; -}// nii_saveNII3Deq() - -/*int nii_saveNII3Deq(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts, float * sliceMMarray ) { - //convert image with unequal slice distances to equal slice distances - //sliceMMarray = 0.0 3.0 6.0 12.0 22.0 <- ascending distance from first slice - if (opts.isOnlyBIDS) return EXIT_SUCCESS; - - int nVox2D = hdr.dim[1]*hdr.dim[2]; - if ((nVox2D < 1) || (hdr.dim[0] != 3) ) return EXIT_FAILURE; - if ((hdr.datatype != DT_FLOAT32) && (hdr.datatype != DT_UINT8) && (hdr.datatype != DT_RGB24) && (hdr.datatype != DT_INT16)) { - printMessage("Only able to make equidistant slices from 8,16,24-bit integer or 32-bit float data with at least 3 slices."); - return EXIT_FAILURE; - } - //find lowest and highest slice - float lo = sliceMMarray[0]; - float hi = lo; - for (int i = 1; i < hdr.dim[3]; i++) { - if (sliceMMarray[i] < lo) - lo = sliceMMarray[i]; - if (sliceMMarray[i] > hi) - hi = sliceMMarray[i]; - } - if (isSameFloat(lo,hi)) return EXIT_SUCCESS; - - - float mn = fabs(sliceMMarray[1] - sliceMMarray[0]); - for (int i = 1; i < (hdr.dim[3]-1); i++) { - for (int j = i+1; j < hdr.dim[3]; j++) { - float dx = fabs(sliceMMarray[i] - sliceMMarray[j]); - if ((dx < mn) && (dx > 0.0)) - mn = dx; - } - } - if (mn <= 0.0f) { - printMessage("Unable to equalize slice distances: slice number not consistent with slice position.\n"); - return EXIT_FAILURE; - } - int slices = hdr.dim[3]; - //slices = (int)ceil((sliceMMarray[slices-1]-0.5*(sliceMMarray[slices-1]-sliceMMarray[slices-2]))/mn); //-0.5: fence post - slices = (int)round((hi-lo+mn)/mn); //+mn: fence post - printMessage("lo=%g hi=%g mn=%g slices=%d\n", lo, hi, mn, slices); - if (slices > (hdr.dim[3] * 2)) { - slices = 2 * hdr.dim[3]; - mn = (sliceMMarray[hdr.dim[3]-1]) / (slices-1); - } - //printMessage("-->%g mn slices %d orig %d\n", mn, slices, hdr.dim[3]); - if (slices < 3) return EXIT_FAILURE; - struct nifti_1_header hdrX = hdr; - hdrX.dim[3] = slices; - hdrX.pixdim[3] = mn; - if ((hdr.pixdim[3] != 0.0) && (hdr.pixdim[3] != hdrX.pixdim[3])) { - float Scale = hdrX.pixdim[3] / hdr.pixdim[3]; - //to do: do I change srow_z or srow_x[2], srow_y[2], srow_z[2], - hdrX.srow_z[0] = hdr.srow_z[0] * Scale; - hdrX.srow_z[1] = hdr.srow_z[1] * Scale; - hdrX.srow_z[2] = hdr.srow_z[2] * Scale; - } - unsigned char *imX; - if (hdr.datatype == DT_FLOAT32) { - float * im32 = ( float*) im; - imX = (unsigned char *)malloc( (nVox2D * slices) * 4);//sizeof(float) - float * imX32 = ( float*) imX; - for (int s=0; s < slices; s++) { - float sliceXmm = s * mn; //distance from first slice - int sliceXi = (s * nVox2D);//offset for this slice - int sHi = 0; - while ((sHi < (hdr.dim[3] - 1) ) && (sliceMMarray[sHi] < sliceXmm)) - sHi += 1; - int sLo = sHi - 1; - if (sLo < 0) sLo = 0; - float mmHi = sliceMMarray[sHi]; - float mmLo = sliceMMarray[sLo]; - sLo = sLo * nVox2D; - sHi = sHi * nVox2D; - if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX - //for (int v=0; v < nVox2D; v++) - // imX16[sliceXi+v] = im16[sHi+v]; - memcpy(&imX32[sliceXi], &im32[sHi], nVox2D* sizeof(float)); //memcpy( dest, src, bytes) - - } else { - float fracHi = (sliceXmm-mmLo)/ (mmHi-mmLo); - float fracLo = 1.0 - fracHi; - //weight between two slices - for (int v=0; v < nVox2D; v++) - imX32[sliceXi+v] = round( ( (float)im32[sLo+v]*fracLo) + (float)im32[sHi+v]*fracHi); - } - } - } else if (hdr.datatype == DT_INT16) { - short * im16 = ( short*) im; - imX = (unsigned char *)malloc( (nVox2D * slices) * 2);//sizeof( short) ); - short * imX16 = ( short*) imX; - for (int s=0; s < slices; s++) { - float sliceXmm = s * mn; //distance from first slice - int sliceXi = (s * nVox2D);//offset for this slice - int sHi = 0; - while ((sHi < (hdr.dim[3] - 1) ) && (sliceMMarray[sHi] < sliceXmm)) - sHi += 1; - int sLo = sHi - 1; - if (sLo < 0) sLo = 0; - float mmHi = sliceMMarray[sHi]; - float mmLo = sliceMMarray[sLo]; - sLo = sLo * nVox2D; - sHi = sHi * nVox2D; - if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX - //for (int v=0; v < nVox2D; v++) - // imX16[sliceXi+v] = im16[sHi+v]; - memcpy(&imX16[sliceXi], &im16[sHi], nVox2D* sizeof(unsigned short)); //memcpy( dest, src, bytes) - - } else { - float fracHi = (sliceXmm-mmLo)/ (mmHi-mmLo); - float fracLo = 1.0 - fracHi; - //weight between two slices - for (int v=0; v < nVox2D; v++) - imX16[sliceXi+v] = round( ( (float)im16[sLo+v]*fracLo) + (float)im16[sHi+v]*fracHi); - } - } - } else { - if (hdr.datatype == DT_RGB24) nVox2D = nVox2D * 3; - imX = (unsigned char *)malloc( (nVox2D * slices) * 2);//sizeof( short) ); - for (int s=0; s < slices; s++) { - float sliceXmm = s * mn; //distance from first slice - int sliceXi = (s * nVox2D);//offset for this slice - int sHi = 0; - while ((sHi < (hdr.dim[3] - 1) ) && (sliceMMarray[sHi] < sliceXmm)) - sHi += 1; - int sLo = sHi - 1; - if (sLo < 0) sLo = 0; - float mmHi = sliceMMarray[sHi]; - float mmLo = sliceMMarray[sLo]; - sLo = sLo * nVox2D; - sHi = sHi * nVox2D; - if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX - memcpy(&imX[sliceXi], &im[sHi], nVox2D); //memcpy( dest, src, bytes) - } else { - float fracHi = (sliceXmm-mmLo)/ (mmHi-mmLo); - float fracLo = 1.0 - fracHi; //weight between two slices - for (int v=0; v < nVox2D; v++) - imX[sliceXi+v] = round( ( (float)im[sLo+v]*fracLo) + (float)im[sHi+v]*fracHi); - } - } - } - char niiFilenameEq[2048] = {""}; - strcat(niiFilenameEq,niiFilename); - strcat(niiFilenameEq,"_Eq"); - nii_saveNII3D(niiFilenameEq, hdrX, imX, opts); - free(imX); - return EXIT_SUCCESS; -}// nii_saveNII3Deq() */ - -float PhilipsPreciseVal (float lPV, float lRS, float lRI, float lSS) { - if ((lRS*lSS) == 0) //avoid divide by zero - return 0.0; - else - return (lPV * lRS + lRI) / (lRS * lSS); + return EXIT_FAILURE; + } + int slices = hdr.dim[3]; + slices = (int)ceil((sliceMMarray[slices - 1] - 0.5 * (sliceMMarray[slices - 1] - sliceMMarray[slices - 2])) / mn); //-0.5: fence post + if (slices > (hdr.dim[3] * 2)) { + slices = 2 * hdr.dim[3]; + mn = (sliceMMarray[hdr.dim[3] - 1]) / (slices - 1); + } + //printMessage("-->%g mn slices %d orig %d\n", mn, slices, hdr.dim[3]); + if (slices < 3) + return EXIT_FAILURE; + struct nifti_1_header hdrX = hdr; + hdrX.dim[3] = slices; + hdrX.pixdim[3] = mn; + if ((hdr.pixdim[3] != 0.0) && (hdr.pixdim[3] != hdrX.pixdim[3])) { + float Scale = hdrX.pixdim[3] / hdr.pixdim[3]; + //to do: do I change srow_z or srow_x[2], srow_y[2], srow_z[2], + hdrX.srow_z[0] = hdr.srow_z[0] * Scale; + hdrX.srow_z[1] = hdr.srow_z[1] * Scale; + hdrX.srow_z[2] = hdr.srow_z[2] * Scale; + } + unsigned char *imX; + if (hdr.datatype == DT_FLOAT32) { + float *im32 = (float *)im; + imX = (unsigned char *)malloc((nVox2D * slices) * 4); //sizeof(float) + float *imX32 = (float *)imX; + for (int s = 0; s < slices; s++) { + float sliceXmm = s * mn; //distance from first slice + int sliceXi = (s * nVox2D); //offset for this slice + int sHi = 0; + while ((sHi < (hdr.dim[3] - 1)) && (sliceMMarray[sHi] < sliceXmm)) + sHi += 1; + int sLo = sHi - 1; + if (sLo < 0) + sLo = 0; + float mmHi = sliceMMarray[sHi]; + float mmLo = sliceMMarray[sLo]; + sLo = sLo * nVox2D; + sHi = sHi * nVox2D; + if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX + //for (int v=0; v < nVox2D; v++) + // imX16[sliceXi+v] = im16[sHi+v]; + memcpy(&imX32[sliceXi], &im32[sHi], nVox2D * sizeof(float)); //memcpy( dest, src, bytes) + } else { + float fracHi = (sliceXmm - mmLo) / (mmHi - mmLo); + float fracLo = 1.0 - fracHi; + //weight between two slices + for (int v = 0; v < nVox2D; v++) + imX32[sliceXi + v] = round(((float)im32[sLo + v] * fracLo) + (float)im32[sHi + v] * fracHi); + } + } + } else if (hdr.datatype == DT_INT16) { + short *im16 = (short *)im; + imX = (unsigned char *)malloc((nVox2D * slices) * 2); //sizeof( short) ); + short *imX16 = (short *)imX; + for (int s = 0; s < slices; s++) { + float sliceXmm = s * mn; //distance from first slice + int sliceXi = (s * nVox2D); //offset for this slice + int sHi = 0; + while ((sHi < (hdr.dim[3] - 1)) && (sliceMMarray[sHi] < sliceXmm)) + sHi += 1; + int sLo = sHi - 1; + if (sLo < 0) + sLo = 0; + float mmHi = sliceMMarray[sHi]; + float mmLo = sliceMMarray[sLo]; + sLo = sLo * nVox2D; + sHi = sHi * nVox2D; + if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX + //for (int v=0; v < nVox2D; v++) + // imX16[sliceXi+v] = im16[sHi+v]; + memcpy(&imX16[sliceXi], &im16[sHi], nVox2D * sizeof(unsigned short)); //memcpy( dest, src, bytes) + } else { + float fracHi = (sliceXmm - mmLo) / (mmHi - mmLo); + float fracLo = 1.0 - fracHi; + //weight between two slices + for (int v = 0; v < nVox2D; v++) + imX16[sliceXi + v] = round(((float)im16[sLo + v] * fracLo) + (float)im16[sHi + v] * fracHi); + } + } + } else { + if (hdr.datatype == DT_RGB24) + nVox2D = nVox2D * 3; + imX = (unsigned char *)malloc((nVox2D * slices) * 2); //sizeof( short) ); + for (int s = 0; s < slices; s++) { + float sliceXmm = s * mn; //distance from first slice + int sliceXi = (s * nVox2D); //offset for this slice + int sHi = 0; + while ((sHi < (hdr.dim[3] - 1)) && (sliceMMarray[sHi] < sliceXmm)) + sHi += 1; + int sLo = sHi - 1; + if (sLo < 0) + sLo = 0; + float mmHi = sliceMMarray[sHi]; + float mmLo = sliceMMarray[sLo]; + sLo = sLo * nVox2D; + sHi = sHi * nVox2D; + if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX + memcpy(&imX[sliceXi], &im[sHi], nVox2D); //memcpy( dest, src, bytes) + } else { + float fracHi = (sliceXmm - mmLo) / (mmHi - mmLo); + float fracLo = 1.0 - fracHi; //weight between two slices + for (int v = 0; v < nVox2D; v++) + imX[sliceXi + v] = round(((float)im[sLo + v] * fracLo) + (float)im[sHi + v] * fracHi); + } + } + } + char niiFilenameEq[2048] = {""}; + strcat(niiFilenameEq, niiFilename); + strcat(niiFilenameEq, "_Eq"); + nii_saveNII3D(niiFilenameEq, hdrX, imX, opts, d); + free(imX); + return EXIT_SUCCESS; +} // nii_saveNII3Deq() + +float PhilipsPreciseVal(float lPV, float lRS, float lRI, float lSS) { + if ((lRS * lSS) == 0) //avoid divide by zero + return 0.0; + else + return (lPV * lRS + lRI) / (lRS * lSS); } -void PhilipsPrecise(struct TDICOMdata * d, bool isPhilipsFloatNotDisplayScaling, struct nifti_1_header *h, int verbose) { - if (d->manufacturer != kMANUFACTURER_PHILIPS) return; //not Philips - if (d->isScaleVariesEnh) return; //issue363 rescaled before slice reordering +void PhilipsPrecise(struct TDICOMdata *d, bool isPhilipsFloatNotDisplayScaling, struct nifti_1_header *h, int verbose) { + if (d->manufacturer != kMANUFACTURER_PHILIPS) + return; //not Philips + if (d->isScaleVariesEnh) + return; //issue363 rescaled before slice reordering /* if (!isSameFloatGE(0.0, d->RWVScale)) { //https://github.com/rordenlab/dcm2niix/issues/493 h->scl_slope = d->RWVScale; - h->scl_inter = d->RWVIntercept; + h->scl_inter = d->RWVIntercept; printMessage("Using RWVSlope:RWVIntercept = %g:%g\n",d->RWVScale,d->RWVIntercept); printMessage(" Philips Scaling Values RS:RI:SS = %g:%g:%g (see PMC3998685)\n",d->intenScale,d->intenIntercept,d->intenScalePhilips); if (verbose == 0) return; printMessage("Potential Alternative Intensity Scalings\n"); printMessage(" R = raw value, P = precise value, D = displayed value\n"); - printMessage(" RS = rescale slope, RI = rescale intercept, SS = scale slope\n"); - printMessage(" D = R * RS + RI , P = D/(RS * SS)\n"); + printMessage(" RS = rescale slope, RI = rescale intercept, SS = scale slope\n"); + printMessage(" D = R * RS + RI , P = D/(RS * SS)\n"); return; }*/ - if (d->intenScalePhilips == 0) return; //no Philips Precise + if (d->intenScalePhilips == 0) + return; //no Philips Precise //we will report calibrated "FP" values http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3998685/ - float l0 = PhilipsPreciseVal (0, d->intenScale, d->intenIntercept, d->intenScalePhilips); - float l1 = PhilipsPreciseVal (1, d->intenScale, d->intenIntercept, d->intenScalePhilips); + float l0 = PhilipsPreciseVal(0, d->intenScale, d->intenIntercept, d->intenScalePhilips); + float l1 = PhilipsPreciseVal(1, d->intenScale, d->intenIntercept, d->intenScalePhilips); float intenScaleP = d->intenScale; float intenInterceptP = d->intenIntercept; if (l0 != l1) { intenInterceptP = l0; - intenScaleP = l1-l0; + intenScaleP = l1 - l0; } - if (isSameFloat(d->intenIntercept,intenInterceptP) && isSameFloat(d->intenScale, intenScaleP)) return; //same result for both methods: nothing to do or report! - printMessage("Philips Scaling Values RS:RI:SS = %g:%g:%g (see PMC3998685)\n",d->intenScale,d->intenIntercept,d->intenScalePhilips); + if (isSameFloat(d->intenIntercept, intenInterceptP) && isSameFloat(d->intenScale, intenScaleP)) + return; //same result for both methods: nothing to do or report! + printMessage("Philips Scaling Values RS:RI:SS = %g:%g:%g (see PMC3998685)\n", d->intenScale, d->intenIntercept, d->intenScalePhilips); if (verbose > 0) { printMessage(" R = raw value, P = precise value, D = displayed value\n"); - printMessage(" RS = rescale slope, RI = rescale intercept, SS = scale slope\n"); - printMessage(" D = R * RS + RI , P = D/(RS * SS)\n"); - printMessage(" D scl_slope:scl_inter = %g:%g\n", d->intenScale,d->intenIntercept); - printMessage(" P scl_slope:scl_inter = %g:%g\n", intenScaleP,intenInterceptP); + printMessage(" RS = rescale slope, RI = rescale intercept, SS = scale slope\n"); + printMessage(" D = R * RS + RI; P = D/(RS * SS)\n"); + printMessage(" D scl_slope:scl_inter = %g:%g\n", d->intenScale, d->intenIntercept); + printMessage(" P scl_slope:scl_inter = %g:%g\n", intenScaleP, intenInterceptP); } //#define myUsePhilipsPrecise if (isPhilipsFloatNotDisplayScaling) { - if (verbose > 0) printMessage(" Using P values ('-p n ' for D values)\n"); + if (verbose > 0) + printMessage(" Using P values ('-p n ' for D values)\n"); //to change DICOM: //d->intenScale = intenScaleP; //d->intenIntercept = intenInterceptP; //to change NIfTI - h->scl_slope = intenScaleP; - h->scl_inter = intenInterceptP; - d->intenScalePhilips = 0; //so we never run this TWICE! + h->scl_slope = intenScaleP; + h->scl_inter = intenInterceptP; + d->intenScalePhilips = 0; //so we never run this TWICE! } else if (verbose > 0) printMessage(" Using D values ('-p y ' for P values)\n"); } //PhilipsPrecise() -void smooth1D(int num, double * im) { - if (num < 3) return; - double * src = (double *) malloc(sizeof(double)*num); +void smooth1D(int num, double *im) { + if (num < 3) + return; + double *src = (double *)malloc(sizeof(double) * num); memcpy(&src[0], &im[0], num * sizeof(double)); //memcpy( dest, src, bytes) double frac = 0.25; - for (int i = 1; i < (num-1); i++) - im[i] = (src[i-1]*frac) + (src[i]*frac*2) + (src[i+1]*frac); + for (int i = 1; i < (num - 1); i++) + im[i] = (src[i - 1] * frac) + (src[i] * frac * 2) + (src[i + 1] * frac); free(src); -}// smooth1D() - -int nii_saveCrop(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts, struct TDICOMdata d) { - //remove excess neck slices - assumes output of nii_setOrtho() - if (opts.isOnlyBIDS) return EXIT_SUCCESS; - int nVox2D = hdr.dim[1]*hdr.dim[2]; - if ((nVox2D < 1) || (fabs(hdr.pixdim[3]) < 0.001) || (hdr.dim[0] != 3) || (hdr.dim[3] < 128)) return EXIT_FAILURE; - if ((hdr.datatype != DT_INT16) && (hdr.datatype != DT_UINT16)) { - printMessage("Only able to crop 16-bit volumes."); - return EXIT_FAILURE; - } - short * im16 = ( short*) im; - unsigned short * imu16 = (unsigned short*) im; +} // smooth1D() + +int nii_saveCrop(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) { + //remove excess neck slices - assumes output of nii_setOrtho() + if (opts.isOnlyBIDS) + return EXIT_SUCCESS; + int nVox2D = hdr.dim[1] * hdr.dim[2]; + if ((nVox2D < 1) || (fabs(hdr.pixdim[3]) < 0.001) || (hdr.dim[0] != 3) || (hdr.dim[3] < 128)) + return EXIT_FAILURE; + if ((hdr.datatype != DT_INT16) && (hdr.datatype != DT_UINT16)) { + printMessage("Only able to crop 16-bit volumes."); + return EXIT_FAILURE; + } + short *im16 = (short *)im; + unsigned short *imu16 = (unsigned short *)im; float kThresh = 0.09; //more than 9% of max brightness - int ventralCrop = 0; - //find max value for each slice - int slices = hdr.dim[3]; - double * sliceSums = (double *) malloc(sizeof(double)*slices); - double maxSliceVal = 0.0; - for (int i = (slices-1); i >= 0; i--) { - sliceSums[i] = 0; - int sliceStart = i * nVox2D; - if (hdr.datatype == DT_UINT16) + int ventralCrop = 0; + //find max value for each slice + int slices = hdr.dim[3]; + double *sliceSums = (double *)malloc(sizeof(double) * slices); + double maxSliceVal = 0.0; + for (int i = (slices - 1); i >= 0; i--) { + sliceSums[i] = 0; + int sliceStart = i * nVox2D; + if (hdr.datatype == DT_UINT16) for (int j = 0; j < nVox2D; j++) - sliceSums[i] += imu16[j+sliceStart]; - else + sliceSums[i] += imu16[j + sliceStart]; + else for (int j = 0; j < nVox2D; j++) - sliceSums[i] += im16[j+sliceStart]; + sliceSums[i] += im16[j + sliceStart]; if (sliceSums[i] > maxSliceVal) - maxSliceVal = sliceSums[i]; - } - if (maxSliceVal <= 0) { - free(sliceSums); - return EXIT_FAILURE; - } - smooth1D(slices, sliceSums); - for (int i = 0; i < slices; i++) sliceSums[i] = sliceSums[i] / maxSliceVal; //so brightest slice has value 1 + maxSliceVal = sliceSums[i]; + } + if (maxSliceVal <= 0) { + free(sliceSums); + return EXIT_FAILURE; + } + smooth1D(slices, sliceSums); + for (int i = 0; i < slices; i++) + sliceSums[i] = sliceSums[i] / maxSliceVal; //so brightest slice has value 1 //dorsal crop: eliminate slices with more than 5% brightness int dorsalCrop; - for (dorsalCrop = (slices-1); dorsalCrop >= 1; dorsalCrop--) - if (sliceSums[dorsalCrop-1] > kThresh) break; + for (dorsalCrop = (slices - 1); dorsalCrop >= 1; dorsalCrop--) + if (sliceSums[dorsalCrop - 1] > kThresh) + break; if (dorsalCrop <= 1) { free(sliceSums); return EXIT_FAILURE; } - const double kMaxDVmm = 169.0; - ventralCrop = dorsalCrop - round( kMaxDVmm / hdr.pixdim[3]); - if (ventralCrop < 0) ventralCrop = 0; + const double kMaxDVmm = 169.0; + ventralCrop = dorsalCrop - round(kMaxDVmm / hdr.pixdim[3]); + if (ventralCrop < 0) + ventralCrop = 0; //apply crop printMessage(" Cropping from slice %d to %d (of %d)\n", ventralCrop, dorsalCrop, slices); - struct nifti_1_header hdrX = hdr; - slices = dorsalCrop - ventralCrop + 1; - hdrX.dim[3] = slices; - //translate origin to account for missing slices - hdrX.srow_x[3] += hdr.srow_x[2]*ventralCrop; - hdrX.srow_y[3] += hdr.srow_y[2]*ventralCrop; - hdrX.srow_z[3] += hdr.srow_z[2]*ventralCrop; + struct nifti_1_header hdrX = hdr; + slices = dorsalCrop - ventralCrop + 1; + hdrX.dim[3] = slices; + //translate origin to account for missing slices + hdrX.srow_x[3] += hdr.srow_x[2] * ventralCrop; + hdrX.srow_y[3] += hdr.srow_y[2] * ventralCrop; + hdrX.srow_z[3] += hdr.srow_z[2] * ventralCrop; //convert data - unsigned char *imX; - imX = (unsigned char *)malloc( (nVox2D * slices) * 2);//sizeof( short) ); - short * imX16 = ( short*) imX; - for (int s=0; s < slices; s++) { - int sIn = s+ventralCrop; + unsigned char *imX; + imX = (unsigned char *)malloc((nVox2D * slices) * 2); //sizeof( short) ); + short *imX16 = (short *)imX; + for (int s = 0; s < slices; s++) { + int sIn = s + ventralCrop; int sOut = s; sOut = sOut * nVox2D; sIn = sIn * nVox2D; - memcpy(&imX16[sOut], &im16[sIn], nVox2D* sizeof(unsigned short)); //memcpy( dest, src, bytes) - } - char niiFilenameCrop[2048] = {""}; - strcat(niiFilenameCrop,niiFilename); - strcat(niiFilenameCrop,"_Crop"); - const int returnCode = nii_saveNII3D(niiFilenameCrop, hdrX, imX, opts, d); - free(imX); - return returnCode; -}// nii_saveCrop() - -double dicomTimeToSec (double dicomTime) { -//convert HHMMSS to seconds, 135300.024 -> 135259.731 are 0.293 sec apart + memcpy(&imX16[sOut], &im16[sIn], nVox2D * sizeof(unsigned short)); //memcpy( dest, src, bytes) + } + char niiFilenameCrop[2048] = {""}; + strcat(niiFilenameCrop, niiFilename); + strcat(niiFilenameCrop, "_Crop"); + const int returnCode = nii_saveNII3D(niiFilenameCrop, hdrX, imX, opts, d); + free(imX); + return returnCode; +} // nii_saveCrop() + +double dicomTimeToSec(double dicomTime) { + //convert HHMMSS to seconds, 135300.024 -> 135259.731 are 0.293 sec apart char acqTimeBuf[64]; snprintf(acqTimeBuf, sizeof acqTimeBuf, "%+013.5f", (double)dicomTime); - int ahour,amin; + int ahour, amin; double asec; int count = 0; sscanf(acqTimeBuf, "%3d%2d%lf%n", &ahour, &amin, &asec, &count); - if (!count) return -1; - return (ahour * 3600)+(amin * 60) + asec; + if (!count) + return -1; + return (ahour * 3600) + (amin * 60) + asec; } -double acquisitionTimeDifference(struct TDICOMdata * d1, struct TDICOMdata * d2) { - if (d1->acquisitionDate != d2->acquisitionDate) return -1; //to do: scans running across midnight +double acquisitionTimeDifference(struct TDICOMdata *d1, struct TDICOMdata *d2) { + if (d1->acquisitionDate != d2->acquisitionDate) + return -1; //to do: scans running across midnight double sec1 = dicomTimeToSec(d1->acquisitionTime); double sec2 = dicomTimeToSec(d2->acquisitionTime); //printMessage("%g\n",d2->acquisitionTime); - if ((sec1 < 0) || (sec2 < 0)) return -1; + if ((sec1 < 0) || (sec2 < 0)) + return -1; return (sec2 - sec1); } -void checkDateTimeOrder(struct TDICOMdata * d, struct TDICOMdata * d1) { - if (d->acquisitionDate < d1->acquisitionDate) return; //d1 occurred on later date - if (d->acquisitionTime <= d1->acquisitionTime) return; //d1 occurred on later (or same) time +void checkDateTimeOrder(struct TDICOMdata *d, struct TDICOMdata *d1) { + if (d->acquisitionDate < d1->acquisitionDate) + return; //d1 occurred on later date + if (d->acquisitionTime <= d1->acquisitionTime) + return; //d1 occurred on later (or same) time if (d->imageNum > d1->imageNum) printWarning("Images not sorted in ascending instance number (0020,0013)\n"); else - printWarning("Images sorted by instance number [0020,0013](%d..%d), but AcquisitionTime [0008,0032] suggests a different order (%g..%g) \n", d->imageNum,d1->imageNum, d->acquisitionTime,d1->acquisitionTime); + printWarning("Images sorted by instance number [0020,0013](%d..%d), but AcquisitionTime [0008,0032] suggests a different order (%g..%g) \n", d->imageNum, d1->imageNum, d->acquisitionTime, d1->acquisitionTime); } -void checkSliceTiming(struct TDICOMdata * d, struct TDICOMdata * d1, int verbose, int isForceSliceTimeHHMMSS) { -//detect images with slice timing errors. https://github.com/rordenlab/dcm2niix/issues/126 -//modified 20190704: this function now ensures all slice times are in msec - if ((d->TR < 0.0) || (d->CSA.sliceTiming[0] < 0.0)) return; //no slice timing - if (d->manufacturer == kMANUFACTURER_GE) return; //compute directly from Protocol Block - if (d->modality == kMODALITY_PT) return; //issue407 +void checkSliceTiming(struct TDICOMdata *d, struct TDICOMdata *d1, int verbose, int isForceSliceTimeHHMMSS) { + //detect images with slice timing errors. https://github.com/rordenlab/dcm2niix/issues/126 + //modified 20190704: this function now ensures all slice times are in msec + if ((d->TR < 0.0) || (d->CSA.sliceTiming[0] < 0.0)) + return; //no slice timing + if (d->manufacturer == kMANUFACTURER_GE) + return; //compute directly from Protocol Block + if (d->modality == kMODALITY_PT) + return; //issue407 int nSlices = 0; while ((nSlices < kMaxEPI3D) && (d->CSA.sliceTiming[nSlices] >= 0.0)) nSlices++; - if (nSlices < 1) return; - if (d->CSA.sliceTiming[kMaxEPI3D-1] < -1.0) //the value -2.0 is used as a flag for negative MosaicRefAcqTimes in checkSliceTimes(), see issue 271 - printWarning("Adjusting for negative MosaicRefAcqTimes (issue 271).\n"); + if (nSlices < 1) + return; + if (d->CSA.sliceTiming[kMaxEPI3D - 1] < -1.0) //the value -2.0 is used as a flag for negative MosaicRefAcqTimes in checkSliceTimes(), see issue 271 + printWarning("Adjusting for negative MosaicRefAcqTimes (issue 271).\n"); bool isSliceTimeHHMMSS = (d->manufacturer == kMANUFACTURER_UIH); - if (isForceSliceTimeHHMMSS) isSliceTimeHHMMSS = true; + 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 - if (isSliceTimeHHMMSS) {//handle midnight crossing + if (isSliceTimeHHMMSS) { //handle midnight crossing for (int i = 0; i < nSlices; i++) d->CSA.sliceTiming[i] = dicomTimeToSec(d->CSA.sliceTiming[i]); float minT = d->CSA.sliceTiming[0]; float maxT = minT; for (int i = 0; i < nSlices; i++) { - if (d->CSA.sliceTiming[i] < minT) minT = d->CSA.sliceTiming[i]; - if (d->CSA.sliceTiming[i] < maxT) maxT = d->CSA.sliceTiming[i]; + if (d->CSA.sliceTiming[i] < minT) + minT = d->CSA.sliceTiming[i]; + if (d->CSA.sliceTiming[i] < maxT) + maxT = d->CSA.sliceTiming[i]; } - //printf("%d %g ---> %g..%g\n", nSlices, d->TR, minT, maxT); + //printf("%d %g ---> %g..%g\n", nSlices, d->TR, minT, maxT); float kMidnightSec = 86400; float kNoonSec = 43200; if ((maxT - minT) > kNoonSec) { //volume started before midnight but ended next day! //identify and fix 'Cinderella error' where clock resets at midnight: untested printWarning("Acquisition crossed midnight: check slice timing\n"); for (int i = 0; i < nSlices; i++) - if (d->CSA.sliceTiming[i] > kNoonSec) d->CSA.sliceTiming[i] = d->CSA.sliceTiming[i] - kMidnightSec; - minT = d->CSA.sliceTiming[0]; + if (d->CSA.sliceTiming[i] > kNoonSec) + d->CSA.sliceTiming[i] = d->CSA.sliceTiming[i] - kMidnightSec; + minT = d->CSA.sliceTiming[0]; for (int i = 0; i < nSlices; i++) - if (d->CSA.sliceTiming[i] < minT) minT = d->CSA.sliceTiming[i]; + if (d->CSA.sliceTiming[i] < minT) + minT = d->CSA.sliceTiming[i]; } for (int i = 0; i < nSlices; i++) d->CSA.sliceTiming[i] = d->CSA.sliceTiming[i] - minT; @@ -4595,9 +4709,12 @@ void checkSliceTiming(struct TDICOMdata * d, struct TDICOMdata * d1, int verbose float minT = d->CSA.sliceTiming[0]; float maxT = minT; for (int i = 0; i < kMaxEPI3D; i++) { - if (d->CSA.sliceTiming[i] < 0.0) break; - if (d->CSA.sliceTiming[i] < minT) minT = d->CSA.sliceTiming[i]; - if (d->CSA.sliceTiming[i] > maxT) maxT = d->CSA.sliceTiming[i]; + if (d->CSA.sliceTiming[i] < 0.0) + break; + if (d->CSA.sliceTiming[i] < minT) + minT = d->CSA.sliceTiming[i]; + if (d->CSA.sliceTiming[i] > maxT) + maxT = d->CSA.sliceTiming[i]; } if (isSliceTimeHHMMSS) //convert HHMMSS to msec for (int i = 0; i < kMaxEPI3D; i++) @@ -4608,26 +4725,34 @@ void checkSliceTiming(struct TDICOMdata * d, struct TDICOMdata * d1, int verbose printMessage("Slice timing range appears reasonable (range %g..%g, TR=%g ms)\n", minT, maxT, TRms); 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 - if (verbose > 1) printMessage("Slice timing range of first volume: range %g..%g, TR=%g ms)\n", minT, maxT, TRms); + 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 + if (verbose > 1) + printMessage("Slice timing range of first volume: range %g..%g, TR=%g ms)\n", minT, maxT, TRms); //check if 2nd image has valid slice timing float minT1 = d1->CSA.sliceTiming[0]; float maxT1 = minT1; for (int i = 0; i < nSlices; i++) { //if (d1->CSA.sliceTiming[i] < 0.0) break; - if (d1->CSA.sliceTiming[i] < minT1) minT1 = d1->CSA.sliceTiming[i]; - if (d1->CSA.sliceTiming[i] > maxT1) maxT1 = d1->CSA.sliceTiming[i]; + if (d1->CSA.sliceTiming[i] < minT1) + minT1 = d1->CSA.sliceTiming[i]; + if (d1->CSA.sliceTiming[i] > maxT1) + maxT1 = d1->CSA.sliceTiming[i]; } - if (verbose > 1) printMessage("Slice timing range of 2nd volume: range %g..%g, TR=%g ms)\n", minT, maxT, TRms); - if ((minT1 < maxT1) && (minT1 > 0.0) && ((maxT1-minT1) <= TRms) ) { //issue 429: 2nd volume may not start from zero + if (verbose > 1) + printMessage("Slice timing range of 2nd volume: range %g..%g, TR=%g ms)\n", minT, maxT, TRms); + if ((minT1 < maxT1) && (minT1 > 0.0) && ((maxT1 - minT1) <= TRms)) { //issue 429: 2nd volume may not start from zero for (int i = 0; i < nSlices; i++) d1->CSA.sliceTiming[i] -= minT1; maxT1 -= minT1; - minT1 -= minT1; + minT1 -= minT1; } - if ((minT1 < 0.0) && (d->rtia_timerGE >= 0.0)) return; //use rtia timer + if ((minT1 < 0.0) && (d->rtia_timerGE >= 0.0)) + return; //use rtia timer if (minT1 < 0.0) { //https://github.com/neurolabusc/MRIcroGL/issues/31 if (d->modality == kMODALITY_MR) printWarning("Siemens MoCo? Bogus slice timing (range %g..%g, TR=%g seconds)\n", minT1, maxT1, TRms); @@ -4640,55 +4765,61 @@ void checkSliceTiming(struct TDICOMdata * d, struct TDICOMdata * d1, int verbose //1st image corrupted, but 2nd looks ok - substitute values from 2nd image for (int i = 0; i < kMaxEPI3D; i++) { d->CSA.sliceTiming[i] = d1->CSA.sliceTiming[i]; - if (d1->CSA.sliceTiming[i] < 0.0) break; + if (d1->CSA.sliceTiming[i] < 0.0) + break; } d->CSA.multiBandFactor = d1->CSA.multiBandFactor; printMessage("CSA slice timing based on 2nd volume, 1st volume corrupted (CMRR bug, range %g..%g, TR=%g ms)\n", minT, maxT, TRms); -}//checkSliceTiming() - -void sliceTimingXA(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struct nifti_1_header * hdr, int verbose, const char * filename, int nConvert) { - //Siemens XA10 slice timing - // Ignore first volume: For an example of erroneous first volume timing, see series 10 (Functional_w_SMS=3) https://github.com/rordenlab/dcm2niix/issues/240 - // an alternative would be to use 0018,9074 - this would need to be converted from DT to Secs, and is scrambled if de-identifies data see enhanced de-identified series 26 from issue 236 - uint64_t indx0 = dcmSort[0].indx; //first volume - if ((!dcmList[indx0].isXA10A) || (hdr->dim[3] < 1)) return; - if ( (nConvert == (hdr->dim[3]*hdr->dim[4])) && (hdr->dim[3] < (kMaxEPI3D-1)) && (hdr->dim[3] > 1) && (hdr->dim[4] > 1)) { +} //checkSliceTiming() + +void sliceTimingXA(struct TDCMsort *dcmSort, struct TDICOMdata *dcmList, struct nifti_1_header *hdr, int verbose, const char *filename, int nConvert) { + //Siemens XA10 slice timing + // Ignore first volume: For an example of erroneous first volume timing, see series 10 (Functional_w_SMS=3) https://github.com/rordenlab/dcm2niix/issues/240 + // an alternative would be to use 0018,9074 - this would need to be converted from DT to Secs, and is scrambled if de-identifies data see enhanced de-identified series 26 from issue 236 + uint64_t indx0 = dcmSort[0].indx; //first volume + if ((!dcmList[indx0].isXA10A) || (hdr->dim[3] < 1)) + return; + if ((nConvert == (hdr->dim[3] * hdr->dim[4])) && (hdr->dim[3] < (kMaxEPI3D - 1)) && (hdr->dim[3] > 1) && (hdr->dim[4] > 1)) { //XA11 2D classic for (int v = 0; v < hdr->dim[3]; v++) dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].CSA.sliceTiming[0]; - } else if ( (nConvert == (hdr->dim[4])) && (hdr->dim[3] < (kMaxEPI3D-1)) && (hdr->dim[3] > 1) && (hdr->dim[4] > 1)) { + } else if ((nConvert == (hdr->dim[4])) && (hdr->dim[3] < (kMaxEPI3D - 1)) && (hdr->dim[3] > 1) && (hdr->dim[4] > 1)) { //XA10 mosaics - these are missing a lot of information float mn = dcmList[dcmSort[1].indx].CSA.sliceTiming[0]; //get slice timing from second volume for (int v = 0; v < hdr->dim[3]; v++) { dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[1].indx].CSA.sliceTiming[v]; - if (dcmList[indx0].CSA.sliceTiming[v] < mn) mn = dcmList[indx0].CSA.sliceTiming[v]; + if (dcmList[indx0].CSA.sliceTiming[v] < mn) + mn = dcmList[indx0].CSA.sliceTiming[v]; } - if (mn < 0.0) mn = 0.0; + if (mn < 0.0) + mn = 0.0; int mb = 0; for (int v = 0; v < hdr->dim[3]; v++) { dcmList[indx0].CSA.sliceTiming[v] -= mn; - if (isSameFloatGE(dcmList[indx0].CSA.sliceTiming[v], 0.0)) mb ++; + if (isSameFloatGE(dcmList[indx0].CSA.sliceTiming[v], 0.0)) + mb++; } if ((dcmList[indx0].CSA.multiBandFactor < 2) && (mb > 1)) dcmList[indx0].CSA.multiBandFactor = mb; return; //we have subtracted min } //issue429: subtract min - float mn = dcmList[indx0].CSA.sliceTiming[0]; + float mn = dcmList[indx0].CSA.sliceTiming[0]; for (int v = 0; v < hdr->dim[3]; v++) mn = min(mn, dcmList[indx0].CSA.sliceTiming[v]); - if (isSameFloatGE(mn, 0.0)) return; + if (isSameFloatGE(mn, 0.0)) + return; for (int v = 0; v < hdr->dim[3]; v++) dcmList[indx0].CSA.sliceTiming[v] -= mn; } //sliceTimingXA() -void sliceTimeGE (struct TDICOMdata * d, int mb, int dim3, float TR, bool isInterleaved, bool is27r3, float groupDelaysec) { -//mb : multiband factor -//dim3 : number of slices in volume -//TRsec : repetition time in seconds -//isInterleaved : interleaved or sequential slice order -//is27r3 : software release 27.0 R03 or later +void sliceTimeGE(struct TDICOMdata *d, int mb, int dim3, float TR, bool isInterleaved, bool is27r3, float groupDelaysec) { + //mb : multiband factor + //dim3 : number of slices in volume + //TRsec : repetition time in seconds + //isInterleaved : interleaved or sequential slice order + //is27r3 : software release 27.0 R03 or later float sliceTiming[kMaxEPI3D]; //multiband can be fractional! 'extra' slices discarded int nExcitations = ceil(float(dim3) / float(mb)); @@ -4701,9 +4832,9 @@ void sliceTimeGE (struct TDICOMdata * d, int mb, int dim3, float TR, bool isInte int nOdd = (nExcitations - 1) / 2; for (int i = 0; i < nExcitations; i++) { if (i % 2 == 0) //ODD slices since we index from 0! - sliceTiming[i] = (i/2) * secPerSlice; + sliceTiming[i] = (i / 2) * secPerSlice; else - sliceTiming[i] = (nOdd+((i+1)/2)) * secPerSlice; + sliceTiming[i] = (nOdd + ((i + 1) / 2)) * secPerSlice; } //for each slice if ((mb > 1) && (is27r3) && (isInterleaved) && (nExcitations > 2) && ((nExcitations % 2) == 0)) { float tmp = sliceTiming[nExcitations - 1]; @@ -4714,81 +4845,82 @@ void sliceTimeGE (struct TDICOMdata * d, int mb, int dim3, float TR, bool isInte } //if interleaved for (int i = 0; i < dim3; i++) sliceTiming[i] = sliceTiming[i % nExcitations]; - //#define testSliceTimesGE //note that early GE HyperBand sequences reported single-band values in x0021x105E - #ifdef testSliceTimesGE +//#define testSliceTimesGE //note that early GE HyperBand sequences reported single-band values in x0021x105E +#ifdef testSliceTimesGE float maxErr = 0.0; for (int i = 0; i < dim3; i++) maxErr = max(maxErr, fabs(sliceTiming[i] - d->CSA.sliceTiming[i])); - if ((d->CSA.sliceTiming[0] >= 0.0) && (maxErr > 1.0) ) { //allow a 1.0 msec tolerance for rounding + if ((d->CSA.sliceTiming[0] >= 0.0) && (maxErr > 1.0)) { //allow a 1.0 msec tolerance for rounding printMessage("GE estimated slice times differ from reported (max error: %g)\n", maxErr); printMessage("Slice\tEstimated\tReported\n"); for (int i = 0; i < dim3; i++) { - printMessage("%d %g %g\n", i, sliceTiming[i], d->CSA.sliceTiming[i]); - maxErr = max(maxErr, fabs(sliceTiming[i] - d->CSA.sliceTiming[i])); - } + printMessage("%d %g %g\n", i, sliceTiming[i], d->CSA.sliceTiming[i]); + maxErr = max(maxErr, fabs(sliceTiming[i] - d->CSA.sliceTiming[i])); + } } - #endif +#endif for (int i = 0; i < dim3; i++) d->CSA.sliceTiming[i] = sliceTiming[i]; } // sliceTimeGE() -void readSoftwareVersionsGE(char softwareVersionsGE[], int verbose,char geVersionPrefix[], float* geMajorVersion, int* geMajorVersionInt, int* geMinorVersionInt, int* geReleaseVersionInt, bool* is27r3) { - // softwareVersionsGE - // "27\LX\MR Software release:RX27.0_R02_1831.a" -> 27 - // "28\LX\MR29.1_EA_2039.g" -> 29 - // geVersionPrefix - // RX27.0_R02_1831.a -> RX - // MR29.1_EA_2039.g -> MR - // geMajorVersion - // RX27.0_R02_1831.a -> 27.0 - // MR29.1_EA_2039.g -> 29.1 - // geMajorVersionInt - // RX27.0_R02_1831.a -> 27 - // MR29.1_EA_2039.g -> 29 - // geMinorVersionInt - // RX27.0_R02_1831.a -> 0 - // MR29.1_EA_2039.g -> 1 - // geReleaseVersionInt - // EA->0, R01->1, R02->2, R03->4 - // RX27.0_R02_1831.a -> 2 - // MR29.1_EA_2039.g -> 0 - - int len = 0; - // If softwareVersionsGE is 27\LX\MR Software release:RX27.0_R02_1831.a - char *sepStart = strchr(softwareVersionsGE, ':'); - if (sepStart == NULL) { - // If softwareVersionsGE is 28\LX\MR29.1_EA_2039.g - sepStart = strrchr(softwareVersionsGE, '\\'); - if (sepStart == NULL) return; - } - sepStart += 1; - len = 11; - char * versionString = (char *)malloc(sizeof(char) * len); - versionString[len-1] =0; - memcpy(versionString, sepStart, len); - int ver1, ver2, ver3; - char c1, c2, c3, c4; - // RX27.0_R02_ or MR29.1_EA_2 - sscanf( versionString, "%c%c%d.%d_%c%c%d", &c1, &c2, geMajorVersionInt, geMinorVersionInt, &c3, &c4, geReleaseVersionInt); - memcpy(geVersionPrefix, &c1, 1); - memcpy(geVersionPrefix+1, &c2, 1); - if ( (c3 == 'E') && (c4 == 'A') ){ - *geReleaseVersionInt = 0; - } - free(versionString); - *geMajorVersion = (float) *geMajorVersionInt + (float) 0.1 * (float) *geMinorVersionInt; - *is27r3 = ((*geMajorVersion >= 27.1) || ((*geMajorVersionInt == 27) && (*geReleaseVersionInt >= 3))); - if (verbose > 1) { - printMessage("GE Software VersionPrefix: %s\n", geVersionPrefix); - printMessage("GE Software MajorVersion: %d\n", *geMajorVersionInt); - printMessage("GE Software MinorVersion: %d\n", *geMinorVersionInt); - printMessage("GE Software ReleaseVersion: %d\n", *geReleaseVersionInt); - printMessage("GE Software is27r3: %d\n", *is27r3); - } +void readSoftwareVersionsGE(char softwareVersionsGE[], int verbose, char geVersionPrefix[], float *geMajorVersion, int *geMajorVersionInt, int *geMinorVersionInt, int *geReleaseVersionInt, bool *is27r3) { + // softwareVersionsGE + // "27\LX\MR Software release:RX27.0_R02_1831.a" -> 27 + // "28\LX\MR29.1_EA_2039.g" -> 29 + // geVersionPrefix + // RX27.0_R02_1831.a -> RX + // MR29.1_EA_2039.g -> MR + // geMajorVersion + // RX27.0_R02_1831.a -> 27.0 + // MR29.1_EA_2039.g -> 29.1 + // geMajorVersionInt + // RX27.0_R02_1831.a -> 27 + // MR29.1_EA_2039.g -> 29 + // geMinorVersionInt + // RX27.0_R02_1831.a -> 0 + // MR29.1_EA_2039.g -> 1 + // geReleaseVersionInt + // EA->0, R01->1, R02->2, R03->4 + // RX27.0_R02_1831.a -> 2 + // MR29.1_EA_2039.g -> 0 + int len = 0; + // If softwareVersionsGE is 27\LX\MR Software release:RX27.0_R02_1831.a + char *sepStart = strchr(softwareVersionsGE, ':'); + if (sepStart == NULL) { + // If softwareVersionsGE is 28\LX\MR29.1_EA_2039.g + sepStart = strrchr(softwareVersionsGE, '\\'); + if (sepStart == NULL) + return; + } + sepStart += 1; + len = 11; + char *versionString = (char *)malloc(sizeof(char) * len); + versionString[len - 1] = 0; + memcpy(versionString, sepStart, len); + int ver1, ver2, ver3; + char c1, c2, c3, c4; + // RX27.0_R02_ or MR29.1_EA_2 + sscanf(versionString, "%c%c%d.%d_%c%c%d", &c1, &c2, geMajorVersionInt, geMinorVersionInt, &c3, &c4, geReleaseVersionInt); + memcpy(geVersionPrefix, &c1, 1); + memcpy(geVersionPrefix + 1, &c2, 1); + if ((c3 == 'E') && (c4 == 'A')) { + *geReleaseVersionInt = 0; + } + free(versionString); + *geMajorVersion = (float)*geMajorVersionInt + (float)0.1 * (float)*geMinorVersionInt; + *is27r3 = ((*geMajorVersion >= 27.1) || ((*geMajorVersionInt == 27) && (*geReleaseVersionInt >= 3))); + if (verbose > 1) { + printMessage("GE Software VersionPrefix: %s\n", geVersionPrefix); + printMessage("GE Software MajorVersion: %d\n", *geMajorVersionInt); + printMessage("GE Software MinorVersion: %d\n", *geMinorVersionInt); + printMessage("GE Software ReleaseVersion: %d\n", *geReleaseVersionInt); + printMessage("GE Software is27r3: %d\n", *is27r3); + } } // readSoftwareVersionsGE() -void sliceTimingGE_Testx0021x105E(struct TDICOMdata * d, struct TDCMopts opts, struct nifti_1_header * hdr, struct TDCMsort *dcmSort,struct TDICOMdata *dcmList) { - if ((!opts.isTestx0021x105E) || (hdr->dim[3] < 2) || (hdr->dim[4] < 1)) return; +void sliceTimingGE_Testx0021x105E(struct TDICOMdata *d, struct TDCMopts opts, struct nifti_1_header *hdr, struct TDCMsort *dcmSort, struct TDICOMdata *dcmList) { + if ((!opts.isTestx0021x105E) || (hdr->dim[3] < 2) || (hdr->dim[4] < 1)) + return; if (d->rtia_timerGE <= 0.0) { printMessage("DICOM images do not report RTIA timer(0021,105E)\n"); return; @@ -4797,34 +4929,39 @@ void sliceTimingGE_Testx0021x105E(struct TDICOMdata * d, struct TDCMopts opts, s float sliceTiming[kMaxEPI3D]; float mn = INFINITY; for (int v = 0; v < hdr->dim[3]; v++) { - sliceTiming[v] = dcmList[dcmSort[v+j].indx].rtia_timerGE; - mn = min(mn, sliceTiming[v]); + sliceTiming[v] = dcmList[dcmSort[v + j].indx].rtia_timerGE; + mn = min(mn, sliceTiming[v]); } - if (mn < 0.0) return; - float mxErr = 0.0; + if (mn < 0.0) + return; + float mxErr = 0.0; for (int v = 0; v < hdr->dim[3]; v++) { sliceTiming[v] = (sliceTiming[v] - mn) * 1000.0; //subtract offset, convert sec -> ms mxErr = max(mxErr, float(fabs(sliceTiming[v] - d->CSA.sliceTiming[v]))); } printMessage("Slice Timing Error between calculated and RTIA timer(0021,105E): %gms\n", mxErr); - if ((mxErr < 1.0) && (opts.isVerbose < 1)) return; - for (int v = 0; v < hdr->dim[3]; v++) - printMessage("\t%g\t%g\n", d->CSA.sliceTiming[v], sliceTiming[v]); + if ((mxErr < 1.0) && (opts.isVerbose < 1)) + return; + for (int v = 0; v < hdr->dim[3]; v++) + printMessage("\t%g\t%g\n", d->CSA.sliceTiming[v], sliceTiming[v]); } -void sliceTimingGE(struct TDICOMdata * d, const char * filename, struct TDCMopts opts, struct nifti_1_header * hdr, struct TDCMsort *dcmSort,struct TDICOMdata *dcmList) { +void sliceTimingGE(struct TDICOMdata *d, const char *filename, struct TDCMopts opts, struct nifti_1_header *hdr, struct TDCMsort *dcmSort, struct TDICOMdata *dcmList) { //we can often read GE slice timing from TriggerTime (0018,1060) or RTIA Timer (0021,105E) // if both of these methods fail, we can often guess based on slice order stored in the Private Protocol Data Block (0025,101B) // this is referred to as "rescue" as we only know the TR, not the TA. So assumes continuous scans with no gap - if ((d->is3DAcq) || (d->isLocalizer) || (hdr->dim[4] < 2)) return; //no need for slice times - if (hdr->dim[3] < 2) return; - if (d->manufacturer != kMANUFACTURER_GE) return; + if ((d->is3DAcq) || (d->isLocalizer) || (hdr->dim[4] < 2)) + return; //no need for slice times + if (hdr->dim[3] < 2) + return; + if (d->manufacturer != kMANUFACTURER_GE) + return; if ((d->protocolBlockStartGE < 128) || (d->protocolBlockLengthGE < 10)) { d->CSA.sliceTiming[0] = -1; printWarning("Unable to determine GE Slice timing, no Protocol Data Block GE (0025,101B): %s\n", filename); - return; + return; } - //start version check: + //start version check: float geMajorVersion = 0; int geMajorVersionInt = 0, geMinorVersionInt = 0, geReleaseVersionInt = 0; char geVersionPrefix[2] = " "; @@ -4842,9 +4979,10 @@ void sliceTimingGE(struct TDICOMdata * d, const char * filename, struct TDCMopts }*/ //end: version check if (d->maxEchoNumGE > 0) - printWarning("GE sequence with %d echoes. See issue 359\n", d->maxEchoNumGE); - if ((d->protocolBlockStartGE < 1) || (d->protocolBlockLengthGE < 19)) return; - #ifdef myReadGeProtocolBlock + printWarning("GE sequence with %d echoes. See issue 359\n", d->maxEchoNumGE); + if ((d->protocolBlockStartGE < 1) || (d->protocolBlockLengthGE < 19)) + return; +#ifdef myReadGeProtocolBlock //GE final desperate attempt to determine slice order // GE does not provide a good estimate for TA: here we use TR, which will be wrong for sparse designs // Also, unclear what happens if slice order is flipped @@ -4869,32 +5007,32 @@ void sliceTimingGE(struct TDICOMdata * d, const char * filename, struct TDCMopts bool isInterleaved = (sliceOrderGE != 0); groupDelay *= 1000.0; //sec -> ms // - // Estimate GE Slice Time only for EPI Multi-Phase (epi) or EPI fMRI/BrainWave (epiRT) + // Estimate GE Slice Time only for EPI Multi-Phase (epi) or EPI fMRI/BrainWave (epiRT) // - // BrainWave (epiRT) - if ((d->epiVersionGE == 1) || (strstr(ioptGE,"FMRI") != NULL)) { //-1 = not epi, 0 = epi, 1 = epiRT + // BrainWave (epiRT) + if ((d->epiVersionGE == 1) || (strstr(ioptGE, "FMRI") != NULL)) { //-1 = not epi, 0 = epi, 1 = epiRT d->epiVersionGE = 1; d->internalepiVersionGE = 1; // 'EPI'(gradient echo)/'EPI2'(spin echo) if (!isSameFloatGE(groupDelay, d->groupDelay)) printWarning("With epiRT (i.e. FMRI option), Group delay reported in private tag (0043,107C = %g) and Protocol Block (0025,101B = %g) differ.\n", d->groupDelay, groupDelay); } // EPI Multi-Phase (epi) - else if ((d->epiVersionGE == 0) || (strstr(ioptGE,"MPh") != NULL)) { //-1 = not epi, 0 = epi, 1 = epiRT + else if ((d->epiVersionGE == 0) || (strstr(ioptGE, "MPh") != NULL)) { //-1 = not epi, 0 = epi, 1 = epiRT d->epiVersionGE = 0; d->internalepiVersionGE = 1; // 'EPI'(gradient echo)/'EPI2'(spin echo) - if (groupDelay > 0) { + if (groupDelay > 0) { d->TR += groupDelay; d->groupDelay = groupDelay; } // EPI Multi-Phase (epi) with Variable Delays (Unsupported) - if (groupDelay < -0.5) { + if (groupDelay < -0.5) { printWarning("SliceTiming Unspported: GE Multi-Phase EPI with Variable Delays\n"); d->CSA.sliceTiming[0] = -1; return; } } // Diffusion (Unsupported) - else if ( (d->epiVersionGE == 2) || (d->internalepiVersionGE == 2) || (strstr(ioptGE,"DIFF") != NULL) ) { + else if ((d->epiVersionGE == 2) || (d->internalepiVersionGE == 2) || (strstr(ioptGE, "DIFF") != NULL)) { printWarning("Unable to compute slice times for GE Diffusion\n"); d->CSA.sliceTiming[0] = -1.0; return; @@ -4911,15 +5049,20 @@ void sliceTimingGE(struct TDICOMdata * d, const char * filename, struct TDCMopts } sliceTimeGE(d, d->CSA.multiBandFactor, hdr->dim[3], d->TR, isInterleaved, is27r3, d->groupDelay); sliceTimingGE_Testx0021x105E(d, opts, hdr, dcmSort, dcmList); - #endif +#endif } //sliceTimingGE() -void reverseSliceTiming(struct TDICOMdata * d, int verbose, int nSL) { - if ((d->CSA.protocolSliceNumber1 == 0) || ((d->CSA.protocolSliceNumber1 == 1))) return; //slices not flipped - if (d->is3DAcq) return; //no need for slice times - if (d->CSA.sliceTiming[0] < 0.0) return; //no slice times - if (nSL > kMaxEPI3D) return; - if (nSL < 2) return; +void reverseSliceTiming(struct TDICOMdata *d, int verbose, int nSL) { + if ((d->CSA.protocolSliceNumber1 == 0) || ((d->CSA.protocolSliceNumber1 == 1))) + return; //slices not flipped + if (d->is3DAcq) + return; //no need for slice times + if (d->CSA.sliceTiming[0] < 0.0) + return; //no slice times + if (nSL > kMaxEPI3D) + return; + if (nSL < 2) + return; if (verbose) printMessage("Slices were spatially flipped, so slice times are flipped\n"); d->CSA.protocolSliceNumber1 = 0; @@ -4927,63 +5070,77 @@ void reverseSliceTiming(struct TDICOMdata * d, int verbose, int nSL) { for (int i = 0; i < nSL; i++) sliceTiming[i] = d->CSA.sliceTiming[i]; for (int i = 0; i < nSL; i++) - d->CSA.sliceTiming[i] = sliceTiming[(nSL-1)-i]; + d->CSA.sliceTiming[i] = sliceTiming[(nSL - 1) - i]; } -int sliceTimingSiemens2D(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struct nifti_1_header * hdr, int verbose, const char * filename, int nConvert) { +int sliceTimingSiemens2D(struct TDCMsort *dcmSort, struct TDICOMdata *dcmList, struct nifti_1_header *hdr, int verbose, const char *filename, int nConvert) { //only for Siemens 2D images, use acquisitionTime uint64_t indx0 = dcmSort[0].indx; //first volume - if (!(dcmList[indx0].manufacturer == kMANUFACTURER_SIEMENS)) return 0; - if (dcmList[indx0].is3DAcq) return 0; //no need for slice times - if (dcmList[indx0].CSA.sliceTiming[0] >= 0.0) return 0; //slice times calculated - if (dcmList[indx0].CSA.mosaicSlices > 1) return 0; - if (nConvert != (hdr->dim[3]*hdr->dim[4])) return 0; - if (hdr->dim[3] > (kMaxEPI3D-1)) return 0; - int nZero = 0; //infer multiband: E11C may not populate kPATModeText - for (int v = 0; v < hdr->dim[3]; v++) { - dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].acquisitionTime; //nb format is HHMMSS we need to handle midnight-crossing and convert to ms, see checkSliceTiming() - if (dcmList[indx0].CSA.sliceTiming[v] == dcmList[indx0].CSA.sliceTiming[0]) nZero++; + if (!(dcmList[indx0].manufacturer == kMANUFACTURER_SIEMENS)) + return 0; + if (dcmList[indx0].is3DAcq) + return 0; //no need for slice times + if (dcmList[indx0].CSA.sliceTiming[0] >= 0.0) + return 0; //slice times calculated + if (dcmList[indx0].CSA.mosaicSlices > 1) + return 0; + if (nConvert != (hdr->dim[3] * hdr->dim[4])) + return 0; + if (hdr->dim[3] > (kMaxEPI3D - 1)) + return 0; + int nZero = 0; //infer multiband: E11C may not populate kPATModeText + for (int v = 0; v < hdr->dim[3]; v++) { + dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].acquisitionTime; //nb format is HHMMSS we need to handle midnight-crossing and convert to ms, see checkSliceTiming() + if (dcmList[indx0].CSA.sliceTiming[v] == dcmList[indx0].CSA.sliceTiming[0]) + nZero++; } if ((dcmList[indx0].CSA.multiBandFactor < 2) && (nZero > 1)) - dcmList[indx0].CSA.multiBandFactor = nZero; + dcmList[indx0].CSA.multiBandFactor = nZero; return 1; } -void rescueSliceTimingSiemens(struct TDICOMdata * d, int verbose, int nSL, const char * filename) { - if (d->is3DAcq) return; //no need for slice times - if (d->CSA.multiBandFactor > 1) return; //pattern of multiband slice order unknown - if (d->CSA.sliceTiming[0] >= 0.0) return; //slice times calculated - if (d->CSA.mosaicSlices < 2) return; //20190807 E11C 2D (not mosaic) files do not report mosaicAcqTimes or multi-band factor. - if (nSL < 2) return; - if ((d->manufacturer != kMANUFACTURER_SIEMENS) || (d->CSA.SeriesHeader_offset < 1) || (d->CSA.SeriesHeader_length < 1)) return; +void rescueSliceTimingSiemens(struct TDICOMdata *d, int verbose, int nSL, const char *filename) { + if (d->is3DAcq) + return; //no need for slice times + if (d->CSA.multiBandFactor > 1) + return; //pattern of multiband slice order unknown + if (d->CSA.sliceTiming[0] >= 0.0) + return; //slice times calculated + if (d->CSA.mosaicSlices < 2) + return; //20190807 E11C 2D (not mosaic) files do not report mosaicAcqTimes or multi-band factor. + if (nSL < 2) + return; + if ((d->manufacturer != kMANUFACTURER_SIEMENS) || (d->CSA.SeriesHeader_offset < 1) || (d->CSA.SeriesHeader_length < 1)) + return; #ifdef myReadAsciiCsa float shimSetting[8]; 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); int ucMode = csaAscii.ucMode; - if ((ucMode < 1) || (ucMode == 3) || (ucMode > 4)) return; + if ((ucMode < 1) || (ucMode == 3) || (ucMode > 4)) + return; float trSec = d->TR / 1000.0; - float delaySec = csaAscii.delayTimeInTR/ 1000000.0; + float delaySec = csaAscii.delayTimeInTR / 1000000.0; float taSec = trSec - delaySec; float sliceTiming[kMaxEPI3D]; for (int i = 0; i < nSL; i++) - sliceTiming[i] = i * taSec/nSL * 1000.0; //expected in ms + sliceTiming[i] = i * taSec / nSL * 1000.0; //expected in ms if (ucMode == 1) //asc for (int i = 0; i < nSL; i++) d->CSA.sliceTiming[i] = sliceTiming[i]; if (ucMode == 2) //desc for (int i = 0; i < nSL; i++) - d->CSA.sliceTiming[i] = sliceTiming[(nSL-1) - i]; + d->CSA.sliceTiming[i] = sliceTiming[(nSL - 1) - i]; if (ucMode == 4) { //int int oddInc = 0; //for slices 1,3,5 - int evenInc = (nSL+1) / 2; //for 4 slices 0,1,2,3 we will order [2,0,3,1] for 5 slices [0,3,1,4,2] + int evenInc = (nSL + 1) / 2; //for 4 slices 0,1,2,3 we will order [2,0,3,1] for 5 slices [0,3,1,4,2] if (nSL % 2 == 0) { //Siemens interleaved for acquisitions with odd number of slices https://www.mccauslandcenter.sc.edu/crnl/tools/stc oddInc = evenInc; evenInc = 0; } for (int i = 0; i < nSL; i++) { - if (i % 2 == 0) {//odd slice 1,3,etc [indexed from 0]! + if (i % 2 == 0) { //odd slice 1,3,etc [indexed from 0]! d->CSA.sliceTiming[i] = sliceTiming[oddInc]; //printf("%d %d\n", i, oddInc); oddInc += 1; @@ -4999,14 +5156,18 @@ void rescueSliceTimingSiemens(struct TDICOMdata * d, int verbose, int nSL, const #endif } -void sliceTimingUIH(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struct nifti_1_header * hdr, int verbose, const char * filename, int nConvert) { +void sliceTimingUIH(struct TDCMsort *dcmSort, struct TDICOMdata *dcmList, struct nifti_1_header *hdr, int verbose, const char *filename, int nConvert) { uint64_t indx0 = dcmSort[0].indx; //first volume - if (!(dcmList[indx0].manufacturer == kMANUFACTURER_UIH)) return; - if (nConvert != (hdr->dim[3]*hdr->dim[4])) return; - if (hdr->dim[3] > (kMaxEPI3D-1)) return; - if (hdr->dim[4] < 2) return; - for (int v = 0; v < hdr->dim[3]; v++) - dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].acquisitionTime; //nb format is HHMMSS we need to handle midnight-crossing and convert to ms, see checkSliceTiming() + if (!(dcmList[indx0].manufacturer == kMANUFACTURER_UIH)) + return; + if (nConvert != (hdr->dim[3] * hdr->dim[4])) + return; + if (hdr->dim[3] > (kMaxEPI3D - 1)) + return; + if (hdr->dim[4] < 2) + return; + for (int v = 0; v < hdr->dim[3]; v++) + dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].acquisitionTime; //nb format is HHMMSS we need to handle midnight-crossing and convert to ms, see checkSliceTiming() } /* @@ -5095,7 +5256,6 @@ void oldSliceTimingGE(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struc printMessage("...\n"); if ((v < 4) || (v == (hdr->dim[3]-1))) printMessage("\t%g\t%g\t%g\t%g\t%d\n", dcmList[indx0].CSA.sliceTiming[v] / 1000.0, dcmList[dcmSort[v+j].indx].patientPosition[1], dcmList[dcmSort[v+j].indx].patientPosition[2], dcmList[dcmSort[v+j].indx].patientPosition[3], dcmList[dcmSort[v+j].indx].imageNum); - } //for v } //verbose > 1 } //if maxTime != minTIme @@ -5103,23 +5263,25 @@ void oldSliceTimingGE(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struc } //oldSliceTimingGE() */ -int sliceTimingCore(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struct nifti_1_header * hdr, int verbose, const char * filename, int nConvert, struct TDCMopts opts) { +int sliceTimingCore(struct TDCMsort *dcmSort, struct TDICOMdata *dcmList, struct nifti_1_header *hdr, int verbose, const char *filename, int nConvert, struct TDCMopts opts) { int sliceDir = 0; - if (hdr->dim[3] < 2) return sliceDir; + if (hdr->dim[3] < 2) + return sliceDir; //uint64_t indx0 = dcmSort[0].indx; //uint64_t indx1 = dcmSort[1].indx; - struct TDICOMdata * d0 = &dcmList[dcmSort[0].indx]; + struct TDICOMdata *d0 = &dcmList[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]; + struct TDICOMdata *d1 = &dcmList[indx1]; //oldSliceTimingGE(dcmSort, dcmList, hdr, verbose, filename, nConvert); sliceTimingUIH(dcmSort, dcmList, hdr, verbose, filename, nConvert); int isSliceTimeHHMMSS = sliceTimingSiemens2D(dcmSort, dcmList, hdr, verbose, filename, nConvert); sliceTimingXA(dcmSort, dcmList, hdr, verbose, filename, nConvert); checkSliceTiming(d0, d1, verbose, isSliceTimeHHMMSS); rescueSliceTimingSiemens(d0, verbose, hdr->dim[3], filename); //desperate attempts if conventional methods fail - if (hdr->dim[3] > 1)sliceDir = headerDcm2Nii2(dcmList[dcmSort[0].indx], dcmList[indx1] , hdr, true); + if (hdr->dim[3] > 1) + sliceDir = headerDcm2Nii2(dcmList[dcmSort[0].indx], dcmList[indx1], hdr, 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' (System/Miscellaneous/ImageNumbering reversed): VALIDATE SLICETIMING AND BVECS\n"); @@ -5128,240 +5290,216 @@ int sliceTimingCore(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struct sliceDir = -1; //not sure how to handle negative determinants? } if (sliceDir < 0) { - if ((dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_UIH) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_GE)) + if ((dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_UIH) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_GE)) 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; + 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() -/*void reportMat44o(char *str, mat44 A) { - printMessage("%s = [%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1]\n",str, - A.m[0][0],A.m[0][1],A.m[0][2],A.m[0][3], - A.m[1][0],A.m[1][1],A.m[1][2],A.m[1][3], - A.m[2][0],A.m[2][1],A.m[2][2],A.m[2][3]); -}*/ - -/*int issue377(struct TDICOMdata d, struct nifti_1_header *h) { - int reconMatrixPE = d.phaseEncodingLines; - if ((h->dim[2] > 0) && (h->dim[1] > 0)) { - if (h->dim[1] == h->dim[2]) //phase encoding does not matter - reconMatrixPE = h->dim[2]; - else if (d.phaseEncodingRC =='C') - reconMatrixPE = h->dim[2]; //see dcm_qa: NOPF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_200PFOV_AP_0034 - else if (d.phaseEncodingRC =='R') - reconMatrixPE = h->dim[1]; - } - printf("SeriesNumber((0020,0011))\tProtocolName(0018,1030)\tEchoTrainLength(0018,0091 or 2001,1013)\tWaterFatShift(2001,1022)\tImagingFrequency(0018,0084)\tReconMatrixPE\tPixelBandwidth(0018,0095)\tPhaseEncodingLines(0018,1310 or 0018,9231)\tPhaseEncodingSteps(0018,0089)\tPercentSampling(0018,0093)\tPercentPhaseFieldOfView(0018,0094)\n"); - printf("%ld\t%s\t%d\t%g\t%g\t%d\t%g\t%d\t%d\t%g\t%g\n", d.seriesNum, d.protocolName, d.echoTrainLength, d.waterFatShift, d.imagingFrequency, reconMatrixPE, d.pixelBandwidth, d.phaseEncodingLines, d.phaseEncodingSteps, d.percentSampling, d.phaseFieldofView ); - return 0; -}*/ - -void loadOverlay(char* imgname, unsigned char * img, int offset, int x, int y, int z) { - int nvox = x * y * z; - size_t imgszRead = (nvox+7) >> 3; //overlay stored as 1 bit per voxel - FILE *file = fopen(imgname , "rb"); +void loadOverlay(char *imgname, unsigned char *img, int offset, int x, int y, int z) { + int nvox = x * y * z; + size_t imgszRead = (nvox + 7) >> 3; //overlay stored as 1 bit per voxel + FILE *file = fopen(imgname, "rb"); if (!file) { - printError("Unable to open '%s'\n", imgname); - return; - } + printError("Unable to open '%s'\n", imgname); + return; + } fseek(file, 0, SEEK_END); - long fileLen=ftell(file); - if (fileLen < (imgszRead+offset)) { - printWarning("File not large enough to store overlay: %s\n", imgname); - return; - } - fseek(file, (long) offset, SEEK_SET); - unsigned char *bImg = (unsigned char *)malloc(imgszRead); - size_t sz = fread(bImg, 1, imgszRead, file); - //static unsigned char mask[] = {128, 64, 32, 16, 8, 4, 2, 1}; - static unsigned char mask[] = {1, 2, 4, 8, 16, 32, 64, 128}; + long fileLen = ftell(file); + if (fileLen < (imgszRead + offset)) { + printWarning("File not large enough to store overlay: %s\n", imgname); + return; + } + fseek(file, (long)offset, SEEK_SET); + unsigned char *bImg = (unsigned char *)malloc(imgszRead); + size_t sz = fread(bImg, 1, imgszRead, file); + //static unsigned char mask[] = {128, 64, 32, 16, 8, 4, 2, 1}; + static unsigned char mask[] = {1, 2, 4, 8, 16, 32, 64, 128}; for (int i = 0; i < nvox; i++) { - int byt = (i >> 3); + int byt = (i >> 3); int bit = (i % 8); img[i] = ((bImg[byt] & mask[bit]) != 0); } - /* - if (isFlipY) { - unsigned char *tImg = (unsigned char *)malloc(nvox); - memcpy(&tImg[0], &img[0], nvox); - int i = 0; - for (int yi = y-1; yi >= 0; yi--) - for (int xi = 0; xi < x; xi++) { - img[(yi*x)+xi] = tImg[i]; - i++; - } - free(tImg); - }*/ - free(bImg); + free(bImg); fclose(file); return; } //loadOverlay() -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); +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 - uint64_t indx = dcmSort[0].indx; - uint64_t indx0 = dcmSort[0].indx; - uint64_t indx1 = indx0; - if (nConvert > 1) indx1 = dcmSort[1].indx; - uint64_t indxEnd = dcmSort[nConvert-1].indx; + uint64_t indx = dcmSort[0].indx; + uint64_t indx0 = dcmSort[0].indx; + uint64_t indx1 = indx0; + if (nConvert > 1) + indx1 = dcmSort[1].indx; + uint64_t 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); - if (isnan(dcmList[indx0].gantryTilt)) return EXIT_FAILURE; - } - #endif //newTilt see issue 254 - if (dcmList[indx0].isPrivateCreatorRemap) - printWarning("PrivateCreator remapping detected. DICOMs are not archival quality (issue 435).\n"); - if (dcmList[indx0].isScaleVariesEnh) //issue363 - iVaries = true; - if ((dcmList[indx].isXA10A) && (dcmList[indx].CSA.mosaicSlices < 0)) { - printMessage("Siemens XA10 Mosaics are not primary images and lack vital data.\n"); - printMessage(" See https://github.com/rordenlab/dcm2niix/issues/236\n"); - #ifdef mySaveXA10Mosaics +#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); + if (isnan(dcmList[indx0].gantryTilt)) + return EXIT_FAILURE; + } +#endif //newTilt see issue 254 + if (dcmList[indx0].isPrivateCreatorRemap) + printWarning("PrivateCreator remapping detected. DICOMs are not archival quality (issue 435).\n"); + if (dcmList[indx0].isScaleVariesEnh) //issue363 + iVaries = true; + if ((dcmList[indx].isXA10A) && (dcmList[indx].CSA.mosaicSlices < 0)) { + printMessage("Siemens XA10 Mosaics are not primary images and lack vital data.\n"); + printMessage(" See https://github.com/rordenlab/dcm2niix/issues/236\n"); +#ifdef mySaveXA10Mosaics int n; printMessage("INPUT REQUIRED FOR %s\n", dcmList[indx].imageBaseName); printMessage("PLEASE ENTER NUMBER OF SLICES IN MOSAIC:\n"); - scanf ("%d",&n); + scanf("%d", &n); for (int i = 0; i < nConvert; i++) dcmList[dcmSort[i].indx].CSA.mosaicSlices = n; - #endif - } - if (opts.isIgnoreDerivedAnd2D && dcmList[indx].isDerived) { - printMessage("Ignoring derived image(s) of series %ld %s\n", dcmList[indx].seriesNum, nameList->str[indx]); - return EXIT_SUCCESS; - } - if ((opts.isIgnoreDerivedAnd2D) && ((dcmList[indx].isLocalizer) || (strcmp(dcmList[indx].sequenceName, "_tfl2d1")== 0) || (strcmp(dcmList[indx].sequenceName, "_fl3d1_ns")== 0) || (strcmp(dcmList[indx].sequenceName, "_fl2d1")== 0)) ) { - printMessage("Ignoring localizer (sequence '%s') of series %ld %s\n", dcmList[indx].sequenceName, dcmList[indx].seriesNum, nameList->str[indx]); - return EXIT_SUCCESS; - } - if ((opts.isIgnoreDerivedAnd2D) && (nConvert < 2) && (dcmList[indx].CSA.mosaicSlices < 2) && (dcmList[indx].xyzDim[3] < 2)) { - printMessage("Ignoring 2D image of series %ld %s\n", dcmList[indx].seriesNum, nameList->str[indx]); - return EXIT_SUCCESS; - } - if (dcmList[indx].manufacturer == kMANUFACTURER_UNKNOWN) +#endif + } + if (opts.isIgnoreDerivedAnd2D && dcmList[indx].isDerived) { + printMessage("Ignoring derived image(s) of series %ld %s\n", dcmList[indx].seriesNum, nameList->str[indx]); + return EXIT_SUCCESS; + } + if ((opts.isIgnoreDerivedAnd2D) && ((dcmList[indx].isLocalizer) || (strcmp(dcmList[indx].sequenceName, "_tfl2d1") == 0) || (strcmp(dcmList[indx].sequenceName, "_fl3d1_ns") == 0) || (strcmp(dcmList[indx].sequenceName, "_fl2d1") == 0))) { + printMessage("Ignoring localizer (sequence '%s') of series %ld %s\n", dcmList[indx].sequenceName, dcmList[indx].seriesNum, nameList->str[indx]); + return EXIT_SUCCESS; + } + if ((opts.isIgnoreDerivedAnd2D) && (nConvert < 2) && (dcmList[indx].CSA.mosaicSlices < 2) && (dcmList[indx].xyzDim[3] < 2)) { + printMessage("Ignoring 2D image of series %ld %s\n", dcmList[indx].seriesNum, nameList->str[indx]); + return EXIT_SUCCESS; + } + if (dcmList[indx].manufacturer == kMANUFACTURER_UNKNOWN) printWarning("Unable to determine manufacturer (0008,0070), so conversion is not tuned for vendor.\n"); - #ifdef myForce3DPhaseRealImaginary //compiler option: segment each phase/real/imaginary map - bool saveAs3D = dcmList[indx].isHasPhase || dcmList[indx].isHasReal || dcmList[indx].isHasImaginary; - #else - bool saveAs3D = false; - #endif - struct nifti_1_header hdr0; - unsigned char * img = nii_loadImgXL(nameList->str[indx], &hdr0,dcmList[indx], iVaries, opts.compressFlag, opts.isVerbose, dti4D); - if (strlen(opts.imageComments) > 0) { - for (int i = 0; i < 24; i++) hdr0.aux_file[i] = 0; //remove dcm.imageComments - snprintf(hdr0.aux_file,24,"%s",opts.imageComments); - } - if (opts.isVerbose) - printMessage("Converting %s\n",nameList->str[indx]); - if (img == NULL) return EXIT_FAILURE; - size_t imgsz = nii_ImgBytes(hdr0); - unsigned char *imgM = (unsigned char *)malloc(imgsz* (uint64_t)nConvert); - memcpy(&imgM[0], &img[0], imgsz); - free(img); - //printMessage(" %d %d %d %d %lu\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], (unsigned long)[imgM length]); - bool isHasOverlay = dcmList[indx0].isHasOverlay; - if (nConvert > 1) { - //next: detect trigger time see example https://www.slicer.org/wiki/Documentation/4.4/Modules/MultiVolumeExplorer - double triggerDx = dcmList[dcmSort[nConvert-1].indx].triggerDelayTime - dcmList[indx0].triggerDelayTime; - if (triggerDx > 0.0) //issue 384 - dcmList[indx0].triggerDelayTime = triggerDx; - //next: determine gantry tilt - if (dcmList[indx0].gantryTilt != 0.0f) - printWarning("Note these images have gantry tilt of %g degrees (manufacturer ID = %d)\n", dcmList[indx0].gantryTilt, dcmList[indx0].manufacturer); - if (hdr0.dim[3] < 2) { - //stack volumes with multiple acquisitions - int nAcq = 1; - //Next line works in theory, but fails with Siemens CT that saves pairs of slices as acquisitions, see example "testSiemensStackAcq" - // nAcq = 1+abs( dcmList[dcmSort[nConvert-1].indx].acquNum-dcmList[indx0].acquNum); - //therefore, the 'same position' is the most robust solution in the real world. - if ((dcmList[indx0].manufacturer == kMANUFACTURER_SIEMENS) && (isSameFloat(dcmList[indx0].TR ,0.0f))) { - nConvert = siemensCtKludge(nConvert, dcmSort,dcmList); - } - //resolve GE discrepancy between tags 0020,1002; 0021,104F; 0054,0081 - // e.g. T1 scans can be interpolated in the slice direction, so choose large number - // EPI can report total number of slices (dim3*dim4), so choose smaller number - if ((nAcq == 1 ) && (dcmList[indx0].locationsInAcquisitionConflict > 0) && ((nConvert % dcmList[indx0].locationsInAcquisitionConflict) == 0)) { - //printMessage(" >>>%d %d %d %d\n", nAcq, nConvert, dcmList[indx0].locationsInAcquisitionConflict, dcmList[indx0].locationsInAcquisition); - nAcq = 0; - for (int i = 0; i < nConvert; i++) - if (isSamePosition(dcmList[dcmSort[0].indx],dcmList[dcmSort[i].indx])) nAcq++; - int nAcqConflict = nConvert/dcmList[indx0].locationsInAcquisitionConflict; - if (nAcq == nAcqConflict) { - printMessage("Resolved discrepancy between tags (0020,1002; 0021,104F; 0054,0081)\n"); - dcmList[indx0].locationsInAcquisition = dcmList[indx0].locationsInAcquisitionConflict; - } - } - if ((nConvert > 1) && (nAcq == 1 ) && (dcmList[indx0].locationsInAcquisition > 0) ){ +#ifdef myForce3DPhaseRealImaginary //compiler option: segment each phase/real/imaginary map + bool saveAs3D = dcmList[indx].isHasPhase || dcmList[indx].isHasReal || dcmList[indx].isHasImaginary; +#else + bool saveAs3D = false; +#endif + struct nifti_1_header hdr0; + unsigned char *img = nii_loadImgXL(nameList->str[indx], &hdr0, dcmList[indx], iVaries, opts.compressFlag, opts.isVerbose, dti4D); + if (strlen(opts.imageComments) > 0) { + for (int i = 0; i < 24; i++) + hdr0.aux_file[i] = 0; //remove dcm.imageComments + snprintf(hdr0.aux_file, 24, "%s", opts.imageComments); + } + if (opts.isVerbose) + printMessage("Converting %s\n", nameList->str[indx]); + if (img == NULL) + return EXIT_FAILURE; + size_t imgsz = nii_ImgBytes(hdr0); + unsigned char *imgM = (unsigned char *)malloc(imgsz * (uint64_t)nConvert); + memcpy(&imgM[0], &img[0], imgsz); + free(img); + //printMessage(" %d %d %d %d %lu\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], (unsigned long)[imgM length]); + bool isHasOverlay = dcmList[indx0].isHasOverlay; + if (nConvert > 1) { + //next: detect trigger time see example https://www.slicer.org/wiki/Documentation/4.4/Modules/MultiVolumeExplorer + double triggerDx = dcmList[dcmSort[nConvert - 1].indx].triggerDelayTime - dcmList[indx0].triggerDelayTime; + if (triggerDx > 0.0) //issue 384 + dcmList[indx0].triggerDelayTime = triggerDx; + //next: determine gantry tilt + if (dcmList[indx0].gantryTilt != 0.0f) + printWarning("Note these images have gantry tilt of %g degrees (manufacturer ID = %d)\n", dcmList[indx0].gantryTilt, dcmList[indx0].manufacturer); + if (hdr0.dim[3] < 2) { + //stack volumes with multiple acquisitions + int nAcq = 1; + //Next line works in theory, but fails with Siemens CT that saves pairs of slices as acquisitions, see example "testSiemensStackAcq" + // nAcq = 1+abs( dcmList[dcmSort[nConvert-1].indx].acquNum-dcmList[indx0].acquNum); + //therefore, the 'same position' is the most robust solution in the real world. + if ((dcmList[indx0].manufacturer == kMANUFACTURER_SIEMENS) && (isSameFloat(dcmList[indx0].TR, 0.0f))) { + nConvert = siemensCtKludge(nConvert, dcmSort, dcmList); + } + //resolve GE discrepancy between tags 0020,1002; 0021,104F; 0054,0081 + // e.g. T1 scans can be interpolated in the slice direction, so choose large number + // EPI can report total number of slices (dim3*dim4), so choose smaller number + if ((nAcq == 1) && (dcmList[indx0].locationsInAcquisitionConflict > 0) && ((nConvert % dcmList[indx0].locationsInAcquisitionConflict) == 0)) { + //printMessage(" >>>%d %d %d %d\n", nAcq, nConvert, dcmList[indx0].locationsInAcquisitionConflict, dcmList[indx0].locationsInAcquisition); + nAcq = 0; + for (int i = 0; i < nConvert; i++) + if (isSamePosition(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx])) + nAcq++; + int nAcqConflict = nConvert / dcmList[indx0].locationsInAcquisitionConflict; + if (nAcq == nAcqConflict) { + printMessage("Resolved discrepancy between tags (0020,1002; 0021,104F; 0054,0081)\n"); + dcmList[indx0].locationsInAcquisition = dcmList[indx0].locationsInAcquisitionConflict; + } + } + 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++; - } - /*int nImg = 1+abs( dcmList[dcmSort[nConvert-1].indx].imageNum-dcmList[dcmSort[0].indx].imageNum); - if (((nConvert/nAcq) > 1) && ((nConvert%nAcq)==0) && (nImg == nConvert) && (dcmList[dcmSort[0].indx].locationsInAcquisition == 0) ) { - printMessage(" stacking %d acquisitions as a single volume\n", nAcq); - //some Siemens CT scans use multiple acquisitions for a single volume, perhaps also check that slice position does not repeat? - hdr0.dim[3] = nConvert; - } else*/ if ( (nAcq > 1) && ((nConvert/nAcq) > 1) && ((nConvert%nAcq)==0) ) { - hdr0.dim[3] = nConvert/nAcq; - hdr0.dim[4] = nAcq; - hdr0.dim[0] = 4; - if ((dcmList[indx0].locationsInAcquisition > 0) && (dcmList[indx0].locationsInAcquisition != hdr0.dim[3])) - printMessage("DICOM images may be missing, expected %d spatial locations per volume, but found %d.\n", dcmList[indx0].locationsInAcquisition, hdr0.dim[3]); - } else if ((dcmList[indx0].isXA10A) && (nConvert > nAcq) && (nAcq > 1) ) { - nAcq -= 1; - hdr0.dim[3] = nConvert/nAcq; - hdr0.dim[4] = nAcq; - hdr0.dim[0] = 4; - if ((nAcq > 1) && (nConvert != nAcq)) { - printMessage("Slice positions repeated, but number of slices (%d) not divisible by number of repeats (%d): converting only complete volumes.\n", nConvert, nAcq); - } - } else { - hdr0.dim[3] = nConvert; - if ((nAcq > 1) && (nConvert != nAcq)) { - printMessage("Slice positions repeated, but number of slices (%d) not divisible by number of repeats (%d): missing images?\n", nConvert, nAcq); - if (dcmList[indx0].locationsInAcquisition > 0) - printMessage("Hint: expected %d locations\n", dcmList[indx0].locationsInAcquisition); - } - } - //next options removed: features now thoroughly detected in nii_loadDir() - for (int i = 0; i < nConvert; i++) { //make sure 1st volume describes shared features - if (dcmList[dcmSort[i].indx].isHasOverlay) isHasOverlay = true; - if (dcmList[dcmSort[i].indx].isCoilVaries) dcmList[indx0].isCoilVaries = true; - if (dcmList[dcmSort[i].indx].isMultiEcho) dcmList[indx0].isMultiEcho = true; - if (dcmList[dcmSort[i].indx].isNonParallelSlices) dcmList[indx0].isNonParallelSlices = true; - if (dcmList[dcmSort[i].indx].isHasPhase) dcmList[indx0].isHasPhase = true; - if (dcmList[dcmSort[i].indx].isHasReal) dcmList[indx0].isHasReal = true; - if (dcmList[dcmSort[i].indx].isHasImaginary) dcmList[indx0].isHasImaginary = true; + if (nAcq < 2) { + nAcq = 0; + for (int i = 0; i < nConvert; i++) + if (isSamePosition(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx])) + nAcq++; + } + if ((nAcq > 1) && ((nConvert / nAcq) > 1) && ((nConvert % nAcq) == 0)) { + hdr0.dim[3] = nConvert / nAcq; + hdr0.dim[4] = nAcq; + hdr0.dim[0] = 4; + if ((dcmList[indx0].locationsInAcquisition > 0) && (dcmList[indx0].locationsInAcquisition != hdr0.dim[3])) + printMessage("DICOM images may be missing, expected %d spatial locations per volume, but found %d.\n", dcmList[indx0].locationsInAcquisition, hdr0.dim[3]); + } else if ((dcmList[indx0].isXA10A) && (nConvert > nAcq) && (nAcq > 1)) { + nAcq -= 1; + hdr0.dim[3] = nConvert / nAcq; + hdr0.dim[4] = nAcq; + hdr0.dim[0] = 4; + if ((nAcq > 1) && (nConvert != nAcq)) { + printMessage("Slice positions repeated, but number of slices (%d) not divisible by number of repeats (%d): converting only complete volumes.\n", nConvert, nAcq); + } + } else { + hdr0.dim[3] = nConvert; + if ((nAcq > 1) && (nConvert != nAcq)) { + printMessage("Slice positions repeated, but number of slices (%d) not divisible by number of repeats (%d): missing images?\n", nConvert, nAcq); + if (dcmList[indx0].locationsInAcquisition > 0) + printMessage("Hint: expected %d locations\n", dcmList[indx0].locationsInAcquisition); + } + } + //next options removed: features now thoroughly detected in nii_loadDir() + for (int i = 0; i < nConvert; i++) { //make sure 1st volume describes shared features + if (dcmList[dcmSort[i].indx].isHasOverlay) + isHasOverlay = true; + if (dcmList[dcmSort[i].indx].isCoilVaries) + dcmList[indx0].isCoilVaries = true; + if (dcmList[dcmSort[i].indx].isMultiEcho) + dcmList[indx0].isMultiEcho = true; + if (dcmList[dcmSort[i].indx].isNonParallelSlices) + dcmList[indx0].isNonParallelSlices = true; + if (dcmList[dcmSort[i].indx].isHasPhase) + dcmList[indx0].isHasPhase = true; + if (dcmList[dcmSort[i].indx].isHasReal) + dcmList[indx0].isHasReal = true; + if (dcmList[dcmSort[i].indx].isHasImaginary) + dcmList[indx0].isHasImaginary = true; } //next: detect variable inter-volume time https://github.com/rordenlab/dcm2niix/issues/184 - //if ((nConvert > 1) && ((dcmList[indx0].modality == kMODALITY_PT)|| (opts.isForceOnsetTimes))) { - if ((nConvert > 1) && ((dcmList[indx0].modality == kMODALITY_PT)|| ((opts.isForceOnsetTimes) && (dcmList[indx0].manufacturer != kMANUFACTURER_GE)))) { - //note: GE 0008,0032 unreliable, see mb=6 data from sw27.0 20201026 + //if ((nConvert > 1) && ((dcmList[indx0].modality == kMODALITY_PT)|| (opts.isForceOnsetTimes))) { + if ((nConvert > 1) && ((dcmList[indx0].modality == kMODALITY_PT) || ((opts.isForceOnsetTimes) && (dcmList[indx0].manufacturer != kMANUFACTURER_GE)))) { + //note: GE 0008,0032 unreliable, see mb=6 data from sw27.0 20201026 //issue 407 int nTR = 0; for (int i = 0; i < nConvert; i++) - if (isSamePosition(dcmList[indx0],dcmList[dcmSort[i].indx])) { - dti4D->frameDuration[nTR] = dcmList[dcmSort[i].indx].frameDuration; - nTR += 1; - if (nTR >= kMaxDTI4D) break; - } - + if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { + dti4D->frameDuration[nTR] = dcmList[dcmSort[i].indx].frameDuration; + nTR += 1; + if (nTR >= kMaxDTI4D) + break; + } bool trVaries = false; bool dayVaries = false; float tr = -1.0; @@ -5371,19 +5509,22 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc int nVol = 0; uint64_t prevVolIndx = indx0; for (int i = 0; i < nConvert; i++) - if (isSamePosition(dcmList[indx0],dcmList[dcmSort[i].indx])) { - float trDiff = acquisitionTimeDifference(&dcmList[prevVolIndx], &dcmList[dcmSort[i].indx]); - prevVolIndx = dcmSort[i].indx; - nVol ++; - if (trDiff <= 0) continue; - mintr = min(mintr, trDiff); - maxtr = max(maxtr, trDiff); - if (tr < 0) tr = trDiff; - if (trDiff < 0) dayVaries = true; - float trDiff0 = acquisitionTimeDifference(&dcmList[indx0], &dcmList[dcmSort[i].indx]); - volumeTimeStartFirstStartLast = max(volumeTimeStartFirstStartLast, trDiff0); - } - float toleranceSec = 50.0/1000.0; //e.g. 50/1000 = 50ms + if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { + float trDiff = acquisitionTimeDifference(&dcmList[prevVolIndx], &dcmList[dcmSort[i].indx]); + prevVolIndx = dcmSort[i].indx; + nVol++; + if (trDiff <= 0) + continue; + mintr = min(mintr, trDiff); + maxtr = max(maxtr, trDiff); + if (tr < 0) + tr = trDiff; + if (trDiff < 0) + dayVaries = true; + float trDiff0 = acquisitionTimeDifference(&dcmList[indx0], &dcmList[dcmSort[i].indx]); + volumeTimeStartFirstStartLast = max(volumeTimeStartFirstStartLast, trDiff0); + } + float toleranceSec = 50.0 / 1000.0; //e.g. 50/1000 = 50ms if ((nVol > 1) && (volumeTimeStartFirstStartLast > 0.0)) { tr = volumeTimeStartFirstStartLast / (nVol - 1.0); if (fabs(tr - hdr0.pixdim[4]) > toleranceSec) { @@ -5395,7 +5536,8 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc dcmList[indx0].TR = tr * 1000.0; //as msec } } - if ((maxtr - mintr) > toleranceSec) trVaries = true; + if ((maxtr - mintr) > toleranceSec) + trVaries = true; if (trVaries) { if (dayVaries) printWarning("Seconds between volumes varies (perhaps run through midnight)\n"); @@ -5404,91 +5546,96 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc //issue 407 int nTR = 0; for (int i = 0; i < nConvert; i++) - if (isSamePosition(dcmList[indx0],dcmList[dcmSort[i].indx])) { - float trDiff = acquisitionTimeDifference(&dcmList[indx0], &dcmList[dcmSort[i].indx]); - dti4D->volumeOnsetTime[nTR] = trDiff; - dti4D->decayFactor[nTR] = dcmList[dcmSort[i].indx].decayFactor; - //printf("%d %g\n", i, dcmList[dcmSort[i].indx].decayFactor); - nTR += 1; - if (nTR >= kMaxDTI4D) break; - } - if (dcmList[indx0].modality != kMODALITY_PT) + if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { + float trDiff = acquisitionTimeDifference(&dcmList[indx0], &dcmList[dcmSort[i].indx]); + dti4D->volumeOnsetTime[nTR] = trDiff; + dti4D->decayFactor[nTR] = dcmList[dcmSort[i].indx].decayFactor; + //printf("%d %g\n", i, dcmList[dcmSort[i].indx].decayFactor); + nTR += 1; + if (nTR >= kMaxDTI4D) + break; + } + if (dcmList[indx0].modality != kMODALITY_PT) dti4D->decayFactor[0] = -1.0; //only for PET else hdr0.pixdim[4] = 0.0; // saveAs3D = true; - // printWarning("Creating independent volumes as time between volumes varies\n"); + // printWarning("Creating independent volumes as time between volumes varies\n"); if (opts.isVerbose) { printMessage(" OnsetTime = ["); for (int i = 0; i < nConvert; i++) - if (isSamePosition(dcmList[indx0],dcmList[dcmSort[i].indx])) { - float trDiff = acquisitionTimeDifference(&dcmList[indx0], &dcmList[dcmSort[i].indx]); - printMessage(" %g", trDiff); - } + if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { + float trDiff = acquisitionTimeDifference(&dcmList[indx0], &dcmList[dcmSort[i].indx]); + printMessage(" %g", trDiff); + } printMessage(" ]\n"); } } //if trVaries - } //if PET - //next: detect variable inter-slice distance - float dx = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[1].indx]); - #ifdef myInstanceNumberOrderIsNotSpatial - if (!isSameFloat(dx, 0.0)) //only for XYZT, not TXYZ: perhaps run for swapDim3Dim4? Extremely rare anomaly - if (!ensureSequentialSlicePositions(hdr0.dim[3],hdr0.dim[4], dcmSort, dcmList)) - dx = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[1].indx]); - indx0 = dcmSort[0].indx; - if (nConvert > 1) indx1 = dcmSort[1].indx; - #endif - bool dxVaries = false; - for (int i = 1; i < nConvert; i++) - if (!isSameFloatT(dx,intersliceDistance(dcmList[dcmSort[i-1].indx],dcmList[dcmSort[i].indx]),0.2)) - dxVaries = true; - if (hdr0.dim[4] < 2) { - if (dxVaries) { - sliceMMarray = (float *) malloc(sizeof(float)*nConvert); - sliceMMarray[0] = 0.0f; - for (int i = 1; i < nConvert; i++) - sliceMMarray[i] = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[i].indx]); - printWarning("Interslice distance varies in this volume (incompatible with NIfTI format).\n"); - if (opts.isVerbose) { + } //if PET + //next: detect variable inter-slice distance + float dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); +#ifdef myInstanceNumberOrderIsNotSpatial + if (!isSameFloat(dx, 0.0)) //only for XYZT, not TXYZ: perhaps run for swapDim3Dim4? Extremely rare anomaly + if (!ensureSequentialSlicePositions(hdr0.dim[3], hdr0.dim[4], dcmSort, dcmList)) + dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); + indx0 = dcmSort[0].indx; + if (nConvert > 1) + indx1 = dcmSort[1].indx; +#endif + bool dxVaries = false; + for (int i = 1; i < nConvert; i++) + if (!isSameFloatT(dx, intersliceDistance(dcmList[dcmSort[i - 1].indx], dcmList[dcmSort[i].indx]), 0.2)) + dxVaries = true; + if (hdr0.dim[4] < 2) { + if (dxVaries) { + sliceMMarray = (float *)malloc(sizeof(float) * nConvert); + sliceMMarray[0] = 0.0f; + for (int i = 1; i < nConvert; i++) + sliceMMarray[i] = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); + printWarning("Interslice distance varies in this volume (incompatible with NIfTI format).\n"); + if (opts.isVerbose) { printMessage("Dimensions %d %d %d %d nAcq %d nConvert %d\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], nAcq, nConvert); printMessage(" Distance from first slice:\n"); printMessage("dx=[0"); for (int i = 1; i < nConvert; i++) printMessage(" %g", sliceMMarray[i]); printMessage("]\n"); - } - #ifndef myInstanceNumberOrderIsNotSpatial - //kludge to handle single volume without instance numbers (0020,0013), e.g. https://www.morphosource.org/Detail/MediaDetail/Show/media_id/8430 + } +#ifndef myInstanceNumberOrderIsNotSpatial + //kludge to handle single volume without instance numbers (0020,0013), e.g. https://www.morphosource.org/Detail/MediaDetail/Show/media_id/8430 bool isInconsistenSliceDir = false; int slicePositionRepeats = 1; //how many times is first position repeated if (nConvert > 2) { - float dxPrev = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[1].indx]); - if (isSameFloatGE(dxPrev, 0.0)) slicePositionRepeats++; + float dxPrev = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); + if (isSameFloatGE(dxPrev, 0.0)) + slicePositionRepeats++; for (int i = 2; i < nConvert; i++) { - float dx = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[i].indx]); + float dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); if (dx < dxPrev) isInconsistenSliceDir = true; - if (isSameFloatGE(dxPrev, 0.0)) slicePositionRepeats++; + if (isSameFloatGE(dxPrev, 0.0)) + slicePositionRepeats++; dxPrev = dx; - } + } } - if ((isInconsistenSliceDir) && (slicePositionRepeats == 1)) { + if ((isInconsistenSliceDir) && (slicePositionRepeats == 1)) { //printWarning("Slice order as defined by instance number not spatially sequential.\n"); //printWarning("Attempting to reorder slices based on spatial position.\n"); - ensureSequentialSlicePositions(hdr0.dim[3],hdr0.dim[4], dcmSort, dcmList); - dx = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[1].indx]); + ensureSequentialSlicePositions(hdr0.dim[3], hdr0.dim[4], dcmSort, dcmList); + dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); hdr0.pixdim[3] = dx; isInconsistenSliceDir = false; //code below duplicates prior code, could be written as modular function(s) indx0 = dcmSort[0].indx; - if (nConvert > 1) indx1 = dcmSort[1].indx; - dxVaries = false; - dx = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[1].indx]); - for (int i = 1; i < nConvert; i++) - if (!isSameFloatT(dx,intersliceDistance(dcmList[dcmSort[i-1].indx],dcmList[dcmSort[i].indx]),0.2)) - dxVaries = true; - for (int i = 1; i < nConvert; i++) - sliceMMarray[i] = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[i].indx]); + if (nConvert > 1) + indx1 = dcmSort[1].indx; + dxVaries = false; + dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); + for (int i = 1; i < nConvert; i++) + if (!isSameFloatT(dx, intersliceDistance(dcmList[dcmSort[i - 1].indx], dcmList[dcmSort[i].indx]), 0.2)) + dxVaries = true; + for (int i = 1; i < nConvert; i++) + sliceMMarray[i] = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); //printf("dx=["); //for (int i = 1; i < nConvert; i++) // printf("%g ", intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[i].indx]) ); @@ -5496,22 +5643,23 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc bool isInconsistenSliceDir = false; int slicePositionRepeats = 1; //how many times is first position repeated if (nConvert > 2) { - float dxPrev = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[1].indx]); - if (isSameFloatGE(dxPrev, 0.0)) slicePositionRepeats++; + float dxPrev = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); + if (isSameFloatGE(dxPrev, 0.0)) + slicePositionRepeats++; for (int i = 2; i < nConvert; i++) { - float dx = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[i].indx]); + float dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); if (dx < dxPrev) isInconsistenSliceDir = true; - if (isSameFloatGE(dxPrev, 0.0)) slicePositionRepeats++; + if (isSameFloatGE(dxPrev, 0.0)) + slicePositionRepeats++; dxPrev = dx; - } + } } if (!dxVaries) { printMessage("Slice re-ordering resolved inter-slice distance variability.\n"); free(sliceMMarray); - sliceMMarray = NULL; + sliceMMarray = NULL; } - } if (isInconsistenSliceDir) { printMessage("Unable to equalize slice distances: slice order not consistently ascending.\n"); @@ -5519,13 +5667,13 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc printError(" Recompiling with '-DmyInstanceNumberOrderIsNotSpatial' might help.\n"); return EXIT_FAILURE; } - #endif - int imageNumRange = 1 + abs( dcmList[dcmSort[nConvert-1].indx].imageNum - dcmList[dcmSort[0].indx].imageNum); +#endif + int imageNumRange = 1 + abs(dcmList[dcmSort[nConvert - 1].indx].imageNum - dcmList[dcmSort[0].indx].imageNum); if ((imageNumRange > 1) && (imageNumRange != nConvert)) { - if ((dcmList[dcmSort[0].indx].locationsInAcquisition > 0) && ((nConvert % dcmList[dcmSort[0].indx].locationsInAcquisition) != 0) ) - printError("Missing images. Found %d images, expected %d slices per volume and instance number (0020,0013) ranges from %d to %d\n", nConvert, dcmList[dcmSort[0].indx].locationsInAcquisition, dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[nConvert-1].indx].imageNum); + if ((dcmList[dcmSort[0].indx].locationsInAcquisition > 0) && ((nConvert % dcmList[dcmSort[0].indx].locationsInAcquisition) != 0)) + printError("Missing images. Found %d images, expected %d slices per volume and instance number (0020,0013) ranges from %d to %d\n", nConvert, dcmList[dcmSort[0].indx].locationsInAcquisition, dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[nConvert - 1].indx].imageNum); else - printWarning("Missing images? Expected %d images, but instance number (0020,0013) ranges from %d to %d\n", nConvert, dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[nConvert-1].indx].imageNum); + printWarning("Missing images? Expected %d images, but instance number (0020,0013) ranges from %d to %d\n", nConvert, dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[nConvert - 1].indx].imageNum); if (opts.isVerbose) { printMessage("instance=["); for (int i = 0; i < nConvert; i++) { @@ -5533,60 +5681,47 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc } printMessage("]\n"); } - } //imageNum not sequential + } //imageNum not sequential } //dx varies - } //not 4D - if ((hdr0.dim[4] > 0) && (dxVaries) && (dx == 0.0) && ((dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_UNKNOWN) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_GE) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_PHILIPS)) ) { //Niels Janssen has provided GE sequential multi-phase acquisitions that also require swizzling - swapDim3Dim4(hdr0.dim[3],hdr0.dim[4],dcmSort); - dx = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[1].indx]); - if (opts.isVerbose) - printMessage("Swizzling 3rd and 4th dimensions (XYTZ -> XYZT), assuming interslice distance is %f\n",dx); - } - if ((dx == 0.0 ) && (!dxVaries)) { //all images are the same slice - 16 Dec 2014 - printWarning("All images appear to be a single slice - please check slice/vector orientation\n"); - hdr0.dim[3] = 1; - hdr0.dim[4] = nConvert; - hdr0.dim[0] = 4; - } - if ((dx > 0) && (!isSameFloatGE(dx, hdr0.pixdim[3]))) - hdr0.pixdim[3] = dx; - dcmList[dcmSort[0].indx].xyzMM[3] = dx; //16Sept2014 : correct DICOM for true distance between slice centers: - // e.g. MCBI Siemens ToF 0018:0088 reports 16mm SpacingBetweenSlices, but actually 0.5mm - } else if (hdr0.dim[4] < 2) { - hdr0.dim[4] = nConvert; - hdr0.dim[0] = 4; - } else { - hdr0.dim[5] = nConvert; - hdr0.dim[0] = 5; - } - if (((dcmList[indx0].manufacturer == kMANUFACTURER_TOSHIBA) || (dcmList[indx0].manufacturer == kMANUFACTURER_CANON)) && (dcmList[indx0].CSA.numDti < 1) && (nConvert > 1) && ( hdr0.dim[4] > 1)) { - //TOSHIBA omits 0018,9087 from B=0 volumes in diffusion series. Since most diffusion series start with B=0 volume, this would cause us to detect a diffusion series - for (int i = 1; i < nConvert; i++) - if (dcmList[dcmSort[i].indx].CSA.numDti > 0) - dcmList[indx0].CSA.numDti =1; - } - /*if (nConvert > 1) { //next determine if TR is true time between volumes - double startTime = dcmList[indx0].acquisitionTime; - double endTime = startTime; - for (int i = 1; i < nConvert; i++) { - double sliceTime = dcmList[dcmSort[i].indx].acquisitionTime; - if (sliceTime < startTime) startTime = sliceTime; - if (sliceTime > endTime) endTime = sliceTime; - } - double seriesTime = (endTime - startTime); - if (endTime > 0) - printMessage("%g - %g = %g\n", endTime, startTime, seriesTime); - - }*/ - //printMessage(" %d %d %d %d %lu\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], (unsigned long)[imgM length]); - struct nifti_1_header hdrI; - //double time = -1.0; - if ((!opts.isOnlyBIDS) && (nConvert > 1)) { - //for (int i = 0; i < nConvert; i++) - // printMessage("%d\t%s\n", i, nameList->str[indx]); - //int iStart = 1; - //if (isReorder) iStart = 0; - //for (int i = 1; i < nConvert; i++) { //<- works except where ensureSequentialSlicePositions() changes 1st slice + } //not 4D + if ((hdr0.dim[4] > 0) && (dxVaries) && (dx == 0.0) && ((dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_UNKNOWN) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_GE) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_PHILIPS))) { //Niels Janssen has provided GE sequential multi-phase acquisitions that also require swizzling + swapDim3Dim4(hdr0.dim[3], hdr0.dim[4], dcmSort); + dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); + if (opts.isVerbose) + printMessage("Swizzling 3rd and 4th dimensions (XYTZ -> XYZT), assuming interslice distance is %f\n", dx); + } + if ((dx == 0.0) && (!dxVaries)) { //all images are the same slice - 16 Dec 2014 + printWarning("All images appear to be a single slice - please check slice/vector orientation\n"); + hdr0.dim[3] = 1; + hdr0.dim[4] = nConvert; + hdr0.dim[0] = 4; + } + if ((dx > 0) && (!isSameFloatGE(dx, hdr0.pixdim[3]))) + hdr0.pixdim[3] = dx; + dcmList[dcmSort[0].indx].xyzMM[3] = dx; //16Sept2014 : correct DICOM for true distance between slice centers: + // e.g. MCBI Siemens ToF 0018:0088 reports 16mm SpacingBetweenSlices, but actually 0.5mm + } else if (hdr0.dim[4] < 2) { + hdr0.dim[4] = nConvert; + hdr0.dim[0] = 4; + } else { + hdr0.dim[5] = nConvert; + hdr0.dim[0] = 5; + } + if (((dcmList[indx0].manufacturer == kMANUFACTURER_TOSHIBA) || (dcmList[indx0].manufacturer == kMANUFACTURER_CANON)) && (dcmList[indx0].CSA.numDti < 1) && (nConvert > 1) && (hdr0.dim[4] > 1)) { + //TOSHIBA omits 0018,9087 from B=0 volumes in diffusion series. Since most diffusion series start with B=0 volume, this would cause us to detect a diffusion series + for (int i = 1; i < nConvert; i++) + if (dcmList[dcmSort[i].indx].CSA.numDti > 0) + dcmList[indx0].CSA.numDti = 1; + } + //printMessage(" %d %d %d %d %lu\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], (unsigned long)[imgM length]); + struct nifti_1_header hdrI; + //double time = -1.0; + if ((!opts.isOnlyBIDS) && (nConvert > 1)) { + //for (int i = 0; i < nConvert; i++) + // printMessage("%d\t%s\n", i, nameList->str[indx]); + //int iStart = 1; + //if (isReorder) iStart = 0; + //for (int i = 1; i < nConvert; i++) { //<- works except where ensureSequentialSlicePositions() changes 1st slice for (int i = 0; i < nConvert; i++) { //stack additional images indx = dcmSort[i].indx; //double time2 = dcmList[dcmSort[i].indx].acquisitionTime; @@ -5594,209 +5729,209 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc // printWarning("%g\n", time2); //time = time2; //if (headerDcm2Nii(dcmList[indx], &hdrI) == EXIT_FAILURE) return EXIT_FAILURE; - img = nii_loadImgXL(nameList->str[indx], &hdrI, dcmList[indx],iVaries, opts.compressFlag, opts.isVerbose, dti4D); - if (img == NULL) return EXIT_FAILURE; + img = nii_loadImgXL(nameList->str[indx], &hdrI, dcmList[indx], iVaries, opts.compressFlag, opts.isVerbose, dti4D); + if (img == NULL) + return EXIT_FAILURE; if ((hdr0.dim[1] != hdrI.dim[1]) || (hdr0.dim[2] != hdrI.dim[2]) || (hdr0.bitpix != hdrI.bitpix)) { - printError("Image dimensions differ %s %s",nameList->str[dcmSort[0].indx], nameList->str[indx]); + printError("Image dimensions differ %s %s", nameList->str[dcmSort[0].indx], nameList->str[indx]); free(imgM); free(img); return EXIT_FAILURE; } - memcpy(&imgM[(uint64_t)i*imgsz], &img[0], imgsz); + memcpy(&imgM[(uint64_t)i * imgsz], &img[0], imgsz); free(img); } - } //skip if we are only creating BIDS - if (hdr0.dim[4] > 1) //for 4d datasets, last volume should be acquired before first - checkDateTimeOrder(&dcmList[dcmSort[0].indx], &dcmList[dcmSort[nConvert-1].indx]); - } + } //skip if we are only creating BIDS + if (hdr0.dim[4] > 1) //for 4d datasets, last volume should be acquired before first + checkDateTimeOrder(&dcmList[dcmSort[0].indx], &dcmList[dcmSort[nConvert - 1].indx]); + } int sliceDir = sliceTimingCore(dcmSort, dcmList, &hdr0, opts.isVerbose, nameList->str[dcmSort[0].indx], nConvert, opts); - #ifdef myReportSliceFilenames - if (sliceDir < 0) { - for (int i = nConvert; i > 0; --i) - printMessage("|%d|%s\n", i, nameList->str[dcmSort[i-1].indx]); - - } else { - for (int i = 0; i < nConvert; i++) - 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) { //make sure call is subsequent to rescueProtocolName() - free(imgM); - return EXIT_FAILURE; - } - if (strlen(pathoutname) <1) { - 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; - 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; - } +#ifdef myReportSliceFilenames + if (sliceDir < 0) { + for (int i = nConvert; i > 0; --i) + printMessage("|%d|%s\n", i, nameList->str[dcmSort[i - 1].indx]); + } else { + for (int i = 0; i < nConvert; i++) + 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) { //make sure call is subsequent to rescueProtocolName() + free(imgM); + return EXIT_FAILURE; + } + if (strlen(pathoutname) < 1) { + 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; + 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); - return EXIT_SUCCESS; - } + 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); + return EXIT_SUCCESS; + } if ((segVol >= 0) && (hdr0.dim[4] > 1)) { - int inVol = hdr0.dim[4]; - int nVol = 0; - for (int v = 0; v < inVol; v++) - if (dti4D->gradDynVol[v] == segVol) - nVol ++; - if (nVol < 1) { - printError("Series %d does not exist\n", segVol); - return EXIT_FAILURE; - } - size_t imgsz4D = imgsz; - if (nVol < 2) - hdr0.dim[0] = 3; //3D - hdr0.dim[4] = 1; - size_t imgsz3D = nii_ImgBytes(hdr0); + int inVol = hdr0.dim[4]; + int nVol = 0; + for (int v = 0; v < inVol; v++) + if (dti4D->gradDynVol[v] == segVol) + nVol++; + if (nVol < 1) { + printError("Series %d does not exist\n", segVol); + return EXIT_FAILURE; + } + size_t imgsz4D = imgsz; + if (nVol < 2) + hdr0.dim[0] = 3; //3D + hdr0.dim[4] = 1; + size_t imgsz3D = nii_ImgBytes(hdr0); unsigned char *img4D = (unsigned char *)malloc(imgsz4D); - memcpy(&img4D[0], &imgM[0], imgsz4D); - free(imgM); - imgM = (unsigned char *)malloc(imgsz3D * nVol); - int outVol = 0; - for (int v = 0; v < inVol; v++) { - if ((dti4D->gradDynVol[v] == segVol) && (outVol < nVol)) { - memcpy(&imgM[outVol * imgsz3D], &img4D[v * imgsz3D], imgsz3D); - outVol ++; - } - } - hdr0.dim[4] = nVol; + memcpy(&img4D[0], &imgM[0], imgsz4D); + free(imgM); + imgM = (unsigned char *)malloc(imgsz3D * nVol); + int outVol = 0; + for (int v = 0; v < inVol; v++) { + if ((dti4D->gradDynVol[v] == segVol) && (outVol < nVol)) { + memcpy(&imgM[outVol * imgsz3D], &img4D[v * imgsz3D], imgsz3D); + outVol++; + } + } + hdr0.dim[4] = nVol; imgsz = nii_ImgBytes(hdr0); - free(img4D); - saveAs3D = false; - } - // Prevent these DICOM files from being reused. - for(int i = 0; i < nConvert; ++i) - dcmList[dcmSort[i].indx].converted2NII = 1; - if (opts.numSeries < 0) { //report series number but do not convert - int segVolEcho = segVol; - if ((dcmList[dcmSort[0].indx].echoNum > 1) && (segVolEcho <= 0)) - segVolEcho = dcmList[dcmSort[0].indx].echoNum+1; - if (segVolEcho >= 0) { - printMessage("\t%u.%d\t%s\n", dcmList[dcmSort[0].indx].seriesUidCrc, segVolEcho-1, pathoutname); - //printMessage("\t%ld.%d\t%s\n", dcmList[dcmSort[0].indx].seriesNum, segVol-1, pathoutname); - } else { - printMessage("\t%u\t%s\n", dcmList[dcmSort[0].indx].seriesUidCrc, pathoutname); - //printMessage("\t%ld\t%s\n", dcmList[dcmSort[0].indx].seriesNum, pathoutname); - } - printMessage(" %s\n",nameList->str[dcmSort[0].indx]); - return EXIT_SUCCESS; - } - struct nifti_1_header hdrrx = hdr0; - bool isFlipZ = false; - if (sliceDir < 0) { - 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! - } + free(img4D); + saveAs3D = false; + } + // Prevent these DICOM files from being reused. + for (int i = 0; i < nConvert; ++i) + dcmList[dcmSort[i].indx].converted2NII = 1; + if (opts.numSeries < 0) { //report series number but do not convert + int segVolEcho = segVol; + if ((dcmList[dcmSort[0].indx].echoNum > 1) && (segVolEcho <= 0)) + segVolEcho = dcmList[dcmSort[0].indx].echoNum + 1; + if (segVolEcho >= 0) { + printMessage("\t%u.%d\t%s\n", dcmList[dcmSort[0].indx].seriesUidCrc, segVolEcho - 1, pathoutname); + //printMessage("\t%ld.%d\t%s\n", dcmList[dcmSort[0].indx].seriesNum, segVol-1, pathoutname); + } else { + printMessage("\t%u\t%s\n", dcmList[dcmSort[0].indx].seriesUidCrc, pathoutname); + //printMessage("\t%ld\t%s\n", dcmList[dcmSort[0].indx].seriesNum, pathoutname); + } + printMessage(" %s\n", nameList->str[dcmSort[0].indx]); + return EXIT_SUCCESS; + } + struct nifti_1_header hdrrx = hdr0; + bool isFlipZ = false; + if (sliceDir < 0) { + 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! + } nii_saveText(pathoutname, dcmList[dcmSort[0].indx], opts, &hdr0, nameList->str[indx]); int numADC = 0; - int * volOrderIndex = nii_saveDTI(pathoutname,nConvert, dcmSort, dcmList, opts, sliceDir, dti4D, &numADC, hdr0.dim[4]); - PhilipsPrecise(&dcmList[dcmSort[0].indx], opts.isPhilipsFloatNotDisplayScaling, &hdr0, opts.isVerbose); - if ((dcmList[dcmSort[0].indx].bitsStored == 12) && (dcmList[dcmSort[0].indx].bitsAllocated == 16)) - nii_mask12bit(imgM, &hdr0); - if ((opts.isMaximize16BitRange == kMaximize16BitRange_True) && (hdr0.datatype == DT_INT16)) { - nii_scale16bitSigned(imgM, &hdr0, opts.isVerbose); //allow INT16 to use full dynamic range - } else if ((opts.isMaximize16BitRange == kMaximize16BitRange_True) && (hdr0.datatype == DT_UINT16) && (!dcmList[dcmSort[0].indx].isSigned)) { - nii_scale16bitUnsigned(imgM, &hdr0, opts.isVerbose); //allow UINT16 to use full dynamic range - } else if ((opts.isMaximize16BitRange == kMaximize16BitRange_False) && (hdr0.datatype == DT_UINT16) && (!dcmList[dcmSort[0].indx].isSigned)) - nii_check16bitUnsigned(imgM, &hdr0, opts.isVerbose); //save UINT16 as INT16 if we can do this losslessly - if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert < 2)) - printWarning("Siemens XA DICOM inadequate for robust conversion (issue 236)\n"); - if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert > 1)) - printWarning("Siemens XA exported as classic not enhanced DICOM (issue 236)\n"); - printMessage( "Convert %d DICOM as %s (%dx%dx%dx%d)\n", nConvert, pathoutname, hdr0.dim[1],hdr0.dim[2],hdr0.dim[3],hdr0.dim[4]); - #ifndef USING_R - fflush(stdout); //show immediately if run from MRIcroGL GUI - #endif + int *volOrderIndex = nii_saveDTI(pathoutname, nConvert, dcmSort, dcmList, opts, sliceDir, dti4D, &numADC, hdr0.dim[4]); + PhilipsPrecise(&dcmList[dcmSort[0].indx], opts.isPhilipsFloatNotDisplayScaling, &hdr0, opts.isVerbose); + if ((dcmList[dcmSort[0].indx].bitsStored == 12) && (dcmList[dcmSort[0].indx].bitsAllocated == 16)) + nii_mask12bit(imgM, &hdr0); + if ((opts.isMaximize16BitRange == kMaximize16BitRange_True) && (hdr0.datatype == DT_INT16)) { + nii_scale16bitSigned(imgM, &hdr0, opts.isVerbose); //allow INT16 to use full dynamic range + } else if ((opts.isMaximize16BitRange == kMaximize16BitRange_True) && (hdr0.datatype == DT_UINT16) && (!dcmList[dcmSort[0].indx].isSigned)) { + nii_scale16bitUnsigned(imgM, &hdr0, opts.isVerbose); //allow UINT16 to use full dynamic range + } else if ((opts.isMaximize16BitRange == kMaximize16BitRange_False) && (hdr0.datatype == DT_UINT16) && (!dcmList[dcmSort[0].indx].isSigned)) + nii_check16bitUnsigned(imgM, &hdr0, opts.isVerbose); //save UINT16 as INT16 if we can do this losslessly + if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert < 2)) + printWarning("Siemens XA DICOM inadequate for robust conversion (issue 236)\n"); + if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert > 1)) + printWarning("Siemens XA exported as classic not enhanced DICOM (issue 236)\n"); + printMessage("Convert %d DICOM as %s (%dx%dx%dx%d)\n", nConvert, pathoutname, hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4]); +#ifndef USING_R + fflush(stdout); //show immediately if run from MRIcroGL GUI +#endif //~ if (!dcmList[dcmSort[0].indx].isSlicesSpatiallySequentialPhilips) - //~ nii_reorderSlices(imgM, &hdr0, dti4D); - //hdr0.pixdim[3] = dxNoTilt; - if (hdr0.dim[3] < 2) - printWarning("Check that 2D images are not mirrored.\n"); + //~ nii_reorderSlices(imgM, &hdr0, dti4D); + //hdr0.pixdim[3] = dxNoTilt; + if (hdr0.dim[3] < 2) + printWarning("Check that 2D images are not mirrored.\n"); #ifndef USING_R - else - fflush(stdout); //GUI buffers printf, display all results + else + fflush(stdout); //GUI buffers printf, display all results #endif //3D-EPI vs 3D SPACE/MPRAGE/ETC bool isFlipY = false; bool isSetOrtho = false; if ((opts.isRotate3DAcq) && (dcmList[dcmSort[0].indx].is3DAcq) && (!dcmList[dcmSort[0].indx].isEPI) && (hdr0.dim[3] > 1) && (hdr0.dim[0] < 4)) { - imgM = nii_setOrtho(imgM, &hdr0); - isSetOrtho = true; - } else if (opts.isFlipY){//(FLIP_Y) //(dcmList[indx0].CSA.mosaicSlices < 2) && - imgM = nii_flipY(imgM, &hdr0); - isFlipY = true; - } else - printMessage("DICOM row order preserved: may appear upside down in tools that ignore spatial transforms\n"); + imgM = nii_setOrtho(imgM, &hdr0); + isSetOrtho = true; + } else if (opts.isFlipY) { //(FLIP_Y) //(dcmList[indx0].CSA.mosaicSlices < 2) && + imgM = nii_flipY(imgM, &hdr0); + isFlipY = true; + } else + printMessage("DICOM row order preserved: may appear upside down in tools that ignore spatial transforms\n"); //begin: gantry tilt we need to save the shear in the transform mat44 sForm; LOAD_MAT44(sForm, - hdr0.srow_x[0],hdr0.srow_x[1],hdr0.srow_x[2],hdr0.srow_x[3], - hdr0.srow_y[0],hdr0.srow_y[1],hdr0.srow_y[2],hdr0.srow_y[3], - hdr0.srow_z[0],hdr0.srow_z[1],hdr0.srow_z[2],hdr0.srow_z[3]); + hdr0.srow_x[0], hdr0.srow_x[1], hdr0.srow_x[2], hdr0.srow_x[3], + hdr0.srow_y[0], hdr0.srow_y[1], hdr0.srow_y[2], hdr0.srow_y[3], + hdr0.srow_z[0], hdr0.srow_z[1], hdr0.srow_z[2], hdr0.srow_z[3]); if (!isSameFloatGE(dcmList[indx0].gantryTilt, 0.0)) { - float thetaRad = dcmList[indx0].gantryTilt * M_PI / 180.0; - float c = cos(thetaRad); - if (!isSameFloatGE(c, 0.0)) { - mat33 shearMat; - LOAD_MAT33(shearMat, 1.0, 0.0, 0.0, - 0.0, 1.0, sin(thetaRad)/c, - 0.0, 0.0, 1.0); - mat33 s; - LOAD_MAT33(s,hdr0.srow_x[0],hdr0.srow_x[1],hdr0.srow_x[2], - hdr0.srow_y[0],hdr0.srow_y[1],hdr0.srow_y[2], - hdr0.srow_z[0],hdr0.srow_z[1],hdr0.srow_z[2]); + float thetaRad = dcmList[indx0].gantryTilt * M_PI / 180.0; + float c = cos(thetaRad); + if (!isSameFloatGE(c, 0.0)) { + mat33 shearMat; + LOAD_MAT33(shearMat, 1.0, 0.0, 0.0, + 0.0, 1.0, sin(thetaRad) / c, + 0.0, 0.0, 1.0); + mat33 s; + LOAD_MAT33(s, hdr0.srow_x[0], hdr0.srow_x[1], hdr0.srow_x[2], + hdr0.srow_y[0], hdr0.srow_y[1], hdr0.srow_y[2], + hdr0.srow_z[0], hdr0.srow_z[1], hdr0.srow_z[2]); s = nifti_mat33_mul(shearMat, s); mat44 shearForm; - LOAD_MAT44(shearForm, s.m[0][0],s.m[0][1],s.m[0][2],hdr0.srow_x[3], - s.m[1][0],s.m[1][1],s.m[1][2],hdr0.srow_y[3], - s.m[2][0],s.m[2][1],s.m[2][2],hdr0.srow_z[3]); - setQSForm(&hdr0,shearForm, true); - } //avoid div/0: cosine not zero - } //if gantry tilt - //end: gantry tilt we need to save the shear in the transform - int returnCode = EXIT_FAILURE; + LOAD_MAT44(shearForm, s.m[0][0], s.m[0][1], s.m[0][2], hdr0.srow_x[3], + s.m[1][0], s.m[1][1], s.m[1][2], hdr0.srow_y[3], + s.m[2][0], s.m[2][1], s.m[2][2], hdr0.srow_z[3]); + setQSForm(&hdr0, shearForm, true); + } //avoid div/0: cosine not zero + } //if gantry tilt + //end: gantry tilt we need to save the shear in the transform + int returnCode = EXIT_FAILURE; #ifndef myNoSave - // Indicates success or failure of the (last) save - if (opts.isSaveNRRD) - removeSclSlopeInter(&hdr0, imgM); - //printMessage(" x--> %d ----\n", nConvert); - if (! opts.isRGBplanar) //save RGB as packed RGBRGBRGB... instead of planar RRR..RGGG..GBBB..B - imgM = nii_planar2rgb(imgM, &hdr0, true); //NIfTI is packed while Analyze was planar - if ((hdr0.dim[4] > 1) && (saveAs3D)) - returnCode = nii_saveNII3D(pathoutname, hdr0, imgM,opts, dcmList[dcmSort[0].indx]); - else { - if (volOrderIndex) //reorder volumes - imgM = reorderVolumes(&hdr0, imgM, volOrderIndex); + // Indicates success or failure of the (last) save + if (opts.isSaveNRRD) + removeSclSlopeInter(&hdr0, imgM); + //printMessage(" x--> %d ----\n", nConvert); + if (!opts.isRGBplanar) //save RGB as packed RGBRGBRGB... instead of planar RRR..RGGG..GBBB..B + imgM = nii_planar2rgb(imgM, &hdr0, true); //NIfTI is packed while Analyze was planar + if ((hdr0.dim[4] > 1) && (saveAs3D)) + returnCode = nii_saveNII3D(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); + else { + if (volOrderIndex) //reorder volumes + imgM = reorderVolumes(&hdr0, imgM, volOrderIndex); #ifndef USING_R if ((opts.isIgnoreDerivedAnd2D) && (numADC > 0)) printMessage("Ignoring derived diffusion image(s). Better isotropic and ADC maps can be generated later processing.\n"); - if ((!opts.isIgnoreDerivedAnd2D) && (numADC > 0)) {//ADC maps can disrupt analysis: save a copy with the ADC map, and another without + if ((!opts.isIgnoreDerivedAnd2D) && (numADC > 0)) { //ADC maps can disrupt analysis: save a copy with the ADC map, and another without char pathoutnameADC[2048] = {""}; - strcat(pathoutnameADC,pathoutname); - strcat(pathoutnameADC,"_ADC"); + strcat(pathoutnameADC, pathoutname); + strcat(pathoutnameADC, "_ADC"); if (opts.isSave3D) nii_saveNII3D(pathoutnameADC, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); else @@ -5804,20 +5939,25 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc } if (isHasOverlay) { //each series can have up to 16 overlays, overlays may not be on all slices for (int j = 0; j < kMaxOverlay; j++) { - bool isOverlay = false; + bool isOverlay = false; for (int i = 0; i < nConvert; i++) - if (dcmList[dcmSort[i].indx].overlayStart[j] > 0) isOverlay = true; - if (!isOverlay) continue; + if (dcmList[dcmSort[i].indx].overlayStart[j] > 0) + isOverlay = true; + if (!isOverlay) + continue; char pathoutnameROI[2048] = {""}; - strcat(pathoutnameROI,pathoutname); + strcat(pathoutnameROI, pathoutname); char append[128] = {""}; - sprintf(append,"_ROI%d",j+1); - strcat(pathoutnameROI,append); + sprintf(append, "_ROI%d", j + 1); + strcat(pathoutnameROI, append); struct nifti_1_header hdrr = hdrrx; hdrr.dim[0] = 3; - if (hdrr.dim[1] < 1) hdrr.dim[1] = 1; - if (hdrr.dim[2] < 1) hdrr.dim[2] = 1; - if (hdrr.dim[3] < 1) hdrr.dim[3] = 1; + if (hdrr.dim[1] < 1) + hdrr.dim[1] = 1; + if (hdrr.dim[2] < 1) + hdrr.dim[2] = 1; + if (hdrr.dim[3] < 1) + hdrr.dim[3] = 1; hdrr.dim[4] = 1; hdrr.bitpix = 8; hdrr.datatype = 2; @@ -5839,14 +5979,14 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc } //if overlay on slice } //for each volume } // - if (isFlipZ) - imgR = nii_flipZ(imgR, &hdrr); - if (isSetOrtho) - imgR = nii_setOrtho(imgR, &hdrr); + if (isFlipZ) + imgR = nii_flipZ(imgR, &hdrr); + if (isSetOrtho) + imgR = nii_setOrtho(imgR, &hdrr); if (isFlipY) imgR = nii_flipY(imgR, &hdrr); - nii_saveNII(pathoutnameROI, hdrr, imgR, opts, dcmList[dcmSort[0].indx]); - } + nii_saveNII(pathoutnameROI, hdrr, imgR, opts, dcmList[dcmSort[0].indx]); + } } #endif imgM = removeADC(&hdr0, imgM, numADC); @@ -5858,46 +5998,46 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc else if (opts.isSave3D) returnCode = nii_saveNII3D(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); else - returnCode = nii_saveNII(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); + returnCode = nii_saveNII(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); #endif - } + } #endif - if (dcmList[indx0].gantryTilt != 0.0) { - setQSForm(&hdr0,sForm, true); - //if (dcmList[indx0].isResampled) { //we no detect based on image orientation https://github.com/rordenlab/dcm2niix/issues/253 - // printMessage("Tilt correction skipped: 0008,2111 reports RESAMPLED\n"); - //} else - if (opts.isTiltCorrect) { - imgM = nii_saveNII3Dtilt(pathoutname, &hdr0, imgM,opts, dcmList[dcmSort[0].indx], sliceMMarray, dcmList[indx0].gantryTilt, dcmList[indx0].manufacturer); - strcat(pathoutname,"_Tilt"); - } else - printMessage("Tilt correction skipped\n"); - } - if (sliceMMarray != NULL) { - if (dcmList[indx0].isResampled) { - printMessage("Slice thickness correction skipped: 0008,2111 reports RESAMPLED\n"); - } - else - returnCode = nii_saveNII3Deq(pathoutname, hdr0, imgM,opts, dcmList[dcmSort[0].indx], sliceMMarray); - free(sliceMMarray); - } - //3D-EPI vs 3D SPACE/MPRAGE/ETC - if ((opts.isRotate3DAcq) && (opts.isCrop) && (dcmList[indx0].is3DAcq) && (!dcmList[indx0].isEPI) && (hdr0.dim[3] > 1) && (hdr0.dim[0] < 4))//for T1 scan: && (dcmList[indx0].TE < 25) - returnCode = nii_saveCrop(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); //n.b. must be run AFTER nii_setOrtho()! + if (dcmList[indx0].gantryTilt != 0.0) { + setQSForm(&hdr0, sForm, true); + //if (dcmList[indx0].isResampled) { //we no detect based on image orientation https://github.com/rordenlab/dcm2niix/issues/253 + // printMessage("Tilt correction skipped: 0008,2111 reports RESAMPLED\n"); + //} else + if (opts.isTiltCorrect) { + imgM = nii_saveNII3Dtilt(pathoutname, &hdr0, imgM, opts, dcmList[dcmSort[0].indx], sliceMMarray, dcmList[indx0].gantryTilt, dcmList[indx0].manufacturer); + strcat(pathoutname, "_Tilt"); + } else + printMessage("Tilt correction skipped\n"); + } + if (sliceMMarray != NULL) { + if (dcmList[indx0].isResampled) { + printMessage("Slice thickness correction skipped: 0008,2111 reports RESAMPLED\n"); + } else + returnCode = nii_saveNII3Deq(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx], sliceMMarray); + free(sliceMMarray); + } + //3D-EPI vs 3D SPACE/MPRAGE/ETC + if ((opts.isRotate3DAcq) && (opts.isCrop) && (dcmList[indx0].is3DAcq) && (!dcmList[indx0].isEPI) && (hdr0.dim[3] > 1) && (hdr0.dim[0] < 4)) //for T1 scan: && (dcmList[indx0].TE < 25) + returnCode = nii_saveCrop(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); //n.b. must be run AFTER nii_setOrtho()! #ifdef USING_R - // Note that for R, only one image should be created per series - // Hence this extra test + // Note that for R, only one image should be created per series + // Hence this extra test if (returnCode != EXIT_SUCCESS) - returnCode = nii_saveNII(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); - if (returnCode == EXIT_SUCCESS) - nii_saveAttributes(dcmList[dcmSort[0].indx], hdr0, opts, nameList->str[dcmSort[0].indx]); + returnCode = nii_saveNII(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); + if (returnCode == EXIT_SUCCESS) + nii_saveAttributes(dcmList[dcmSort[0].indx], hdr0, opts, nameList->str[dcmSort[0].indx]); #endif - free(imgM); - if (dcmList[dcmSort[0].indx].xyzDim[0] > 1) returnCode = kEXIT_INCOMPLETE_VOLUMES_FOUND; //issue515 - return returnCode;//EXIT_SUCCESS; -}// saveDcm2NiiCore() + free(imgM); + if (dcmList[dcmSort[0].indx].xyzDim[0] > 1) + returnCode = kEXIT_INCOMPLETE_VOLUMES_FOUND; //issue515 + return returnCode; //EXIT_SUCCESS; +} // saveDcm2NiiCore() -int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts, struct TDTI4D *dti4D) { +int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts, struct TDTI4D *dti4D) { //this wrapper does nothing if all the images share the same echo time and scale // however, it segments images when these properties vary uint64_t indx = dcmSort[0].indx; @@ -5907,7 +6047,7 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis printError("Unexpected error for image with varying echo time or intensity scaling\n"); return EXIT_FAILURE; } - int ret = EXIT_SUCCESS; + int ret = EXIT_SUCCESS; //check for repeated echoes - count unique number of echoes //code below checks for multi-echoes - not required if maxNumberOfEchoes reported in PARREC int echoNum[kMaxDTI4D]; @@ -5917,13 +6057,15 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis echoNum[0] = 1; for (int i = 1; i < dcmList[indx].xyzDim[4]; i++) { for (int j = 0; j < i; j++) - if (dti4D->TE[i] == dti4D->TE[j]) echoNum[i] = echoNum[j]; + if (dti4D->TE[i] == dti4D->TE[j]) + echoNum[i] = echoNum[j]; if (echoNum[i] == 0) { echo++; echoNum[i] = echo; } } - if (echo > 1) dcmList[indx].isMultiEcho = true; + if (echo > 1) + dcmList[indx].isMultiEcho = true; //check for repeated volumes int series = 1; for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) @@ -5969,8 +6111,8 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis 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; + if (dti4D->gradDynVol[i] == s) { + //dti4D->gradDynVol[i] = s; //nVol ++; dcmList[indx].TE = dti4D->TE[i]; //dcmList[indx].intenScale = dti4D->intenScale[i]; @@ -5988,7 +6130,7 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis } } dcmList[indx].isScaleVariesEnh = false; - if (isScaleVariesEnh) { //check if intensity scale varies for this particular output image, this will force 32-bit output + if (isScaleVariesEnh) { //check if intensity scale varies for this particular output image, this will force 32-bit output int nz = 0; for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) { //for each volume if (dti4D->gradDynVol[i] == s) { @@ -5998,15 +6140,18 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis dti4Ds.intenIntercept[nz] = dti4D->intenIntercept[ix]; dti4Ds.intenScalePhilips[nz] = dti4D->intenScalePhilips[ix]; dti4Ds.RWVIntercept[nz] = dti4D->RWVIntercept[ix]; - dti4Ds.RWVScale[nz] = dti4D->RWVScale[ix]; - nz ++; - } //for z: each slice + dti4Ds.RWVScale[nz] = dti4D->RWVScale[ix]; + nz++; + } //for z: each slice } //if series matches } //for each volume for (int i = 0; i < nz; i++) { - if (dti4Ds.intenIntercept[i] != dti4Ds.intenIntercept[0]) dcmList[indx].isScaleVariesEnh = true; - if (dti4Ds.intenScale[i] != dti4Ds.intenScale[0]) dcmList[indx].isScaleVariesEnh = true; - if (dti4Ds.intenScalePhilips[i] != dti4Ds.intenScalePhilips[0]) dcmList[indx].isScaleVariesEnh = true; + if (dti4Ds.intenIntercept[i] != dti4Ds.intenIntercept[0]) + dcmList[indx].isScaleVariesEnh = true; + if (dti4Ds.intenScale[i] != dti4Ds.intenScale[0]) + dcmList[indx].isScaleVariesEnh = true; + if (dti4Ds.intenScalePhilips[i] != dti4Ds.intenScalePhilips[0]) + dcmList[indx].isScaleVariesEnh = true; } dcmList[indx].intenScale = dti4Ds.intenScale[0]; dcmList[indx].intenIntercept = dti4Ds.intenIntercept[0]; @@ -6014,37 +6159,40 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis dcmList[indx].RWVIntercept = dti4Ds.RWVIntercept[0]; dcmList[indx].RWVScale = dti4Ds.RWVScale[0]; } - if (s > 1) dcmList[indx].CSA.numDti = 0; //only save bvec for first type (magnitude) + 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 - } - return ret; -}// saveDcm2Nii() - -void fillTDCMsort(struct TDCMsort& tdcmref, const uint64_t indx, const struct TDICOMdata& dcmdata){ - // Copy the relevant parts of dcmdata to tdcmref. - tdcmref.indx = indx; - //printf("series/image %d %d\n", dcmdata.seriesNum, dcmdata.imageNum); - tdcmref.img = ((uint64_t)dcmdata.seriesNum << 32) + dcmdata.imageNum; - for(int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; ++i) - tdcmref.dimensionIndexValues[i] = dcmdata.dimensionIndexValues[i]; - //lines below added to cope with extreme anonymization - // https://github.com/rordenlab/dcm2niix/issues/211 - if (tdcmref.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS-1] != 0) return; - //Since dimensionIndexValues are indexed from 1, 0 indicates unused - // we leverage this as a hail mary attempt to distinguish images with identical series and instance numbers - //See Correction Number CP-1242: - // "Clarify in the description of dimension indices ... start from 1" - // 0008,0032 stored as HHMMSS.FFFFFF, there are 86400000 ms per day - // dimensionIndexValues stored as uint32, so encode acquisition time in ms - uint32_t h = trunc(dcmdata.acquisitionTime / 10000.0); - double tm = dcmdata.acquisitionTime - (h * 10000.0); - uint32_t m = trunc(tm / 100.0); - tm = tm - (m * 100.0); - uint32_t ms = round(tm * 1000); - ms += (h * 3600000) + (m * 60000); - //printf("HHMMSS.FFFF %.5f -> %d ms\n", dcmdata.acquisitionTime, ms); - tdcmref.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS-1] = ms; + if (ret2 != EXIT_SUCCESS) + ret = ret2; //return EXIT_SUCCESS only if ALL are successful + } + return ret; +} // saveDcm2Nii() + +void fillTDCMsort(struct TDCMsort &tdcmref, const uint64_t indx, const struct TDICOMdata &dcmdata) { + // Copy the relevant parts of dcmdata to tdcmref. + tdcmref.indx = indx; + //printf("series/image %d %d\n", dcmdata.seriesNum, dcmdata.imageNum); + tdcmref.img = ((uint64_t)dcmdata.seriesNum << 32) + dcmdata.imageNum; + for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; ++i) + tdcmref.dimensionIndexValues[i] = dcmdata.dimensionIndexValues[i]; + //lines below added to cope with extreme anonymization + // https://github.com/rordenlab/dcm2niix/issues/211 + if (tdcmref.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 1] != 0) + return; + //Since dimensionIndexValues are indexed from 1, 0 indicates unused + // we leverage this as a hail mary attempt to distinguish images with identical series and instance numbers + //See Correction Number CP-1242: + // "Clarify in the description of dimension indices ... start from 1" + // 0008,0032 stored as HHMMSS.FFFFFF, there are 86400000 ms per day + // dimensionIndexValues stored as uint32, so encode acquisition time in ms + uint32_t h = trunc(dcmdata.acquisitionTime / 10000.0); + double tm = dcmdata.acquisitionTime - (h * 10000.0); + uint32_t m = trunc(tm / 100.0); + tm = tm - (m * 100.0); + uint32_t ms = round(tm * 1000); + ms += (h * 3600000) + (m * 60000); + //printf("HHMMSS.FFFF %.5f -> %d ms\n", dcmdata.acquisitionTime, ms); + tdcmref.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 1] = ms; } // fillTDCMsort() int compareTDCMsort(void const *item1, void const *item2) { @@ -6052,78 +6200,40 @@ int compareTDCMsort(void const *item1, void const *item2) { struct TDCMsort const *dcm1 = (const struct TDCMsort *)item1; struct TDCMsort const *dcm2 = (const struct TDCMsort *)item2; //to do: detect duplicates with SOPInstanceUID (0008,0018) - accurate but slow text comparison - int retval = 0; // tie + int retval = 0; // tie if (dcm1->img < dcm2->img) retval = -1; else if (dcm1->img > dcm2->img) retval = 1; - //printf("%d %d\n", dcm1->img, dcm2->img); + //printf("%d %d\n", dcm1->img, dcm2->img); //for(int i=0; i < MAX_NUMBER_OF_DIMENSIONS; i++) - // printf("%d %d\n", dcm1->dimensionIndexValues[i], dcm2->dimensionIndexValues[i]); - if(retval != 0) return retval; //sorted images + // printf("%d %d\n", dcm1->dimensionIndexValues[i], dcm2->dimensionIndexValues[i]); + if (retval != 0) + return retval; //sorted images // Check the dimensionIndexValues (useful for enhanced DICOM 4D series). // ->img is basically behaving as a (seriesNum, imageNum) sort key - // concatenated into a (large) integer for qsort. That is unwieldy when + // concatenated into a (large) integer for qsort. That is unwieldy when // dimensionIndexValues need to be compared, because the existence of - // uint128_t, uint256_t, etc. is not guaranteed. This sorts by + // uint128_t, uint256_t, etc. is not guaranteed. This sorts by // (seriesNum, ImageNum, div[0], div[1], ...), or if you think of it as a // number, the dimensionIndexValues come after the decimal point. - for(int i=0; i < MAX_NUMBER_OF_DIMENSIONS; ++i){ - if(dcm1->dimensionIndexValues[i] < dcm2->dimensionIndexValues[i]) - return -1; - else if(dcm1->dimensionIndexValues[i] > dcm2->dimensionIndexValues[i]) - return 1; + for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; ++i) { + if (dcm1->dimensionIndexValues[i] < dcm2->dimensionIndexValues[i]) + return -1; + else if (dcm1->dimensionIndexValues[i] > dcm2->dimensionIndexValues[i]) + return 1; } return retval; } //compareTDCMsort() -/*int compareTDCMsort(void const *item1, void const *item2) { - //for quicksort http://blog.ablepear.com/2011/11/objective-c-tuesdays-sorting-arrays.html - struct TDCMsort const *dcm1 = (const struct TDCMsort *)item1; - struct TDCMsort const *dcm2 = (const struct TDCMsort *)item2; - - int retval = 0; // tie - - if (dcm1->img < dcm2->img) - retval = -1; - else if (dcm1->img > dcm2->img) - retval = 1; - - if(retval == 0){ - // Check the dimensionIndexValues (useful for enhanced DICOM 4D series). - // ->img is basically behaving as a (seriesNum, imageNum) sort key - // concatenated into a (large) integer for qsort. That is unwieldy when - // dimensionIndexValues need to be compared, because the existence of - // uint128_t, uint256_t, etc. is not guaranteed. This sorts by - // (seriesNum, ImageNum, div[0], div[1], ...), or if you think of it as a - // number, the dimensionIndexValues come after the decimal point. - for(int i=0; i < MAX_NUMBER_OF_DIMENSIONS; ++i){ - if(dcm1->dimensionIndexValues[i] < dcm2->dimensionIndexValues[i]){ - retval = -1; - break; - } - else if(dcm1->dimensionIndexValues[i] > dcm2->dimensionIndexValues[i]){ - retval = 1; - break; - } - } - } - return retval; -} //compareTDCMsort()*/ - -/*int isSameFloatGE (float a, float b) { -//Kludge for bug in 0002,0016="DIGITAL_JACKET", 0008,0070="GE MEDICAL SYSTEMS" DICOM data: Orient field (0020:0037) can vary 0.00604261 == 0.00604273 !!! - //return (a == b); //niave approach does not have any tolerance for rounding errors - return (fabs (a - b) <= 0.0001); -}*/ - -int isSameFloatDouble (double a, double b) { - //Kludge for bug in 0002,0016="DIGITAL_JACKET", 0008,0070="GE MEDICAL SYSTEMS" DICOM data: Orient field (0020:0037) can vary 0.00604261 == 0.00604273 !!! - // return (a == b); //niave approach does not have any tolerance for rounding errors - return (fabs (a - b) <= 0.0001); + +int isSameFloatDouble(double a, double b) { + //Kludge for bug in 0002,0016="DIGITAL_JACKET", 0008,0070="GE MEDICAL SYSTEMS" DICOM data: Orient field (0020:0037) can vary 0.00604261 == 0.00604273 !!! + // return (a == b); //niave approach does not have any tolerance for rounding errors + return (fabs(a - b) <= 0.0001); } struct TWarnings { //generate a warning only once per set - bool manufacturerVaries, modalityVaries, derivedVaries, acqNumVaries, dimensionVaries, dateTimeVaries, studyUidVaries, 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() { @@ -6147,42 +6257,47 @@ TWarnings setWarnings() { return r; } -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; +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 ((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; + 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; + 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 - 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; + printMessage("Volumes not stacked: derived varies.\n"); + warnings->derivedVaries = true; + } + } + if (d1.manufacturer != d2.manufacturer) + return false; //do not stack data from different vendors + 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("Volumes stacked despite varying series number (use '-m o' to turn off merging).\n"); - warnings->forceStackSeries = true; - isForceStackSeries = true; + printMessage("Volumes stacked despite varying series number (use '-m o' to turn off merging).\n"); + warnings->forceStackSeries = true; + isForceStackSeries = true; } - if ((d1.manufacturer == kMANUFACTURER_SIEMENS) && (strcmp(d1.protocolName, d2.protocolName) == 0) && (strlen(d1.softwareVersions) > 4) && (strlen(d1.sequenceName) > 4) && (strlen(d2.sequenceName) > 4)) { - if (strstr(d1.sequenceName, "_ep_b") && strstr(d2.sequenceName, "_ep_b") && (strstr(d1.softwareVersions, "VB13") || strstr(d1.softwareVersions, "VB12")) ) { + if ((d1.manufacturer == kMANUFACTURER_SIEMENS) && (strcmp(d1.protocolName, d2.protocolName) == 0) && (strlen(d1.softwareVersions) > 4) && (strlen(d1.sequenceName) > 4) && (strlen(d2.sequenceName) > 4)) { + if (strstr(d1.sequenceName, "_ep_b") && strstr(d2.sequenceName, "_ep_b") && (strstr(d1.softwareVersions, "VB13") || strstr(d1.softwareVersions, "VB12"))) { //Siemens B12/B13 users with a "DWI" but not "DTI" license would ofter create multi-series acquisitions if (!warnings->forceStackSeries) - printMessage("Diffusion images stacked despite varying series number (early Siemens DTI).\n"); - warnings->forceStackSeries = true; - isForceStackSeries = true; + printMessage("Diffusion images stacked despite varying series number (early Siemens DTI).\n"); + warnings->forceStackSeries = true; + isForceStackSeries = true; } } if (isForceStackSeries) @@ -6190,447 +6305,453 @@ bool isSameSet (struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts* opt else if ((d1.isXA10A) && (d2.isXA10A) && (d1.seriesNum > 1000) && (d2.seriesNum > 1000)) { //kludge XA10A (0020,0011) increments [16001, 16002, ...] https://github.com/rordenlab/dcm2niix/issues/236 //images from series 16001,16002 report different study times (0008,0030)! - if ((d1.seriesNum / 1000) != (d2.seriesNum / 1000)) return false; - } else if (d1.seriesNum != d2.seriesNum) return false; - #ifdef mySegmentByAcq - if (d1.acquNum != d2.acquNum) return false; - #endif - bool isSameStudyInstanceUID = false; - if ((strlen(d1.studyInstanceUID)> 1) && (strlen(d2.studyInstanceUID)> 1)) { - if (strcmp(d1.studyInstanceUID, d2.studyInstanceUID) == 0) + if ((d1.seriesNum / 1000) != (d2.seriesNum / 1000)) + return false; + } else if (d1.seriesNum != d2.seriesNum) + return false; +#ifdef mySegmentByAcq + if (d1.acquNum != d2.acquNum) + return false; +#endif + bool isSameStudyInstanceUID = false; + if ((strlen(d1.studyInstanceUID) > 1) && (strlen(d2.studyInstanceUID) > 1)) { + if (strcmp(d1.studyInstanceUID, d2.studyInstanceUID) == 0) isSameStudyInstanceUID = true; - } - bool isSameTime = isSameFloatDouble(d1.dateTime, d2.dateTime); - if ((isSameStudyInstanceUID) && (d1.isXA10A) && (d2.isXA10A)) + } + 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 - bool isDimensionVaries = ( (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; + 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; + 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; - return false; - } - #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); - warnings->dateTimeVaries = true; - return false; - } - #endif - if ((opts->isForceStackSameSeries == 1) || ((opts->isForceStackSameSeries == 2) && (d1.isXRay) )) { - // "isForceStackSameSeries == 2" will automatically stack CT scans but not MR - //if (((!(isSameFloat(d1.TE, d2.TE))) || (d1.echoNum != d2.echoNum)) && (!d1.isXRay)) { - // *isMultiEcho = true; - //} - 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 (!warnings->phaseVaries) - 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; - } - //if ((d1.TE != d2.TE) || (d1.echoNum != d2.echoNum)) { - if ((!(isSameFloat(d1.TE, d2.TE)) ) || (d1.echoNum != d2.echoNum)) { - if ((!warnings->echoVaries) && (d1.isXRay)) //for CT/XRay we check DICOM tag 0018,1152 (XRayExposure) - printMessage("Slices not stacked: X-Ray Exposure varies (exposure %g, %g; number %d, %d). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE,d1.echoNum, d2.echoNum ); - if ((!warnings->echoVaries) && (!d1.isXRay)) //for MRI - printMessage("Slices not stacked: echo varies (TE %g, %g; echo %d, %d). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE,d1.echoNum, d2.echoNum ); - warnings->echoVaries = true; - *isMultiEcho = true; - return false; - } - if ((d1.triggerDelayTime != d2.triggerDelayTime) && (d1.manufacturer == kMANUFACTURER_PHILIPS)) { //issue 384 - if (!warnings->triggerVaries) - printMessage("Slices not stacked: trigger time varies\n"); - warnings->triggerVaries = true; - return false; - } - if (d1.coilCrc != d2.coilCrc) { + if (isDimensionVaries) { + if (!warnings->dimensionVaries) + printMessage("Slices not stacked: dimensions vary across slices\n"); + warnings->dimensionVaries = true; + return false; + } +#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); + warnings->dateTimeVaries = true; + return false; + } +#endif + if ((opts->isForceStackSameSeries == 1) || ((opts->isForceStackSameSeries == 2) && (d1.isXRay))) { + // "isForceStackSameSeries == 2" will automatically stack CT scans but not MR + //if (((!(isSameFloat(d1.TE, d2.TE))) || (d1.echoNum != d2.echoNum)) && (!d1.isXRay)) { + // *isMultiEcho = true; + //} + 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 (!warnings->phaseVaries) + 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; + } + //if ((d1.TE != d2.TE) || (d1.echoNum != d2.echoNum)) { + if ((!(isSameFloat(d1.TE, d2.TE))) || (d1.echoNum != d2.echoNum)) { + if ((!warnings->echoVaries) && (d1.isXRay)) //for CT/XRay we check DICOM tag 0018,1152 (XRayExposure) + printMessage("Slices not stacked: X-Ray Exposure varies (exposure %g, %g; number %d, %d). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE, d1.echoNum, d2.echoNum); + if ((!warnings->echoVaries) && (!d1.isXRay)) //for MRI + printMessage("Slices not stacked: echo varies (TE %g, %g; echo %d, %d). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE, d1.echoNum, d2.echoNum); + warnings->echoVaries = true; + *isMultiEcho = true; + return false; + } + if ((d1.triggerDelayTime != d2.triggerDelayTime) && (d1.manufacturer == kMANUFACTURER_PHILIPS)) { //issue 384 + if (!warnings->triggerVaries) + printMessage("Slices not stacked: trigger time varies\n"); + warnings->triggerVaries = true; + return false; + } + if (d1.coilCrc != d2.coilCrc) { if (opts->isForceStackDCE) { - if (!warnings->coilVaries) - printMessage("Slices stacked despite coil variation '%s' vs '%s' (use '-m o' to turn off merging)\n", d1.coilName, d2.coilName); - warnings->coilVaries = true; - *isCoilVaries = true; + if (!warnings->coilVaries) + printMessage("Slices stacked despite coil variation '%s' vs '%s' (use '-m o' to turn off merging)\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 (!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) - printWarning("Empty protocol name(s) (0018,1030)\n"); - warnings->nameEmpty = true; - } else if ((strcmp(d1.protocolName, d2.protocolName) != 0)) { - if (!warnings->nameVaries) - printMessage("Slices not stacked: protocol name varies '%s' != '%s'\n", d1.protocolName, d2.protocolName); - warnings->nameVaries = true; - return false; - } - if (( *isNonParallelSlices) && (d1.CSA.mosaicSlices > 1 )) return false; //issue481 - if ((!isSameFloatGE(d1.orient[1], d2.orient[1]) || !isSameFloatGE(d1.orient[2], d2.orient[2]) || !isSameFloatGE(d1.orient[3], d2.orient[3]) || - !isSameFloatGE(d1.orient[4], d2.orient[4]) || !isSameFloatGE(d1.orient[5], d2.orient[5]) || !isSameFloatGE(d1.orient[6], d2.orient[6]) ) ) { - if ((!warnings->orientVaries) && (!d1.isNonParallelSlices) && (!d1.isLocalizer)) - printMessage("Slices not stacked: orientation varies (vNav or localizer?) [%g %g %g %g %g %g] != [%g %g %g %g %g %g]\n", - d1.orient[1], d1.orient[2], d1.orient[3],d1.orient[4], d1.orient[5], d1.orient[6], - d2.orient[1], d2.orient[2], d2.orient[3],d2.orient[4], d2.orient[5], d2.orient[6]); - warnings->orientVaries = true; - *isNonParallelSlices = true; - return false; - } - if (d1.acquNum != d2.acquNum) { - if ((!warnings->acqNumVaries) && (opts->isVerbose)) //virtually always people want to stack these - printMessage("Slices stacked despite varying acquisition numbers (if this is not desired recompile with 'mySegmentByAcq')\n"); - warnings->acqNumVaries = true; - } - if ((!isForceStackSeries) && (d1.seriesUidCrc != d2.seriesUidCrc)) { - if (!warnings->seriesUidVaries) - printMessage("Slices not stacked: series instance UID varies (duplicates all other properties)\n"); - warnings->seriesUidVaries = true; - return false; - } - return true; -}// isSameSet() + } + if ((strlen(d1.protocolName) < 1) && (strlen(d2.protocolName) < 1)) { + if (!warnings->nameEmpty) + printWarning("Empty protocol name(s) (0018,1030)\n"); + warnings->nameEmpty = true; + } else if ((strcmp(d1.protocolName, d2.protocolName) != 0)) { + if (!warnings->nameVaries) + printMessage("Slices not stacked: protocol name varies '%s' != '%s'\n", d1.protocolName, d2.protocolName); + warnings->nameVaries = true; + return false; + } + if ((*isNonParallelSlices) && (d1.CSA.mosaicSlices > 1)) + return false; //issue481 + if ((!isSameFloatGE(d1.orient[1], d2.orient[1]) || !isSameFloatGE(d1.orient[2], d2.orient[2]) || !isSameFloatGE(d1.orient[3], d2.orient[3]) || + !isSameFloatGE(d1.orient[4], d2.orient[4]) || !isSameFloatGE(d1.orient[5], d2.orient[5]) || !isSameFloatGE(d1.orient[6], d2.orient[6]))) { + if ((!warnings->orientVaries) && (!d1.isNonParallelSlices) && (!d1.isLocalizer)) + printMessage("Slices not stacked: orientation varies (vNav or localizer?) [%g %g %g %g %g %g] != [%g %g %g %g %g %g]\n", + d1.orient[1], d1.orient[2], d1.orient[3], d1.orient[4], d1.orient[5], d1.orient[6], + d2.orient[1], d2.orient[2], d2.orient[3], d2.orient[4], d2.orient[5], d2.orient[6]); + warnings->orientVaries = true; + *isNonParallelSlices = true; + return false; + } + if (d1.acquNum != d2.acquNum) { + if ((!warnings->acqNumVaries) && (opts->isVerbose)) //virtually always people want to stack these + printMessage("Slices stacked despite varying acquisition numbers (if this is not desired recompile with 'mySegmentByAcq')\n"); + warnings->acqNumVaries = true; + } + if ((!isForceStackSeries) && (d1.seriesUidCrc != d2.seriesUidCrc)) { + if (!warnings->seriesUidVaries) + printMessage("Slices not stacked: series instance UID varies (duplicates all other properties)\n"); + warnings->seriesUidVaries = true; + return false; + } + return true; +} // isSameSet() void freeNameList(struct TSearchList nameList) { - if (nameList.numItems > 0) { - unsigned long n = nameList.numItems; - if (n > nameList.maxItems) n = nameList.maxItems; //assigned if (nameList->numItems < nameList->maxItems) - for (unsigned long i = 0; i < n; i++) - free(nameList.str[i]); - } - free(nameList.str); + if (nameList.numItems > 0) { + unsigned long n = nameList.numItems; + if (n > nameList.maxItems) + n = nameList.maxItems; //assigned if (nameList->numItems < nameList->maxItems) + for (unsigned long i = 0; i < n; i++) + free(nameList.str[i]); + } + free(nameList.str); } -int singleDICOM(struct TDCMopts* opts, char *fname) { - if (isDICOMfile(fname) == 0) { - printError("Not a DICOM image : %s\n", fname); - return 0; - } - struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc( sizeof(struct TDICOMdata)); - struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); - struct TSearchList nameList; +int singleDICOM(struct TDCMopts *opts, char *fname) { + if (isDICOMfile(fname) == 0) { + printError("Not a DICOM image : %s\n", fname); + return 0; + } + struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc(sizeof(struct TDICOMdata)); + struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); + struct TSearchList nameList; struct TDCMprefs prefs; - opts2Prefs (opts, &prefs); - 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 - nameList.numItems = 0; - nameList.str[nameList.numItems] = (char *)malloc(strlen(fname)+1); - strcpy(nameList.str[nameList.numItems],fname); - nameList.numItems++; - TDCMsort * dcmSort = (TDCMsort *)malloc(sizeof(TDCMsort)); - dcmList[0].converted2NII = 1; - dcmList[0] = readDICOMx(nameList.str[0], &prefs, 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); - freeNameList(nameList); - free(dti4D); + opts2Prefs(opts, &prefs); + 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 + nameList.numItems = 0; + nameList.str[nameList.numItems] = (char *)malloc(strlen(fname) + 1); + strcpy(nameList.str[nameList.numItems], fname); + nameList.numItems++; + TDCMsort *dcmSort = (TDCMsort *)malloc(sizeof(TDCMsort)); + dcmList[0].converted2NII = 1; + dcmList[0] = readDICOMx(nameList.str[0], &prefs, 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); + freeNameList(nameList); + free(dti4D); free(dcmSort); - free(dcmList); - return ret; -}// singleDICOM() + free(dcmList); + return ret; +} // singleDICOM() -size_t fileBytes(const char * fname) { - FILE *fp = fopen(fname, "rb"); - if (!fp) return 0; +size_t fileBytes(const char *fname) { + FILE *fp = fopen(fname, "rb"); + if (!fp) + return 0; fseek(fp, 0, SEEK_END); size_t fileLen = ftell(fp); - fclose(fp); - return fileLen; + fclose(fp); + return fileLen; } //fileBytes() -void searchDirForDICOM(char *path, struct TSearchList *nameList, int maxDepth, int depth, struct TDCMopts* opts ) { - tinydir_dir dir; - tinydir_open(&dir, path); - while (dir.has_next) { - tinydir_file file; - file.is_dir = 0; //avoids compiler warning: this is set by tinydir_readfile - tinydir_readfile(&dir, &file); - //printMessage("%s\n", file.name); - char filename[768] =""; - strcat(filename, path); - strcat(filename,kFileSep); - strcat(filename, file.name); - if ((file.is_dir) && (depth < maxDepth) && (file.name[0] != '.')) - searchDirForDICOM(filename, nameList, maxDepth, depth+1, opts); - else if (!file.is_reg) //ignore files "." and ".." - ; - else if ((strlen(file.name) < 1) || (file.name[0]=='.')) - ; //printMessage("skipping hidden file %s\n", file.name); - else if ((strlen(file.name) == 8) && (strcicmp(file.name, "DICOMDIR") == 0)) - ; //printMessage("skipping DICOMDIR\n"); - else if ((isDICOMfile(filename) > 0) || (isExt(filename, ".par")) ) { - if (nameList->numItems < nameList->maxItems) { - nameList->str[nameList->numItems] = (char *)malloc(strlen(filename)+1); - strcpy(nameList->str[nameList->numItems],filename); - } - nameList->numItems++; - //printMessage("dcm %lu %s \n",nameList->numItems, filename); +void searchDirForDICOM(char *path, struct TSearchList *nameList, int maxDepth, int depth, struct TDCMopts *opts) { + tinydir_dir dir; + tinydir_open(&dir, path); + while (dir.has_next) { + tinydir_file file; + file.is_dir = 0; //avoids compiler warning: this is set by tinydir_readfile + tinydir_readfile(&dir, &file); + //printMessage("%s\n", file.name); + char filename[768] = ""; + strcat(filename, path); + strcat(filename, kFileSep); + strcat(filename, file.name); + if ((file.is_dir) && (depth < maxDepth) && (file.name[0] != '.')) + searchDirForDICOM(filename, nameList, maxDepth, depth + 1, opts); + else if (!file.is_reg) //ignore files "." and ".." + ; + else if ((strlen(file.name) < 1) || (file.name[0] == '.')) + ; //printMessage("skipping hidden file %s\n", file.name); + else if ((strlen(file.name) == 8) && (strcicmp(file.name, "DICOMDIR") == 0)) + ; //printMessage("skipping DICOMDIR\n"); + else if ((isDICOMfile(filename) > 0) || (isExt(filename, ".par"))) { + if (nameList->numItems < nameList->maxItems) { + nameList->str[nameList->numItems] = (char *)malloc(strlen(filename) + 1); + strcpy(nameList->str[nameList->numItems], filename); + } + nameList->numItems++; + //printMessage("dcm %lu %s \n",nameList->numItems, filename); #ifndef USING_R - } else { - if (fileBytes(filename) > 2048) - convert_foreign (filename, *opts); - #ifdef MY_DEBUG - printMessage("Not a dicom:\t%s\n", filename); - #endif + } else { + if (fileBytes(filename) > 2048) + convert_foreign(filename, *opts); +#ifdef MY_DEBUG + printMessage("Not a dicom:\t%s\n", filename); #endif - } - tinydir_next(&dir); - } - tinydir_close(&dir); -}// searchDirForDICOM() - -int removeDuplicates(int nConvert, struct TDCMsort dcmSort[]){ - //done AFTER sorting, so duplicates will be sequential - if (nConvert < 2) return nConvert; - int nDuplicates = 0; - for (int i = 1; i < nConvert; i++) { - if (compareTDCMsort(&dcmSort[i], &dcmSort[i-1]) == 0) { - nDuplicates ++; - } else { - dcmSort[i-nDuplicates].img = dcmSort[i].img; - dcmSort[i-nDuplicates].indx = dcmSort[i].indx; - for(int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; ++j) - dcmSort[i - nDuplicates].dimensionIndexValues[j] = dcmSort[i].dimensionIndexValues[j]; - } - } - if (nDuplicates > 0) - printMessage("%d images have identical time, series, acquisition and instance values. DUPLICATES REMOVED.\n", nDuplicates); - return nConvert - nDuplicates; -}// removeDuplicates() - -int removeDuplicatesVerbose(int nConvert, struct TDCMsort dcmSort[], struct TSearchList *nameList){ - //done AFTER sorting, so duplicates will be sequential - if (nConvert < 2) return nConvert; - int nDuplicates = 0; - for (int i = 1; i < nConvert; i++) { - if (compareTDCMsort(&dcmSort[i], &dcmSort[i-1]) == 0) { - printMessage("\t%s\t=\t%s\n",nameList->str[dcmSort[i-1].indx],nameList->str[dcmSort[i].indx]); - nDuplicates ++; - } else { - dcmSort[i-nDuplicates].img = dcmSort[i].img; - dcmSort[i-nDuplicates].indx = dcmSort[i].indx; - for(int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; ++j) - dcmSort[i - nDuplicates].dimensionIndexValues[j] = dcmSort[i].dimensionIndexValues[j]; - } - } - if (nDuplicates > 0) - printMessage("%d images have identical time, series, acquisition and instance values. Duplicates removed.\n", nDuplicates); - return nConvert - nDuplicates; -}// removeDuplicatesVerbose() - -int convert_parRec(char * fnm, struct TDCMopts opts) { - //sample dataset from Ed Gronenschild - struct TSearchList nameList; - int ret = EXIT_FAILURE; - nameList.numItems = 1; - nameList.maxItems = 1; - nameList.str = (char **) malloc((nameList.maxItems+1) * sizeof(char *)); //we reserve one pointer (32 or 64 bits) per potential file - struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc(nameList.numItems * sizeof(struct TDICOMdata)); - nameList.str[0] = (char *)malloc(strlen(fnm)+1); - strcpy(nameList.str[0], fnm); - //nameList.str[0] = (char *)malloc(strlen(opts.indir)+1); - //strcpy(nameList.str[0],opts.indir); - 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); +#endif + } + tinydir_next(&dir); + } + tinydir_close(&dir); +} // searchDirForDICOM() + +int removeDuplicates(int nConvert, struct TDCMsort dcmSort[]) { + //done AFTER sorting, so duplicates will be sequential + if (nConvert < 2) + return nConvert; + int nDuplicates = 0; + for (int i = 1; i < nConvert; i++) { + if (compareTDCMsort(&dcmSort[i], &dcmSort[i - 1]) == 0) { + nDuplicates++; + } else { + dcmSort[i - nDuplicates].img = dcmSort[i].img; + dcmSort[i - nDuplicates].indx = dcmSort[i].indx; + for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; ++j) + dcmSort[i - nDuplicates].dimensionIndexValues[j] = dcmSort[i].dimensionIndexValues[j]; + } + } + if (nDuplicates > 0) + printMessage("%d images have identical time, series, acquisition and instance values. DUPLICATES REMOVED.\n", nDuplicates); + return nConvert - nDuplicates; +} // removeDuplicates() + +int removeDuplicatesVerbose(int nConvert, struct TDCMsort dcmSort[], struct TSearchList *nameList) { + //done AFTER sorting, so duplicates will be sequential + if (nConvert < 2) + return nConvert; + int nDuplicates = 0; + for (int i = 1; i < nConvert; i++) { + if (compareTDCMsort(&dcmSort[i], &dcmSort[i - 1]) == 0) { + printMessage("\t%s\t=\t%s\n", nameList->str[dcmSort[i - 1].indx], nameList->str[dcmSort[i].indx]); + nDuplicates++; + } else { + dcmSort[i - nDuplicates].img = dcmSort[i].img; + dcmSort[i - nDuplicates].indx = dcmSort[i].indx; + for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; ++j) + dcmSort[i - nDuplicates].dimensionIndexValues[j] = dcmSort[i].dimensionIndexValues[j]; + } + } + if (nDuplicates > 0) + printMessage("%d images have identical time, series, acquisition and instance values. Duplicates removed.\n", nDuplicates); + return nConvert - nDuplicates; +} // removeDuplicatesVerbose() + +int convert_parRec(char *fnm, struct TDCMopts opts) { + //sample dataset from Ed Gronenschild + struct TSearchList nameList; + int ret = EXIT_FAILURE; + nameList.numItems = 1; + nameList.maxItems = 1; + nameList.str = (char **)malloc((nameList.maxItems + 1) * sizeof(char *)); //we reserve one pointer (32 or 64 bits) per potential file + struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc(nameList.numItems * sizeof(struct TDICOMdata)); + nameList.str[0] = (char *)malloc(strlen(fnm) + 1); + strcpy(nameList.str[0], fnm); + //nameList.str[0] = (char *)malloc(strlen(opts.indir)+1); + //strcpy(nameList.str[0],opts.indir); + 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); free(dti4D); - free(dcmList);//if (nConvertTotal == 0) - if (nameList.numItems < 1) - printMessage("No valid PAR/REC files were found\n"); - freeNameList(nameList); - return ret; -}// convert_parRec() - -int copyFile (char * src_path, char * dst_path) { - #define BUFFSIZE 32768 + free(dcmList); //if (nConvertTotal == 0) + if (nameList.numItems < 1) + printMessage("No valid PAR/REC files were found\n"); + freeNameList(nameList); + return ret; +} // convert_parRec() + +int copyFile(char *src_path, char *dst_path) { +#define BUFFSIZE 32768 unsigned char buffer[BUFFSIZE]; - FILE *fin = fopen(src_path, "rb"); - if (fin == NULL) { - printError("Check file permissions: Unable to open input %s\n", src_path); - return EXIT_SUCCESS; - } - if (is_fileexists(dst_path)) { - if (true) { - printWarning("Naming conflict (duplicates?): '%s' '%s'\n", src_path, dst_path); - return EXIT_SUCCESS; - } else { - printError("File naming conflict. Existing file %s\n", dst_path); - return EXIT_FAILURE; - } - } + FILE *fin = fopen(src_path, "rb"); + if (fin == NULL) { + printError("Check file permissions: Unable to open input %s\n", src_path); + return EXIT_SUCCESS; + } + if (is_fileexists(dst_path)) { + if (true) { + printWarning("Naming conflict (duplicates?): '%s' '%s'\n", src_path, dst_path); + return EXIT_SUCCESS; + } else { + printError("File naming conflict. Existing file %s\n", dst_path); + return EXIT_FAILURE; + } + } FILE *fou = fopen(dst_path, "wb"); - if (fou == NULL) { - printError("Check file permission. Unable to open output %s\n", dst_path); - return EXIT_FAILURE; - } - size_t bytes; - while ((bytes = fread(buffer, 1, BUFFSIZE, fin)) != 0) { - if(fwrite(buffer, 1, bytes, fou) != bytes) { - printError("Unable to write %zu bytes to output %s\n", bytes, dst_path); - return EXIT_FAILURE; - } - } - fclose(fin); - fclose(fou); - return EXIT_SUCCESS; + if (fou == NULL) { + printError("Check file permission. Unable to open output %s\n", dst_path); + return EXIT_FAILURE; + } + size_t bytes; + while ((bytes = fread(buffer, 1, BUFFSIZE, fin)) != 0) { + if (fwrite(buffer, 1, bytes, fou) != bytes) { + printError("Unable to write %zu bytes to output %s\n", bytes, dst_path); + return EXIT_FAILURE; + } + } + fclose(fin); + fclose(fou); + return EXIT_SUCCESS; } #ifdef USING_R // This implementation differs enough from the mainline one to be separated -int searchDirRenameDICOM(char *path, int maxDepth, int depth, struct TDCMopts* opts ) { - // The tinydir_open_sorted function reads the whole directory at once, - // which is necessary in this context since we may be creating new - // files in the same directory, which we don't want to further examine - tinydir_dir dir; - int count = 0; - if (tinydir_open_sorted(&dir, path) != 0) - return -1; - - for (size_t i=0; i(sourcePath.c_str()); - if ((file.is_dir) && (depth < maxDepth) && (file.name[0] != '.')) { - const int subdirectoryCount = searchDirRenameDICOM(sourcePathPtr, maxDepth, depth+1, opts); - if (subdirectoryCount < 0) { - tinydir_close(&dir); - return -1; - } - count += subdirectoryCount; - } else if (file.is_reg && strlen(file.name) > 0 && file.name[0] != '.' && strcicmp(file.name,"DICOMDIR") != 0 && isDICOMfile(sourcePathPtr)) { - TDICOMdata dcm = readDICOM(sourcePathPtr); +int searchDirRenameDICOM(char *path, int maxDepth, int depth, struct TDCMopts *opts) { + // The tinydir_open_sorted function reads the whole directory at once, + // which is necessary in this context since we may be creating new + // files in the same directory, which we don't want to further examine + tinydir_dir dir; + int count = 0; + if (tinydir_open_sorted(&dir, path) != 0) + return -1; + for (size_t i = 0; i < dir.n_files; i++) { + // If this directory entry is a subdirectory, search it recursively + tinydir_file &file = dir._files[i]; + const std::string sourcePath = std::string(path) + kFileSep + file.name; + char *sourcePathPtr = const_cast(sourcePath.c_str()); + if ((file.is_dir) && (depth < maxDepth) && (file.name[0] != '.')) { + const int subdirectoryCount = searchDirRenameDICOM(sourcePathPtr, maxDepth, depth + 1, opts); + if (subdirectoryCount < 0) { + tinydir_close(&dir); + return -1; + } + count += subdirectoryCount; + } else if (file.is_reg && strlen(file.name) > 0 && file.name[0] != '.' && strcicmp(file.name, "DICOMDIR") != 0 && isDICOMfile(sourcePathPtr)) { + TDICOMdata dcm = readDICOM(sourcePathPtr); if (dcm.imageNum > 0) { - if ((opts->isIgnoreDerivedAnd2D) && ((dcm.isLocalizer) || (strcmp(dcm.sequenceName, "_tfl2d1")== 0) || (strcmp(dcm.sequenceName, "_fl3d1_ns")== 0) || (strcmp(dcm.sequenceName, "_fl2d1")== 0)) ) { + if ((opts->isIgnoreDerivedAnd2D) && ((dcm.isLocalizer) || (strcmp(dcm.sequenceName, "_tfl2d1") == 0) || (strcmp(dcm.sequenceName, "_fl3d1_ns") == 0) || (strcmp(dcm.sequenceName, "_fl2d1") == 0))) { printMessage("Ignoring localizer %s\n", sourcePathPtr); - opts->ignoredPaths.push_back(sourcePath); - } else if ((opts->isIgnoreDerivedAnd2D && dcm.isDerived) ) { + opts->ignoredPaths.push_back(sourcePath); + } else if ((opts->isIgnoreDerivedAnd2D && dcm.isDerived)) { printMessage("Ignoring derived %s\n", sourcePathPtr); - opts->ignoredPaths.push_back(sourcePath); + opts->ignoredPaths.push_back(sourcePath); } else { // Create an initial file name - char outname[PATH_MAX] = {""}; + char outname[PATH_MAX] = {""}; if (dcm.echoNum > 1) - dcm.isMultiEcho = true; + dcm.isMultiEcho = true; nii_createFilename(dcm, outname, *opts); - - // If the file name part of the target path has no extension, add ".dcm" - std::string targetPath(outname); - std::string targetStem, targetExtension; - const size_t periodLoc = targetPath.find_last_of('.'); - if (periodLoc == targetPath.length() - 1) { - targetStem = targetPath.substr(0, targetPath.length() - 1); - targetExtension = ".dcm"; - } else if (periodLoc == std::string::npos || periodLoc < targetPath.find_last_of("\\/")) { - targetStem = targetPath; - targetExtension = ".dcm"; - } else { - targetStem = targetPath.substr(0, periodLoc); - targetExtension = targetPath.substr(periodLoc); - } - - // Deduplicate the target path to avoid overwriting existing files - targetPath = targetStem + targetExtension; - GetRNGstate(); - while (is_fileexists(targetPath.c_str())) { - std::ostringstream suffix; - unsigned suffixValue = static_cast(round(R::unif_rand() * (R_pow_di(2.0,24) - 1.0))); - suffix << std::hex << std::setfill('0') << std::setw(6) << suffixValue; - targetPath = targetStem + "_" + suffix.str() + targetExtension; - } - PutRNGstate(); - - // Copy the file, unless the source and target paths are the same - if (targetPath.compare(sourcePath) == 0) { - if (opts->isVerbose > 1) - printMessage("Skipping %s, which would be copied onto itself\n", sourcePathPtr); - } else if (copyFile(sourcePathPtr, const_cast(targetPath.c_str())) == EXIT_SUCCESS) { - opts->sourcePaths.push_back(sourcePath); - opts->targetPaths.push_back(targetPath); - count++; - if (opts->isVerbose > 0) - printMessage("Copying %s -> %s\n", sourcePathPtr, targetPath.c_str()); - } else { - printWarning("Unable to copy to path %s\n", targetPath.c_str()); - } + // If the file name part of the target path has no extension, add ".dcm" + std::string targetPath(outname); + std::string targetStem, targetExtension; + const size_t periodLoc = targetPath.find_last_of('.'); + if (periodLoc == targetPath.length() - 1) { + targetStem = targetPath.substr(0, targetPath.length() - 1); + targetExtension = ".dcm"; + } else if (periodLoc == std::string::npos || periodLoc < targetPath.find_last_of("\\/")) { + targetStem = targetPath; + targetExtension = ".dcm"; + } else { + targetStem = targetPath.substr(0, periodLoc); + targetExtension = targetPath.substr(periodLoc); + } + // Deduplicate the target path to avoid overwriting existing files + targetPath = targetStem + targetExtension; + GetRNGstate(); + while (is_fileexists(targetPath.c_str())) { + std::ostringstream suffix; + unsigned suffixValue = static_cast(round(R::unif_rand() * (R_pow_di(2.0, 24) - 1.0))); + suffix << std::hex << std::setfill('0') << std::setw(6) << suffixValue; + targetPath = targetStem + "_" + suffix.str() + targetExtension; + } + PutRNGstate(); + // Copy the file, unless the source and target paths are the same + if (targetPath.compare(sourcePath) == 0) { + if (opts->isVerbose > 1) + printMessage("Skipping %s, which would be copied onto itself\n", sourcePathPtr); + } else if (copyFile(sourcePathPtr, const_cast(targetPath.c_str())) == EXIT_SUCCESS) { + opts->sourcePaths.push_back(sourcePath); + opts->targetPaths.push_back(targetPath); + count++; + if (opts->isVerbose > 0) + printMessage("Copying %s -> %s\n", sourcePathPtr, targetPath.c_str()); + } else { + printWarning("Unable to copy to path %s\n", targetPath.c_str()); + } } } - } - } - return count; + } + } + return count; } #else -int searchDirRenameDICOM(char *path, int maxDepth, int depth, struct TDCMopts* opts ) { - int retAll = 0; - tinydir_dir dir; - if (tinydir_open_sorted(&dir, path) != 0) { +int searchDirRenameDICOM(char *path, int maxDepth, int depth, struct TDCMopts *opts) { + int retAll = 0; + tinydir_dir dir; + if (tinydir_open_sorted(&dir, path) != 0) { if (opts->isVerbose > 0) printMessage("Unable to open %s\n", path); - return -1; - } - if (dir.n_files < 1) { + return -1; + } + if (dir.n_files < 1) { if (opts->isVerbose > 0) printMessage("No files in %s\n", path); - return 0; - } + return 0; + } if (opts->isVerbose > 0) printMessage("Found %zu items in %s\n", dir.n_files, path); //%lu -> %zu - for (size_t i=0; i