Skip to content

Commit

Permalink
Philips ASL volume order (#529)
Browse files Browse the repository at this point in the history
  • Loading branch information
neurolabusc committed Aug 5, 2021
1 parent 89de6e7 commit ff74861
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 37 deletions.
9 changes: 9 additions & 0 deletions Philips/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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").
Expand Down Expand Up @@ -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)
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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.
Expand All @@ -144,13 +143,15 @@ 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.
- [dicom2bids](https://github.com/Jolinda/lcnimodules) includes python modules for converting dicom files to nifti in a bids-compatible file structure that use 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.
Expand Down
57 changes: 40 additions & 17 deletions console/nii_dicom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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++)
Expand Down Expand Up @@ -4191,14 +4192,15 @@ 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"
#define kDiffusionDirectionality 0x0018 + uint32_t(0x9075 << 16) // NONE, ISOTROPIC, or DIRECTIONAL
#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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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'))
Expand Down Expand Up @@ -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++) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit ff74861

Please sign in to comment.