From 540361281365b4edaf5190527db4a092b3c2bf31 Mon Sep 17 00:00:00 2001 From: yhuang43 <92936691+yhuang43@users.noreply.github.com> Date: Mon, 20 Dec 2021 23:26:01 -0500 Subject: [PATCH] set isForceStackSameSeries to 1 (#904) * 1. check in package dcm2niix 2. add DICOMRead3/niiRead3 to convert dicom using dcm2niix 3. fix dcmGetDWIParams() to use stricmp() to compare the Manufacturer tag * set TI to -1 when there is no TI in dicom file for MGH_FREESURFER * remove unused codes * This is the correct version. * redo the check in * For some unknown reasons, dcm2niix and dcm2niixfsexe executables failed linking in nightly builds. They are excluded from the build until we figure out why. libdcm2niixfs.a is renamed to libdcm2niix.a * 1. rename dcm2fsWrapper.h => dcm2niix_fswrapper.h, dcm2fsWrapper.cpp => dcm2niix_fswrapper.cpp 2. add comments to dcm2niix changes * fix bvecs * 1. introduced new type MRI_USHRT (10) - unsigned short, convert DT_UINT16 to MRI_USHRT, MRI_USHRT to DT_UINT16 2. added macro MRIUSseq_vox & MRIUSvox to access voxel in pre-allocated memory 3. added handling of type MRI_USHRT in various functions 4. introduced environment variable FS_MGZIO_USEVOXELBUF to read/write (mgzRead/mgzWrite) voxels in one buffer access. FS_MGZIO_USEVOXELBUF is ignored if mri voxel buffer is not allocated as a big chunk. 5. introduced environment variable FS_MGZIO_TIMING to time voxel read/write in mgzRead/mgzWrite * ignore FS_MGZIO_USEVOXELBUF if mri voxel buffer is not allocated as a big chunk * add dcm2niix license.txt * license files for dcm2niix - license.dcm2niix.txt and license.charls.txt * 1. set isForceStackSameSeries = 1 in dcm2niix_fswrapper (dcm2niix command line option - '-m y' merge 2D slice) 2. print messages for slices stacked Co-authored-by: Yujing Huang --- packages/dcm2niix/dcm2niix_fswrapper.cpp | 132 ++++++++++++++++++++++- packages/dcm2niix/dcm2niix_fswrapper.h | 2 + packages/dcm2niix/nii_dicom.cpp | 3 + packages/dcm2niix/nii_dicom_batch.cpp | 102 ++++++++++++++++-- packages/dcm2niix/nii_dicom_batch.h | 1 + 5 files changed, 230 insertions(+), 10 deletions(-) diff --git a/packages/dcm2niix/dcm2niix_fswrapper.cpp b/packages/dcm2niix/dcm2niix_fswrapper.cpp index 28db86d6bc7..c07054051a9 100644 --- a/packages/dcm2niix/dcm2niix_fswrapper.cpp +++ b/packages/dcm2niix/dcm2niix_fswrapper.cpp @@ -68,7 +68,7 @@ void dcm2niix_fswrapper::setOpts(const char* dcmindir, const char* niioutdir) tdcmOpts.isIgnoreSeriesInstanceUID = true; tdcmOpts.isCreateBIDS = false; tdcmOpts.isGz = false; - //tdcmOpts.isForceStackSameSeries = 1; // merge 2D slice '-m y' + tdcmOpts.isForceStackSameSeries = 1; // merge 2D slice '-m y' tdcmOpts.isForceStackDCE = false; //tdcmOpts.isForceOnsetTimes = false; } @@ -119,3 +119,133 @@ const unsigned char* dcm2niix_fswrapper::getMRIimg(void) return mrifsStruct->imgM; } +void dcm2niix_fswrapper::dicomDump(const char* dicomdir) +{ + strcpy(tdcmOpts.indir, dicomdir); + tdcmOpts.isDumpNotConvert = true; + nii_loadDirCore(tdcmOpts.indir, &tdcmOpts); + + return; + +#if 0 + struct TSearchList nameList; + int nConvertTotal = 0; +#if defined(_WIN64) || defined(_WIN32) || defined(USING_R) + nameList.maxItems = 24000; // larger requires more memory, smaller more passes +#else //UNIX, not R + nameList.maxItems = 96000; // larger requires more memory, smaller more passes +#endif + + if ((is_fileNotDir(opts->indir)) && isExt(opts->indir, ".txt")) { + nameList.str = (char **)malloc((nameList.maxItems + 1) * sizeof(char *)); //reserve one pointer (32 or 64 bits) per potential file + nameList.numItems = 0; + FILE *fp = fopen(opts->indir, "r"); //textDICOM + if (fp == NULL) + return EXIT_FAILURE; + char dcmname[2048]; + while (fgets(dcmname, sizeof(dcmname), fp)) { + int sz = (int)strlen(dcmname); + if (sz > 0 && dcmname[sz - 1] == '\n') + dcmname[sz - 1] = 0; //Unix LF + if (sz > 1 && dcmname[sz - 2] == '\r') + dcmname[sz - 2] = 0; //Windows CR/LF + if ((!is_fileexists(dcmname)) || (!is_fileNotDir(dcmname))) { //<-this will accept meta data + fclose(fp); + printError("Problem with file '%s'\n", dcmname); + return EXIT_FAILURE; + } + if (nameList.numItems < nameList.maxItems) { + nameList.str[nameList.numItems] = (char *)malloc(strlen(dcmname) + 1); + strcpy(nameList.str[nameList.numItems], dcmname); + } + nameList.numItems++; + } + fclose(fp); + if (nameList.numItems >= nameList.maxItems) { + printError("Too many file names in '%s'\n", opts->indir); + return EXIT_FAILURE; + } + if (nameList.numItems < 1) + return kEXIT_NO_VALID_FILES_FOUND; + printMessage("Found %lu files in '%s'\n", nameList.numItems, opts->indir); + } else { + //1: find filenames of dicom files: up to two passes if we found more files than we allocated memory + for (int i = 0; i < 2; i++) { + nameList.str = (char **)malloc((nameList.maxItems + 1) * sizeof(char *)); //reserve one pointer (32 or 64 bits) per potential file + nameList.numItems = 0; + int ret = searchDirForDICOM(indir, &nameList, opts->dirSearchDepth, 0, opts); + if (ret == EXIT_SUCCESS) //e.g. converted ECAT + nConvertTotal++; + if (nameList.numItems <= nameList.maxItems) + break; + freeNameList(nameList); + nameList.maxItems = nameList.numItems + 1; + //printMessage("Second pass required, found %ld images\n", nameList.numItems); + } + + if (nameList.numItems < 1) { + if ((opts->dirSearchDepth > 0) && (nConvertTotal < 1)) + printError("Unable to find any DICOM images in %s (or subfolders %d deep)\n", indir, opts->dirSearchDepth); + else //keep silent for dirSearchDepth = 0 - presumably searching multiple folders + { + }; + free(nameList.str); //ignore compile warning - memory only freed on first of 2 passes + if (nConvertTotal > 0) return EXIT_SUCCESS; //e.g. converted ECAT + return kEXIT_NO_VALID_FILES_FOUND; + } + } + + size_t nDcm = nameList.numItems; + printMessage("Found %lu DICOM file(s)\n", nameList.numItems); //includes images and other non-image DICOMs + + 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); + bool compressionWarning = false; + bool convertError = false; + bool isDcmExt = isExt(opts->filename, ".dcm"); // "%r.dcm" with multi-echo should generate "1.dcm", "1e2.dcm" + if (isDcmExt) + opts->filename[strlen(opts->filename) - 4] = 0; // "%s_%r.dcm" -> "%s_%r" + //consider OpenMP + // g++-9 -I. main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG -fopenmp + + for (int i = 0; i < (int)nDcm; i++) { + if ((isExt(nameList.str[i], ".par")) && (isDICOMfile(nameList.str[i]) < 1)) { + //strcpy(opts->indir, nameList.str[i]); //set to original file name, not path + dcmList[i].converted2NII = 1; + int ret = convert_parRec(nameList.str[i], *opts); + if (ret == EXIT_SUCCESS) + nConvertTotal++; + else + convertError = true; + continue; + } + + 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]); + + if ((dcmList[i].isValid) && ((dti4D->sliceOrder[0] >= 0) || (dcmList[i].CSA.numDti > 1))) { //4D dataset: dti4D arrays require huge amounts of RAM - write this immediately + struct TDCMsort dcmSort[1]; + fillTDCMsort(dcmSort[0], i, dcmList[i]); + //printMessage("***MGH_FREESURFER***: calling saveDcm2Nii() (%s:%s:%d)\n", __FILE__, __func__, __LINE__); + dcmList[i].converted2NII = 1; + int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, dti4D); + if (ret == EXIT_SUCCESS) + nConvertTotal++; + else + convertError = true; + } + + if ((dcmList[i].compressionScheme != kCompressNone) && (!compressionWarning) && (opts->compressFlag != kCompressNone)) { + compressionWarning = true; //generate once per conversion rather than once per image + printMessage("Image Decompression is new: please validate conversions\n"); + } + if (opts->isProgress) + progressPct = reportProgress(progressPct, kStage1Frac + (kStage2Frac * (float)i / (float)nDcm)); //proportion correct, 0..100 + } +#endif +} diff --git a/packages/dcm2niix/dcm2niix_fswrapper.h b/packages/dcm2niix/dcm2niix_fswrapper.h index 5d24914e63a..c61471cee00 100644 --- a/packages/dcm2niix/dcm2niix_fswrapper.h +++ b/packages/dcm2niix/dcm2niix_fswrapper.h @@ -32,6 +32,8 @@ class dcm2niix_fswrapper // return image data saved in MRIFSSTRUCT static const unsigned char* getMRIimg(void); + static void dicomDump(const char* dicomdir); + private: static struct TDCMopts tdcmOpts; }; diff --git a/packages/dcm2niix/nii_dicom.cpp b/packages/dcm2niix/nii_dicom.cpp index 11908b43190..a350152bad0 100644 --- a/packages/dcm2niix/nii_dicom.cpp +++ b/packages/dcm2niix/nii_dicom.cpp @@ -3549,6 +3549,7 @@ unsigned char *nii_loadImgJPEGLS(char *imgname, struct nifti_1_header hdr, struc #ifdef myEnableJPEGLS1 if (JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms) != OK) { #else + printMessage("myEnableJPEGLS defined. JpegLsReadHeader() ...\n"); using namespace charls; if (JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) { #endif @@ -3558,6 +3559,7 @@ unsigned char *nii_loadImgJPEGLS(char *imgname, struct nifti_1_header hdr, struc #ifdef myEnableJPEGLS1 if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms) != OK) { #else + printMessage("myEnableJPEGLS defined. JpegLsDecode() ...\n"); if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) { #endif free(bImg); @@ -3585,6 +3587,7 @@ unsigned char *nii_loadImgXL(char *imgname, struct nifti_1_header *hdr, struct T #endif } else if (dcm.compressionScheme == kCompressJPEGLS) { #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) + printMessage("myEnableJPEGLS defined. nii_loadImgJPEGLS() ...\n"); 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 diff --git a/packages/dcm2niix/nii_dicom_batch.cpp b/packages/dcm2niix/nii_dicom_batch.cpp index 62651a7b5fd..a293689a6e0 100644 --- a/packages/dcm2niix/nii_dicom_batch.cpp +++ b/packages/dcm2niix/nii_dicom_batch.cpp @@ -6244,7 +6244,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d } //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]); - } + } // end of if (nConvert > 1) if (opts.isVerbose > 1) reportProtocolBlockGE(&dcmList[indx0], nameList->str[dcmSort[0].indx]); int sliceDir = sliceTimingCore(dcmSort, dcmList, &hdr0, opts.isVerbose, nameList->str[dcmSort[0].indx], nConvert, opts); @@ -6577,6 +6577,27 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d } // saveDcm2NiiCore() int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts, struct TDTI4D *dti4D) { +#ifdef USING_DCM2NIIXFSWRAPPER + if (opts.isDumpNotConvert) { + int indx0 = dcmSort[0].indx; + if (opts.isIgnoreSeriesInstanceUID) + printMessage("%d %s %s (total %d)\n", dcmList[indx0].seriesUidCrc, dcmList[indx0].protocolName, nameList->str[indx0], nConvert); + else + printMessage("%d %ld %s %s (total %d)\n", dcmList[indx0].seriesUidCrc, dcmList[indx0].seriesNum, dcmList[indx0].protocolName, nameList->str[indx0], nConvert); + +#if 0 + for (int i = 0; i < nConvert; i++) { + int indx = dcmSort[i].indx; + if (opts.isIgnoreSeriesInstanceUID) + printMessage("\t#\%d: %d %s\n", i+1, dcmList[indx].seriesUidCrc, nameList->str[indx]); + else + printMessage("\t#\%d: %d %ld %s\n", i+1, dcmList[indx].seriesUidCrc, dcmList[indx].seriesNum, nameList->str[indx]); + } +#endif + + return 0; + } +#endif //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; @@ -6774,7 +6795,7 @@ int isSameFloatDouble(double a, double b) { } 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, nonParallelMosaicsSlices; }; TWarnings setWarnings() { @@ -6795,6 +6816,7 @@ TWarnings setWarnings() { r.nameVaries = false; r.nameEmpty = false; r.orientVaries = false; + r.nonParallelMosaicsSlices = false; return r; } @@ -6895,9 +6917,65 @@ bool isSameSet(struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts *opts // *isMultiEcho = true; //} #ifdef USING_DCM2NIIXFSWRAPPER - printf("isForceStackSameSeries = true, seriesNum %ld, %ld, seriesInstanceUidCrc %d, %d\n", d1.seriesNum, d2.seriesNum, d1.seriesUidCrc, d2.seriesUidCrc); -#endif - return true; //we will stack these images, even if they differ in the following attributes + if ((d1.isHasImaginary != d2.isHasImaginary) || (d1.isHasPhase != d2.isHasPhase) || (d1.isHasReal != d2.isHasReal)) { + if (!warnings->phaseVaries) + printMessage("Slices stacked: some are phase/real/imaginary/phase maps, others are not. Instances %d %d\n", d1.imageNum, d2.imageNum); + warnings->phaseVaries = true; + } else 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 stacked: X-Ray Exposure varies (exposure %g, %g; number %d, %d).\n", d1.TE, d2.TE, d1.echoNum, d2.echoNum); + if ((!warnings->echoVaries) && (!d1.isXRay)) //for MRI + printMessage("Slices stacked: echo varies (TE %g, %g; echo %d, %d).\n", d1.TE, d2.TE, d1.echoNum, d2.echoNum); + warnings->echoVaries = true; + *isMultiEcho = true; + } else if ((d1.triggerDelayTime != d2.triggerDelayTime) && (d1.manufacturer == kMANUFACTURER_PHILIPS) && (d1.aslFlags == kASL_FLAG_NONE)) { //issue 384 + if (!warnings->triggerVaries) + printMessage("Slices stacked: trigger time varies\n"); + warnings->triggerVaries = true; + } else 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; + } else { + if (!warnings->coilVaries) + printMessage("Slices stacked: coil varies '%s' vs '%s'\n", d1.coilName, d2.coilName); + warnings->coilVaries = true; + *isCoilVaries = true; + } + } else 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 stacked: protocol name varies '%s' != '%s'\n", d1.protocolName, d2.protocolName); + warnings->nameVaries = true; + } else if ((*isNonParallelSlices) && (d1.CSA.mosaicSlices > 1)) { + if (!warnings->nonParallelMosaicsSlices)//issue 481 + printMessage("Slices stacked: non-parallel mosaics slices\n"); + warnings->nonParallelMosaicsSlices = true; + + } else 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 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; + } else 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; + } else if ((!isForceStackSeries) && (d1.seriesUidCrc != d2.seriesUidCrc)) { + if (!warnings->seriesUidVaries) + printMessage("Slices stacked: series instance UID varies (duplicates all other properties)\n"); + warnings->seriesUidVaries = true; + } +#endif + return true; //we will stack these images, even if they differ in the following attributes } if ((d1.isHasImaginary != d2.isHasImaginary) || (d1.isHasPhase != d2.isHasPhase) || (d1.isHasReal != d2.isHasReal)) { if (!warnings->phaseVaries) @@ -7639,6 +7717,8 @@ int nii_loadDirCore(char *indir, struct TDCMopts *opts) { nConvert++; } } //for all images with same seriesUID as first one + +#ifndef USING_DCM2NIIXFSWRAPPER if ((isNonParallelSlices) && (dcmList[ii].CSA.mosaicSlices > 1) && (nConvert > 0)) { //issue481: if ANY volumes are non-parallel, save ALL as 3D printWarning("Saving mosaics with non-parallel slices as 3D (issue 481)\n"); for (int j = i; j < (int)nDcm; j++) { @@ -7661,6 +7741,8 @@ int nii_loadDirCore(char *indir, struct TDCMopts *opts) { } continue; } //issue481 +#endif + //issue 381: ensure all images are informed if there are variations in echo, parallel slices, coil name: if (isMultiEcho) for (int j = i; j <= jMax; j++) { @@ -7727,7 +7809,7 @@ int nii_loadDirOneDirAtATime(char *path, struct TDCMopts *opts, int maxDepth, in //return kEXIT_NO_VALID_FILES_FOUND if no files in ANY sub folders //return EXIT_FAILURE if ANY failure //return EXIT_SUCCESS if no failures and at least one image converted - int ret = nii_loadDirCore(path, opts); + int ret = nii_loadDirCore(path, opts); if (ret == EXIT_FAILURE) return ret; tinydir_dir dir; @@ -7828,7 +7910,7 @@ int nii_loadDir(struct TDCMopts *opts) { #endif if (is_fileNotDir(rname) && is_fileNotDir(pname)) { //strcpy(opts->indir, pname); //set to original file name, not path - return convert_parRec(pname, *opts); + return convert_parRec(pname, *opts); }; } if (isFile && (opts->isOnlySingleFile) && isExt(indir, ".txt")) { @@ -7847,14 +7929,14 @@ int nii_loadDir(struct TDCMopts *opts) { return EXIT_SUCCESS; } if ((isFile) && (opts->isOnlySingleFile)) - return singleDICOM(opts, indir); + return singleDICOM(opts, indir); if (opts->isOneDirAtATime) { int maxDepth = opts->dirSearchDepth; opts->dirSearchDepth = 0; strcpy(indir, opts->indir); return nii_loadDirOneDirAtATime(indir, opts, maxDepth, 0); } else - return nii_loadDirCore(opts->indir, opts); + return nii_loadDirCore(opts->indir, opts); } // nii_loadDir() #if defined(_WIN64) || defined(_WIN32) || defined(USING_R) @@ -8071,6 +8153,8 @@ void setDefaultOpts(struct TDCMopts *opts, const char *argv[]) { //either "setDe opts->numSeries = 0; memset(opts->seriesNumber, 0, sizeof(opts->seriesNumber)); strcpy(opts->filename, "%f_%p_%t_%s"); + + opts->isDumpNotConvert = false; } // setDefaultOpts() #if defined(_WIN64) || defined(_WIN32) diff --git a/packages/dcm2niix/nii_dicom_batch.h b/packages/dcm2niix/nii_dicom_batch.h index 9edb0b0be21..9abad6dc22b 100644 --- a/packages/dcm2niix/nii_dicom_batch.h +++ b/packages/dcm2niix/nii_dicom_batch.h @@ -56,6 +56,7 @@ void nii_clrMrifsStruct(); #define MAX_NUM_SERIES 16 struct TDCMopts { + bool isDumpNotConvert; bool isIgnoreTriggerTimes, isTestx0021x105E, isAddNamePostFixes, isSaveNativeEndian, isOneDirAtATime, isRenameNotConvert, isSave3D, isGz, isPipedGz, isFlipY, isCreateBIDS, isSortDTIbyBVal, isAnonymizeBIDS, isOnlyBIDS, isCreateText, isForceOnsetTimes,isIgnoreDerivedAnd2D, isPhilipsFloatNotDisplayScaling, isTiltCorrect, isRGBplanar, isOnlySingleFile, isForceStackDCE, isIgnoreSeriesInstanceUID, isRotate3DAcq, isCrop; int saveFormat, 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];