From 16577304753910e21ad424050c2437f6ad963b49 Mon Sep 17 00:00:00 2001 From: neurolabusc Date: Tue, 22 Oct 2024 08:40:08 -0400 Subject: [PATCH] Deidentification refactor (https://github.com/rordenlab/dcm2niix/issues/877) --- README.md | 9 +++++-- console/nii_dicom.cpp | 18 +++++++++---- console/nii_dicom.h | 5 ++-- console/nii_dicom_batch.cpp | 53 +++++++++++++++++-------------------- 4 files changed, 46 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 0593e4e9..81e74e29 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ make - [dinifti](https://as.nyu.edu/cbi/resources/Software/DINIfTI.html) is focused on conversion of classic Siemens DICOMs. - [DWIConvert](https://github.com/BRAINSia/BRAINSTools/tree/master/DWIConvert) converts DICOM images to NRRD and NIfTI formats. - [mcverter](http://lcni.uoregon.edu/%7Ejolinda/MRIConvert/) a great tool for classic DICOMs. - - [mri_convert](https://surfer.nmr.mgh.harvard.edu/pub/docs/html/mri_convert.help.xml.html) is part of the popular FreeSurfer package. In my limited experience this tool works well for GE and Siemens data, but fails with Philips 4D datasets. + - [mri_convert](https://surfer.nmr.mgh.harvard.edu/pub/docs/html/mri_convert.help.xml.html) is part of the popular FreeSurfer package. - [MRtrix mrconvert](http://mrtrix.readthedocs.io/en/latest/reference/commands/mrconvert.html) is a useful general purpose image converter and handles DTI data well. It is an outstanding tool for modern Philips enhanced images. - [nanconvert](https://github.com/spinicist/nanconvert) uses the ITK library to convert DICOM from GE and proprietary Bruker to standard formats like DICOM. - [Plastimatch](https://plastimatch.org/) is a Swiss Army knife - it computes registration, image processing, @@ -125,10 +125,13 @@ make ## Links +dcm2niix is a core dependency of BIDS converters: + - [Table of DICOM to BIDS converters](https://bids.neuroimaging.io/benefits#mri-and-pet-converterss) The following tools exploit dcm2niix + - [@niivue/dcm2niix](https://www.npmjs.com/package/@niivue/dcm2niix) is a WebAssembly (WASM) package of dcm2niix, allowing it to be embedded into web pages, as seen in this [live demo](https://niivue.github.io/niivue-dcm2niix/). - [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. @@ -182,6 +185,8 @@ The following tools exploit dcm2niix - [kipettools](https://github.com/mathesong/kipettools) uses dcm2niix to load PET data. - [LEAD-DBS](http://www.lead-dbs.org/) uses dcm2niix for [DICOM import](https://github.com/leaddbs/leaddbs/blob/master/ea_dicom_import.m). - [lin4neuro](http://www.lin4neuro.net/lin4neuro/18.04bionic/vm/) releases such as the English l4n-18.04.4-amd64-20200801-en.ova include MRIcroGL and dcm2niix pre-installed. This allows user with VirtualBox or VMWarePlayer to use these tools (and many other neuroimaging tools) in a graphical virtual machine. + - [mercure-dcm2bids](https://github.com/mercure-imaging/mercure-dcm2bids) is a Mercure module to perform DICOM to BIDS conversion using dcm2bids and dcm2niix. + - [mri_convert](https://surfer.nmr.mgh.harvard.edu/pub/docs/html/mri_convert.help.xml.html) is part of the popular FreeSurfer package and wraps dcm2niix to improve DICOM support. - [MRIcroGL](https://github.com/neurolabusc/MRIcroGL) is available for MacOS, Linux and Windows and provides a graphical interface for dcm2niix. You can get compiled copies from the [MRIcroGL NITRC web site](https://www.nitrc.org/projects/mricrogl/). - [MrPyConvert](https://github.com/Jolinda/mrpyconvert) Python library dicom to bids conversion. - [Nekton](https://github.com/deepc-health/nekton) is a python package for DICOM to NifTi and NifTi to DICOM-SEG and GSPS conversion. @@ -206,7 +211,7 @@ The following tools exploit dcm2niix - [Retina_OCT_dcm2nii](https://github.com/Choupan/Retina_OCT_dcm2nii) converts optical coherence tomography (OCT) data to NIfTI. - [sci-tran dcm2niix](https://github.com/scitran-apps/dcm2niix) Flywheel Gear (docker). - [shimming-toolbox](https://github.com/shimming-toolbox/shimming-toolbox) enabled static and real-time shimming, using dcm2niix to import DICOM data. - - [SlicerDcm2nii extension](https://github.com/Slicer/ExtensionsIndex/blob/master/SlicerDcm2nii.s4ext) is one method to import DICOM data into Slicer. + - [SlicerDcm2nii](https://github.com/SlicerDMRI/SlicerDcm2nii) is an extension to import DICOM data into 3D Slicer. - [tar2bids](https://github.com/khanlab/tar2bids) converts DICOM tarball(s) to BIDS using heudiconv which invokes dcm2niix. - [TORTOISE](https://tortoise.nibib.nih.gov) is used for processing diffusion MRI data, and uses dcm2niix to import DICOM images. - [TractoR (Tracto­graphy with R) uses dcm2niix for image conversion](http://www.tractor-mri.org.uk/TractoR-and-DICOM). diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 7aacae08..e627ee2a 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -749,6 +749,7 @@ struct TDICOMdata clear_dicom_data() { for (int i = 0; i < 7; i++) d.orient[i] = 0.0f; strcpy(d.patientName, ""); + strcpy(d.deidentificationMethod, ""); strcpy(d.patientID, ""); strcpy(d.accessionNumber, ""); strcpy(d.imageType, ""); @@ -4254,14 +4255,21 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D dti4D->frameReferenceTime[0] = -1; // dti4D->fragmentOffset[0] = -1; dti4D->intenScale[0] = 0.0; -#ifdef USING_R // Ensure dti4D fields are initialised, as in nii_readParRec() for (int i = 0; i < kMaxDTI4D; i++) { dti4D->S[i].V[0] = -1.0; dti4D->TE[i] = -1.0; } -#endif d.deID_CS_n = 0; + for (int i = 0; i < MAX_DEID_CS; i++) { + // n.b. knowing deID_CS_n is insufficient to know number of strings + // e.g. dcm_qa_deident CodingSchemeVersion (0008,0103) provided for only some entries + strcpy(dti4D->deID_CS[i].CodeValue, ""); + strcpy(dti4D->deID_CS[i].CodeMeaning, ""); + strcpy(dti4D->deID_CS[i].CodingSchemeDesignator, ""); + strcpy(dti4D->deID_CS[i].CodingSchemeVersion, ""); + } + struct TVolumeDiffusion volDiffusion = initTVolumeDiffusion(&d, dti4D); struct stat s; if (stat(fname, &s) == 0) { @@ -5670,9 +5678,9 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D break; } case kDeidentificationMethod: { // issue 383 - dcmStr(lLength, &buffer[lPos], dti4D->deidentificationMethod); - int slen = (int)strlen(dti4D->deidentificationMethod); - if ((slen < 10) || (strstr(dti4D->deidentificationMethod, "DICOMANON") == NULL)) + dcmStr(lLength, &buffer[lPos], d.deidentificationMethod); + int slen = (int)strlen(d.deidentificationMethod); + if ((slen < 10) || (strstr(d.deidentificationMethod, "DICOMANON") == NULL)) break; isDICOMANON = true; printWarning("Matlab DICOMANON can scramble SeriesInstanceUID (0020,000e) and remove crucial data (see issue 383). \n"); diff --git a/console/nii_dicom.h b/console/nii_dicom.h index 441e3117..08657a95 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.20241015" +#define kDCMdate "v1.0.20241022" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; // maximum number of EPI images in Siemens Mosaic @@ -207,7 +207,6 @@ struct TDTI4D { bool isImaginary[kMaxDTI4D]; bool isPhase[kMaxDTI4D]; float repetitionTimeExcitation, repetitionTimeInversion; - char deidentificationMethod[kDICOMStr]; struct TDeIDCodeSequence deID_CS[MAX_DEID_CS]; }; @@ -260,7 +259,7 @@ struct TDICOMdata { 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], 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 deidentificationMethod[kDICOMStr], 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]; int deID_CS_n; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 04baddc0..5925e163 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1464,49 +1464,44 @@ tse3d: T2*/ fprintf(fp, "\t\"NonlinearGradientCorrection\": false,\n"); if (d.isDerived) // DICOM is derived image or non-spatial file (sounds, etc) fprintf(fp, "\t\"RawImage\": false,\n"); - struct TDTI4D *d4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); - struct TDICOMdata d2; // No need to preallocate with malloc() - char *fname = (char *)malloc(strlen(filename) + 1); - strcpy(fname, filename); - d2 = readDICOMv(fname, 0, 1, d4D); - if (strlen(d4D->deidentificationMethod) > 0) { + if (strlen(d.deidentificationMethod) > 0) { fprintf(fp, "\t\"DeidentificationMethod\": [\""); bool isSep = false; - for (size_t i = 0; i < strlen(d4D->deidentificationMethod); i++) { - if (d4D->deidentificationMethod[i] != '\\') { + for (size_t i = 0; i < strlen(d.deidentificationMethod); i++) { + if (d.deidentificationMethod[i] != '\\') { if (isSep) fprintf(fp, "\", \""); isSep = false; - fprintf(fp, "%c", d4D->deidentificationMethod[i]); + fprintf(fp, "%c", d.deidentificationMethod[i]); } else isSep = true; } fprintf(fp, "\"],\n"); } - free(d4D); - free(fname); if (d.deID_CS_n > 0) { char *fname = (char *)malloc(strlen(filename) + 1); strcpy(fname, filename); - //struct TDICOMdata *d2 = (struct TDICOMdata *)malloc(sizeof(struct TDICOMdata)); - struct TDTI4D *d4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); - struct TDICOMdata d2; // No need to preallocate with malloc() - d2 = readDICOMv(fname, 0, 1, d4D); - free(fname); - fprintf(fp, "\t\"DeidentificationMethodCodeSequence\": [ \n"); - for (int i = 0; i < d.deID_CS_n && i < MAX_DEID_CS; i++) { - fprintf(fp, "\t { \n"); - json_Str(fp, "\t\t\"CodeValue\": \"%s\",\n", d4D->deID_CS[i].CodeValue); - json_Str(fp, "\t\t\"CodingSchemeDesignator\": \"%s\",\n", d4D->deID_CS[i].CodingSchemeDesignator); - json_Str(fp, "\t\t\"CodingSchemeVersion\": \"%s\",\n", d4D->deID_CS[i].CodingSchemeVersion); - json_Str(fp, "\t\t\"CodeMeaning\": \"%s\"\n", d4D->deID_CS[i].CodeMeaning); - if (i + 1 < d.deID_CS_n) - fprintf(fp, "\t },\n"); - else - fprintf(fp, "\t }\n"); + if (is_fileexists(fname)) { + struct TDTI4D *d4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); + struct TDICOMdata d2 = readDICOMv(fname, 0, 1, d4D); + fprintf(fp, "\t\"DeidentificationMethodCodeSequence\": [ \n"); + for (int i = 0; i < d.deID_CS_n && i < MAX_DEID_CS; i++) { + fprintf(fp, "\t { \n"); + json_Str(fp, "\t\t\"CodeValue\": \"%s\",\n", d4D->deID_CS[i].CodeValue); + json_Str(fp, "\t\t\"CodingSchemeDesignator\": \"%s\",\n", d4D->deID_CS[i].CodingSchemeDesignator); + json_Str(fp, "\t\t\"CodingSchemeVersion\": \"%s\",\n", d4D->deID_CS[i].CodingSchemeVersion); + json_Str(fp, "\t\t\"CodeMeaning\": \"%s\"\n", d4D->deID_CS[i].CodeMeaning); + if (i + 1 < d.deID_CS_n) + fprintf(fp, "\t },\n"); + else + fprintf(fp, "\t }\n"); + } + fprintf(fp, "\t],\n"); + free(d4D); + } else { + printWarning("Issue877 unable to find file for DeidentificationMethod: %s\n", fname); } - fprintf(fp, "\t],\n"); - free(d4D); + free(fname); } // d.deID_CS_n > 0 if (d.seriesNum > 0) fprintf(fp, "\t\"SeriesNumber\": %ld,\n", d.seriesNum);