From 1b4505ee973420a965c220a8fa76666aba01ca5d Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Thu, 21 Mar 2024 11:43:15 -0400 Subject: [PATCH] Experimental start for reproin support --- BIDS/README.md | 1 + console/nii_dicom.cpp | 5 +++ console/nii_dicom.h | 2 +- console/nii_dicom_batch.cpp | 86 +++++++++++++++++++++++++++++++++++-- 4 files changed, 89 insertions(+), 5 deletions(-) diff --git a/BIDS/README.md b/BIDS/README.md index f6f0dbc6..31a440da 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -82,6 +82,7 @@ These fields are present regardless of modality (e.g. MR, CT, PET). | PatientPosition | | DICOM tag 0020,0032 | D | | ProcedureStepDescription | | DICOM tag 0040,0254 | D | | SoftwareVersions | | DICOM tag 0020,1020 | B | +| StudyDescription | | DICOM tag 0008,1030 | D | | SeriesDescription | | DICOM tag 0008,103E | D | | ProtocolName | | DICOM tag 0018,1030 | D | | ScanningSequence | | DICOM tag 0018,0020 | B | diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 3d1b8bb0..c2a5f2c2 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -772,6 +772,7 @@ struct TDICOMdata clear_dicom_data() { strcpy(d.deviceSerialNumber, ""); strcpy(d.softwareVersions, ""); strcpy(d.stationName, ""); + strcpy(d.studyDescription, ""); strcpy(d.scanOptions, ""); //strcpy(d.mrAcquisitionType, ""); strcpy(d.seriesInstanceUID, ""); @@ -4368,6 +4369,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kTracerRadionuclide1 0x0008 + (0x0100 << 16) //SH #define kTracerRadionuclide2 0x0008 + (0x0104 << 16) //LO #define kStationName 0x0008 + (0x1010 << 16) +#define kStudyDescription 0x0008 + (0x1030 << 16) //LO #define kSeriesDescription 0x0008 + (0x103E << 16) // '0008' '103E' 'LO' 'SeriesDescription' #define kInstitutionalDepartmentName 0x0008 + (0x1040 << 16) #define kManufacturersModelName 0x0008 + (0x1090 << 16) @@ -5695,6 +5697,9 @@ const uint32_t kEffectiveTE = 0x0018 + uint32_t(0x9082 << 16); //FD case kStationName: dcmStr(lLength, &buffer[lPos], d.stationName); break; + case kStudyDescription: + dcmStr(lLength, &buffer[lPos], d.studyDescription); + break; case kSeriesDescription: dcmStr(lLength, &buffer[lPos], d.seriesDescription); #ifdef USING_DCM2NIIXFSWRAPPER diff --git a/console/nii_dicom.h b/console/nii_dicom.h index ccc92240..74cde92c 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -248,7 +248,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 imagingFrequency, acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; 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 prescanReuseString[kDICOMStr], imageOrientationText[kDICOMStr], pulseSequenceName[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], stationName[kDICOMStr], studyDescription[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]; struct TCSAdata CSA; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 5f080c66..a07fe3df 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1395,6 +1395,7 @@ tse3d: T2*/ fprintf(fp, "\t\"MRAcquisitionType\": \"2D\",\n"); if (d.is3DAcq) fprintf(fp, "\t\"MRAcquisitionType\": \"3D\",\n"); + json_Str(fp, "\t\"StudyDescription\": \"%s\",\n", d.studyDescription); json_Str(fp, "\t\"SeriesDescription\": \"%s\",\n", d.seriesDescription); json_Str(fp, "\t\"ProtocolName\": \"%s\",\n", d.protocolName); json_Str(fp, "\t\"ScanningSequence\": \"%s\",\n", d.scanningSequence); @@ -3416,6 +3417,66 @@ void cleanISO8859(char *cString) { } } +void heudiconvStrPth(char *cString) { + int len = strlen(cString); + int j = 0; + int i = 0; + bool hasCaret = false; + while (i < len) { + if (cString[i] == '^') { + cString[j++] = kPathSeparator; + hasCaret = true; + } else if ((!hasCaret) && (cString[i] == '_') ) { + cString[j++] = kPathSeparator; + } else if (cString[i] != '-') { + cString[j] = cString[i]; + j ++; + } + i++; + } + cString[j] = '\0'; +} + +void heudiconvStr(char *cString) { + int len = strlen(cString); + int j = 0; + int i = 0; + const char kTempPathSeparator = '\a'; + bool hasCaret = false; + while (i < len) { + if (cString[i] != '-') { + cString[j] = cString[i]; + j ++; + } + i++; + } + cString[j] = '\0'; +} + +void mkDirs(char *pth) { + if (strlen(pth) < 1) return; + char newdir[2048] = {""}; + for (size_t pos = 0; pos < strlen(pth); pos++) { + if (pth[pos] == kPathSeparator) { + if (!is_dir(newdir, true)) +#if defined(_WIN64) || defined(_WIN32) + mkdir(newdir); +#else + mkdir(newdir, 0700); +#endif + } + char ch[128] = {""}; + snprintf(ch, 128, "%c", pth[pos]); + strcat(newdir, ch); + } + if (!is_dir(newdir, true)) +#if defined(_WIN64) || defined(_WIN32) + mkdir(newdir); +#else + mkdir(newdir, 0700); +#endif +} //mkDirs() + void createDummyBidsBoilerplate(char *pth, bool isFunc) { //https://remi-gau.github.io/bids_cookbook/#starters char pathSep[2] = {"a"}; @@ -3539,13 +3600,30 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts strcat(outname, opts.indirParent); if (f == 'G') strcat(outname, dcm.accessionNumber); - if (f == 'H') { - printWarning("hazardous (%%h) bids naming experimental\n"); + if ((f == 'H') || (f == 'V')) { + printWarning("hazardous (%%h) or volatile (%%v) bids naming experimental\n"); + bool isReproin = (f == 'V'); + if (isReproin) { + //https://dbic-handbook.readthedocs.io/en/latest/mri/reproin.html + //reproin convention is hard for one-pass, as `ses` may only be reported in one series in the session (e.g. localizer) + //printf("study %s\n", dcm.studyDescription); + //printf("series %s\n", dcm.seriesDescription); + //printf("id %s\n", dcm.patientID); + snprintf(newstr, PATH_MAX, "%s", dcm.studyDescription); + heudiconvStrPth(newstr); + if ((strlen(pth) > 0) && (pth[strlen(pth)-1] != kPathSeparator)) + strcat(pth, kFileSep);//kPathSeparator); + strcat(pth, newstr); + mkDirs(pth); + strcpy(opts.bidsSubject, dcm.patientID); + heudiconvStr(opts.bidsSubject); + } char bidsSubject[kOptsStr] = "sub-"; if (strlen(opts.bidsSubject) <= 0) strcat(bidsSubject, "1"); else strcat(bidsSubject, opts.bidsSubject); + printf("%s<<<:::\n", bidsSubject); char bidsSession[kOptsStr] = "ses-"; if (strlen(opts.bidsSession) <= 0) strcat(bidsSession, "1"); @@ -3626,13 +3704,13 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts if (strlen(dcm.protocolName) < 1) printWarning("Unable to append protocol name (0018,1030) to filename (it is empty).\n"); } + if (f == 'Q') + strcat(outname, dcm.scanningSequence); if (f == 'R') { snprintf(newstr, PATH_MAX, "%d", dcm.imageNum); strcat(outname, newstr); isImageNumReported = true; } - if (f == 'Q') - strcat(outname, dcm.scanningSequence); if (f == 'S') { snprintf(newstr, PATH_MAX, "%ld", dcm.seriesNum); strcat(outname, newstr);