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