Skip to content

Commit

Permalink
PET BIDS (#802)
Browse files Browse the repository at this point in the history
  • Loading branch information
neurolabusc committed Mar 11, 2024
1 parent cb60b43 commit c975d1f
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 25 deletions.
47 changes: 29 additions & 18 deletions BIDS/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ These fields are present regardless of modality (e.g. MR, CT, PET).

| Field | Unit | Comments | Defined By |
|--------------------------|------|---------------------|------------|
| BodyPartExamined | | DICOM tag 0018,0015 | D |
| BodyPart | | DICOM tag 0018,0015 | B |
| PatientPosition | | DICOM tag 0020,0032 | D |
| ProcedureStepDescription | | DICOM tag 0040,0254 | D |
| SoftwareVersions | | DICOM tag 0020,1020 | B |
Expand Down Expand Up @@ -107,6 +107,9 @@ These fields contain personally identifiable information. By default dcm2niix wi
| PatientID | | DICOM tag 0010,0020 | D |
| AccessionNumber | | DICOM tag 0008,0050 | D |
| PatientBirthDate | | DICOM tag 0010,0030 | D |
| PatientSex | | DICOM tag 0010,0040 | D |
| PatientAge | | DICOM tag 0010,1010 | D |
| PatientSize | | DICOM tag 0010,1020 | D |
| PatientWeight | kg | DICOM tag 0010,1030 | D |
| AcquisitionDateTime | | DICOM tag 0008,002A | D |

Expand Down Expand Up @@ -181,23 +184,31 @@ PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/curre

The term ECAT in the comments suggests that values are defined by the [ECAT7](http://www.turkupetcentre.net/petanalysis/format_image_ecat.html) format. Therefore, these fields will not be populated for DICOM data.

| Field | Unit | Comments | Defined By |
|------------------------------|------|-----------------------------|------------|
| Radiopharmaceutical | | DICOM tag 0018,0031 or ECAT | D |
| RadionuclidePositronFraction | f | DICOM tag 0018,1076 | D |
| RadionuclideTotalDose | MBq | DICOM tag 0018,1074 | D |
| RadionuclideHalfLife | s | DICOM tag 0018,1075 | D |
| DoseCalibrationFactor | | DICOM tag 0054,1322 | D |
| IsotopeHalfLife | | ECAT | D |
| Dosage | | ECAT | D |
| ConvolutionKernel | | DICOM tag 0018,1210 | D |
| Units | | DICOM tag 0054,1001 | D |
| DecayCorrection | | DICOM tag 0054,1102 | D |
| AttenuationCorrectionMethod | | DICOM tag 0054,1101 | D |
| ReconstructionMethod | | DICOM tag 0054,1103 | D |
| DecayFactor | | DICOM tag 0054,1321 | D |
| FrameTimesStart | s | DICOM tags 0008,0022 | D |
| FrameDuration | s | DICOM tag 0018,1242 | D |
| Field | Unit | Comments | Defined By |
|------------------------------|------|----------------------------------|------------|
| IsotopeHalfLife | | ECAT | D |
| Dosage | | ECAT | D |
| FrameTimesStart | s | DICOM tag 0008,0022 | D |
| TracerRadionuclide | | DICOM tag 0008,0100 or 0008,0104 | B |
| Radiopharmaceutical | | DICOM tag 0018,0031 or ECAT | D |
| InjectedRadioactivity | MBq | DICOM tag 0018,1074 | B |
| RadionuclideHalfLife | s | DICOM tag 0018,1075 | D |
| RadionuclidePositronFraction | f | DICOM tag 0018,1076 | D |
| ConvolutionKernel | | DICOM tag 0018,1210 | D |
| Units | | DICOM tag 0054,1001 | B |
| AttenuationCorrectionMethod | | DICOM tag 0054,1101 | B |
| DecayCorrection | | DICOM tag 0054,1102 | D |
| ReconstructionMethod | | DICOM tag 0054,1103 | B |
| DecayCorrectionFactor | | DICOM tag 0054,1321 | B |
| DoseCalibrationFactor | | DICOM tag 0054,1322 | B |
| ScatterFraction | | DICOM tag 0054,1323 | B |
| FrameDuration | s | DICOM tag 0018,1242 | B |

n.b. ConvolutionKernel (0018,1210) can be parsed to BIDS `ReconFilterType` and `ReconFilterSize`. However, manufacturer variations (Siemens: `XYZGAUSSIAN3.00` GE `Rad: \ rectangle \ 4.000000 mm \ Ax: \ rectangle \ 8.500000 mm` require complex logic).

n.b. ReconstructionMethod (0054,1103) can be parsed to ReconMethodName, ReconMethodParameterLabels, ReconMethodParameterUnits and ReconMethodParameterValues. However, this requires maniufacturer specific logic (e.g. Siemens `OP-OSEM4i21s`, GE `3D Kinahan - Rogers`

For Philips scanners, [Source Isotope Name](https://dicom.innolitics.com/ciods/rt-brachy-treatment-record/rt-brachy-session-record/30080100/300a0226) may be a good source for TracerRadionuclide

## Manufacturer Fields

Expand Down
37 changes: 37 additions & 0 deletions console/nii_dicom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,7 @@ struct TDICOMdata clear_dicom_data() {
strcpy(d.convolutionKernel, "");
strcpy(d.parallelAcquisitionTechnique, "");
strcpy(d.imageOrientationText, "");
strcpy(d.tracerRadionuclide, "");
strcpy(d.unitsPT, "");
strcpy(d.decayCorrection, "");
strcpy(d.attenuationCorrectionMethod, "");
Expand Down Expand Up @@ -833,6 +834,7 @@ struct TDICOMdata clear_dicom_data() {
d.shimGradientZ = -33333;//impossible value for UINT16
strcpy(d.prescanReuseString, "");
d.decayFactor = 0.0;
d.scatterFraction = 0.0;
d.percentSampling = 0.0;
d.phaseFieldofView = 0.0;
d.dwellTime = 0;
Expand Down Expand Up @@ -936,6 +938,7 @@ struct TDICOMdata clear_dicom_data() {
d.numberOfImagesInGridUIH = 0;
d.phaseEncodingRC = '?';
d.patientSex = '?';
d.patientSize = 0.0;
d.patientWeight = 0.0;
strcpy(d.patientBirthDate, "");
strcpy(d.patientAge, "");
Expand Down Expand Up @@ -4362,6 +4365,8 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D
#define kInstitutionName 0x0008 + (0x0080 << 16)
#define kInstitutionAddress 0x0008 + (0x0081 << 16)
#define kReferringPhysicianName 0x0008 + (0x0090 << 16)
#define kTracerRadionuclide1 0x0008 + (0x0100 << 16) //SH
#define kTracerRadionuclide2 0x0008 + (0x0104 << 16) //LO
#define kStationName 0x0008 + (0x1010 << 16)
#define kSeriesDescription 0x0008 + (0x103E << 16) // '0008' '103E' 'LO' 'SeriesDescription'
#define kInstitutionalDepartmentName 0x0008 + (0x1040 << 16)
Expand All @@ -4376,6 +4381,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D
#define kPatientBirthDate 0x0010 + (0x0030 << 16)
#define kPatientSex 0x0010 + (0x0040 << 16)
#define kPatientAge 0x0010 + (0x1010 << 16)
#define kPatientSize 0x0010 + (0x1020 << 16) //DS
#define kPatientWeight 0x0010 + (0x1030 << 16)
#define kAnatomicalOrientationType 0x0010 + (0x2210 << 16)
#define kDeidentificationMethod 0x0012 + (0x0063 << 16) //[DICOMANON, issue 383
Expand Down Expand Up @@ -4581,6 +4587,7 @@ const uint32_t kEffectiveTE = 0x0018 + uint32_t(0x9082 << 16); //FD
#define kReconstructionMethod 0x0054 + (0x1103 << 16) //LO
#define kFrameReferenceTime 0x0054 + (0x1300 << 16) //DS
#define kDecayFactor 0x0054 + (0x1321 << 16) //DS
#define kScatterFraction 0x0054 + (0x1323 << 16) //DS
//ftp://dicom.nema.org/MEDICAL/dicom/2014c/output/chtml/part03/sect_C.8.9.4.html
//If ImageType is REPROJECTION we slice direction is reversed - need example to test
// #define kSeriesType 0x0054+(0x1000 << 16 )
Expand Down Expand Up @@ -5656,9 +5663,35 @@ const uint32_t kEffectiveTE = 0x0018 + uint32_t(0x9082 << 16); //FD
case kPatientAge:
dcmStr(lLength, &buffer[lPos], d.patientAge);
break;
case kPatientSize:
d.patientSize = dcmStrFloat(lLength, &buffer[lPos]);
break;
case kPatientWeight:
d.patientWeight = dcmStrFloat(lLength, &buffer[lPos]);
break;
/* n.b. check GeneralElectricAdvance-NIMH example dataset: need to speficy SQ

Check failure on line 5672 in console/nii_dicom.cpp

View workflow job for this annotation

GitHub Actions / Check for spelling errors

speficy ==> specify
(0018,0031) LO [FDG -- fluorodeoxyglucose ] # 26,1 Radiopharmaceutical
(0018,1071) DS [0 ] # 2,1 Radiopharmaceutical Volume
(0018,1072) TM [092345.00 ] # 10,1 Radiopharmaceutical Start Time
(0018,1074) DS [ 75850000] # 14,1 Radionuclide Total Dose
(0018,1075) DS [6588] # 4,1 Radionuclide Half Life
(0018,1076) DS [0.97000002861023] # 16,1 Radionuclide Positron Fraction
(0054,0300) SQ (Sequence with undefined length) # u/l,1 Radionuclide Code Sequence
(fffe,e000) na (Item with defined length)
(0008,0100) SH [C-111A1 ] # 8,1 Code Value
(0008,0102) SH [99SDM ] # 6,1 Coding Scheme Designator
(0008,0104) LO [18F ] # 4,1 Code Meaning
(fffe,e0dd)
(0054,0304) SQ (Sequence with undefined length) # u/l,1 Radiopharmaceutical Code Sequence
(fffe,e000) na (Item with defined length)
(0008,0100) SH [Y-X1743 ] # 8,1 Code Value
(0008,0102) SH [99SDM ] # 6,1 Coding Scheme Designator
(0008,0104) LO [FDG -- fluorodeoxyglucose ] # 26,1 Code Meaning
(fffe,e0dd)
case kTracerRadionuclide1:
case kTracerRadionuclide2:
dcmStr(lLength, &buffer[lPos], d.tracerRadionuclide);
break;*/
case kStationName:
dcmStr(lLength, &buffer[lPos], d.stationName);
break;
Expand Down Expand Up @@ -6645,6 +6678,10 @@ const uint32_t kEffectiveTE = 0x0018 + uint32_t(0x9082 << 16); //FD
case kDecayFactor:
d.decayFactor = dcmStrFloat(lLength, &buffer[lPos]);
break;
case kScatterFraction:
d.scatterFraction = dcmStrFloat(lLength, &buffer[lPos]);
printf("SF%g\n", d.scatterFraction); //for each slice?
break;
case kIconImageSequence:
if (lLength > 8)
break; //issue638: we will skip entire icon if there is an explicit length
Expand Down
6 changes: 3 additions & 3 deletions console/nii_dicom.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ extern "C" {
#define kCPUsuf " " //unknown CPU
#endif

#define kDCMdate "v1.0.20240229"
#define kDCMdate "v1.0.20240308"
#define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf

static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic
Expand Down Expand Up @@ -241,13 +241,13 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8;
uint32_t coilCrc, seriesUidCrc, instanceUidCrc;
int overlayStart[kMaxOverlay];
int postLabelDelay, shimGradientX, shimGradientY, shimGradientZ, phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfTR, numberOfImagesInGridUIH, numberOfDiffusionT2GE, numberOfDiffusionDirectionGE, tensorFileGE, diffCyclingModeGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, frequencyEncodingSteps, phaseEncodingStepsOutOfPlane, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme;
float compressedSensingFactor, xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4], velocityEncodeScaleGE;
float compressedSensingFactor, xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, scatterFraction, percentSampling,waterFatShift, numberOfAverages, patientSize, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4], velocityEncodeScaleGE;
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)
float frameReferenceTime, frameDuration, ecat_isotope_halflife, ecat_dosage;
float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present.
double imagingFrequency, acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode;
char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr], transferSyntax[kDICOMStr];
char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], tracerRadionuclide[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr], transferSyntax[kDICOMStr];
char prescanReuseString[kDICOMStr], imageOrientationText[kDICOMStr], pulseSequenceName[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], stationName[kDICOMStr], softwareVersions[kDICOMStr], deviceSerialNumber[kDICOMStr], institutionName[kDICOMStr], referringPhysicianName[kDICOMStr], instanceUID[kDICOMStr], seriesInstanceUID[kDICOMStr], studyInstanceUID[kDICOMStr], bodyPartExamined[kDICOMStr], procedureStepDescription[kDICOMStr], imageTypeText[kDICOMStr], imageType[kDICOMStr], institutionalDepartmentName[kDICOMStr], manufacturersModelName[kDICOMStr], patientID[kDICOMStr], patientOrient[kDICOMStr], patientName[kDICOMStr], accessionNumber[kDICOMStr], seriesDescription[kDICOMStr], studyID[kDICOMStr], sequenceName[kDICOMStr], protocolName[kDICOMStr],sequenceVariant[kDICOMStr],scanningSequence[kDICOMStr], patientBirthDate[kDICOMStr], patientAge[kDICOMStr], studyDate[kDICOMStr],studyTime[kDICOMStr];
char deepLearningText[kDICOMStrLarge], scanOptions[kDICOMStrLarge], institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge];
uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS];
Expand Down
10 changes: 6 additions & 4 deletions console/nii_dicom_batch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1371,6 +1371,7 @@ tse3d: T2*/
}
if (d.patientSex != '?')
fprintf(fp, "\t\"PatientSex\": \"%c\",\n", d.patientSex);
json_Float(fp, "\t\"PatientSize\": %g,\n", d.patientSize);
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;
Expand All @@ -1385,7 +1386,7 @@ tse3d: T2*/
}
if (d.isQuadruped)
json_Bool(fp, "\t\"Quadruped\": %s,\n", true); // BIDS suggests 0018,9020 but Siemens V-series do not populate this, alternatives are CSA or (0018,0021) CS [SK\MTC\SP]
json_Str(fp, "\t\"BodyPartExamined\": \"%s\",\n", d.bodyPartExamined);
json_Str(fp, "\t\"BodyPart\": \"%s\",\n", d.bodyPartExamined); //renamed to match BIDS https://bids-specification.readthedocs.io/en/stable/modality-specific-files/positron-emission-tomography.html
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);
Expand Down Expand Up @@ -1497,19 +1498,20 @@ tse3d: T2*/
if ((d.isRealIsPhaseMapHz) && ((d.manufacturer == kMANUFACTURER_GE) || (d.isHasReal)))
fprintf(fp, "\t\"Units\": \"Hz\",\n"); //
//PET ISOTOPE MODULE ATTRIBUTES
json_Str(fp, "\t\"TracerRadionuclide\": \"%s\",\n", d.tracerRadionuclide);
json_Str(fp, "\t\"Radiopharmaceutical\": \"%s\",\n", d.radiopharmaceutical);
json_Float(fp, "\t\"RadionuclidePositronFraction\": %g,\n", d.radionuclidePositronFraction);
json_Float(fp, "\t\"RadionuclideTotalDose\": %g,\n", d.radionuclideTotalDose);
json_Float(fp, "\t\"InjectedRadioactivity\": %g,\n", d.radionuclideTotalDose); //renamed https://bids-specification.readthedocs.io/en/stable/glossary.html#objects.metadata.InjectedRadioactivity
json_Float(fp, "\t\"RadionuclideHalfLife\": %g,\n", d.radionuclideHalfLife);
json_Float(fp, "\t\"DoseCalibrationFactor\": %g,\n", d.doseCalibrationFactor);
json_Float(fp, "\t\"IsotopeHalfLife\": %g,\n", d.ecat_isotope_halflife);
json_Float(fp, "\t\"Dosage\": %g,\n", d.ecat_dosage);
json_Str(fp, "\t\"ConvolutionKernel\": \"%s\",\n", d.convolutionKernel);
json_Str(fp, "\t\"Units\": \"%s\",\n", d.unitsPT); //https://github.com/bids-standard/bids-specification/pull/773
json_Str(fp, "\t\"DecayCorrection\": \"%s\",\n", d.decayCorrection);
json_Str(fp, "\t\"DecayCorrectionFactor\": \"%s\",\n", d.decayCorrection); //renamed https://bids-specification.readthedocs.io/en/stable/glossary.html#objects.metadata.DecayCorrectionFactor
json_Str(fp, "\t\"AttenuationCorrectionMethod\": \"%s\",\n", d.attenuationCorrectionMethod);
json_Str(fp, "\t\"ReconstructionMethod\": \"%s\",\n", d.reconstructionMethod);
//json_Float(fp, "\t\"DecayFactor\": %g,\n", d.decayFactor);
json_Float(fp, "\t\"ScatterFraction\": %g,\n", d.scatterFraction);
if (dti4D->decayFactor[0] >= 0.0) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0
fprintf(fp, "\t\"DecayFactor\": [\n");
for (int i = 0; i < h->dim[4]; i++) {
Expand Down

0 comments on commit c975d1f

Please sign in to comment.