diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 72cf0a9c..b9bfb4cf 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -4239,9 +4239,8 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kSeriesInstanceUID 0x0020 + (0x000E << 16) #define kImagePositionPatient 0x0020 + (0x0032 << 16) // Actually ! #define kOrientationACR 0x0020 + (0x0035 << 16) -//#define kTemporalPositionIdentifier 0x0020+(0x0100 << 16 ) //IS #define kOrientation 0x0020 + (0x0037 << 16) -//#define kTemporalPosition 0x0020+(0x0100 << 16 ) //IS +#define kTemporalPosition 0x0020+(0x0100 << 16 ) //IS //#define kNumberOfTemporalPositions 0x0020+(0x0105 << 16 ) //IS public tag for NumberOfDynamicScans #define kTemporalResolution 0x0020 + (0x0110 << 16) //DS #define kImagesInAcquisition 0x0020 + (0x1002 << 16) //IS @@ -4360,7 +4359,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kDiffusionDirectionFH 0x2005 + (0x10B2 << 16) #define kPrivatePerFrameSq 0x2005 + (0x140F << 16) #define kMRImageDiffBValueNumber 0x2005 + (0x1412 << 16) //IS -//#define kMRImageGradientOrientationNumber 0x2005+(0x1413 << 16) //IS +#define kMRImageGradientOrientationNumber 0x2005+(0x1413 << 16) //IS #define kSharedFunctionalGroupsSequence 0x5200 + uint32_t(0x9229 << 16) // SQ #define kPerFrameFunctionalGroupsSequence 0x5200 + uint32_t(0x9230 << 16) // SQ #define kWaveformSq 0x5400 + (0x0100 << 16) @@ -4416,7 +4415,8 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D int imagesInAcquisition = 0; //int sumSliceNumberMrPhilips = 0; int sliceNumberMrPhilips = 0; - int volumeNumberMrPhilips = 0; + int volumeNumberMrPhilips = -1; + int gradientOrientationNumberPhilips = -1; int numberOfFrames = 0; //int MRImageGradientOrientationNumber = 0; //int minGradNum = kMaxDTI4D + 1; @@ -6114,7 +6114,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D } case kMRfMRIStatusIndicationPhilips: //fmri volume number if (d.manufacturer != kMANUFACTURER_PHILIPS) - break; //see GE dataset in dcm_qa_nih + break; volumeNumberMrPhilips = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kMRAcquisitionTypePhilips: //kMRAcquisitionType @@ -6390,9 +6390,10 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D // printWarning("expected VR of 2005,140F to be 'SQ' (prior DICOM->DICOM conversion error?)\n"); is2005140FSQ = true; //is2005140FSQwarned = true; - //case kMRImageGradientOrientationNumber : - // if (d.manufacturer == kMANUFACTURER_PHILIPS) - // MRImageGradientOrientationNumber = dcmStrInt(lLength, &buffer[lPos]); + break; + case kMRImageGradientOrientationNumber : + if (d.manufacturer == kMANUFACTURER_PHILIPS) + gradientOrientationNumberPhilips = dcmStrInt(lLength, &buffer[lPos]); break; case kMRImageDiffBValueNumber: if (d.manufacturer != kMANUFACTURER_PHILIPS) @@ -6651,6 +6652,11 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D isOrient = true; break; } + case kTemporalPosition: //fall through, both kSliceNumberMrPhilips (2001,100A) and kTemporalPosition are is + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + volumeNumberMrPhilips = dcmStrInt(lLength, &buffer[lPos]); + break; case kTemporalResolution: temporalResolutionMS = dcmStrFloat(lLength, &buffer[lPos]); break; @@ -7030,12 +7036,12 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D exit(kEXIT_CORRUPT_FILE_FOUND); #endif } - if ((numDimensionIndexValues == 0) && (sliceNumberMrPhilips > 0) && (volumeNumberMrPhilips > 0) && (locationsInAcquisitionPhilips > 0)) {//issue529 + /*if ((numDimensionIndexValues == 0) && (sliceNumberMrPhilips > 0) && (volumeNumberMrPhilips > 0) && (locationsInAcquisitionPhilips > 0)) {//issue529 int instanceNum = ((volumeNumberMrPhilips-1) * locationsInAcquisitionPhilips) + sliceNumberMrPhilips; if ((d.imageNum != instanceNum) && (isVerbose)) printWarning("Philips instance number (%d) does not make sense: slice %d of %d, volume %d\n", d.imageNum, sliceNumberMrPhilips, locationsInAcquisitionPhilips, volumeNumberMrPhilips); d.imageNum = instanceNum; - } + }*/ if ((numberOfFrames > 1) && (numDimensionIndexValues == 0) && (numberOfFrames == nSliceMM)) { //issue 372 fidx *objects = (fidx *)malloc(sizeof(struct fidx) * numberOfFrames); for (int i = 0; i < numberOfFrames; i++) { @@ -7345,6 +7351,12 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D //in practice 0020,0110 not used //https://github.com/bids-standard/bep001/blob/repetitiontime/Proposal_RepetitionTime.md } + //start: issue529 + if ((!isSameFloat(d.CSA.dtiV[0], 0.0f)) && ((isSameFloat(d.CSA.dtiV[1], 0.0f)) && (isSameFloat(d.CSA.dtiV[2], 0.0f)) && (isSameFloat(d.CSA.dtiV[3], 0.0f)) ) ) + gradientOrientationNumberPhilips = kMaxDTI4D + 1; //Philips includes derived Trace/ADC images into raw DTI, these should be removed... + d.rawDataRunNumber = (d.rawDataRunNumber > volumeNumberMrPhilips) ? d.rawDataRunNumber : volumeNumberMrPhilips; + d.rawDataRunNumber = (d.rawDataRunNumber > gradientOrientationNumberPhilips) ? d.rawDataRunNumber : gradientOrientationNumberPhilips; + //end: issue529 if (hasDwiDirectionality) d.isVectorFromBMatrix = false; //issue 265: Philips/Siemens have both directionality and bmatrix, Bruker only has bmatrix //printf("%s\t%s\t%s\t%s\t%s_%s\n",d.patientBirthDate, d.procedureStepDescription,d.patientName, fname, d.studyDate, d.studyTime); diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 9f1fc9b4..9ba81492 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -76,6 +76,39 @@ const char kFileSep[2] = "/"; #define newTilt +#ifdef USING_R + +#ifndef max +#define max(a, b) std::max(a, b) +#endif + +#ifndef min +#define min(a, b) std::min(a, b) +#endif + +#else + +#ifndef max +#define max(a, b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) +#endif + +#ifndef min +#define min(a, b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) +#endif + +#endif + +bool isADCnotDTI(TDTI bvec) { //returns true if bval!=0 but all bvecs == 0 (Philips code for derived ADC image) + return ((!isSameFloat(bvec.V[0], 0.0f)) && //not a B-0 image + ((isSameFloat(bvec.V[1], 0.0f)) && (isSameFloat(bvec.V[2], 0.0f)) && (isSameFloat(bvec.V[3], 0.0f)))); +} + struct TDCMsort { uint64_t indx, img; uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; @@ -1869,11 +1902,6 @@ void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, #endif -bool isADCnotDTI(TDTI bvec) { //returns true if bval!=0 but all bvecs == 0 (Philips code for derived ADC image) - return ((!isSameFloat(bvec.V[0], 0.0f)) && //not a B-0 image - ((isSameFloat(bvec.V[1], 0.0f)) && (isSameFloat(bvec.V[2], 0.0f)) && (isSameFloat(bvec.V[3], 0.0f)))); -} - unsigned char *removeADC(struct nifti_1_header *hdr, unsigned char *inImg, int numADC) { //for speed we just clip the number of volumes, the realloc routine would be nice // we do not want to copy input to a new smaller array since 4D DTI datasets can be huge @@ -2461,16 +2489,20 @@ float intersliceDistanceSigned(struct TDICOMdata d1, struct TDICOMdata d2) { //https://stackoverflow.com/questions/36714030/c-sort-float-array-while-keeping-track-of-indices/36714204 struct TFloatSort { - float value; - int index; + float position; + int volume, index; }; int compareTFloatSort(const void *a, const void *b) { struct TFloatSort *a1 = (struct TFloatSort *)a; struct TFloatSort *a2 = (struct TFloatSort *)b; - if ((*a1).value > (*a2).value) + if ((*a1).volume > (*a2).volume) return 1; - if ((*a1).value < (*a2).value) + if ((*a1).volume < (*a2).volume) + return -1; + if ((*a1).position > (*a2).position) + return 1; + if ((*a1).position < (*a2).position) return -1; //if value is tied, retain index order (useful for TXYZ images?) if ((*a1).index > (*a2).index) @@ -2489,35 +2521,46 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s float dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); bool isAscending1 = (dx > 0); bool isConsistent = true; - for (int i = 1; i < d3; i++) { - dx = intersliceDistanceSigned(dcmList[dcmSort[i - 1].indx], dcmList[dcmSort[i].indx]); - bool isAscending = (dx > 0); - if (isAscending != isAscending1) - isConsistent = false; //direction reverses + for (int v = 0; v < d4; v++) { + int volStart = v * d3; + for (int i = 1; i < d3; i++) { + dx = intersliceDistanceSigned(dcmList[dcmSort[volStart + i - 1].indx], dcmList[dcmSort[volStart + i].indx]); + bool isAscending = (dx > 0); + //printf("volume %d slice %d distanceFromSlice1 %g DICOMvolume %d\n", v, i+1, dx, dcmList[dcmSort[volStart + i].indx].rawDataRunNumber); + if (isAscending != isAscending1) + isConsistent = false; //direction reverses + } } if (isConsistent) return true; - printWarning("Order specified by DICOM instance number is not spatial (reordering).\n"); - TFloatSort *floatSort = (TFloatSort *)malloc(d3 * sizeof(TFloatSort)); - for (int i = 0; i < d3; i++) { + TFloatSort *floatSort = (TFloatSort *)malloc(nConvert * sizeof(TFloatSort)); + int minVol = dcmList[dcmSort[0].indx].rawDataRunNumber; + int maxVol = minVol; + for (int i = 0; i < nConvert; i++) { dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); - floatSort[i].value = dx; + int vol = dcmList[dcmSort[i].indx].rawDataRunNumber; + floatSort[i].volume = vol; + if (vol > kMaxDTI4D) //issue529 Philips derived Trace/ADC embedded into DWI + vol = d4 + 1; + minVol = min(minVol, vol); + maxVol = max(maxVol, vol); + floatSort[i].position = dx; floatSort[i].index = i; } + if ((maxVol-minVol+1) != d4) + printError("Check sorted order: 4D dataset has %d volumes, but volume index ranges from %d..%d\n", d4, minVol, maxVol); + else + printWarning("Order specified by DICOM instance number is not spatial (reordering).\n"); TDCMsort *dcmSortIn = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); for (int i = 0; i < nConvert; i++) dcmSortIn[i] = dcmSort[i]; - qsort(floatSort, d3, sizeof(struct TFloatSort), compareTFloatSort); //sort based on series and image numbers.... - for (int vol = 0; vol < d4; vol++) { - int volInc = vol * d3; - for (int i = 0; i < d3; i++) - dcmSort[volInc + i] = dcmSortIn[volInc + floatSort[i].index]; - } + qsort(floatSort, nConvert, sizeof(struct TFloatSort), compareTFloatSort); //sort based on series and image numbers.... + for (int i = 0; i < nConvert; i++) + dcmSort[i] = dcmSortIn[floatSort[i].index]; free(floatSort); free(dcmSortIn); return false; } // ensureSequentialSlicePositions() -//#endif //myInstanceNumberOrderIsNotSpatial void swapDim3Dim4(int d3, int d4, struct TDCMsort dcmSort[]) { //swap space and time: input A0,A1...An,B0,B1...Bn output A0,B0,A1,B1,... @@ -3686,34 +3729,6 @@ int nii_saveNRRD(char *niiFilename, struct nifti_1_header hdr, unsigned char *im #endif -#ifdef USING_R - -#ifndef max -#define max(a, b) std::max(a, b) -#endif - -#ifndef min -#define min(a, b) std::min(a, b) -#endif - -#else - -#ifndef max -#define max(a, b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a > _b ? _a : _b; }) -#endif - -#ifndef min -#define min(a, b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a < _b ? _a : _b; }) -#endif - -#endif - void removeSclSlopeInter(struct nifti_1_header *hdr, unsigned char *img) { //NRRD does not have scl_slope scl_inter. Adjust data if possible // https://discourse.slicer.org/t/preserve-image-rescale-and-slope-when-saving-in-nrrd-file/13357 @@ -5490,6 +5505,9 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d //next: detect variable inter-volume time https://github.com/rordenlab/dcm2niix/issues/184 //if ((nConvert > 1) && ((dcmList[indx0].modality == kMODALITY_PT)|| (opts.isForceOnsetTimes))) { if ((nConvert > 1) && ((dcmList[indx0].modality == kMODALITY_PT) || ((opts.isForceOnsetTimes) && (dcmList[indx0].manufacturer != kMANUFACTURER_GE)))) { + if (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_PHILIPS) + ensureSequentialSlicePositions(hdr0.dim[3], hdr0.dim[4], dcmSort, dcmList); //issue529 + //printf("Bogo529\n"); return EXIT_SUCCESS; //note: GE 0008,0032 unreliable, see mb=6 data from sw27.0 20201026 //issue 407 int nTR = 0;