diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index c57a2198..d663de2a 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -712,6 +712,7 @@ struct TDICOMdata clear_dicom_data() { strcpy(d.patientID, ""); strcpy(d.accessionNumber, ""); strcpy(d.imageType, ""); + strcpy(d.imageTypeText, ""); strcpy(d.imageComments, ""); strcpy(d.imageBaseName, ""); strcpy(d.phaseEncodingDirectionDisplayedUIH, ""); @@ -4271,6 +4272,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kImagingFrequency2 0x0018 + uint32_t(0x9098 << 16) //FD #define kParallelReductionFactorOutOfPlane 0x0018 + uint32_t(0x9155 << 16) //FD //#define kFrameAcquisitionDuration 0x0018+uint32_t(0x9220 << 16 ) //FD +#define kArterialSpinLabelingContrast 0x0018 + uint32_t(0x9250 << 16) //CS #define kASLPulseTrainDuration 0x0018 + uint32_t(0x9258 << 16) //UL #define kDiffusionBValueXX 0x0018 + uint32_t(0x9602 << 16) //FD #define kDiffusionBValueXY 0x0018 + uint32_t(0x9603 << 16) //FD @@ -4333,6 +4335,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kBandwidthPerPixelPhaseEncode21 0x0021 + (0x1153 << 16) //FD #define kCoilElements 0x0021 + (0x114F << 16) //LO #define kAcquisitionMatrixText21 0x0021 + (0x1158 << 16) //SH +#define kImageTypeText 0x0021 + (0x1175 << 16) //CS //Private Group 21 as used by GE: #define kLocationsInAcquisitionGE 0x0021 + (0x104F << 16) //SS 'LocationsInAcquisitionGE' #define kRTIA_timer 0x0021 + (0x105E << 16) //DS @@ -6206,6 +6209,18 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); d.xyzMM[3] = dcmStrFloat(lLength, &buffer[lPos]); d.zThick = d.xyzMM[3]; break; + case kImageTypeText: { + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + dcmStr(lLength, &buffer[lPos], d.imageTypeText); + int slen = (int)strlen(d.imageTypeText); + if (slen > 1) { + for (int i = 0; i < slen; i++) + if (d.imageTypeText[i] == '\\') + d.imageTypeText[i] = '_'; + } + break; + } case kAcquisitionMatrixText21: //fall through to kAcquisitionMatrixText case kAcquisitionMatrixText: { @@ -6493,6 +6508,18 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //case kFrameAcquisitionDuration : // frameAcquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); //issue369 // break; + case kArterialSpinLabelingContrast: { //CS + char st[kDICOMStr]; + //aslFlags + dcmStr(lLength, &buffer[lPos], st); + if (strstr(st, "PSEUDOCONTINUOUS") != NULL) + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_PSEUDOCONTINUOUS); + else if (strstr(st, "CONTINUOUS") != NULL) + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_CONTINUOUS); + else if (strstr(st, "PULSED") != NULL) + d.aslFlags = (d.aslFlags | kASL_FLAG_GE_PULSED); + break; + } case kASLPulseTrainDuration: { d.postLabelDelay = dcmInt(4, &buffer[lPos], d.isLittleEndian); break; diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 4fb9b5f6..dbdfb139 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.20220707" +#define kDCMdate "v1.0.20220717" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic @@ -133,6 +133,7 @@ static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI dire #define kASL_FLAG_GE_CONTINUOUS 8 #define kASL_FLAG_PHILIPS_CONTROL 16 #define kASL_FLAG_PHILIPS_LABEL 32 +#define kASL_FLAG_GE_PULSED 64 //for spoiling 0018,9016 @@ -230,7 +231,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. double acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[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], 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 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 scanOptions[kDICOMStrLarge], institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge]; uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; struct TCSAdata CSA; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 13b9ebfd..f8dadbbd 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -652,12 +652,14 @@ typedef struct { float alFree[kMaxWipFree]; float adFree[kMaxWipFree]; float alTI[kMaxWipFree]; - float dAveragesDouble, dThickness, ulShape, sPositionDTra, sNormalDTra; + float sPostLabelingDelay, ulLabelingDuration, dAveragesDouble, dThickness, ulShape, sPositionDTra, sNormalDTra; } TCsaAscii; void siemensCsaAscii(const char *filename, TCsaAscii *csaAscii, int csaOffset, int csaLength, float *shimSetting, char *coilID, char *consistencyInfo, char *coilElements, char *pulseSequenceDetails, char *fmriExternalInfo, char *protocolName, char *wipMemBlock) { //reads ASCII portion of CSASeriesHeaderInfo and returns lEchoTrainDuration or lEchoSpacing value // returns 0 if no value found + csaAscii->sPostLabelingDelay = 0.0; + csaAscii->ulLabelingDuration = 0.0; csaAscii->TE0 = 0.0; csaAscii->TE1 = 0.0; csaAscii->delayTimeInTR = -0.001; @@ -786,6 +788,10 @@ void siemensCsaAscii(const char *filename, TCsaAscii *csaAscii, int csaOffset, i csaAscii->TE0 = readKeyFloatNan(keyStrTE0, keyPos, csaLengthTrim); char keyStrTE1[] = "alTE[1]"; csaAscii->TE1 = readKeyFloatNan(keyStrTE1, keyPos, csaLengthTrim); + char keyStrPLD[] = "sAsl.sPostLabelingDelay[0]"; + csaAscii->sPostLabelingDelay = readKeyFloatNan(keyStrPLD, keyPos, csaLengthTrim); + char keyStrLD[] = "sAsl.ulLabelingDuration"; + csaAscii->ulLabelingDuration = readKeyFloatNan(keyStrLD, keyPos, csaLengthTrim); //read ALL alTI[*] values for (int k = 0; k < kMaxWipFree; k++) csaAscii->alTI[k] = NAN; @@ -1306,6 +1312,20 @@ tse3d: T2*/ fprintf(fp, "\", \"FIELDMAPHZ"); fprintf(fp, "\"],\n"); } + if (strlen(d.imageTypeText) > 0) { + fprintf(fp, "\t\"ImageTypeText\": [\""); + bool isSep = false; + for (size_t i = 0; i < strlen(d.imageTypeText); i++) { + if (d.imageTypeText[i] != '_') { + if (isSep) + fprintf(fp, "\", \""); + isSep = false; + fprintf(fp, "%c", d.imageTypeText[i]); + } else + isSep = true; + } + fprintf(fp, "\"],\n"); + } if (d.isDerived) //DICOM is derived image or non-spatial file (sounds, etc) fprintf(fp, "\t\"RawImage\": false,\n"); if (d.seriesNum > 0) @@ -1518,6 +1538,11 @@ tse3d: T2*/ bool isPCASL = false; bool isPASL = false; //ASL specific tags - 2D pCASL Danny J.J. Wang http://www.loft-lab.org + //ASL specific tags - 2D PASL Siemens Product XA30, n.b pasl/casl/pcasl set using 0018,9250 + if (strstr(pulseSequenceDetails, "ep2d_asl")) { + json_FloatNotNan(fp, "\t\"LabelingDuration\": %g,\n", csaAscii.ulLabelingDuration * (1.0 / 1000000.0)); //usec->sec + json_FloatNotNan(fp, "\t\"PostLabelingDelay\": %g,\n", csaAscii.sPostLabelingDelay * (1.0 / 1000000.0)); //usec -> sec + } if ((strstr(pulseSequenceDetails, "ep2d_pcasl")) || (strstr(pulseSequenceDetails, "ep2d_pcasl_UI_PHC"))) { isPCASL = true; repetitionTimePreparation = d.TR; @@ -1781,15 +1806,13 @@ tse3d: T2*/ //generic public ASL tags if (d.postLabelDelay > 0) json_Float(fp, "\t\"PostLabelDelay\": %g,\n", float(d.postLabelDelay) / 1000.0); - //GE ASL specific tags - if (d.aslFlags & kASL_FLAG_GE_CONTINUOUS) - fprintf(fp, "\t\"ASLContrastTechnique\": \"CONTINUOUS\",\n"); - if (d.aslFlags & kASL_FLAG_GE_PSEUDOCONTINUOUS) - fprintf(fp, "\t\"ASLContrastTechnique\": \"PSEUDOCONTINUOUS\",\n"); - if (d.aslFlags & kASL_FLAG_GE_3DPCASL) - fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D pulsed continuous ASL technique\",\n"); - if (d.aslFlags & kASL_FLAG_GE_3DCASL) - fprintf(fp, "\t\"ASLLabelingTechnique\": \"3D continuous ASL technique\",\n"); + //ASL BIDS tags + if ((d.aslFlags & kASL_FLAG_GE_CONTINUOUS) || (d.aslFlags & kASL_FLAG_GE_3DCASL)) + fprintf(fp, "\t\"ArterialSpinLabelingType\": \"CASL\",\n"); + if ((d.aslFlags & kASL_FLAG_GE_PSEUDOCONTINUOUS) || (d.aslFlags & kASL_FLAG_GE_3DPCASL)) + fprintf(fp, "\t\"ArterialSpinLabelingType\": \"PCASL\",\n"); + if (d.aslFlags & kASL_FLAG_GE_PULSED) + fprintf(fp, "\t\"ArterialSpinLabelingType\": \"PASL\",\n"); if (d.durationLabelPulseGE > 0) { json_Float(fp, "\t\"LabelingDuration\": %g,\n", d.durationLabelPulseGE / 1000.0); json_Float(fp, "\t\"PostLabelDelay\": %g,\n", d.TI / 1000.0); //For GE ASL: InversionTime -> Post-label delay