Skip to content

Commit

Permalink
Merge pull request #246 from rordenlab/development
Browse files Browse the repository at this point in the history
New fixes
  • Loading branch information
neurolabusc authored Nov 25, 2018
2 parents 3d74eab + 054e4a0 commit 32160d7
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 19 deletions.
6 changes: 4 additions & 2 deletions RENAMING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ dcm2niix is primarily designed to convert DICOM images into NIfTI images. Howeve

dcm2niix renames and copies the DICOM images, but the current version does not copy or create a new [DICOMDIR](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_F.2.2.2.html) file. Most users ignore these files. However, you should not use this featire if you wish to preserve your DICOMDIR files.

Note that this feature only copies your DICOM images with a new filename. It does not modify the contents of the DICOM header. This means it will not compress or anonymize your files. Free tools for these functions include (dcmcjpeg)[https://dicom.offis.de/dcmtk.php.en], [gdcmanon](http://gdcm.sourceforge.net/html/gdcmanon.html) and [gdcmconv](http://gdcm.sourceforge.net/html/gdcmconv.html).
Note that this feature only copies your DICOM images with a new filename. It does not modify the contents of the DICOM header. This means it will not compress or anonymize your files. Free tools for these functions include [dcmcjpeg](https://dicom.offis.de/dcmtk.php.en), [gdcmanon](http://gdcm.sourceforge.net/html/gdcmanon.html) and [gdcmconv](http://gdcm.sourceforge.net/html/gdcmconv.html).

In addition, this tool assumes that the DICOM images can be uniquely identified by the filenaming argument you provide.

## Usage

The command line argument `-r y` instructs dcm2niix to rename your DICOM files rather than convert them. It does not delete your DICOM images, but rather creates a copy with the organization specified by the [filenaming argument `-f`](FILENAMING.md). Here is an example where the DICOM images will be sorted into folders, with the folder name reflecting the study time (`%t`) and series number (`%s`), each DICOM image will be named based on the image number (`%r`) which will be padded with zeros to fill 5 characters.
- `dcm2niix -r y -f %t_%s/%5r.dcm -o ~/out ~/in`

Therefore, the 9th DICOM image from series 3 acquired on 4 February 2012 would be saved as ~/out/20120204084424_3/00009.dcm.

It is very important that your file naming disambiguates all your images. For example, consider a naming scheme that only used the image number (`-f %r.dcm`) and was applied to multiple series (each which had an image number 1,2,...). When there are naming conflicts, dcm2niix will terminate with an error message, e.g. `Error: File naming conflict. Existing file /home/c/dcm/1.dcm`.

A special situation is the fieldmaps generated by Siemens scanners. Users often acquire gradient-echo fieldmaps so they can undistort EPI images. These fieldmaps acquire two (or more) echoes. Unfortunately, Siemens will give each of these echoes an identical series and image number. DICOM tools that are unaware of this often [overwrite](https://neurostars.org/t/field-mapping-siemens-scanners-dcm2niix-output-2-bids/2075/7) some of the images from each echo. To combat this situation, dcm2niix will add the post-fix `_e2` to the second echo. Therefore, if you converted a series with `-f %s_%4r` your fieldmap might generate files named `5_0001.dcm` and `5_0001_e2.dcm`. Note you could also explicitly number each echo (`-f %s_%4r_%e`), though in this case all your series (not just the fieldmaps) will have the echo appended.
## Alternatives

An advantage of using dcm2niix is simplicity: it is a free, single file executable that you can [download](https://github.com/rordenlab/dcm2niix/releases) that is available for MacOS, Linux and Windows that you can run from the command line. On the other hand, this simplicity means it is fairly inflexible. You may want to consider a DICOM renamer built in your favorite scripting language.
Expand Down
2 changes: 1 addition & 1 deletion console/main_console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ void showHelp(const char * argv[], struct TDCMopts opts) {
#else
#define kQstr ""
#endif
printf(" -f : filename (%%a=antenna (coil) name, %%b=basename, %%c=comments, %%d=description, %%e=echo number, %%f=folder name, %%i=ID of patient, %%j=seriesInstanceUID, %%k=studyInstanceUID, %%m=manufacturer, %%n=name of patient, %%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(" -f : filename (%%a=antenna (coil) name, %%b=basename, %%c=comments, %%d=description, %%e=echo number, %%f=folder name, %%i=ID of patient, %%j=seriesInstanceUID, %%k=studyInstanceUID, %%m=manufacturer, %%n=name of patient, %%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");
Expand Down
6 changes: 3 additions & 3 deletions console/nii_dicom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1472,10 +1472,10 @@ mat33 nifti_mat33_reorder_cols( mat33 m, ivec3 v ) {
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);
}
} //changeExt()

void cleanStr(char* lOut) {
Expand Down Expand Up @@ -4938,7 +4938,7 @@ double TE = 0.0; //most recent echo time recorded
if (d.manufacturer != kMANUFACTURER_UIH) break;
char accelStr[kDICOMStr];
dcmStr (lLength, &buffer[lPos], accelStr);
char *ptr;
//char *ptr;
dcmStrDigitsDotOnlyKey(':', accelStr); //e.g. if "p2s4" return "2", if "s4" return ""
d.accelFactPE = atof(accelStr);
break; }
Expand Down
5 changes: 3 additions & 2 deletions console/nii_dicom.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ extern "C" {
#define kCCsuf " CompilerNA" //unknown compiler!
#endif

#define kDCMvers "v1.0.20181114 " kJP2suf kLSsuf kCCsuf
#define kDCMvers "v1.0.20181125 " kJP2suf kLSsuf kCCsuf

static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic
static const int kMaxDTI4D = 18000; //maximum number of DTI directions for 4D (Philips) images, also maximum number of 3D slices for Philips 3D and 4D images
Expand Down Expand Up @@ -155,7 +155,8 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8;
struct TDICOMdata {
long seriesNum;
int xyzDim[5];
int numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, coilCrc, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, compressionScheme;
uint32_t coilCrc;
int numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, compressionScheme;
float imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4];
float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4];
float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57)
Expand Down
46 changes: 35 additions & 11 deletions console/nii_dicom_batch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,18 @@ void json_Float(FILE *fp, const char *sLabel, float sVal) {
fprintf(fp, sLabel, sVal );
} //json_Float

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;
int baseResolution, interpInt, partialFourier, echoSpacing, parallelReductionFactorInPlane;
float pf = 1.0f; //partial fourier
float phaseOversampling, delayTimeInTR, phaseResolution, txRefAmp, shimSetting[8];
char protocolName[kDICOMStrLarge], fmriExternalInfo[kDICOMStrLarge], coilID[kDICOMStrLarge], consistencyInfo[kDICOMStrLarge], coilElements[kDICOMStrLarge], pulseSequenceDetails[kDICOMStrLarge], wipMemBlock[kDICOMStrLarge];
siemensCsaAscii(filename, d->CSA.SeriesHeader_offset, d->CSA.SeriesHeader_length, &delayTimeInTR, &phaseOversampling, &phaseResolution, &txRefAmp, shimSetting, &baseResolution, &interpInt, &partialFourier, &echoSpacing, &parallelReductionFactorInPlane, coilID, consistencyInfo, coilElements, pulseSequenceDetails, fmriExternalInfo, protocolName, wipMemBlock);
strcpy(d->protocolName, protocolName);
}

void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char * filename) {
//https://docs.google.com/document/d/1HFUkAEE-pB-angVcYe6pf_-fVf4sCpOHKesUvfb8Grc/edit#
// Generate Brain Imaging Data Structure (BIDS) info
Expand Down Expand Up @@ -1776,7 +1788,7 @@ int nii_createFilename(struct TDICOMdata dcm, char * niiFilename, struct TDCMopt
isEchoReported = true;
}
if ((dcm.isNonParallelSlices) && (!isImageNumReported)) {
sprintf(newstr, "_i%05d", dcm.imageNum);
sprintf(newstr, "_i%05d", dcm.imageNum);
strcat (outname,newstr);
}
if ((!isSeriesReported) && (!isEchoReported) && (dcm.echoNum > 1)) { //last resort: user provided no method to disambiguate echo number in filename
Expand Down Expand Up @@ -3029,7 +3041,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc
printMessage("Slice positions repeated, but number of slices (%d) not divisible by number of repeats (%d): missing images?\n", nConvert, nAcq);
}
}
//next options removed: featuresnow thoroughly detected in nii_loadDir()
//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].isCoilVaries) dcmList[indx0].isCoilVaries = true;
if (dcmList[dcmSort[i].indx].isMultiEcho) dcmList[indx0].isMultiEcho = true;
Expand Down Expand Up @@ -3283,6 +3295,8 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc
free(img4D);
saveAs3D = false;
}
if (strlen(dcmList[dcmSort[0].indx].protocolName) < 1)
rescueProtocolName(&dcmList[dcmSort[0].indx], nameList->str[dcmSort[0].indx]);
char pathoutname[2048] = {""};
if (nii_createFilename(dcmList[dcmSort[0].indx], pathoutname, opts) == EXIT_FAILURE) {
free(imgM);
Expand Down Expand Up @@ -3899,22 +3913,22 @@ int copyFile (char * src_path, char * dst_path) {
unsigned char buffer[BUFFSIZE];
FILE *fin = fopen(src_path, "rb");
if (fin == NULL) {
printf("Unable to open input %s\n", src_path);
printError("Check file permissions: Unable to open input %s\n", src_path);
return EXIT_FAILURE;
}
if (is_fileexists(dst_path)) {
printf("Skipping existing file %s\n", dst_path);
printError("File naming conflict. Existing file %s\n", dst_path);
return EXIT_FAILURE;
}
FILE *fou = fopen(dst_path, "wb");
if (fou == NULL) {
printf("Unable to open output %s\n", dst_path);
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) {
printf("Unable to write %zu bytes to output %s\n", bytes, dst_path);
printError("Unable to write %zu bytes to output %s\n", bytes, dst_path);
return EXIT_FAILURE;
}
}
Expand Down Expand Up @@ -4005,6 +4019,8 @@ int nii_loadDir(struct TDCMopts* opts) {
int nConvertTotal = 0;
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"
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
Expand All @@ -4020,8 +4036,14 @@ int nii_loadDir(struct TDCMopts* opts) {
//~ if ((dcmList[i].isValid) &&((dcmList[i].totalSlicesIn4DOrder != NULL) ||(dcmList[i].patientPositionNumPhilips > 1) || (dcmList[i].CSA.numDti > 1))) { //4D dataset: dti4D arrays require huge amounts of RAM - write this immediately
if ((dcmList[i].imageNum > 0) && (opts->isRenameNotConvert > 0)) { //use imageNum instead of isValid to convert non-images (kWaveformSq will have instance number but is not a valid image)
char outname[PATH_MAX] = {""};
if (dcmList[i].echoNum > 1) dcmList[i].isMultiEcho = true; //last resort: Siemens gives different echoes the same image number: avoid overwriting, e.g "-f %r.dcm" should generate "1.dcm", "1_e2.dcm" for multi-echo volumes
nii_createFilename(dcmList[i], outname, *opts);
copyFile (nameList.str[i], outname);
if (isDcmExt) strcat (outname,".dcm");
int ret = copyFile (nameList.str[i], outname);
if (ret != EXIT_SUCCESS) {
printError("Unable to rename all DICOM images.\n");
return ret;
}
if (opts->isVerbose > 0)
printMessage("Renaming %s -> %s\n", nameList.str[i], outname);
dcmList[i].isValid = false;
Expand Down Expand Up @@ -4090,10 +4112,10 @@ int nii_loadDir(struct TDCMopts* opts) {
if (nConvert < 1) nConvert = 1; //prevents compiler warning for next line: never executed since j=i always causes nConvert ++
TDCMsort * dcmSort = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort));
nConvert = 0;
isMultiEcho = false;
isNonParallelSlices = false;
isCoilVaries = false;
for (int j = i; j < (int)nDcm; j++)
for (int j = i; j < (int)nDcm; j++) {
isMultiEcho = false;
isNonParallelSlices = false;
isCoilVaries = false;
if (isSameSet(dcmList[i], dcmList[j], opts, &warnings, &isMultiEcho, &isNonParallelSlices, &isCoilVaries)) {
dcmList[j].converted2NII = 1; //do not reprocess repeats
fillTDCMsort(dcmSort[nConvert], j, dcmList[j]);
Expand All @@ -4111,7 +4133,9 @@ int nii_loadDir(struct TDCMopts* opts) {
dcmList[i].isCoilVaries = true;
dcmList[j].isCoilVaries = true;
}

} //unable to stack images: mark files that may need file name dis-ambiguation
}
qsort(dcmSort, nConvert, sizeof(struct TDCMsort), compareTDCMsort); //sort based on series and image numbers....
//dcmList[dcmSort[0].indx].isMultiEcho = isMultiEcho;
if (opts->isVerbose)
Expand Down

0 comments on commit 32160d7

Please sign in to comment.