From ff748611bb5b349502271e81eac391fdf43a0fb6 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Thu, 5 Aug 2021 14:17:22 -0400 Subject: [PATCH] Philips ASL volume order (https://github.com/rordenlab/dcm2niix/issues/529) --- Philips/README.md | 9 ++++++ README.md | 5 ++-- console/nii_dicom.cpp | 57 ++++++++++++++++++++++++++----------- console/nii_dicom.h | 8 ++++-- console/nii_dicom_batch.cpp | 53 +++++++++++++++++++++++----------- 5 files changed, 95 insertions(+), 37 deletions(-) diff --git a/Philips/README.md b/Philips/README.md index 97fa3138..4bce2ccd 100644 --- a/Philips/README.md +++ b/Philips/README.md @@ -71,6 +71,14 @@ Formulas: FP = DV / (RS * SS) ``` +## Volume Ordering + +The DICOM standard does not require that the [Instance Number (0020,0013)](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0020,0013)) be sequential or even unique. As a convention, most manufacturers provide instance numbers that are sequential with the temporal or spatial order. However, Philips often generates these values in a random order. This can lead to images appearing in a jumbled order when displayed with many DICOM viewers (e.g. [Horos](https://horosproject.org)). dcm2niix will attempt to automatically resolve this. Hopefully, fMRI volumes will be ordered temporally, diffusion data will be ordered by gradient number (with derived TRACE/ADC maps being made the final volume), and ASL scans will follow the order hierarchical order of [repeat, phase, label/control](https://github.com/neurolabusc/dcm_qa_philips_asl)). dcm2niix makes assumptions about the volume based on assumptions observed in previous Philips data. These heuristics may not be robust for future Philips data or for DICOM images that have been manipulated (e.g. anonymized, dcuncat, touched by an AGFA/dcmche PACS). Therefore, users need to use caution when dealing with Philips data converted by dcm2niix. + +## Arterial Spin Labelling + +Details and sample datasets for Philips Arterial Spin Labeling (ASL) are provided with the [dcm_qa_philips_asl](https://github.com/neurolabusc/dcm_qa_philips_asl) repository. + ## Derived parametric maps stored with raw diffusion data Some Philips diffusion DICOM images include derived image(s) along with the images. Other manufacturers save these derived images as a separate series number, and the DICOM standard seems ambiguous on whether it is allowable to mix raw and derived data in the same series (see PS 3.3-2008, C.7.6.1.1.2-3). In practice, many Philips diffusion images append [derived parametric maps](http://www.revisemri.com/blog/2008/diffusion-tensor-imaging/) with the original data. With Philips, appending the derived isotropic image is optional - it is only created for the 'clinical' DTI schemes for radiography analysis and is triggered if the first three vectors in the gradient table are the unit X,Y and Z vectors. For conventional DWI, the result is the conventional mean of the ADC X,Y,Z for DTI it the conventional mean of the 3 principle Eigen vectors. As scientists, we want to discard these derived images, as they will disrupt data processing and we can generate better parametric maps after we have applied undistortion methods such as [Eddy and Topup](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/eddy/UsersGuide). The current version of dcm2niix uses the Diffusion Directionality (0018,9075) tag to detect B=0 unweighted ("NONE"), B-weighted ("DIRECTIONAL"), and derived ("ISOTROPIC") images. Note that the Dimension Index Values (0020,9157) tag provides an alternative approach to discriminate these images. Here are sample tags from a Philips enhanced image that includes and derived map (3rd dimension is "1" while the other images set this to "2"). @@ -167,4 +175,5 @@ Prior versions of dcm2niix used different methods to sort images. However, these - [Archival samples](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Archival_MRI) - [Diffusion Examples](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging) - [Additional Diffusion Examples](https://github.com/neurolabusc/dcm_qa_philips) + - Classic and enhanced [ASL Examples](https://github.com/neurolabusc/dcm_qa_philips_asl) - [Enhanced DICOMs](https://github.com/neurolabusc/dcm_qa_enh) \ No newline at end of file diff --git a/README.md b/README.md index 5b78c731..c21e6c70 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ If you have any problems with the cmake build script described above or want to The following tools exploit dcm2niix - - [abcd-dicom2bids](https://github.com/DCAN-Labs/abcd-dicom2bids) selectively downloads high quality ABCD datasets. + - [abcd-dicom2bids](https://github.com/DCAN-Labs/abcd-dicom2bids) selectively downloads high quality ABCD datasets. - [autobids](https://github.com/khanlab/autobids) automates dcm2bids which uses dcm2niix. - [BiDirect_BIDS_Converter](https://github.com/wulms/BiDirect_BIDS_Converter) for conversion from DICOM to the BIDS standard. - [BIDScoin](https://github.com/Donders-Institute/bidscoin) is a DICOM to BIDS converter with a GUI and thorough [documentation](https://bidscoin.readthedocs.io). @@ -135,7 +135,6 @@ The following tools exploit dcm2niix - [Brain imAgiNg Analysis iN Arcana (Banana)](https://pypi.org/project/banana/) is a collection of brain imaging analysis workflows, it uses dcm2niix for format conversions. - [BraTS-Preprocessor](https://neuronflow.github.io/BraTS-Preprocessor/) uses dcm2niix to import files for [Brain Tumor Segmentation](https://www.frontiersin.org/articles/10.3389/fnins.2020.00125/full). - [clinica](https://github.com/aramis-lab/clinica) is a software platform for clinical neuroimaging studies that uses dcm2niix to convert DICOM images. - - [dcm2niix can help convert data from the Adolescent Brain Cognitive Development (ABCD) DICOM to BIDS](https://github.com/ABCD-STUDY/abcd-dicom2bids) - [bidsify](https://github.com/spinoza-rec/bidsify) is a Python project that uses dcm2niix to convert DICOM and Philips PAR/REC images to the BIDS standard. - [bidskit](https://github.com/jmtyszka/bidskit) uses dcm2niix to create [BIDS](http://bids.neuroimaging.io/) datasets. - [BioImage Suite Web Project](https://github.com/bioimagesuiteweb/bisweb) is a JavaScript project that uses dcm2niix for its DICOM conversion module. @@ -144,6 +143,7 @@ The following tools exploit dcm2niix - [DAC2BIDS](https://github.com/dangom/dac2bids) uses dcm2niibatch to create [BIDS](http://bids.neuroimaging.io/) datasets. - [Dcm2Bids](https://github.com/cbedetti/Dcm2Bids) uses dcm2niix to create [BIDS](http://bids.neuroimaging.io/) datasets. Here is a [tutorial](https://andysbrainbook.readthedocs.io/en/latest/OpenScience/OS/BIDS_Overview.html) describing usage. - [dcm2niir](https://github.com/muschellij2/dcm2niir) R wrapper for dcm2niix/dcm2nii. + - [dcm2niixpy](https://github.com/Svdvoort/dcm2niixpy) Python package of dcm2niix. - [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. - [dcm2niiXL](https://github.com/neurolabusc/dcm2niiXL) is a shell script and tuned compilation of dcm2niix designed for accelerated conversion of extra large datasets. - [DICOM2BIDS](https://github.com/klsea/DICOM2BIDS) is a Python 2 script for creating BIDS files. @@ -151,6 +151,7 @@ The following tools exploit dcm2niix - [dcmwrangle](https://github.com/jbteves/dcmwrangle) a Python interactive and static tool for organizing dicoms. - [dicom2nifti_batch](https://github.com/scanUCLA/dicom2nifti_batch) is a Matlab script for automating dcm2niix. - [divest](https://github.com/jonclayden/divest) R interface to dcm2niix. + - [ExploreASL](https://sites.google.com/view/exploreasl/exploreasl) uses dcm2niix to import images. - [ezBIDS](https://github.com/brainlife/ezbids) is a web service for converting directory full of DICOM images into BIDS without users having to learn python nor custom configuration file. - [fmrif tools](https://github.com/nih-fmrif/fmrif_tools) uses dcm2niix for its [oxy2bids](https://fmrif-tools.readthedocs.io/en/latest/#) tool. - [fMRIprep.dcm2niix](https://github.com/BrettNordin/fMRIprep.dcm2niix) is designed to convert DICOM format to the NIfTI format. diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 0fde0282..784e57f9 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -854,12 +854,13 @@ struct TDICOMdata clear_dicom_data() { d.epiVersionGE = -1; d.internalepiVersionGE = -1; d.durationLabelPulseGE = -1; - d.aslFlagsGE = 0; + d.aslFlags = kASL_FLAG_NONE; d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_UNKNOWN; d.mtState = -1; d.numberOfExcitations = -1; d.numberOfArms = -1; d.numberOfPointsPerArm = -1; + d.phaseNumber = - 1; //Philips Multi-Phase ASL d.spoiling = kSPOILING_UNKOWN; d.interp3D = -1; for (int i = 0; i < kMaxOverlay; i++) @@ -4191,6 +4192,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kMagnetizationTransferAttribute 0x0018 + uint32_t(0x9020 << 16) //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE' #define kRectilinearPhaseEncodeReordering 0x0018 + uint32_t(0x9034 << 16) //'CS' 'REVERSE_LINEAR'/'LINEAR' #define kPartialFourierDirection 0x0018 + uint32_t(0x9036 << 16) //'CS' +#define kCardiacSynchronizationTechnique 0x0018 + uint32_t(0x9037 << 16) //'CS' #define kParallelReductionFactorInPlane 0x0018 + uint32_t(0x9069 << 16) //FD #define kAcquisitionDuration 0x0018 + uint32_t(0x9073 << 16) //FD //#define kFrameAcquisitionDateTime 0x0018+uint32_t(0x9074<< 16 ) //DT "20181019212528.232500" @@ -4198,7 +4200,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kParallelAcquisitionTechnique 0x0018 + uint32_t(0x9078 << 16) //CS: SENSE, SMASH #define kInversionTimes 0x0018 + uint32_t(0x9079 << 16) //FD #define kPartialFourier 0x0018 + uint32_t(0x9081 << 16) //CS - const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); +const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //#define kDiffusionBFactorSiemens 0x0019+(0x100C<< 16 ) // 0019;000C;SIEMENS MR HEADER;B_value #define kDiffusion_bValue 0x0018 + uint32_t(0x9087 << 16) // FD #define kDiffusionOrientation 0x0018 + uint32_t(0x9089 << 16) // FD, seen in enhanced DICOM from Philips 5.* and Siemens XA10. @@ -4329,6 +4331,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kPMSCT_RLE1 0x07a1 + (0x100a << 16) //Elscint/Philips compression #define kPrivateCreator 0x2001 + (0x0010 << 16) // LO (Private creator is any tag where group is odd and element is x0010-x00FF #define kDiffusion_bValuePhilips 0x2001 + (0x1003 << 16) // FL +#define kPhaseNumber 0x2001 + (0x1008 << 16) //IS #define kCardiacSync 0x2001 + (0x1010 << 16) //CS //#define kDiffusionDirectionPhilips 0x2001+(0x1004 << 16 )//CS Diffusion Direction #define kSliceNumberMrPhilips 0x2001 + (0x100A << 16) //IS Slice_Number_MR @@ -4360,6 +4363,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kPrivatePerFrameSq 0x2005 + (0x140F << 16) #define kMRImageDiffBValueNumber 0x2005 + (0x1412 << 16) //IS #define kMRImageGradientOrientationNumber 0x2005+(0x1413 << 16) //IS +#define kMRImageLabelType 0x2005 + (0x1429 << 16) //CS ASL LBL_CTL https://github.com/physimals/dcm_convert_phillips/ #define kSharedFunctionalGroupsSequence 0x5200 + uint32_t(0x9229 << 16) // SQ #define kPerFrameFunctionalGroupsSequence 0x5200 + uint32_t(0x9230 << 16) // SQ #define kWaveformSq 0x5400 + (0x0100 << 16) @@ -4390,8 +4394,10 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D int overlayRows = 0; int overlayCols = 0; bool isTriggerSynced = false; + bool isProspectiveSynced = false; bool isDICOMANON = false; //issue383 bool isMATLAB = false; //issue383 + bool isASL = false; //double contentTime = 0.0; int echoTrainLengthPhil = 0; int philMRImageDiffBValueNumber = 0; @@ -4624,7 +4630,11 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D dcmDim[numDimensionIndexValues].intenScalePhilips = d.intenScalePhilips; dcmDim[numDimensionIndexValues].RWVScale = d.RWVScale; dcmDim[numDimensionIndexValues].RWVIntercept = d.RWVIntercept; - if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime)) + //printf("%d %d %g????\n", isTriggerSynced, isProspectiveSynced, d.triggerDelayTime); + if ((isASL) || (d.aslFlags != kASL_FLAG_NONE)) d.triggerDelayTime = 0.0; //see dcm_qa_philips_asl + if ((d.manufacturer == kMANUFACTURER_PHILIPS) && ((!isTriggerSynced) || (!isProspectiveSynced)) ) //issue408 + d.triggerDelayTime = 0.0; + if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime) ) dcmDim[numDimensionIndexValues].triggerDelayTime = 0.0; //issue395 else dcmDim[numDimensionIndexValues].triggerDelayTime = d.triggerDelayTime; @@ -5165,6 +5175,8 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D dcmStr(lLength, &buffer[lPos], acqContrast); if (((int)strlen(acqContrast) > 8) && (strstr(acqContrast, "DIFFUSION") != NULL)) d.isDiffusion = true; + if (((int)strlen(acqContrast) > 8) && (strstr(acqContrast, "PERFUSION") != NULL)) + isASL = true; //see series 301 of dcm_qa_philips_asl break; case kAcquisitionTime: { char acquisitionTimeTxt[kDICOMStr]; @@ -5335,6 +5347,10 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_COMBINATION; break; } + case kCardiacSynchronizationTechnique: + if (toupper(buffer[lPos]) == 'P') + isProspectiveSynced = true; + break; case kParallelReductionFactorInPlane: if (d.manufacturer == kMANUFACTURER_SIEMENS) break; @@ -6182,6 +6198,11 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D B0Philips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); set_bVal(&volDiffusion, B0Philips); break; + case kPhaseNumber: //IS issue529 + if (d.manufacturer != kMANUFACTURER_PHILIPS) + break; + d.phaseNumber = dcmStrInt(lLength, &buffer[lPos]); //see dcm_qa_philips_asl + break; case kCardiacSync: //CS [TRIGGERED],[NO] if (lLength < 2) break; @@ -6398,6 +6419,13 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D if (d.manufacturer == kMANUFACTURER_PHILIPS) gradientOrientationNumberPhilips = dcmStrInt(lLength, &buffer[lPos]); break; + case kMRImageLabelType : //CS ??? LBL CTL + if ((d.manufacturer != kMANUFACTURER_PHILIPS) || (lLength < 2)) break; + //TODO529: issue529 for ASL LBL/CTL "LABEL" + //if (toupper(buffer[lPos]) == 'L') isLabel = true; + if (toupper(buffer[lPos]) == 'L') d.aslFlags = kASL_FLAG_PHILIPS_LABEL; + if (toupper(buffer[lPos]) == 'C') d.aslFlags = kASL_FLAG_PHILIPS_CONTROL; + break; case kMRImageDiffBValueNumber: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; @@ -6577,12 +6605,12 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D if (d.manufacturer != kMANUFACTURER_GE) break; char st[kDICOMStr]; - //aslFlagsGE + //aslFlags dcmStr(lLength, &buffer[lPos], st); if (strstr(st, "PSEUDOCONTINUOUS") != NULL) - d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_PSEUDOCONTINUOUS); + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_PSEUDOCONTINUOUS); else if (strstr(st, "CONTINUOUS") != NULL) - d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_CONTINUOUS); + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_CONTINUOUS); break; } case kASLLabelingTechniqueGE: { //LO issue427GE @@ -6591,9 +6619,9 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D char st[kDICOMStr]; dcmStr(lLength, &buffer[lPos], st); if (strstr(st, "3D continuous") != NULL) - d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_3DCASL); + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_3DCASL); if (strstr(st, "3D pulsed continuous") != NULL) - d.aslFlagsGE = (d.aslFlagsGE | kASL_FLAG_GE_3DPCASL); + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_3DPCASL); break; } case kDurationLabelPulseGE: { //IS @@ -6782,7 +6810,6 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D isStr = true; if ((vr[0] == 'A') && (vr[1] == 'S')) isStr = true; - //if ((vr[0]=='A') && (vr[1]=='T')) isStr = xxx; if ((vr[0] == 'C') && (vr[1] == 'S')) isStr = true; if ((vr[0] == 'D') && (vr[1] == 'A')) @@ -7039,12 +7066,6 @@ 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 - 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++) { @@ -7260,8 +7281,8 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D strcpy(d.seriesInstanceUID, d.studyInstanceUID); d.seriesUidCrc = mz_crc32X((unsigned char *)&d.protocolName, strlen(d.protocolName)); } - if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (!isTriggerSynced)) //issue408 - d.triggerDelayTime = 0.0; + if ((d.manufacturer == kMANUFACTURER_PHILIPS) && ((!isTriggerSynced) || (!isProspectiveSynced)) ) //issue408 + d.triggerDelayTime = 0.0; //Philips ASL use "(0018,9037) CS [NONE]" but "(2001,1010) CS [TRIGGERED]", a situation not described in issue408 if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime)) //issue395 d.triggerDelayTime = 0.0; //printf("%d\t%g\t%g\t%g\n", d.imageNum, d.acquisitionTime, d.triggerDelayTime, MRImageDynamicScanBeginTime); @@ -7357,8 +7378,10 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D //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... + //printf("%d %d %d\n", d.rawDataRunNumber, volumeNumberMrPhilips, phaseNumber); d.rawDataRunNumber = (d.rawDataRunNumber > volumeNumberMrPhilips) ? d.rawDataRunNumber : volumeNumberMrPhilips; d.rawDataRunNumber = (d.rawDataRunNumber > gradientOrientationNumberPhilips) ? d.rawDataRunNumber : gradientOrientationNumberPhilips; + // d.rawDataRunNumber = (d.rawDataRunNumber > d.phaseNumber) ? d.rawDataRunNumber : d.phaseNumber; //will not work: conflict for MultiPhase ASL with multiple averages //end: issue529 if (hasDwiDirectionality) d.isVectorFromBMatrix = false; //issue 265: Philips/Siemens have both directionality and bmatrix, Bruker only has bmatrix diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 709283bb..cf2acb08 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -50,7 +50,7 @@ extern "C" { #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20210724" +#define kDCMdate "v1.0.202100805" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic @@ -110,10 +110,14 @@ static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI dire //0043,10A3 ---: PSEUDOCONTINUOUS //0043,10A4 ---: 3D pulsed continuous ASL technique +#define kASL_FLAG_NONE 0 #define kASL_FLAG_GE_3DPCASL 1 #define kASL_FLAG_GE_3DCASL 2 #define kASL_FLAG_GE_PSEUDOCONTINUOUS 4 #define kASL_FLAG_GE_CONTINUOUS 8 +#define kASL_FLAG_PHILIPS_CONTROL 16 +#define kASL_FLAG_PHILIPS_LABEL 32 + //for spoiling 0018,9016 #define kSPOILING_UNKOWN -1 @@ -201,7 +205,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; int xyzDim[5]; uint32_t coilCrc, seriesUidCrc, instanceUidCrc; int overlayStart[kMaxOverlay]; - int spoiling, mtState, partialFourierDirection, interp3D, aslFlagsGE, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; + int phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; float numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; float orient[7], patientPosition[4], patientPositionLast[4], xyzMM[4], stackOffcentre[4]; float rtia_timerGE, radionuclidePositronFraction, radionuclideTotalDose, radionuclideHalfLife, doseCalibrationFactor; //PET ISOTOPE MODULE ATTRIBUTES (C.8-57) diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 8f833a59..50cb5373 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1677,13 +1677,13 @@ tse3d: T2*/ } #endif //GE ASL specific tags - if (d.aslFlagsGE & kASL_FLAG_GE_CONTINUOUS) + if (d.aslFlags & kASL_FLAG_GE_CONTINUOUS) fprintf(fp, "\t\"ASLContrastTechnique\": \"CONTINUOUS\",\n"); - if (d.aslFlagsGE & kASL_FLAG_GE_PSEUDOCONTINUOUS) + if (d.aslFlags & kASL_FLAG_GE_PSEUDOCONTINUOUS) fprintf(fp, "\t\"ASLContrastTechnique\": \"PSEUDOCONTINUOUS\",\n"); - if (d.aslFlagsGE & kASL_FLAG_GE_3DPCASL) + if (d.aslFlags & kASL_FLAG_GE_3DPCASL) fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D pulsed continuous ASL technique\",\n"); - if (d.aslFlagsGE & kASL_FLAG_GE_3DCASL) + if (d.aslFlags & kASL_FLAG_GE_3DCASL) fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D continuous ASL technique\",\n"); if (d.durationLabelPulseGE > 0) { json_Float(fp, "\t\"LabelingDuration\": %g,\n", d.durationLabelPulseGE / 1000.0); @@ -2536,28 +2536,48 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s isConsistent = false; //direction reverses } } - if (isConsistent) - return true; + //if (isConsistent) + // return true; TFloatSort *floatSort = (TFloatSort *)malloc(nConvert * sizeof(TFloatSort)); int minVol = dcmList[dcmSort[0].indx].rawDataRunNumber; int maxVol = minVol; + int maxPhase = 1; //Philips Multi-Phase for (int i = 0; i < nConvert; i++) { - dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); int vol = dcmList[dcmSort[i].indx].rawDataRunNumber; - if (verbose > 1) //only report slice data for logorrheic verbosity - printf("slice %d position %g volume %d\n", i, dx, vol); - floatSort[i].volume = vol; - if (vol > kMaxDTI4D) //issue529 Philips derived Trace/ADC embedded into DWI - vol = d4; minVol = min(minVol, vol); maxVol = max(maxVol, vol); + maxPhase = max(maxPhase, dcmList[dcmSort[i].indx].phaseNumber); + } + bool isASL = (dcmList[dcmSort[0].indx].aslFlags != kASL_FLAG_NONE); + //we will renumber volumes for Philips ASL (Contrast/Label, phase) and DWI (derived trace) + int minVolOut = kMaxDTI4D + 1; + int maxVolOut = -1; + for (int i = 0; i < nConvert; i++) { + int vol = dcmList[dcmSort[i].indx].rawDataRunNumber; + int rawvol = vol; + int phase = max(1, dcmList[dcmSort[i].indx].phaseNumber); + int isAslLabel = dcmList[dcmSort[i].indx].aslFlags == kASL_FLAG_PHILIPS_LABEL; + dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); + if (isASL) { + //choose order of volumes, see dcm_qa_philips_asl + vol += (phase - 1) * maxVol; + if (isAslLabel) + vol += maxPhase * maxVol; + } + //if (verbose > 1) //only report slice data for logorrheic verbosity + printf("instanceNumber %4d position %g volume %d repeat %d ASLlabel %d phase %d\n", dcmList[dcmSort[i].indx].imageNum, dx, vol, rawvol, isAslLabel, phase); + if (vol > kMaxDTI4D) //issue529 Philips derived Trace/ADC embedded into DWI + vol = maxVol + 1; + minVolOut = min(minVolOut, vol); + maxVolOut = max(maxVolOut, vol); + floatSort[i].volume = 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"); + //n.b. should we change dim[3] and dim[4] if number of volumes = dim[3]? + if ((maxVolOut-minVolOut+1) != d4) + printError("Check sorted order: 4D dataset has %d volumes, but volume index ranges from %d..%d\n", d4, minVolOut, maxVolOut); + //printf("dx = %g instance %d %d\n", intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]), dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[1].indx].imageNum); TDCMsort *dcmSortIn = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); for (int i = 0; i < nConvert; i++) dcmSortIn[i] = dcmSort[i]; @@ -2566,6 +2586,7 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s dcmSort[i] = dcmSortIn[floatSort[i].index]; free(floatSort); free(dcmSortIn); + //printf("dx = %g instance %d %d\n", intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]), dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[1].indx].imageNum); return false; } // ensureSequentialSlicePositions()