diff --git a/BATCH.md b/BATCH.md index b9e489ca..e3c6809b 100644 --- a/BATCH.md +++ b/BATCH.md @@ -10,9 +10,9 @@ The configuration file should be in yaml format as shown in example `batch_confi ```yaml Options: isGz: false - isFlipY: true + isFlipY: false isVerbose: false - isCreateBIDS: true + isCreateBIDS: false isOnlySingleFile: false Files: - diff --git a/BIDS/README.md b/BIDS/README.md index bf596b66..d018fa5d 100644 --- a/BIDS/README.md +++ b/BIDS/README.md @@ -35,6 +35,7 @@ The Defined By column uses: The Unit column uses: +- b : boolean - deg : degrees - f : fraction - kg : Kilogram @@ -214,15 +215,19 @@ Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Deter | PulseSequenceName | | `epi` or `epiRT` | D | | InternalPulseSequenceName | | `EPI` or `EPI2` | D | | PhaseEncodingPolarityGE | | `Unflipped` or `Flipped` | D | -| ASLContrastTechnique | | DICOM tag 0043,10A3 | D | -| ASLLabelingTechnique | | DICOM tag 0043,10A4 | D | -| LabelingDuration | s | DICOM tag 0043,10A5 | B | -| MultibandAccelerationFactor | | DICOM tag 0043,1083 | B | +| PostLabelingDelay | s | DICOM tag 0018,0082 (TI) | B | | NumberOfPointsPerArm | | DICOM tag 0027,1060 | D | | NumberOfArms | | DICOM tag 0027,1061 | D | | NumberOfExcitations | | DICOM tag 0027,1062 | D | +| ShimSetting[0] | | DICOM tag 0043,1002 | D | +| ShimSetting[1] | | DICOM tag 0043,1003 | D | +| ShimSetting[2] | | DICOM tag 0043,1004 | D | | ParallelReductionFactorInPlane | | DICOM tag 0043,1083 | B | -| PostLabelingDelay | s | DICOM tag 0018,0082 (TI) | B | +| PrescanReuseString | | DICOM tag 0043,1095 | D | +| MultibandAccelerationFactor | | DICOM tag 0043,1083 | B | +| ASLContrastTechnique | | DICOM tag 0043,10A3 | D | +| ASLLabelingTechnique | | DICOM tag 0043,10A4 | D | +| LabelingDuration | s | DICOM tag 0043,10A5 | B | ### Manufacturer Philips @@ -312,15 +317,19 @@ Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio | DwellTime | | DICOM tag 0019,1018 | B | | BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0019,1028 | D | | ImageOrientationText | | DICOM tag 0051,100E | D | +| NonlinearGradientCorrection | b | DICOM tag 0008,0008 | B | ### Manufacturer Siemens Magnetic Resonance Imaging (XA) Fields specific to Siemens XA-series MRI systems (Sola, Vida). -| Field | Unit | Comments | Defined By | -|------------------------------|------|---------------------|------------| -| ReceiveCoilActiveElements | | DICOM tag 0021,114F | B | -| BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0021,1153 | D | +| Field | Unit | Comments | Defined By | +|------------------------------|------|------------------------|------------| +| ReceiveCoilActiveElements | | DICOM tag 0021,114F | B | +| BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0021,1153 | D | +| ScanningSequence | | DICOM tag 0021,105a | D | +| PostLabelDelay | s | DICOM tag 0018,9258 | D | +| NonlinearGradientCorrection | b | 0008,0008 or 0021,1175 | B | ### Manufacturer UIH diff --git a/CMakeLists.txt b/CMakeLists.txt index 2afb5414..89e1a282 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.11) +cmake_minimum_required(VERSION 2.8.12) if(COMMAND CMAKE_POLICY) CMAKE_POLICY(SET CMP0003 NEW) diff --git a/Canon/README.md b/Canon/README.md index 6a458856..945ef84c 100644 --- a/Canon/README.md +++ b/Canon/README.md @@ -6,18 +6,26 @@ dcm2niix can convert Canon (né Toshiba) DICOM format images to NIfTI. This page Users of Canon MRI equipment are strongly advised to export data from their scanners as enhanced DICOM (with all images from the series stored as a single file) rather than classic DICOM (each 2D slice stored as a separate file). Limitations of the Canon classic DICOMs are described [here](https://github.com/rordenlab/dcm2niix/issues/495) and [here](https://github.com/neurolabusc/dcm_qa_canon). +## Avoid Enhanced DICOM + +Users of Canon MRI equipment are strongly advised to export data from their scanners as classic DICOM (with each slice from the series stored as a separate files) rather than enhanced DICOM. The enhanced 4D sequences such as fMRI incorrectly sets the public tag TemporalPositionIndex (0020,9128) as 1 for all volumes: both (0020,9157) and (0020,9128). This is a violation of the DICOM standard and interferes with attempts to sort data into the correct temporal order. Any software or user who assumes these tags are truthful will fail. + +## The Enhanced versus Classic DICOM dilemma + +The prior two sections provide conflicting advice, due to limitations in both the classic and enhanced datasets generated by Canon instruments. Users of Canon equipment should lobby the manufacturer to honor their DICOM conformance statement. DICOM data from Canon instruments may not be handled correctly by any software tool including dcm2niix. This reflects a limitation in the source DICOM data, not dcm2niix. + ## Diffusion Weighted Imaging Notes In contrast to several other vendors, Toshiba used public tags to report diffusion properties. Specifically, [DiffusionBValue (0018,9087)](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,9087)) and [DiffusionGradientOrientation (0018,9089)](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,9089)). Be aware that these tags are only populated for images where a diffusion gradient is applied. Consider a typical diffusion series where some volumes are acquired with B=0 while others have B=1000. In this case, only the volumes with B>0 will report a DiffusionBValue. These coordinates are with respect to the scanner bore, not image space. -Since the acquisition by Canon, these public tags are no longer populated for images saved in classic 2D DICOM format. The diffusion gradient directions are now stored in the ASCII Image Comments tag. Like GE (but unlike [Siemens, GE and Toshiba](https://www.na-mic.org/wiki/NAMIC_Wiki:DTI:DICOM_for_DWI_and_DTI)), these directions are with respect to the image space, not the scanner bore. Further, gradient direction is not adjusted for phase encoding polarity, and it is impossible to determine phase encoding polarity. For detailed discussion and a validation dataset that exhibits these attributes please see [dcm_qa_canon](https://github.com/neurolabusc/dcm_qa_canon). A Canon classic DICOM DWI image may report: +Be aware that Canon software V6.0 stored diffusion directions in the ASCII Image Comments tag when exporting to classic DICOM. For detailed discussion and a validation dataset that exhibits these attributes please see [dcm_qa_canon](https://github.com/neurolabusc/dcm_qa_canon). A Canon classic DICOM DWI image may report: ``` (0018,9087) FD 1500 # 8, 1 DiffusionBValue (0020,4000) LT [b=1500(0.445,0.000,0.895)] # 26, 1 ImageComments ``` -In contrast, when exporting images as enhanced (4D) DICOM, information is stored in public tags and does appear to compensate for phase encode polarity. These coordinates are with respect to the scanner bore, not image space. A Canon classic DICOM DWI image may report: +In contrast, Canon software [V6.1](https://github.com/neurolabusc/dcm_qa_canon_61) uses pulbic tags for both classic and enhanced DICOMs. A Canon V6.1 DICOM DWI image may report: ``` (0018,9087) FD 1500 # 8, 1 DiffusionBValue diff --git a/Philips/README.md b/Philips/README.md index b48aa7d8..3ac7e353 100644 --- a/Philips/README.md +++ b/Philips/README.md @@ -148,7 +148,7 @@ The BIDS tag [PartialFourier](https://bids-specification.readthedocs.io/en/stabl Philips DICOMs do not allow one to determine the temporal order of volumes for diffusion (DWI, DTI) and arterial spin labelling (ASL) sequences. This can hinder tools that attempt to model motion motion or T1 effects. For ASL data, dcm2niix will attempt to store volumes in the order phase < control/label < repeat. [For ASL examples and a table describing the ordering hierarchy, see this dataset](https://github.com/neurolabusc/dcm_qa_philips_asl). For diffusion images, dcm2niix may order Philips volumes differently depending on if the data is stored as classic or enhanced DICOM. For enhanced DICOM, dcm2niix follows the hierarchy specified by DimensionIndexValues (0020,9157). On the other hand, for classic DICOMs, volumes with identical b-value index (2005,1412) will be stored sequentially, with ties sorted based on gradient direction number (2005,1413). [For diffusion examples, see series 22 and 23 from this dataset](https://github.com/neurolabusc/dcm_qa_philips_enh). These two different sorting methods may not necessarily sort data identically. -[Slice timing correction](https://www.mccauslandcenter.sc.edu/crnl/tools/stc) can account for some variability in fMRI datasets. Unfortunately, Philips DICOM data [does not encode slice timing information](https://neurostars.org/t/heudiconv-no-extraction-of-slice-timing-data-based-on-philips-dicoms/2201/4). Therefore, dcm2niix is unable to populate the "SliceTiming" BIDS field. However, one can typically infer slice timing by recording the [mode and number of packages](https://en.wikibooks.org/w/index.php?title=SPM/Slice_Timing&stable=0#Philips_scanners) reported for the sequence on the scanner console or the [sequence PDF](http://adni.loni.usc.edu/wp-content/uploads/2017/09/ADNI-3-Basic-Philips-R5.pdf). For precise timing, it is also worth knowing if equidistant "temporal slice spacing" is set and whether "prospect. motion corr." is on or off (if on, a short delay occurs between volumes). +[Slice timing correction](https://www.mccauslandcenter.sc.edu/crnl/tools/stc) can account for some variability in fMRI datasets. Unfortunately, Philips DICOM data [does not explicitly encode slice timing information](https://neurostars.org/t/heudiconv-no-extraction-of-slice-timing-data-based-on-philips-dicoms/2201/4). Therefore, dcm2niix is unable to populate the "SliceTiming" BIDS field. Note, the [philips_order.py script](https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/7) may be able to determine slice timing using an undocumented and unverified technique that works in some instances but certainly fails with others. Additionally, one can typically infer slice timing by recording the [mode and number of packages](https://en.wikibooks.org/w/index.php?title=SPM/Slice_Timing&stable=0#Philips_scanners) reported for the sequence on the scanner console or the [sequence PDF](http://adni.loni.usc.edu/wp-content/uploads/2017/09/ADNI-3-Basic-Philips-R5.pdf). For precise timing, it is also worth knowing if equidistant "temporal slice spacing" is set and whether "prospect. motion corr." is on or off (if on, a short delay occurs between volumes). Likewise, the BIDS tag "PhaseEncodingDirection" allows tools like [eddy](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/eddy) and [TOPUP](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup) to undistort images. While the Philips DICOM header distinguishes the phase encoding axis (e.g. anterior-posterior vs left-right) it does not encode the polarity (A->P vs P->A). diff --git a/README.md b/README.md index e652bba3..39f46ffe 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ dcm2niix is designed to convert neuroimaging data from the DICOM format to the NIfTI format. This web page hosts the developmental source code - a compiled version for Linux, MacOS, and Windows of the most recent stable release is included with [MRIcroGL](https://www.nitrc.org/projects/mricrogl/). A full manual for this software is available in the form of a [NITRC wiki](http://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage). The DICOM format is the standard image format generated by modern medical imaging devices. However, DICOM is very [complicated](https://github.com/jonclayden/divest) and has been interpreted differently by different vendors. The NIfTI format is popular with scientists, it is very simple and explicit. However, this simplicity also imposes limitations (e.g. it demands equidistant slices). dcm2niix is also able to generate a [BIDS JSON format](https://bids-specification.readthedocs.io/en/stable/) `sidecar` which includes relevant information for brain scientists in a vendor agnostic and human readable form. -The [Neuroimaging DICOM and NIfTI Primer]https://github.com/DataCurationNetwork/data-primers/blob/master/Neuroimaging%20DICOM%20and%20NIfTI%20Data%20Curation%20Primer/neuroimaging-dicom-and-nifti-data-curation-primer.md) provides details. +The [Neuroimaging DICOM and NIfTI Primer](https://github.com/DataCurationNetwork/data-primers/blob/master/Neuroimaging%20DICOM%20and%20NIfTI%20Data%20Curation%20Primer/neuroimaging-dicom-and-nifti-data-curation-primer.md) provides details. ## License @@ -111,6 +111,7 @@ If you have any problems with the cmake build script described above or want to - [dicm2nii](http://www.mathworks.com/matlabcentral/fileexchange/42997-dicom-to-nifti-converter) is written in Matlab. The Matlab language makes this very scriptable. - [dicom2nifti](https://github.com/icometrix/dicom2nifti) uses the scriptable Python wrapper utilizes the [high performance GDCMCONV](http://gdcm.sourceforge.net/wiki/index.php/Gdcmconv) executables. - [dicomtonifti](https://github.com/dgobbi/vtk-dicom/wiki/dicomtonifti) leverages [VTK](https://www.vtk.org/). + - [dimon](https://afni.nimh.nih.gov/pub/dist/doc/program_help/Dimon.html) and [to3d](https://afni.nimh.nih.gov/pub/dist/doc/program_help/to3d.html) are included with AFNI. - [dinifti](http://as.nyu.edu/cbi/resources/Software/DINIfTI.html) is focused on conversion of Siemens data. - [DWIConvert](https://github.com/BRAINSia/BRAINSTools/tree/master/DWIConvert) converts DICOM images to NRRD and NIfTI formats. - [mcverter](http://lcni.uoregon.edu/%7Ejolinda/MRIConvert/) has great support for various vendors. @@ -126,57 +127,65 @@ If you have any problems with the cmake build script described above or want to ## Links + - [Table of DICOM to BIDS converters](https://bids.neuroimaging.io/benefits#mri-and-pet-converterss) + The following tools exploit 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. - - [BIDScoin](https://github.com/Donders-Institute/bidscoin) is a DICOM to BIDS converter with a GUI and thorough [documentation](https://bidscoin.readthedocs.io). - [BIDS Toolbox](https://github.com/cardiff-brain-research-imaging-centre/bids-toolbox) is a web service for the creation and manipulation of BIDS datasets, using dcm2niix for importing DICOM data. - - [birc-bids](https://github.com/bircibrain/birc-bids) provides a Docker/Singularity container with various BIDS conversion utilities. - - [BOLD5000_autoencoder](https://github.com/nmningmei/BOLD5000_autoencoder) uses dcm2niix to pipe imaging data into an unsupervised machine learning algorithm. - - [brainnetome DiffusionKit](http://diffusion.brainnetome.org/en/latest/) uses dcm2niix to convert images. - - [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). + - [BIDScoin](https://github.com/Donders-Institute/bidscoin) is a DICOM to BIDS converter with a GUI and thorough [documentation](https://bidscoin.readthedocs.io). + - [bidsconvertr](https://github.com/wulms/bidsconvertr) uses R to converts DICOM data to NIfTI and finally to BIDS. - [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. + - [birc-bids](https://github.com/bircibrain/birc-bids) provides a Docker/Singularity container with various BIDS conversion utilities. + - [BOLD5000_autoencoder](https://github.com/nmningmei/BOLD5000_autoencoder) uses dcm2niix to pipe imaging data into an unsupervised machine learning algorithm. - [boutiques-dcm2niix](https://github.com/lalet/boutiques-dcm2niix) is a dockerfile for installing and validating 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. + - [brainnetome DiffusionKit](http://diffusion.brainnetome.org/en/latest/) uses dcm2niix to convert images. + - [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. - [clpipe](https://github.com/cohenlabUNC/clpipe) uses dcm2bids for DICOM import. - [conversion](https://github.com/pnlbwh/conversion) is a Python library that can convert dcm2niix created NIfTI files to the popular NRRD format (including DWI gradient tables). Note, recent versions of dcm2niix can directly convert DICOM images to NRRD. - [DAC2BIDS](https://github.com/dangom/dac2bids) uses dcm2niibatch to create [BIDS](http://bids.neuroimaging.io/) datasets. + - [Data2Bids](https://github.com/SIMEXP/Data2Bids) converts non-DICOM images with associated JSON files to BIDS. While this tool does not require dcm2niix, it can leverage dcm2niix output similar to niix2bids. - [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. + - [dcm2niixpy](https://github.com/Svdvoort/dcm2niixpy) Python package of dcm2niix. - [dcmwrangle](https://github.com/jbteves/dcmwrangle) a Python interactive and static tool for organizing dicoms. - [DeepDicomSort](https://github.com/Svdvoort/DeepDicomSort) can recognize different scan types. - - [DICOM2BIDS](https://github.com/klsea/DICOM2BIDS) is a Python 2 script for creating BIDS files. + - [DICOM-to-NIfTI-GUI](https://github.com/Zunairviqar/DICOM-to-NIfTI-GUI) is a Python script that provides a graphical wrapper for dcm2niix. - [dicom2bids](https://github.com/Jolinda/lcnimodules) includes python modules for converting dicom files to nifti in a bids-compatible file structure that use dcm2niix. + - [DICOM2BIDS](https://github.com/klsea/DICOM2BIDS) is a Python 2 script for creating BIDS files. - [dicom2nifti_batch](https://github.com/scanUCLA/dicom2nifti_batch) is a Matlab script for automating dcm2niix. - - [DICOM-to-NIfTI-GUI](https://github.com/Zunairviqar/DICOM-to-NIfTI-GUI) is a Python script that provides a graphical wrapper for 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. + - [ezBIDS](https://github.com/brainlife/ezbids) is a [web service](https://brainlife.io/ezbids/) 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. + - [FreeSurfer](https://github.com/freesurfer/freesurfer) includes dcm2niix for image conversion. - [fsleyes](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FSLeyes) is a powerful Python-based image viewer. It uses dcm2niix to handle DICOM files through its fslpy libraries. - [Functional Real-Time Interactive Endogenous Neuromodulation and Decoding (FRIEND) Engine](https://github.com/InstitutoDOr/FriendENGINE) uses dcm2niix. - [heudiconv](https://github.com/nipy/heudiconv) can use dcm2niix to create [BIDS](http://bids.neuroimaging.io/) datasets. Data acquired using the [reproin](https://github.com/ReproNim/reproin) convention can be easily converted to BIDS. + - [Horos (Osirix) Bids Output Extension](https://github.com/mslw/horos-bids-output) is a OsiriX / Horos plugin that uses dcm2niix for creating BIDS output. - [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. - [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/). - - [neurodocker](https://github.com/kaczmarj/neurodocker) includes dcm2niix as a lean, minimal install Dockerfile. - [neuro_docker](https://github.com/Neurita/neuro_docker) includes dcm2niix as part of a single, static Dockerfile. - [NeuroDebian](http://neuro.debian.net/pkgs/dcm2niix.html) provides up-to-date version of dcm2niix for Debian-based systems. - [neurodocker](https://github.com/kaczmarj/neurodocker) generates [custom](https://github.com/rordenlab/dcm2niix/issues/138) Dockerfiles given specific versions of neuroimaging software. + - [neurodocker](https://github.com/kaczmarj/neurodocker) includes dcm2niix as a lean, minimal install Dockerfile. - [NeuroElf](http://neuroelf.net) can use dcm2niix to convert DICOM images. - [Neuroinformatics Database (NiDB)](https://github.com/gbook/nidb) is designed to store, retrieve, analyze, and share neuroimaging data. It uses dcm2niix for image QA and handling some formats. - [NiftyPET](https://niftypet.readthedocs.io/en/latest/install.html) provides PET image reconstruction and analysis, and uses dcm2niix to handle DICOM images. + - [niix2bids](https://github.com/benoitberanger/niix2bids ) attempts to automatically convert Siemens MRI images converted by dcm2niix to BIDS. - [nipype](https://github.com/nipy/nipype) can use dcm2niix to convert images. + - [PET2BIDS](https://github.com/openneuropet/PET2BIDS) uses dcm2niix for DICOM images. - [py2bids](https://github.com/Jolinda/py2bids) dcm2niix dicom to bids conversion wrapper. - [pyBIDSconv provides a graphical format for converting DICOM images to the BIDS format](https://github.com/DrMichaelLindner/pyBIDSconv). It includes clever default heuristics for identifying Siemens scans. - [pydcm2niix is a Python module for working with dcm2niix](https://github.com/jstutters/pydcm2niix). @@ -186,7 +195,8 @@ 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. - - The [SlicerDcm2nii extension](https://github.com/Slicer/ExtensionsIndex/blob/master/SlicerDcm2nii.s4ext) is one method to import DICOM data into Slicer. + - [SlicerDcm2nii extension](https://github.com/Slicer/ExtensionsIndex/blob/master/SlicerDcm2nii.s4ext) is one method to import DICOM data into 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). + - [XNAT2BIDS](https://github.com/kamillipi/2bids) is a simple xnat pipeline to convert DICOM scans to BIDS-compatible output. diff --git a/Siemens/README.md b/Siemens/README.md index 8609f80a..d387031e 100644 --- a/Siemens/README.md +++ b/Siemens/README.md @@ -10,11 +10,12 @@ The DICOM images exported by the X-series is radically different than the V-seri X-series users are strongly encouraged to export data using the "Enhanced" format and to not use any of the "Anonymize" features on the console. The consequences of these options is discussed in detail in [issue 236](https://github.com/rordenlab/dcm2niix/issues/236). Siemens notes `We highly recommend that the Enhanced DICOM format be used. This is because this format retains far more information in the header`. Failure to export data in this format has led to catastrophic data loss for numerous users (for publicly reported details see issues [203](https://github.com/rordenlab/dcm2niix/issues/203), [236](https://github.com/rordenlab/dcm2niix/issues/236), [240](https://github.com/rordenlab/dcm2niix/issues/240), [274](https://github.com/rordenlab/dcm2niix/issues/274), [303](https://github.com/rordenlab/dcm2niix/issues/303), [370](https://github.com/rordenlab/dcm2niix/issues/370), [394](https://github.com/rordenlab/dcm2niix/issues/394)). This reflects limitations of the DICOM data, not dcm2niix. -While X-series consoles allow users to export data as enhanced, mosaic or classic 2D formats, choosing an option other than enhanced dramatically degrades the meta data. Note that the Siemens considers mosaic images `secondary capture` data intended for quality assurance only. The mosaic scans lack several "Type 1" DICOM properties, necessarily limiting conversion. The non-mosaic 2D enhanced DICOMs are compact and efficient, but appear to have limited details relative to the enhanced output. This is unfortunate, as for the V-series the mosaic format has major benefits, so users may be in the habit of preferring mosaic export. Finally, each of the formats (enhanced, mosaic, classic) can be exported as anonymized. The Siemens console anonymization of current XA10A (Fall 2018) strips many useful tags. Siemens suggests `the use an offline/in-house anonymization software instead`. Another limitation of the current X-series format is that it retains no versioning details beyond the minor version for software and hardware stepping (e.g. versions are merely XA10 or XA11 with no details for service packs). If you use a X-series, you are strongly encouraged to manually log every hardware or software upgrade to allow future analyses to identify and regress out any effects of these modifications. Since the X-series format does not have a CSA header, dcm2niix will attempt to use the new private DICOM tags to populate the BIDS file. These tags are described in [issue 240](https://github.com/rordenlab/dcm2niix/issues/240). +While X-series consoles allow users to export data as enhanced, mosaic or classic 2D formats, choosing an option other than enhanced dramatically degrades the meta data. Note that the Siemens considers mosaic images `secondary capture` data intended for quality assurance only. The mosaic scans lack several "Type 1" DICOM properties, necessarily limiting conversion. This is unfortunate, as for the V-series the mosaic format has major benefits, so users may be in the habit of preferring mosaic export. The non-mosaic 2D enhanced DICOMs are compact and efficient, but appear to have limited details relative to the previous generation V-series with its rich CSA header. Finally, each of the formats (enhanced, mosaic, classic) can be exported as anonymized. The Siemens console anonymization of current XA10A (Fall 2018) strips many useful tags. Siemens suggests `the use an offline/in-house anonymization software instead`. Another limitation of the current X-series format is that it retains no versioning details beyond the minor version for software and hardware stepping (e.g. versions are merely XA10 or XA11 with no details for service packs). If you use a X-series, you are strongly encouraged to manually log every hardware or software upgrade to allow future analyses to identify and regress out any effects of these modifications. Since the X-series format does not have a CSA header, dcm2niix will attempt to use the new private DICOM tags to populate the BIDS file. These tags are described in [issue 240](https://github.com/rordenlab/dcm2niix/issues/240). When creating enhanced DICOMs diffusion information is provided in public tags. Based on a limited sample, it seems that classic DICOMs do not store diffusion data for XA10, and use private tags for [XA11](https://www.nitrc.org/forum/forum.php?thread_id=10013&forum_id=4703). Public Tags + ``` (0018,9089) FD -0.20\-0.51\-0.83 #DiffusionGradientOrientation (0018,9087) FD 1000 #DiffusionBValue @@ -22,6 +23,7 @@ Public Tags ``` Private Tags + ``` (0019,100c) IS 1000 #SiemensDiffusionBValue (0019,100e) FD -0.20\-0.51\-0.83 #SiemensDiffusionGradientOrientation @@ -30,6 +32,24 @@ Private Tags In theory, the public DICOM tag 'Frame Acquisition Date Time' (0018,9074) and the private tag 'Time After Start' (0021,1104) should each allow one to infer slice timing. The tag 0018,9074 uses the DT (date time) format, for example "20190621095520.330000" providing the YYYYYMMDDHHMMSS. Unfortunately, the Siemens de-identification routines will scramble these values, as time of data could be considered an identifiable attribute. The tag 0021,1104 is saved in DS (decimal string) format, for example "4.635" reporting the number of seconds since acquisition started. Be aware that some [Siemens Vida multi-band sequences](https://github.com/rordenlab/dcm2niix/issues/303) appear to fill these tags with the single-band times rather than the actual acquisition times. Therefore, neither of these two methods is perfectly reliable in determining slice timing. +The private `ICE_Dims` (0021,1106) tag can prove useful for parsing data. The list below is specific to XA scans: [SPM12](https://github.com/spm/spm12/blob/3085dac00ac804adb190a7e82c6ef11866c8af02/spm_dicom_convert.m#L268) suggests that this tag used to contain fewer elements. dcm2niix will use 0021,1106 to deduce echo number for [XA20 sequences that do not generate the public Echo Number (0018,0086)](https://github.com/rordenlab/dcm2niix/issues/568) tag. For example, consider an image of the 4th echo and 160th slice: + +``` +(0021,1106) LO [X_4_1_1_1_1_160_1_1_1_1_1_277] # ICE_Dims +``` + +1. eco = echo number +2. phs = phase encode +3. set = +4. rep = repetition +5. seg = segment +6. par = partition +7. slc = slice +8. idA = optional index +9. idB = optional index +10. idC = optional index +11. avg = average number + ## CSA Header Many crucial Siemens parameters are stored in the [proprietary CSA header](http://nipy.org/nibabel/dicom/siemens_csa.html), in particular the CSA Image Header Info (0029, 1010) and CSA Series Header Info (0029, 1020). These have binary sections that allows quick reading for many useful parameters. They also include an ASCII text portion that includes a lot of information but is slow to parse and poorly curated. Be aware that Siemens Vida scanners do not generate a CSA header. @@ -58,6 +78,12 @@ For Siemens V-series systems from the B-generation onward (around 2005), the mos Tools like [ExploreASL](https://sites.google.com/view/exploreasl) and [FSL BASIL](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/BASIL) can help process arterial spin labeling data. These tools require sequence details. These details differ between different sequences. If you create a BIDS JSON file with dcm2niix, the following tags will be created, using the same names used in the Siemens sequence PDFs. Note different sequences provide different values. The [dcm_qa_asl](https://github.com/neurolabusc/dcm_qa_asl) repository provides example DICOM ASL datasets. See the [BIDS page for details](../BIDS/README.md). +The Siemens CSA header also stores some ASL details as a base64 stream. These can be read using [gdcmdump](http://gdcm.sourceforge.net/wiki/index.php/Gdcmdump), e.g. `gdcmdump -i i001.dcm --csa-asl --print` + +## Nonlinear Gradient Correction + +dcm2niix does not populate the recommended [NonlinearGradientCorrection](https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#sequence-specifics) BIDS tag. dcm2niix does save the DICOM [Image Type (0008,0008)](https://dicom.innolitics.com/ciods/rt-dose/general-image/00080008) tag as `ImageType`, and recent versions will also export a private tag (0021,1175) as `ImageTypeText`. The inclusion of `DIS2D` or `DIS3D` in these one of these fields (the former prior to XA30, the latter with XA30 and later) is consistent with `NonlinearGradientCorrection` being `true` while `ND` suggests `false`. See [issue 597](https://github.com/rordenlab/dcm2niix/issues/597) for further details. + ## Sample Datasets - [Slice timing dataset](httphttps://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Slice_timing_corrections://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage). diff --git a/SuperBuild/SuperBuild.cmake b/SuperBuild/SuperBuild.cmake index 2a0a956f..e1057a0f 100644 --- a/SuperBuild/SuperBuild.cmake +++ b/SuperBuild/SuperBuild.cmake @@ -5,7 +5,7 @@ if(NOT GIT_FOUND) endif() # Use git protocol or not -option(USE_GIT_PROTOCOL "If behind a firewall turn this off to use http instead." ON) +option(USE_GIT_PROTOCOL "If behind a firewall turn this off to use http instead." OFF) if(USE_GIT_PROTOCOL) set(git_protocol "git") else() @@ -40,9 +40,21 @@ option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF) option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF) option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF) option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF) +option(USE_JNIFTI "Build with JNIFTI support" ON) option(BATCH_VERSION "Build dcm2niibatch for multiple conversions" OFF) +option(BUILD_DCM2NIIXFSLIB "Build libdcm2niixfs.a" OFF) + +if(BUILD_DCM2NIIXFSLIB) + if(USE_OPENJPEG OR USE_TURBOJPEG OR USE_JASPER) + message("-- Set BUILD_DCM2NIIXFSLIB to OFF since USE_TURBOJPEG/USE_JASPER/USE_OPENJPEG is ON.") + set(BUILD_DCM2NIIXFSLIB OFF CACHE BOOL "Build libdcm2niixfs.a" FORCE) + else() + message("-- Build libdcm2niixfs.a: ${BUILD_DCM2NIIXFSLIB}") + endif() +endif() + include(ExternalProject) set(DEPENDENCIES) @@ -139,6 +151,7 @@ ExternalProject_Add(console -DUSE_TURBOJPEG:BOOL=${USE_TURBOJPEG} -DUSE_JASPER:BOOL=${USE_JASPER} -DUSE_JPEGLS:BOOL=${USE_JPEGLS} + -DUSE_JNIFTI:BOOL=${USE_JNIFTI} -DZLIB_IMPLEMENTATION:STRING=${ZLIB_IMPLEMENTATION} -DZLIB_ROOT:PATH=${ZLIB_ROOT} # OpenJPEG @@ -147,6 +160,8 @@ ExternalProject_Add(console # yaml-cpp -DBATCH_VERSION:BOOL=${BATCH_VERSION} -DYAML-CPP_DIR:PATH=${YAML-CPP_DIR} + # Build libdcm2niixfs.a + -DBUILD_DCM2NIIXFSLIB:BOOL=${BUILD_DCM2NIIXFSLIB} ) install(DIRECTORY ${CMAKE_BINARY_DIR}/bin/ DESTINATION bin diff --git a/batch_config.yml b/batch_config.yml index 4bbf8384..d22a987c 100644 --- a/batch_config.yml +++ b/batch_config.yml @@ -1,8 +1,8 @@ Options: isGz: false - isFlipY: true + isFlipY: false isVerbose: false - isCreateBIDS: true + isCreateBIDS: false isOnlySingleFile: false Files: diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index 05a16fbc..0dcfe303 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -66,6 +66,20 @@ endif() set(PROGRAMS dcm2niix) +option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF) +option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF) +option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF) +option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF) + +option(BATCH_VERSION "Build dcm2niibatch for multiple conversions" OFF) + +option(BUILD_DCM2NIIXFSLIB "Build libdcm2niixfs.a" OFF) + +if(USE_OPENJPEG OR USE_TURBOJPEG OR USE_JASPER) + message("-- Set BUILD_DCM2NIIXFSLIB to OFF since USE_TURBOJPEG/USE_JASPER/USE_OPENJPEG is ON.") + set(BUILD_DCM2NIIXFSLIB OFF CACHE BOOL "Build libdcm2niixfs.a" FORCE) +endif() + set(DCM2NIIX_SRCS main_console.cpp nii_dicom.cpp @@ -76,7 +90,26 @@ set(DCM2NIIX_SRCS nii_ortho.cpp nii_dicom_batch.cpp) -option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF) + +option(USE_JNIfTI "Build with JNIfTI support" ON) +if(USE_JNIFTI) + add_definitions(-DmyEnableJNIfTI) + set(DCM2NIIX_SRCS ${DCM2NIIX_SRCS} cJSON.cpp base64.cpp) +endif() + +if(BUILD_DCM2NIIXFSLIB) + set(DCM2NIIXFSLIB dcm2niixfs) + set(DCM2NIIXFSLIB_SRCS + dcm2niix_fswrapper.cpp + nii_dicom.cpp + jpg_0XC3.cpp + ujpeg.cpp + nifti1_io_core.cpp + nii_foreign.cpp + nii_ortho.cpp + nii_dicom_batch.cpp) +endif() + if(USE_JPEGLS) add_definitions(-DmyEnableJPEGLS) if(MSVC) @@ -91,8 +124,16 @@ if(USE_JPEGLS) charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp) add_executable(dcm2niix ${DCM2NIIX_SRCS} ${CHARLS_SRCS}) + + if(BUILD_DCM2NIIXFSLIB) + add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS} ${CHARLS_SRCS}) + endif() else() add_executable(dcm2niix ${DCM2NIIX_SRCS}) + + if(BUILD_DCM2NIIXFSLIB) + add_library(${DCM2NIIXFSLIB} STATIC ${DCM2NIIXFSLIB_SRCS}) + endif() endif() set(ZLIB_IMPLEMENTATION "Miniz" CACHE STRING "Choose zlib implementation.") @@ -110,7 +151,6 @@ if(NOT ${ZLIB_IMPLEMENTATION} STREQUAL "Miniz") target_link_libraries(dcm2niix ${ZLIB_LIBRARIES}) endif() -option(USE_TURBOJPEG "Use TurboJPEG to decode classic JPEG" OFF) if(USE_TURBOJPEG) find_package(PkgConfig REQUIRED) pkg_check_modules(TURBOJPEG REQUIRED libturbojpeg) @@ -119,7 +159,6 @@ if(USE_TURBOJPEG) target_link_libraries(dcm2niix ${TURBOJPEG_LIBRARIES}) endif() -option(USE_JASPER "Build with JPEG2000 support using Jasper" OFF) if(USE_JASPER) find_package(Jasper REQUIRED) add_definitions(-DmyEnableJasper) @@ -127,7 +166,6 @@ if(USE_JASPER) target_link_libraries(dcm2niix ${JASPER_LIBRARIES}) endif() -option(USE_OPENJPEG "Build with JPEG2000 support using OpenJPEG" OFF) if(USE_OPENJPEG) set(OpenJPEG_DIR "${OpenJPEG_DIR}" CACHE PATH "Path to OpenJPEG configuration file" FORCE) @@ -147,7 +185,6 @@ else () add_definitions(-DmyDisableOpenJPEG) endif() -option(BATCH_VERSION "Build dcm2niibatch for multiple conversions" OFF) if(BATCH_VERSION) set(DCM2NIIBATCH_SRCS main_console_batch.cpp @@ -159,6 +196,10 @@ if(BATCH_VERSION) nii_ortho.cpp nii_dicom_batch.cpp) + if(USE_JNIFTI) + set(DCM2NIIBATCH_SRCS ${DCM2NIIBATCH_SRCS} cJSON.cpp base64.cpp) + endif() + if(USE_JPEGLS) add_executable(dcm2niibatch ${DCM2NIIBATCH_SRCS} ${CHARLS_SRCS}) else() @@ -195,6 +236,9 @@ if(BATCH_VERSION) list(APPEND PROGRAMS dcm2niibatch) endif() +if(BUILD_DCM2NIIXFSLIB) + target_compile_definitions(${DCM2NIIXFSLIB} PRIVATE -DUSING_DCM2NIIXFSWRAPPER -DUSING_MGH_NIFTI_IO) +endif() if(APPLE) message("-- Adding Apple plist") @@ -207,3 +251,7 @@ if(APPLE) endif() install(TARGETS ${PROGRAMS} DESTINATION bin) + +if(BUILD_DCM2NIIXFSLIB) + install(TARGETS ${DCM2NIIXFSLIB} DESTINATION lib) +endif() diff --git a/console/base64.cpp b/console/base64.cpp new file mode 100644 index 00000000..e7033279 --- /dev/null +++ b/console/base64.cpp @@ -0,0 +1,162 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005-2011, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +//#include "includes.h" + +//#include "os.h" +#include "base64.h" +#include +#include +#include +#include + +static const unsigned char base64_table[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * base64_encode - Base64 encode + * @src: Data to be encoded + * @len: Length of the data to be encoded + * @out_len: Pointer to output length variable, or %NULL if not used + * Returns: Allocated buffer of out_len bytes of encoded data, + * or %NULL on failure + * + * Caller is responsible for freeing the returned buffer. Returned buffer is + * nul terminated to make it easier to use as a C string. The nul terminator is + * not included in out_len. + */ +unsigned char * base64_encode(const unsigned char *src, size_t len, + size_t *out_len) +{ + unsigned char *out, *pos; + const unsigned char *end, *in; + size_t olen; + + olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */ + #ifdef USE_EOLN + int line_len 0; + olen += olen / 72; /* line feeds */ + #endif + olen++; /* nul termination */ + if (olen < len) + return NULL; /* integer overflow */ + out = (unsigned char *) malloc(olen); //os_ + if (out == NULL) + return NULL; + end = src + len; + in = src; + pos = out; + while (end - in >= 3) { + *pos++ = base64_table[in[0] >> 2]; + *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; + *pos++ = base64_table[in[2] & 0x3f]; + in += 3; + #ifdef USE_EOLN + line_len += 4; + if (line_len >= 72) { + *pos++ = '\n'; + line_len = 0; + } + #endif + } + + if (end - in) { + *pos++ = base64_table[in[0] >> 2]; + if (end - in == 1) { + *pos++ = base64_table[(in[0] & 0x03) << 4]; + *pos++ = '='; + } else { + *pos++ = base64_table[((in[0] & 0x03) << 4) | + (in[1] >> 4)]; + *pos++ = base64_table[(in[1] & 0x0f) << 2]; + } + *pos++ = '='; + #ifdef USE_EOLN + line_len += 4; + #endif + } + #ifdef USE_EOLN + if (line_len) + *pos++ = '\n'; + #endif + *pos = '\0'; + if (out_len) + *out_len = pos - out; + return out; +} + +/** + * base64_decode - Base64 decode + * @src: Data to be decoded + * @len: Length of the data to be decoded + * @out_len: Pointer to output length variable + * Returns: Allocated buffer of out_len bytes of decoded data, + * or %NULL on failure + * + * Caller is responsible for freeing the returned buffer. + */ +unsigned char * base64_decode(const unsigned char *src, size_t len, + size_t *out_len) +{ + unsigned char dtable[256], *out, *pos, block[4], tmp; + size_t i, count, olen; + int pad = 0; + + memset(dtable, 0x80, 256); //os_ + for (i = 0; i < sizeof(base64_table) - 1; i++) + dtable[base64_table[i]] = (unsigned char) i; + dtable['='] = 0; + + count = 0; + for (i = 0; i < len; i++) { + if (dtable[src[i]] != 0x80) + count++; + } + + if (count == 0 || count % 4) + return NULL; + + olen = count / 4 * 3; + pos = out = (unsigned char *) malloc(olen); //os_ + if (out == NULL) + return NULL; + + count = 0; + for (i = 0; i < len; i++) { + tmp = dtable[src[i]]; + if (tmp == 0x80) + continue; + + if (src[i] == '=') + pad++; + block[count] = tmp; + count++; + if (count == 4) { + *pos++ = (block[0] << 2) | (block[1] >> 4); + *pos++ = (block[1] << 4) | (block[2] >> 2); + *pos++ = (block[2] << 6) | block[3]; + count = 0; + if (pad) { + if (pad == 1) + pos--; + else if (pad == 2) + pos -= 2; + else { + /* Invalid padding */ + free(out); //os_ + return NULL; + } + break; + } + } + } + + *out_len = pos - out; + return out; +} \ No newline at end of file diff --git a/console/base64.h b/console/base64.h new file mode 100644 index 00000000..c05eb158 --- /dev/null +++ b/console/base64.h @@ -0,0 +1,25 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef BASE64_H +#define BASE64_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +unsigned char * base64_encode(const unsigned char *src, size_t len,size_t *out_len); +unsigned char * base64_decode(const unsigned char *src, size_t len,size_t *out_len); + +#ifdef __cplusplus +} +#endif + +#endif /* BASE64_H */ diff --git a/console/cJSON.cpp b/console/cJSON.cpp new file mode 100644 index 00000000..bc95cde6 --- /dev/null +++ b/console/cJSON.cpp @@ -0,0 +1,2979 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) { + if (!cJSON_IsString(item)) { + return NULL; + } + + return item->valuestring; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 12) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + if (newbuffer) + { + memcpy(newbuffer, p->buffer, p->offset + 1); + } + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + return (fabs(a - b) <= CJSON_DOUBLE_PRECISION); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if ((d * 0) != 0) + { + length = sprintf((char*)number_buffer, "null"); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = strlen((const char*)value) + sizeof(""); + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +#define cjson_min(a, b) ((a < b) ? a : b) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) + { + hooks->deallocate(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL)) + { + return false; + } + + child = array->child; + + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + } + else + { + /* append to the end */ + while (child->next) + { + child = child->next; + } + suffix_object(child, item); + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return; + } + + add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return; + } + + add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL)) + { + return NULL; + } + + if (item->prev != NULL) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0) + { + return; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + add_item_to_array(array, newitem); + return; + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (parent->child == item) + { + parent->child = replacement; + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return; + } + + cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + replacement->type &= ~cJSON_StringIsConst; + + cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); + + return true; +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0;a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a)) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); +} diff --git a/console/cJSON.h b/console/cJSON.h new file mode 100644 index 00000000..2c535628 --- /dev/null +++ b/console/cJSON.h @@ -0,0 +1,293 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 12 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* Precision of double variables comparison */ +#ifndef CJSON_DOUBLE_PRECISION +#define CJSON_DOUBLE_PRECISION .0000000000000001 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check if the item is a string and return its valuestring */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable adress area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/console/dcm2niix_fswrapper.cpp b/console/dcm2niix_fswrapper.cpp new file mode 100644 index 00000000..28db86d6 --- /dev/null +++ b/console/dcm2niix_fswrapper.cpp @@ -0,0 +1,121 @@ +#include + +#include "nii_dicom.h" +#include "dcm2niix_fswrapper.h" + +struct TDCMopts dcm2niix_fswrapper::tdcmOpts; + +/* These are the TDCMopts defaults set in dcm2niix +isIgnoreTriggerTimes = false +isTestx0021x105E = false +isAddNamePostFixes = true +isSaveNativeEndian = true +isOneDirAtATime = false +isRenameNotConvert = false +isSave3D = false +isGz = false +isPipedGz = false +isFlipY = true +isCreateBIDS = true +isSortDTIbyBVal = false +isAnonymizeBIDS = true +isOnlyBIDS = false +isCreateText = false +isForceOnsetTimes = true +isIgnoreDerivedAnd2D = false +isPhilipsFloatNotDisplayScaling = true +isTiltCorrect = true +isRGBplanar = false +isOnlySingleFile = false +isForceStackDCE = true +isIgnoreSeriesInstanceUID = false // if true, d.seriesUidCrc = d.seriesNum; +isRotate3DAcq = true +isCrop = false +saveFormat = 0 +isMaximize16BitRange = 2 +isForceStackSameSeries = 2 +nameConflictBehavior = 2 +isVerbose = 0 +isProgress = 0 +compressFlag = 0 +dirSearchDepth = 5 +gzLevel = 6 +filename = "%s_%p" // seriesNum_protocol -f "%s_%p" +outdir = "..." +indir = "..." +pigzname = '\000' +optsname = "~/.dcm2nii.ini" +indirParent = "..." +imageComments = "" +seriesNumber = nnn +numSeries = 0 + */ + +// set TDCMopts defaults, overwrite settings to output in mgz orientation +void dcm2niix_fswrapper::setOpts(const char* dcmindir, const char* niioutdir) +{ + memset(&tdcmOpts, 0, sizeof(tdcmOpts)); + setDefaultOpts(&tdcmOpts, NULL); + + if (dcmindir != NULL) + strcpy(tdcmOpts.indir, dcmindir); + if (niioutdir != NULL) + strcpy(tdcmOpts.outdir, niioutdir); + + // set the options for freesurfer mgz orientation + tdcmOpts.isRotate3DAcq = false; + tdcmOpts.isFlipY = false; + tdcmOpts.isIgnoreSeriesInstanceUID = true; + tdcmOpts.isCreateBIDS = false; + tdcmOpts.isGz = false; + //tdcmOpts.isForceStackSameSeries = 1; // merge 2D slice '-m y' + tdcmOpts.isForceStackDCE = false; + //tdcmOpts.isForceOnsetTimes = false; +} + +// interface to isDICOMfile() in nii_dicom.cpp +bool dcm2niix_fswrapper::isDICOM(const char* file) +{ + return isDICOMfile(file); +} + +/* + * interface to nii_loadDirCore() to search all dicom files from the directory input file is in, + * and convert dicom files with the same series as given file. + */ +int dcm2niix_fswrapper::dcm2NiiOneSeries(const char* dcmfile) +{ + // get seriesNo for given dicom file + struct TDICOMdata tdicomData = readDICOM((char*)dcmfile); + + double seriesNo = (double)tdicomData.seriesUidCrc; + if (tdcmOpts.isIgnoreSeriesInstanceUID) + seriesNo = (double)tdicomData.seriesNum; + + // set TDCMopts to convert just one series + tdcmOpts.seriesNumber[0] = seriesNo; + tdcmOpts.numSeries = 1; + + return nii_loadDirCore(tdcmOpts.indir, &tdcmOpts); +} + +// interface to nii_getMrifsStruct() +MRIFSSTRUCT* dcm2niix_fswrapper::getMrifsStruct(void) +{ + return nii_getMrifsStruct(); +} + +// return nifti header saved in MRIFSSTRUCT +nifti_1_header* dcm2niix_fswrapper::getNiiHeader(void) +{ + MRIFSSTRUCT* mrifsStruct = getMrifsStruct(); + return &mrifsStruct->hdr0; +} + +// return image data saved in MRIFSSTRUCT +const unsigned char* dcm2niix_fswrapper::getMRIimg(void) +{ + MRIFSSTRUCT* mrifsStruct = getMrifsStruct(); + return mrifsStruct->imgM; +} + diff --git a/console/dcm2niix_fswrapper.h b/console/dcm2niix_fswrapper.h new file mode 100644 index 00000000..f9e45702 --- /dev/null +++ b/console/dcm2niix_fswrapper.h @@ -0,0 +1,42 @@ +#ifndef DCM2NIIX_FSWRAPPER_H +#define DCM2NIIX_FSWRAPPER_H + +#include "nii_dicom_batch.h" +#include "nii_dicom.h" + +/* + * This is a wrapper class to interface with dcm2niix functions. + * 1. The wrapper class provides interface to convert dicom in mgz orientation. + * 2. The wrapper class and dcm2niix functions are compiled into libdcm2niixfs.a + * with -DUSING_DCM2NIIXFSWRAPPER -DUSING_MGH_NIFTI_IO. + * 3. When using libdcm2niixfs.a, instead of outputting .nii, *.bval, *.bvec to disk, + * nifti header, image data, TDICOMdata, & TDTI information are saved in MRIFSSTRUCT struct. + * 4. If libdcm2niixfs.a is compiled with -DUSING_MGH_NIFTI_IO, the application needs to link with nifti library. + */ +class dcm2niix_fswrapper +{ +public: + // set TDCMopts defaults, overwrite settings to output in mgz orientation. + static void setOpts(const char* dcmindir, const char* niioutdir); + + // interface to isDICOMfile() in nii_dicom.cpp + static bool isDICOM(const char* file); + + // interface to nii_loadDirCore() to search all dicom files from the directory input file is in, + // and convert dicom files with the same series as given file. + static int dcm2NiiOneSeries(const char* dcmfile); + + // interface to nii_getMrifsStruct() + static MRIFSSTRUCT* getMrifsStruct(void); + + // return nifti header saved in MRIFSSTRUCT + static nifti_1_header* getNiiHeader(void); + + // return image data saved in MRIFSSTRUCT + static const unsigned char* getMRIimg(void); + +private: + static struct TDCMopts tdcmOpts; +}; + +#endif diff --git a/console/main_console.cpp b/console/main_console.cpp index a5aaf799..143995c6 100644 --- a/console/main_console.cpp +++ b/console/main_console.cpp @@ -80,9 +80,13 @@ void showHelp(const char *argv[], struct TDCMopts opts) { printf(" -ba : anonymize BIDS (y/n, default %c)\n", bool2Char(opts.isAnonymizeBIDS)); printf(" -c : comment stored in NIfTI aux_file (provide up to 24 characters e.g. '-c first_visit')\n"); printf(" -d : directory search depth. Convert DICOMs in sub-folders of in_folder? (0..9, default %d)\n", opts.dirSearchDepth); - printf(" -e : export as NRRD (y) or MGH (o) instead of NIfTI (y/n/o, default n)\n"); +#ifdef myEnableJNIfTI + printf(" -e : export as NRRD (y) or MGH (o) or JSON/JNIfTI (j) or BJNIfTI (b) instead of NIfTI (y/n/o/j/b, default n)\n"); +#else + printf(" -e : export as NRRD (y) or MGH (o) instead of NIfTI (y/n/o/j/b, default n)\n"); +#endif #ifdef mySegmentByAcq -#define kQstr " %%q=sequence number," +#define kQstr " %q=sequence number," #else #define kQstr "" #endif @@ -174,7 +178,7 @@ void showHelp(const char *argv[], struct TDCMopts opts) { } //showHelp() int invalidParam(int i, const char *argv[]) { - if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == 'o') || (argv[i][0] == 'O') || (argv[i][0] == 'h') || (argv[i][0] == 'H') || (argv[i][0] == 'i') || (argv[i][0] == 'I') || (argv[i][0] == '0') || (argv[i][0] == '1') || (argv[i][0] == '2') || (argv[i][0] == '3')) + if (strchr("yYnNoOhHiIjJBb01234",argv[i][0])) return 0; //if (argv[i][0] != '-') return 0; @@ -359,6 +363,16 @@ int main(int argc, const char *argv[]) { opts.saveFormat = kSaveFormatNRRD; if ((argv[i][0] == 'o') || (argv[i][0] == 'O') || (argv[i][0] == '2')) opts.saveFormat = kSaveFormatMGH; + if ((argv[i][0] == 'j') || (argv[i][0] == 'J') || (argv[i][0] == '3')) + opts.saveFormat = kSaveFormatJNII; + if ((argv[i][0] == 'b') || (argv[i][0] == 'B') || (argv[i][0] == '4')) + opts.saveFormat = kSaveFormatBNII; + #ifndef myEnableJNIfTI + if ((opts.saveFormat == kSaveFormatJNII) || (opts.saveFormat == kSaveFormatBNII)) { + printf("Recompile for JNIfTI support.\n"); + return EXIT_SUCCESS; + } + #endif } else if ((argv[i][1] == 'g') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) diff --git a/console/main_console_batch.cpp b/console/main_console_batch.cpp index 29262d81..e968962e 100644 --- a/console/main_console_batch.cpp +++ b/console/main_console_batch.cpp @@ -49,9 +49,9 @@ void showHelp(const char * argv[]) { printf("### START YAML FILE ###\n"); printf("Options:\n"); printf(" isGz: false\n"); - printf(" isFlipY: true\n"); + printf(" isFlipY: false\n"); printf(" isVerbose: false\n"); - printf(" isCreateBIDS: true\n"); + printf(" isCreateBIDS: false\n"); printf(" isOnlySingleFile: false\n"); printf("Files:\n"); printf(" -\n"); diff --git a/console/makefile b/console/makefile index 49084991..3b14bfa9 100644 --- a/console/makefile +++ b/console/makefile @@ -6,14 +6,20 @@ CFLAGS=-s -O3 #Leak tests: # https://clang.llvm.org/docs/AddressSanitizer.html -# clang++ -O1 -g -fsanitize=address -fno-omit-frame-pointer -I. main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG +# clang++ -O1 -g -fsanitize=address -fno-omit-frame-pointer -I. main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp base64.c cJSON.c -o dcm2niix -DmyDisableOpenJPEG #run "make" for default build #run "JPEGLS=1 make" for JPEGLS build JFLAGS= ifeq "$(JPEGLS)" "1" - JFLAGS=-std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp + JFLAGS=-std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp +endif + +#run "JNIfTI=0 make" to disable JNIFTI build +JSFLAGS= +ifneq "$(JNIfTI)" "0" + JSFLAGS=-DmyEnableJNIfTI base64.cpp cJSON.cpp endif ifneq ($(OS),Windows_NT) @@ -31,4 +37,4 @@ ifneq ($(OS),Windows_NT) endif endif all: - g++ $(CFLAGS) -I. $(JFLAGS) main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG + g++ $(CFLAGS) -I. $(JSFLAGS) $(JFLAGS) main_console.cpp nii_foreign.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -o dcm2niix -DmyDisableOpenJPEG diff --git a/console/nifti1_io_core.cpp b/console/nifti1_io_core.cpp index 5f85a78c..10567a3c 100644 --- a/console/nifti1_io_core.cpp +++ b/console/nifti1_io_core.cpp @@ -32,7 +32,7 @@ #include "print.h" -#ifndef USING_R +#if !defined(USING_R) && !defined(USING_MGH_NIFTI_IO) void nifti_swap_8bytes( size_t n , void *ar ) // 4 bytes at a time { size_t ii ; @@ -267,7 +267,7 @@ mat44 nifti_dicom2mat(float orient[7], float patientPosition[4], float xyzMM[4]) return Q44; } -#ifndef USING_R +#if !defined(USING_R) && !defined(USING_MGH_NIFTI_IO) float nifti_mat33_determ( mat33 R ) /* determinant of 3x3 matrix */ { double r11,r12,r13,r21,r22,r23,r31,r32,r33 ; @@ -314,7 +314,7 @@ mat33 nifti_mat33_transpose( mat33 A ) /* transpose 3x3 matrix */ return B; } -#ifndef USING_R +#if !defined(USING_R) && !defined(USING_MGH_NIFTI_IO) mat33 nifti_mat33_inverse( mat33 R ) /* inverse of 3x3 matrix */ { double r11,r12,r13,r21,r22,r23,r31,r32,r33 , deti ; diff --git a/console/nifti1_io_core.h b/console/nifti1_io_core.h index 98607888..487b782f 100644 --- a/console/nifti1_io_core.h +++ b/console/nifti1_io_core.h @@ -22,7 +22,7 @@ extern "C" { #include #ifndef USING_R -typedef struct { /** 4x4 matrix struct **/ +typedef struct { /** 3x3 matrix struct **/ float m[3][3] ; } mat33 ; typedef struct { /** 4x4 matrix struct **/ @@ -74,8 +74,12 @@ ivec3 setiVec3(int x, int y, int z); vec3 setVec3(float x, float y, float z); vec4 setVec4(float x, float y, float z); #ifndef USING_R +#ifndef USING_MGH_NIFTI_IO // This declaration differs from the equivalent function in the current nifti1_io.h, so avoid the clash -void swap_nifti_header ( struct nifti_1_header *h) ; +void swap_nifti_header ( struct nifti_1_header *h ) ; +#else +void swap_nifti_header ( struct nifti_1_header *h , int is_nifti ) ; +#endif #endif vec4 nifti_vect44mat44_mul(vec4 v, mat44 m ); void nifti_swap_2bytes( size_t n , void *ar ); // 2 bytes at a time @@ -93,4 +97,4 @@ mat44 nifti_quatern_to_mat44( float qb, float qc, float qd, } #endif -#endif +#endif /* _NIFTI_IO_CORE_HEADER_ */ diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 0e08d79a..9c03232d 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, ""); @@ -740,6 +741,7 @@ struct TDICOMdata clear_dicom_data() { strcpy(d.bodyPartExamined, ""); strcpy(d.coilName, ""); strcpy(d.coilElements, ""); + strcpy(d.pulseSequenceName, ""); strcpy(d.radiopharmaceutical, ""); strcpy(d.convolutionKernel, ""); strcpy(d.parallelAcquisitionTechnique, ""); @@ -765,7 +767,11 @@ struct TDICOMdata clear_dicom_data() { d.lastScanLoc = NAN; d.TR = 0.0; d.TE = 0.0; +#ifdef USING_DCM2NIIXFSWRAPPER + d.TI = -1.0; // default when there is no TI in dicom file +#else d.TI = 0.0; +#endif d.flipAngle = 0.0; d.bandwidthPerPixelPhaseEncode = 0.0; d.acquisitionDuration = 0.0; @@ -781,6 +787,11 @@ struct TDICOMdata clear_dicom_data() { d.echoTrainLength = 0; d.waterFatShift = 0.0; d.groupDelay = 0.0; + d.postLabelDelay = 0; + d.shimGradientX = -33333;//impossible value for UINT16 + d.shimGradientY = -33333;//impossible value for UINT16 + d.shimGradientZ = -33333;//impossible value for UINT16 + strcpy(d.prescanReuseString, ""); d.decayFactor = 0.0; d.percentSampling = 0.0; d.phaseFieldofView = 0.0; @@ -806,6 +817,7 @@ struct TDICOMdata clear_dicom_data() { d.doseCalibrationFactor = 0.0; d.ecat_isotope_halflife = 0.0; d.frameDuration = -1.0; + d.frameReferenceTime = -1.0; d.ecat_dosage = 0.0; d.radionuclideTotalDose = 0.0; d.seriesNum = 1; @@ -849,6 +861,7 @@ struct TDICOMdata clear_dicom_data() { d.isLittleEndian = true; //DICOM initially always little endian d.converted2NII = 0; d.numberOfDiffusionDirectionGE = -1; + d.velocityEncodeScaleGE = 1.0; d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNKNOWN; d.rtia_timerGE = -1.0; d.rawDataRunNumber = -1; @@ -912,7 +925,7 @@ void dcmStrDigitsDotOnlyKey(char key, char *lStr) { } else if (!isKey) lStr[i] = ' '; } -} //dcmStrDigitsOnlyKey() +} //dcmStrDigitsDotOnlyKey() void dcmStrDigitsOnlyKey(char key, char *lStr) { //e.g. string "p2s3" returns 2 if key=="p" and 3 if key=="s" @@ -1128,6 +1141,14 @@ double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], c } //dcmFloatDouble() #endif +// SS/IS datatype +int16_t dcmIntSS(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 16 or 32 bit integer + if (littleEndian) + return (uint16_t)lBuffer[0] | ((uint16_t)lBuffer[1] << 8); //shortint vs word? + return (uint16_t)lBuffer[1] | ((uint16_t)lBuffer[1] << 0); //shortint vs word? +} //dcmInt() + +//UL/US unsigned integer int dcmInt(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 16 or 32 bit integer if (littleEndian) { if (lByteLength <= 3) @@ -1179,15 +1200,16 @@ int dcmStrManufacturer(const int lByteLength, unsigned char lBuffer[]) { //read ret = kMANUFACTURER_PHILIPS; if ((toupper(cString[0]) == 'T') && (toupper(cString[1]) == 'O')) ret = kMANUFACTURER_TOSHIBA; - //CANON_MEC if ((toupper(cString[0]) == 'C') && (toupper(cString[1]) == 'A')) ret = kMANUFACTURER_CANON; + if ((toupper(cString[0]) == 'C') && (toupper(cString[1]) == 'P')) + ret = kMANUFACTURER_SIEMENS; //CPS is a Siemens venture, issue 568 if ((toupper(cString[0]) == 'U') && (toupper(cString[1]) == 'I')) ret = kMANUFACTURER_UIH; if ((toupper(cString[0]) == 'B') && (toupper(cString[1]) == 'R')) ret = kMANUFACTURER_BRUKER; - if (ret == kMANUFACTURER_UNKNOWN) - printWarning("Unknown manufacturer %s\n", cString); + //if (ret == kMANUFACTURER_UNKNOWN) //reduce verbosity: single warning for series : Unable to determine manufacturer (0008,0070) + // printWarning("Unknown manufacturer %s\n", cString); //#ifdef _MSC_VER free(cString); //#endif @@ -1488,6 +1510,8 @@ int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeS h->datatype = DT_RGB24; } else if ((d.bitsAllocated == 8) && (d.samplesPerPixel == 1)) h->datatype = DT_UINT8; + else if ((d.bitsAllocated == 1) && (d.samplesPerPixel == 1)) + h->datatype = DT_UINT8; else if ((d.bitsAllocated == 12) && (d.samplesPerPixel == 1)) h->datatype = DT_INT16; else if ((d.bitsAllocated == 16) && (d.samplesPerPixel == 1) && (d.isSigned)) @@ -1526,7 +1550,9 @@ int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeS h->magic[2] = '1'; h->magic[3] = '\0'; h->vox_offset = (float)d.imageStart; - if (d.bitsAllocated == 12) + if (d.bitsAllocated == 1) + h->bitpix = 8 * d.samplesPerPixel; + else if (d.bitsAllocated == 12) h->bitpix = 16 * d.samplesPerPixel; else h->bitpix = d.bitsAllocated * d.samplesPerPixel; @@ -1605,7 +1631,7 @@ void cleanStr(char *lOut) { char *cString = (char *)malloc(sizeof(char) * (lLength + 1)); cString[lLength] = 0; memcpy(cString, (char *)&lOut[0], lLength); - for (int i = 0; i < lLength; i++) + for (int i = 0; i < (int)lLength; i++) //assume specificCharacterSet (0008,0005) is ISO_IR 100 http://en.wikipedia.org/wiki/ISO/IEC_8859-1 if (cString[i] < 1) { unsigned char c = (unsigned char)cString[i]; @@ -1654,12 +1680,12 @@ void cleanStr(char *lOut) { if (c == 255) cString[i] = 'y'; } - for (int i = 0; i < lLength; i++) + for (int i = 0; i < (int)lLength; i++) if ((cString[i] < 1) || (cString[i] == ' ') || (cString[i] == ',') || (cString[i] == '/') || (cString[i] == '\\') || (cString[i] == '%') || (cString[i] == '*') || (cString[i] == 9) || (cString[i] == 10) || (cString[i] == 11) || (cString[i] == 13)) cString[i] = '_'; //issue398 //if ((cString[i]<1) || (cString[i]==' ') || (cString[i]==',') || (cString[i]=='^') || (cString[i]=='/') || (cString[i]=='\\') || (cString[i]=='%') || (cString[i]=='*') || (cString[i] == 9) || (cString[i] == 10) || (cString[i] == 11) || (cString[i] == 13)) cString[i] = '_'; int len = 1; - for (int i = 1; i < lLength; i++) { //remove repeated "_" + for (int i = 1; i < (int)lLength; i++) { //remove repeated "_" if ((cString[i - 1] != '_') || (cString[i] != '_')) { cString[len] = cString[i]; len++; @@ -1691,6 +1717,8 @@ struct TDICOMdata nii_readParRec(char *parname, int isVerbose, struct TDTI4D *dt dti4D->volumeOnsetTime[0] = -1; dti4D->decayFactor[0] = -1; dti4D->frameDuration[0] = -1; + dti4D->frameReferenceTime[0] = -1; + //dti4D->contentTime[0] = -1; //dti4D->fragmentOffset[0] = -1; dti4D->intenScale[0] = 0.0; strcpy(d.protocolName, ""); //erase dummy with empty @@ -2744,11 +2772,23 @@ unsigned char *nii_flipY(unsigned char *bImg, struct nifti_1_header *h) { return nii_flipImgY(bImg, h); } // nii_flipY() +void conv1bit16bit(unsigned char *img, struct nifti_1_header hdr) { //issue572 + printWarning("Support for images that allocate 1 bits is experimental\n"); + int nVox = (int)nii_ImgBytes(hdr) / (hdr.bitpix / 8); + for (int i = (nVox - 1); i >= 0; i--) { + int ibyte = i >> 3; //byte to sample + int ibit = (i % 8); //bit 0..7 + //if (highBit > 0) + // ibit = 7 - ibit; + int val = img[ibyte] >> ibit; + img[i] = (val & 1); + } +} //conv1bit16bit() + void conv12bit16bit(unsigned char *img, struct nifti_1_header hdr) { //convert 12-bit allocated data to 16-bit // works for MR-MONO2-12-angio-an1 from http://www.barre.nom.fr/medical/samples/ // looks wrong: this sample toggles between big and little endian stores - printWarning("Support for images that allocate 12 bits is experimental\n"); int nVox = (int)nii_ImgBytes(hdr) / (hdr.bitpix / 8); for (int i = (nVox - 1); i >= 0; i--) { int i16 = i * 2; @@ -2769,6 +2809,8 @@ unsigned char *nii_loadImgCore(char *imgname, struct nifti_1_header hdr, int bit size_t imgsz = nii_ImgBytes(hdr); size_t imgszRead = imgsz; size_t imageStart = imageStart32; + if (bitsAllocated == 1) + imgszRead = (imgsz + 7) >> 3; if (bitsAllocated == 12) imgszRead = round(imgsz * 0.75); FILE *file = fopen(imgname, "rb"); @@ -2776,12 +2818,19 @@ unsigned char *nii_loadImgCore(char *imgname, struct nifti_1_header hdr, int bit printError("Unable to open '%s'\n", imgname); return NULL; } - fseek(file, 0, SEEK_END); - long fileLen = ftell(file); + //fseek(file, 0, SEEK_END); + //long fileLen = ftell(file); + #ifdef _MSC_VER + _fseeki64(file, 0, SEEK_END); + size_t fileLen = _ftelli64(file); + #else + fseeko(file, 0, SEEK_END); //Windows _fseeki64 + size_t fileLen = ftello(file); //Windows _ftelli64 + #endif if (fileLen < (imgszRead + imageStart)) { //note hdr.vox_offset is a float: issue507 //https://www.nitrc.org/forum/message.php?msg_id=27155 - printMessage("FileSize < (ImageSize+HeaderSize): %ld < (%zu+%zu) \n", fileLen, imgszRead, imageStart); + printMessage("FileSize < (ImageSize+HeaderSize): %zu < (%zu+%zu) \n", fileLen, imgszRead, imageStart); printWarning("File not large enough to store image data: %s\n", imgname); return NULL; } @@ -2796,6 +2845,8 @@ unsigned char *nii_loadImgCore(char *imgname, struct nifti_1_header hdr, int bit printError("Only loaded %zu of %zu bytes for %s\n", sz, imgszRead, imgname); return NULL; } + if (bitsAllocated == 1) + conv1bit16bit(bImg, hdr); if (bitsAllocated == 12) conv12bit16bit(bImg, hdr); return bImg; @@ -2902,7 +2953,7 @@ unsigned char *nii_iVaries(unsigned char *img, struct nifti_1_header *hdr, struc free(img); //release previous image if ((dti4D != NULL) && (dti4D->intenScale[0] != 0.0)) { //enhanced dataset, intensity varies across slices of a single file if (dti4D->RWVScale[0] != 0.0) - printWarning("Intensity scale/slope using 0028,1053 and 0028,1052"); //to do: real-world values and precise values + printWarning("Intensity scale/slope using 0028,1053 and 0028,1052\n"); //to do: real-world values and precise values int dim1to2 = hdr->dim[1] * hdr->dim[2]; int slice = -1; //(0028,1052) SS = scale slope (2005,100E) RealWorldIntercept = (0040,9224) Real World Slope = (0040,9225) @@ -3355,14 +3406,14 @@ unsigned char *nii_loadImgPMSCT_RLE1(char *imgname, struct nifti_1_header hdr, s free(cImg); return NULL; } - if (imgsz == dcm.imageBytes) { // Handle special case that data is not compressed: + if ((int)imgsz == dcm.imageBytes) { // Handle special case that data is not compressed: return (unsigned char *)cImg; } unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output // RLE pass: compressed -> temp (bImg -> tImg) char *tImg = (char *)malloc(imgsz); //temp output - int o = 0; - for (size_t i = 0; i < dcm.imageBytes; ++i) { + size_t o = 0; + for (size_t i = 0; (int)i < dcm.imageBytes; ++i) { if (cImg[i] == (char)0xa5) { int repeat = (unsigned char)cImg[i + 1] + 1; char value = cImg[i + 2]; @@ -3378,13 +3429,13 @@ unsigned char *nii_loadImgPMSCT_RLE1(char *imgname, struct nifti_1_header hdr, s } } //for i free(cImg); - int tempsize = o; + size_t tempsize = o; //Looks like this RLE is pretty ineffective... // printMessage("RLE %d -> %d\n", dcm.imageBytes, o); //Delta encoding pass: temp -> output (tImg -> bImg) unsigned short delta = 0; o = 0; - int n16 = (int)imgsz >> 1; + size_t n16 = (int)imgsz >> 1; unsigned short *bImg16 = (unsigned short *)bImg; for (size_t i = 0; i < tempsize; ++i) { if (tImg[i] == (unsigned char)0x5a) { @@ -3772,7 +3823,8 @@ void set_directionality0018_9075(struct TVolumeDiffusion *ptvd, unsigned char *i int set_bValGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf) { //see Series 16 https://github.com/nikadon/cc-dcm2bids-wrapper/tree/master/dicom-qa-examples/ge-mr750-dwi-b-vals#table b750 = 1000000750\8\0\0 b1500 = 1000001500\8\0\0 int bVal = dcmStrInt(lLength, inbuf); - bVal = (bVal % 10000); + // GE bias value for b-value https://github.com/rordenlab/dcm2niix/issues/602 + bVal = (bVal % 1000000000); ptvd->_dtiV[0] = bVal; //printf("(0043,1039) '%s' Slop_int_6 -->%d \n", inbuf, bVal); //dd.CSA.numDti = 1; // Always true for GE. @@ -4066,8 +4118,13 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D printMessage("Unable to open file %s\n", fname); return d; } - fseek(file, 0, SEEK_END); - long fileLen = ftell(file); //Get file length + #ifdef _MSC_VER + _fseeki64(file, 0, SEEK_END); + size_t fileLen = _ftelli64(file); + #else + fseeko(file, 0, SEEK_END); //Windows _fseeki64 + size_t fileLen = ftello(file); //Windows _ftelli64 + #endif if (fileLen < 256) { printMessage("File too small to be a DICOM image %s\n", fname); return d; @@ -4090,7 +4147,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D if (MaxBufferSz > (size_t)fileLen) MaxBufferSz = fileLen; //printf("%d -> %d\n", MaxBufferSz, fileLen); - long lFileOffset = 0; + size_t lFileOffset = 0; fseek(file, 0, SEEK_SET); //Allocate memory unsigned char *buffer = (unsigned char *)malloc(MaxBufferSz + 1); @@ -4120,7 +4177,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kDirectoryRecordSequence 0x0004 + (0x1220 << 16) //#define kSpecificCharacterSet 0x0008+(0x0005 << 16 ) //someday we should handle foreign characters... #define kImageTypeTag 0x0008 + (0x0008 << 16) -//#define kSOPInstanceUID 0x0008+(0x0018 << 16 ) //Philips inserts time as last item, e.g. ?.?.?.YYYYMMDDHHmmSS.SSSS +#define kSOPInstanceUID 0x0008+(0x0018 << 16 ) //Philips inserts time as last item, e.g. ?.?.?.YYYYMMDDHHmmSS.SSSS // not reliable https://neurostars.org/t/heudiconv-no-extraction-of-slice-timing-data-based-on-philips-dicoms/2201/21 #define kStudyDate 0x0008 + (0x0020 << 16) #define kAcquisitionDate 0x0008 + (0x0022 << 16) @@ -4139,6 +4196,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kInstitutionalDepartmentName 0x0008 + (0x1040 << 16) #define kManufacturersModelName 0x0008 + (0x1090 << 16) #define kDerivationDescription 0x0008 + (0x2111 << 16) +#define kReferencedImageEvidenceSQ (uint32_t)0x0008 + (0x9092 << 16) #define kComplexImageComponent (uint32_t)0x0008 + (0x9208 << 16) //'0008' '9208' 'CS' 'ComplexImageComponent' #define kAcquisitionContrast (uint32_t)0x0008 + (0x9209 << 16) //'0008' '9209' 'CS' 'AcquisitionContrast' #define kPatientName 0x0010 + (0x0010 << 16) @@ -4193,6 +4251,7 @@ struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D #define kInPlanePhaseEncodingDirection 0x0018 + (0x1312 << 16) //CS #define kSAR 0x0018 + (0x1316 << 16) //'DS' 'SAR' #define kPatientOrient 0x0018 + (0x5100 << 16) //0018,5100. patient orientation - 'HFS' +#define kPulseSequenceName 0x0018 + uint32_t(0x9005 << 16) //'SH' 'YES'/'NO' #define kInversionRecovery 0x0018 + uint32_t(0x9009 << 16) //'CS' 'YES'/'NO' #define kSpoiling 0x0018 + uint32_t(0x9016 << 16) //'CS' #define kEchoPlanarPulseSequence 0x0018 + uint32_t(0x9018 << 16) //'CS' 'YES'/'NO' @@ -4214,6 +4273,8 @@ 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 #define kDiffusionBValueXZ 0x0018 + uint32_t(0x9604 << 16) //FD @@ -4241,6 +4302,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kDiffusionDirectionGEY 0x0019 + (0x10BC << 16) //DS frequency diffusion direction #define kDiffusionDirectionGEZ 0x0019 + (0x10BD << 16) //DS slice diffusion direction #define kNumberOfDiffusionDirectionGE 0x0019 + (0x10E0 << 16) ///DS NumberOfDiffusionDirection:UserData24 +#define kVelocityEncodeScaleGE 0x0019 + (0x10E2 << 16) ///DS Velocity Encode Scale #define kStudyID 0x0020 + (0x0010 << 16) #define kSeriesNum 0x0020 + (0x0011 << 16) #define kAcquNum 0x0020 + (0x0012 << 16) @@ -4263,15 +4325,19 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kTemporalPositionIndex 0x0020 + uint32_t(0x9128 << 16) // UL #define kDimensionIndexPointer 0x0020 + uint32_t(0x9165 << 16) //Private Group 21 as Used by Siemens: -#define kSequenceVariant21 0x0021 + (0x105B << 16) //CS +#define kScanningSequenceSiemens 0x0021 + (0x105A << 16) //CS +#define kSequenceVariant21 0x0021 + (0x105B << 16) //CS Siemens ONLY: For GE this is TaggingFlipAngle +#define kScanOptionsSiemens 0x0021 + (0x105C << 16) //CS Siemens ONLY #define kPATModeText 0x0021 + (0x1009 << 16) //LO, see kImaPATModeText +#define kCSASeriesHeaderInfoXA 0x0021 + (0x1019 << 16) #define kTimeAfterStart 0x0021 + (0x1104 << 16) //DS -//#define kICE_Dims 0x0021 + (0x1106 << 16) //LO +#define kICE_dims 0x0021 + (0x1106 << 16) //LO [X_4_1_1_1_1_160_1_1_1_1_1_277] #define kPhaseEncodingDirectionPositiveSiemens 0x0021 + (0x111C << 16) //IS -//#define kRealDwellTime 0x0021+(0x1142<< 16 )//IS +#define kRealDwellTime 0x0021+(0x1142<< 16 )//IS #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 @@ -4288,6 +4354,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kXYSpacing 0x0028 + (0x0030 << 16) //DS 'PixelSpacing' #define kBitsAllocated 0x0028 + (0x0100 << 16) #define kBitsStored 0x0028 + (0x0101 << 16) //US 'BitsStored' +#define kHighBit 0x0028 + (0x0102 << 16) //US 'HighBit' #define kIsSigned 0x0028 + (0x0103 << 16) //PixelRepresentation #define kPixelPaddingValue 0x0028 + (0x0120 << 16) // https://github.com/rordenlab/dcm2niix/issues/262 #define kFloatPixelPaddingValue 0x0028 + (0x0122 << 16) // https://github.com/rordenlab/dcm2niix/issues/262 @@ -4303,6 +4370,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kProcedureStepDescription 0x0040 + (0x0254 << 16) #define kRealWorldIntercept 0x0040 + uint32_t(0x9224 << 16) //IS dicm2nii's SlopInt_6_9 #define kRealWorldSlope 0x0040 + uint32_t(0x9225 << 16) //IS dicm2nii's SlopInt_6_9 +#define kShimGradientX 0x0043 + (0x1002 << 16) //SS +#define kShimGradientY 0x0043 + (0x1003 << 16) //SS +#define kShimGradientZ 0x0043 + (0x1004 << 16) //SS +#define kPrescanReuseString 0x0043 + (0x1095 << 16) //LO #define kUserDefineDataGE 0x0043 + (0x102A << 16) //OB #define kEffectiveEchoSpacingGE 0x0043 + (0x102C << 16) //SS #define kImageTypeGE 0x0043 + (0x102F << 16) //SS 0/1/2/3 for magnitude/phase/real/imaginary @@ -4322,7 +4393,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kAttenuationCorrectionMethod 0x0054 + (0x1101 << 16) //LO #define kDecayCorrection 0x0054 + (0x1102 << 16) //CS #define kReconstructionMethod 0x0054 + (0x1103 << 16) //LO -#define kDecayFactor 0x0054 + (0x1321 << 16) //LO +#define kFrameReferenceTime 0x0054 + (0x1300 << 16) //DS +#define kDecayFactor 0x0054 + (0x1321 << 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 ) @@ -4374,6 +4446,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); #define kMRImageGradientOrientationNumber 0x2005+(0x1413 << 16) //IS #define kMRImageLabelType 0x2005 + (0x1429 << 16) //CS ASL LBL_CTL https://github.com/physimals/dcm_convert_phillips/ #define kMRImageDiffVolumeNumber 0x2005+(0x1596 << 16) //IS +#define kOriginalAttributesSq 0x0400 + (0x0561 << 16) #define kSharedFunctionalGroupsSequence 0x5200 + uint32_t(0x9229 << 16) // SQ #define kPerFrameFunctionalGroupsSequence 0x5200 + uint32_t(0x9230 << 16) // SQ #define kWaveformSq 0x5400 + (0x0100 << 16) @@ -4400,16 +4473,19 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); float temporalResolutionMS = 0.0; float MRImageDynamicScanBeginTime = 0.0; bool is2005140FSQ = false; + bool is4000561SQ = false; //Original Attributes SQ + bool is00089092SQ = false; //Referenced Image Evidence SQ bool overlayOK = true; int userData12GE = 0; int overlayRows = 0; int overlayCols = 0; bool isNeologica = false; - bool isTriggerSynced = false; - bool isProspectiveSynced = false; + //bool isTriggerSynced = false; + //bool isProspectiveSynced = false; bool isDICOMANON = false; //issue383 bool isMATLAB = false; //issue383 - bool isASL = false; + //bool isASL = false; + bool has00200013 = false; //double contentTime = 0.0; int echoTrainLengthPhil = 0; int philMRImageDiffBValueNumber = 0; @@ -4433,6 +4509,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //int temporalPositionIdentifier = 0; int locationsInAcquisitionPhilips = 0; int imagesInAcquisition = 0; + int highBit = 0; //int sumSliceNumberMrPhilips = 0; int sliceNumberMrPhilips = 0; int volumeNumber = -1; @@ -4445,7 +4522,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //int mRSeriesAcquisitionNumber = 0; uint32_t lLength; uint32_t groupElement; - long lPos = 0; + size_t lPos = 0; bool isPhilipsDerived = false; //bool isPhilipsDiffusion = false; if (isPart10prefix) { //for part 10 files, skip preamble and prefix @@ -4466,8 +4543,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); } char vr[2]; //float intenScalePhilips = 0.0; + char scanOptionsSiemens[kDICOMStrLarge] = ""; char seriesTimeTxt[kDICOMStr] = ""; char acquisitionDateTimeTxt[kDICOMStr] = ""; + char scanningSequenceSiemens[kDICOMStr] = ""; char imageType1st[kDICOMStr] = ""; bool isEncapsulatedData = false; int multiBandFactor = 0; @@ -4493,6 +4572,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); float vRLPhilips = 0.0; float vAPPhilips = 0.0; float vFHPhilips = 0.0; + double acquisitionTimePhilips = -1.0; bool isPhase = false; bool isReal = false; bool isImaginary = false; @@ -4593,6 +4673,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); } if (unNest) { is2005140FSQ = false; + is4000561SQ = false; + is00089092SQ = false; if (sqDepth < 0) sqDepth = 0; //should not happen, but protect for faulty anonymization //if we leave the folder MREchoSequence 0018,9114 @@ -4600,7 +4682,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); sqDepth00189114 = -1; //triggered //printf("slice %d---> 0020,9157 = %d %d %d\n", inStackPositionNumber, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2]); // d.aslFlags = kASL_FLAG_PHILIPS_LABEL; kASL_FLAG_PHILIPS_LABEL - if ((nDimIndxVal > 1) && (volumeNumber > 0) && (inStackPositionNumber > 0) && (d.aslFlags == kASL_FLAG_PHILIPS_LABEL) || (d.aslFlags == kASL_FLAG_PHILIPS_CONTROL)) { + if ((nDimIndxVal > 1) && (volumeNumber > 0) && (inStackPositionNumber > 0) && ((d.aslFlags == kASL_FLAG_PHILIPS_LABEL) || (d.aslFlags == kASL_FLAG_PHILIPS_CONTROL))) { + isKludgeIssue533 = true; for (int i = 0; i < nDimIndxVal; i++) d.dimensionIndexValues[i] = 0; @@ -4613,6 +4696,11 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); nDimIndxVal = 4; //slice < phase < control/label < volume //printf("slice %d phase %d control/label %d repeat %d\n", inStackPositionNumber, d.phaseNumber, d.aslFlags == kASL_FLAG_PHILIPS_LABEL, volumeNumber); } + if ((volumeNumber == 1) && (acquisitionTimePhilips >= 0.0) && (inStackPositionNumber > 0)) { + d.CSA.sliceTiming[inStackPositionNumber - 1] = acquisitionTimePhilips; + printf("%d\t%f\n", inStackPositionNumber, acquisitionTimePhilips); + acquisitionTimePhilips = - 1.0; + } int ndim = nDimIndxVal; if (inStackPositionNumber > 0) { //for images without SliceNumberMrPhilips (2001,100A) @@ -4641,7 +4729,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); break; } uint32_t dimensionIndexOrder[MAX_NUMBER_OF_DIMENSIONS]; - for (size_t i = 0; i < nDimIndxVal; i++) + for (int i = 0; i < nDimIndxVal; i++) dimensionIndexOrder[i] = i; // Bruker Enhanced MR IOD: reorder dimensions to ensure InStackPositionNumber corresponds to the first one // This will ensure correct ordering of slices in 4D datasets @@ -4819,7 +4907,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); d.imageBytes = dcmInt(4, &buffer[lPos], d.isLittleEndian); lPos = lPos + 4; lLength = d.imageBytes; - if (d.imageBytes > 128) { + if (d.imageBytes <= 0) goto skipRemap; + if (d.imageBytes > 24) { /*if (encapsulatedDataFragments < kMaxDTI4D) { dti4D->fragmentOffset[encapsulatedDataFragments] = (int)lPos + (int)lFileOffset; dti4D->fragmentLength[encapsulatedDataFragments] = lLength; @@ -4829,6 +4918,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); encapsulatedDataFragmentStart = (int)lPos + (int)lFileOffset; } } + if ((is4000561SQ) || (is00089092SQ)) + groupElement = kUnused; //ignore Original Attributes if ((isIconImageSequence) && ((groupElement & 0x0028) == 0x0028)) groupElement = kUnused; //ignore icon dimensions #ifdef salvageAgfa //issue435 @@ -4902,7 +4993,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); if (strstr(privateCreator, "Image Private Header") != NULL) privateCreatorRemap = 0x0065 + (0x1000 << 16); //sanity check: group should match - if (grp != (privateCreatorRemap & 65535)) + if (grp != (int)(privateCreatorRemap & 65535)) privateCreatorRemap = 0; if (privateCreatorRemap == 0) goto skipRemap; //this is not a known private group @@ -4934,8 +5025,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); printMessage("remapping %04x,%04x -> %04x,%04x\n", groupElement & 65535, groupElement >> 16, remappedGroupElement & 65535, remappedGroupElement >> 16); groupElement = remappedGroupElement; } - skipRemap: #endif // salvageAgfa + skipRemap: if ((lLength % 2) != 0) { //https://www.nitrc.org/forum/forum.php?thread_id=11827&forum_id=4703 printMessage("Illegal DICOM tag %04x,%04x (odd element length %d): %s\n", groupElement & 65535, groupElement >> 16, lLength, fname); //proper to return here, but we can carry on as a hail mary @@ -4948,8 +5039,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); dcmStr(lLength, &buffer[lPos], mediaUID); //Philips "XX_" files //see https://github.com/rordenlab/dcm2niix/issues/328 - if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.66") != NULL) - d.isRawDataStorage = true; + if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.66.4") != NULL) //Segmentation Storage + d.isDerived = true; //Segmentation IOD, issue 572 + else if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.66") != NULL) + d.isRawDataStorage = true; //e.g. Raw Data IOD, https://dicom.nema.org/dicom/2013/output/chtml/part04/sect_i.4.html if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.1") != NULL) d.isRawDataStorage = true; //Private MR Spectrum Storage if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.2") != NULL) @@ -5151,6 +5244,32 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); dcmStr(lLength, &buffer[lPos], acquisitionDateTimeTxt); //printMessage("%s\n",acquisitionDateTimeTxt); break; +/* Failed attempt to infer slice timing for Philips MRI +https://neurostars.org/t/how-dcm2niix-handles-different-imaging-types/22697/6 + + case kSOPInstanceUID: { + if (d.manufacturer != kMANUFACTURER_PHILIPS) break; + char uid[kDICOMStrLarge]; + dcmStr(lLength, &buffer[lPos], uid, true); + char *timeStr = strrchr(uid, '.'); + //nb Manufactuer (0008,0070) comes AFTER (0008,0018) SOPInstanceUID. + //format of (0008,0018) UI + //[1.23.4.2019051416101221842 + // .YYYYMMDDHHmmssxxxxx + timeStr++; //skip "." + if ((strlen(timeStr) != 19) || (strlen(d.studyDate) < 8)) break; + bool sameDay = true; + for (int z = 0; z < 8; z++) + if (timeStr[z] != d.studyDate[z]) sameDay = false; + if (!sameDay) + printf("SOPInstanceUID does not match StudyDate: assuming study cross midnight\n"); + char *hourStr = timeStr + 8; //Skip 8 charactersYear,Month,Day YYYYMMDD + acquisitionTimePhilips = (double) atof(hourStr) * (double) 0.00001; + //printf(" %s %s %f\n", timeStr, hourStr, acquisitionTimePhilips); + + break; + } +*/ case kStudyDate: dcmStr(lLength, &buffer[lPos], d.studyDate); if (((int)strlen(d.studyDate) > 7) && (strstr(d.studyDate, "19000101") != NULL)) @@ -5179,11 +5298,14 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); dcmStr(lLength, &buffer[lPos], d.institutionName); break; case kInstitutionAddress: //VR is "ST": 1024 chars maximum - dcmStr(lLength, &buffer[lPos], d.institutionAddress); + dcmStr(lLength, &buffer[lPos], d.institutionAddress, true); break; case kReferringPhysicianName: dcmStr(lLength, &buffer[lPos], d.referringPhysicianName); break; + case kReferencedImageEvidenceSQ: + is00089092SQ = true; + break; case kComplexImageComponent: if (is2005140FSQ) break; //see Maastricht DICOM data for magnitude data with this field set as REAL! https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging @@ -5218,8 +5340,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); 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 + //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]; @@ -5232,6 +5354,11 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); acquisitionTimesGE_UIH++; break; } + /*case kContentTime: { + char contentTimeTxt[kDICOMStr]; + dcmStr(lLength, &buffer[lPos], contentTimeTxt); + d.contentTime = atof(contentTimeTxt); + }*/ case kSeriesTime: dcmStr(lLength, &buffer[lPos], seriesTimeTxt); break; @@ -5330,6 +5457,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); case kPatientOrient: dcmStr(lLength, &buffer[lPos], d.patientOrient); break; + case kPulseSequenceName: + dcmStr(lLength, &buffer[lPos], d.pulseSequenceName); + break; case kInversionRecovery: // CS [YES],[NO] if (lLength < 2) break; @@ -5390,10 +5520,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_COMBINATION; break; } - case kCardiacSynchronizationTechnique: + /*case kCardiacSynchronizationTechnique: if (toupper(buffer[lPos]) == 'P') isProspectiveSynced = true; - break; + break;*/ case kParallelReductionFactorInPlane: if (d.manufacturer == kMANUFACTURER_SIEMENS) break; @@ -5505,6 +5635,12 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); d.numberOfDiffusionDirectionGE = round(f); break; } + case kVelocityEncodeScaleGE: { + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.velocityEncodeScaleGE = dcmStrFloat(lLength, &buffer[lPos]); + break; + } case kLastScanLoc: d.lastScanLoc = dcmStrFloat(lLength, &buffer[lPos]); break; @@ -5545,18 +5681,17 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); case kPulseSequenceNameGE: { //LO 'epi'/'epiRT' if (d.manufacturer != kMANUFACTURER_GE) break; - char epiStr[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], epiStr); - if (strstr(epiStr, "epi_pepolar") != NULL) { + dcmStr(lLength, &buffer[lPos], d.pulseSequenceName); + if (strstr( d.pulseSequenceName, "epi_pepolar") != NULL) { d.epiVersionGE = kGE_EPI_PEPOLAR_FWD; //n.b. combine with 0019,10B3 - } else if (strstr(epiStr, "epi2") != NULL) { + } else if (strstr( d.pulseSequenceName, "epi2") != NULL) { d.epiVersionGE = kGE_EPI_EPI2; //-1 = not epi, 0 = epi, 1 = epiRT, 2 = epi2 - } else if (strstr(epiStr, "epiRT") != NULL) { + } else if (strstr( d.pulseSequenceName, "epiRT") != NULL) { d.epiVersionGE = kGE_EPI_EPIRT; //-1 = not epi, 0 = epi, 1 = epiRT - } else if (strstr(epiStr, "epi") != NULL) { + } else if (strstr( d.pulseSequenceName, "epi") != NULL) { d.epiVersionGE = kGE_EPI_EPI; //-1 = not epi, 0 = epi, 1 = epiRT } - if (strcmp(epiStr, "3db0map") == 0) { + if (strcmp( d.pulseSequenceName, "3db0map") == 0) { isGEfieldMap = true; //issue501 } break; @@ -5657,6 +5792,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); case kImageNum: //Enhanced Philips also uses this in once per file SQ 0008,1111 //Enhanced Philips also uses this once per slice in SQ 2005,140f + has00200013 = true; if (d.imageNum < 1) d.imageNum = dcmStrInt(lLength, &buffer[lPos]); //Philips renames each image again in 2001,9000, which can lead to duplicates break; @@ -5742,7 +5878,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); dcmStr(lLength, &buffer[lPos], d.imageComments, true); break; //group 21: siemens - //g21 + case kScanOptionsSiemens: + dcmStr(lLength, &buffer[lPos], scanOptionsSiemens, true); + break; case kPATModeText: { //e.g. Siemens iPAT x2 listed as "p2" char accelStr[kDICOMStr]; dcmStr(lLength, &buffer[lPos], accelStr); @@ -5760,6 +5898,14 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //printMessage("p%gs%d\n", d.accelFactPE, multiBandFactor); break; } + case kCSASeriesHeaderInfoXA: + if (d.manufacturer != kMANUFACTURER_SIEMENS) + break; + if ((lPos + lLength) > fileLen) + break; + d.CSA.SeriesHeader_offset = (int)lPos; + d.CSA.SeriesHeader_length = lLength; + break; case kTimeAfterStart: //0021,1104 see https://github.com/rordenlab/dcm2niix/issues/303 // 0021,1104 6@159630 DS 4.635 @@ -5773,14 +5919,20 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //printf("x\t%d\t%g\tkTimeAfterStart\n", acquisitionTimesGE_UIH, d.CSA.sliceTiming[acquisitionTimesGE_UIH]); acquisitionTimesGE_UIH++; break; - /*case kICE_Dims: { //issue568 "X_4_1_1_1_1_160_1_1_1_1_1_277" - if (d.manufacturer != kMANUFACTURER_SIEMENS) + case kICE_dims: { //issue568: LO (0021,1106) [X_4_1_1_1_1_160_1_1_1_1_1_277] + if ((d.manufacturer != kMANUFACTURER_SIEMENS) || (d.echoNum > 1)) break; char iceStr[kDICOMStr]; dcmStr(lLength, &buffer[lPos], iceStr); - printf("do something profound with %s\n", iceStr); + dcmStrDigitsOnly(iceStr); + char *end; + int echo = (int)strtol(iceStr, &end, 10); + //printMessage("%d:%d:'%s'\n", d.echoNum, echo, iceStr); + if (iceStr != end) + d.echoNum = echo; + //printMessage("%d:'%s'\n", echo, iceStr); break; - }*/ + } case kPhaseEncodingDirectionPositiveSiemens: { if (d.manufacturer != kMANUFACTURER_SIEMENS) break; @@ -5791,10 +5943,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; break; } - //case kRealDwellTime : //https://github.com/rordenlab/dcm2niix/issues/240 - // if (d.manufacturer != kMANUFACTURER_SIEMENS) break; - // d.dwellTime = dcmStrInt(lLength, &buffer[lPos]); - // break; + case kRealDwellTime : //https://github.com/rordenlab/dcm2niix/issues/240 + if (d.manufacturer != kMANUFACTURER_SIEMENS) break; + d.dwellTime = dcmStrInt(lLength, &buffer[lPos]); + break; case kBandwidthPerPixelPhaseEncode21: if (d.manufacturer != kMANUFACTURER_SIEMENS) break; @@ -5905,6 +6057,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); case kBitsStored: d.bitsStored = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; + case kHighBit: + highBit = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); + break; case kIsSigned: //http://dicomiseasy.blogspot.com/2012/08/chapter-12-pixel-data.html d.isSigned = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; @@ -6062,6 +6217,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: { @@ -6131,6 +6298,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); case kReconstructionMethod: //LO dcmStr(lLength, &buffer[lPos], d.reconstructionMethod); break; + case kFrameReferenceTime: + d.frameReferenceTime = dcmStrFloat(lLength, &buffer[lPos]); + break; case kDecayFactor: d.decayFactor = dcmStrFloat(lLength, &buffer[lPos]); break; @@ -6174,16 +6344,19 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); d.isEPI = true; break; //warp } + case kScanningSequenceSiemens: + dcmStr(lLength, &buffer[lPos], scanningSequenceSiemens); + break; case kSequenceVariant21: if (d.manufacturer != kMANUFACTURER_SIEMENS) - break; //see GE dataset in dcm_qa_nih + break; //n.b. for GE 0021,105B' TaggingFlipAngle //fall through... case kSequenceVariant: { dcmStr(lLength, &buffer[lPos], d.sequenceVariant); break; } case kScanOptions: - dcmStr(lLength, &buffer[lPos], d.scanOptions); + dcmStr(lLength, &buffer[lPos], d.scanOptions, true); if ((lLength > 1) && (strstr(d.scanOptions, "PFF") != NULL)) d.isPartialFourier = true; //e.g. GE does not populate (0018,9081) break; @@ -6267,12 +6440,12 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); break; d.phaseNumber = dcmStrInt(lLength, &buffer[lPos]); //see dcm_qa_philips_asl break; - case kCardiacSync: //CS [TRIGGERED],[NO] + /*case kCardiacSync: //CS [TRIGGERED],[NO] if (lLength < 2) break; if (toupper(buffer[lPos]) != 'N') isTriggerSynced = true; - break; + break;*/ case kDiffusion_bValue: // 0018,9087 if (d.manufacturer == kMANUFACTURER_UNKNOWN) { d.manufacturer = kMANUFACTURER_PHILIPS; @@ -6327,7 +6500,6 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //d.CSA.dtiV[1] = v[0]; //d.CSA.dtiV[2] = v[1]; //d.CSA.dtiV[3] = v[2]; - //printMessage("><>< 0018,9089: DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); hasDwiDirectionality = true; d.isBVecWorldCoordinates = true; //e.g. Canon saved image space coordinates in Comments, world space in 0018, 9089 set_orientation0018_9089(&volDiffusion, lLength, &buffer[lPos], d.isLittleEndian); @@ -6344,6 +6516,22 @@ 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; + } case kDiffusionBValueXX: { if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix @@ -6427,12 +6615,12 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); if ((toupper(buffer[lPos]) != 'I') && (toupper(buffer[lPos + 1]) != 'N') && (toupper(buffer[lPos + 2]) != 'V')) d.isIR = true; break; - case kRespirationSync: //CS [TRIGGERED],[NO] + /*case kRespirationSync: //CS [TRIGGERED],[NO] if (lLength < 2) break; if (toupper(buffer[lPos]) != 'N') isTriggerSynced = true; - break; + break;*/ case kNumberOfSlicesMrPhilips: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; @@ -6500,12 +6688,16 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); break; philMRImageDiffVolumeNumber = dcmStrInt(lLength, &buffer[lPos]); break; + case kOriginalAttributesSq: + is4000561SQ = true; + break; case kWaveformSq: d.imageStart = 1; //abort!!! printMessage("Skipping DICOM (audio not image) '%s'\n", fname); break; case kSpectroscopyData: //kSpectroscopyDataPointColumns printMessage("Skipping Spectroscopy DICOM '%s'\n", fname); + d.xyzDim[1] = 0; //issue606 d.imageStart = (int)lPos + (int)lFileOffset; break; case kCSAImageHeaderInfo: @@ -6537,6 +6729,26 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); else if (isSameFloat(1.0, d.intenScale)) //give precedence to standard value d.intenScale = d.RWVScale; break; + case kShimGradientX: + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.shimGradientX = dcmIntSS(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kShimGradientY: + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.shimGradientY = dcmIntSS(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kShimGradientZ: + if (d.manufacturer != kMANUFACTURER_GE) + break; + d.shimGradientZ = dcmIntSS(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kPrescanReuseString: //LO + if (d.manufacturer != kMANUFACTURER_GE) + break; + dcmStr(lLength, &buffer[lPos], d.prescanReuseString); + break; case kUserDefineDataGE: { //0043,102A if ((d.manufacturer != kMANUFACTURER_GE) || (lLength < 128)) break; @@ -6563,9 +6775,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); printMessage(" GE header overflows buffer\n"); break; } - uint16_t hdr_offset = dcmInt(2, &buffer[lPos + 24], true); + size_t hdr_offset = dcmInt(2, &buffer[lPos + 24], true); if (isVerboseX > 1) - printMessage(" header offset: %d\n", hdr_offset); + printMessage(" header offset: %zu\n", hdr_offset); if (lLength < (hdr_offset + 916)) { //minimum size is hdr_offset=0, read 0x0394 printMessage(" GE header too small to be valid (B)\n"); break; @@ -6830,7 +7042,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); if (bits == 1) break; //old style Burned-In - printMessage("Illegal/Obsolete DICOM: Overlay Bits Allocated must be 1, not %d\n", bits); + printMessage("Illegal/Obsolete DICOM (%s): Overlay Bits Allocated must be 1, not %d\n", fname, bits); overlayOK = false; break; } @@ -6839,7 +7051,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); if (pos == 0) break; //old style Burned-In - printMessage("Illegal/Obsolete DICOM: Overlay Bit Position shall be 0, not %d\n", pos); + printMessage("Illegal/Obsolete DICOM (%s): Overlay Bit Position shall be 0, not %d\n", fname, pos); overlayOK = false; break; } @@ -6861,19 +7073,27 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); sprintf(str, "%*c%04x,%04x %u@%ld ", sqDepth + 1, ' ', groupElement & 65535, groupElement >> 16, lLength, lFileOffset + lPos); bool isStr = false; if (d.isExplicitVR) { - sprintf(str, "%s%c%c ", str, vr[0], vr[1]); + //sprintf(str, "%s%c%c ", str, vr[0], vr[1]); + //if (snprintf(str2, kDICOMStr-1, "%s%c%c", str, vr[0], vr[1]) < 0) exit(EXIT_FAILURE); + strncat(str, &vr[0], 1); + str[kDICOMStr-1] = '\0'; //silence warning -Wstringop-truncation + strncat(str, &vr[1], 1); + str[kDICOMStr-1] = '\0'; //silence warning -Wstringop-truncation + strcat(str, " "); + //sprintf(str, "%s%c%c ", str2, vr[0], vr[1]); + char str2[kDICOMStr] = ""; if ((vr[0] == 'F') && (vr[1] == 'D')) - sprintf(str, "%s%g ", str, dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian)); + sprintf(str2, "%g ", dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'F') && (vr[1] == 'L')) - sprintf(str, "%s%g ", str, dcmFloat(lLength, &buffer[lPos], d.isLittleEndian)); + sprintf(str2, "%g ", dcmFloat(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'S') && (vr[1] == 'S')) - sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + sprintf(str2, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'S') && (vr[1] == 'L')) - sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + sprintf(str2, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'U') && (vr[1] == 'S')) - sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + sprintf(str2, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'U') && (vr[1] == 'L')) - sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); + sprintf(str, "%d ", dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'A') && (vr[1] == 'E')) isStr = true; if ((vr[0] == 'A') && (vr[1] == 'S')) @@ -6892,10 +7112,6 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); isStr = true; if ((vr[0] == 'L') && (vr[1] == 'T')) isStr = true; - //if ((vr[0]=='O') && (vr[1]=='B')) isStr = xxx; - //if ((vr[0]=='O') && (vr[1]=='D')) isStr = xxx; - //if ((vr[0]=='O') && (vr[1]=='F')) isStr = xxx; - //if ((vr[0]=='O') && (vr[1]=='W')) isStr = xxx; if ((vr[0] == 'P') && (vr[1] == 'N')) isStr = true; if ((vr[0] == 'S') && (vr[1] == 'H')) @@ -6908,11 +7124,12 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); isStr = true; if ((vr[0] == 'U') && (vr[1] == 'T')) isStr = true; + strncat(str, str2, kDICOMStr-4); + str[kDICOMStr-1] = '\0'; } else isStr = (lLength > 12); //implicit encoding: not always true as binary vectors may exceed 12 bytes, but often true if (lLength > 128) { - sprintf(str, "%s<%d bytes> ", str, lLength); - printMessage("%s\n", str); + printMessage("%s<%d bytes>\n", str, lLength); } else if (isStr) { //if length is greater than 8 bytes (+4 hdr) the MIGHT be a string char tagStr[kDICOMStr]; //tagStr[0] = 'X'; //avoid compiler warning: orientStr filled by dcmStr @@ -6937,8 +7154,6 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); free(buffer); if (d.bitsStored < 0) d.isValid = false; - if (d.bitsStored == 1) - printWarning("1-bit binary DICOMs not supported\n"); //maybe not valid - no examples to test //printf("%d bval=%g bvec=%g %g %g<<<\n", d.CSA.numDti, d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); //printMessage("><>< DWI bxyz %g %g %g %g\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3]); if (encapsulatedDataFragmentStart > 0) { @@ -7080,6 +7295,10 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); printWarning("0008,0008=MOSAIC but number of slices not specified: %s\n", fname); if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.dtiV[1] < -1.0) && (d.CSA.dtiV[2] < -1.0) && (d.CSA.dtiV[3] < -1.0)) d.CSA.dtiV[0] = 0; //SiemensTrio-Syngo2004A reports B=0 images as having impossible b-vectors. + if ((strlen(d.scanningSequence) < 1) && (strlen(scanningSequenceSiemens) > 1)) + strcpy(d.scanningSequence, scanningSequenceSiemens); + if ((strlen(d.scanOptions) < 1) && (strlen(scanOptionsSiemens) > 1)) + strcpy(d.scanOptions, scanOptionsSiemens); if ((strlen(d.protocolName) < 1) && (strlen(d.seriesDescription) > 1)) strcpy(d.protocolName, d.seriesDescription); if ((strlen(d.protocolName) > 1) && (isMoCo)) @@ -7184,9 +7403,9 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); mx[j] = dcmDim[0].dimIdx[j]; mn[j] = mx[j]; for (int i = 0; i < numDimensionIndexValues; i++) { - if (mx[j] < dcmDim[i].dimIdx[j]) + if (mx[j] < (int)dcmDim[i].dimIdx[j]) mx[j] = dcmDim[i].dimIdx[j]; - if (mn[j] > dcmDim[i].dimIdx[j]) + if (mn[j] > (int)dcmDim[i].dimIdx[j]) mn[j] = dcmDim[i].dimIdx[j]; } if (mx[j] != mn[j]) { @@ -7210,8 +7429,8 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); stackPositionItem = i; if ((d.manufacturer == kMANUFACTURER_CANON) && (nVariableItems == 1) && (d.xyzDim[4] > 1)) { //WARNING: Canon CANON V6.0SP2001* (0018,9005) = "AX fMRI" strangely sets TemporalPositionIndex(0020,9128) as 1 for all volumes: (0020,9157) and (0020,9128) are INCORRECT! - printf("Invalid enhanced DICOM created by Canon: Only single dimension in DimensionIndexValues (0020,9157) varies, for 4D file (e.g. BOTH space and time should vary)\n"); - printf("%d %d\n", stackPositionItem, maxVariableItem); + printMessage("Invalid enhanced DICOM created by Canon: Only single dimension in DimensionIndexValues (0020,9157) varies, for 4D file (e.g. BOTH space and time should vary)\n"); + printMessage("%d %d\n", stackPositionItem, maxVariableItem); int stackTimeItem = 0; if (stackPositionItem == 0) { maxVariableItem++; @@ -7336,7 +7555,7 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); } if ((hasDwiDirectionality) && (d.CSA.numDti < 1)) d.CSA.numDti = 1; - if ((d.isValid) && (d.imageNum == 0)) { //Philips non-image DICOMs (PS_*, XX_*) are not valid images and do not include instance numbers + if ((d.isValid) && (! has00200013)) { //Philips non-image DICOMs (PS_*, XX_*) are not valid images and do not include instance numbers //TYPE 1 for MR image http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0020,0013) // Only type 2 for some other DICOMs! Therefore, generate warning not error printWarning("Instance number (0020,0013) not found: %s\n", fname); @@ -7509,8 +7728,13 @@ const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //printf("%d\t%g\t%g\t%g\t%g\n", d.imageNum, d.rtia_timerGE, d.patientPosition[1],d.patientPosition[2],d.patientPosition[3]); //printf("%g\t\t%g\t%g\t%g\t%s\n", d.CSA.dtiV[0], d.CSA.dtiV[1], d.CSA.dtiV[2], d.CSA.dtiV[3], fname); //printMessage("buffer usage %d %d %d\n",d.imageStart, lPos+lFileOffset, MaxBufferSz); + if ((d.bitsStored == 1) && (highBit != 0)) { + printWarning("1-bit binary with high bit = %d not supported (issue 572)\n", highBit); + d.isValid = false; + } + //printf("%g\t%g\t%s\n", d.intenIntercept, d.intenScale, fname); return d; -} // readDICOM() +} // readDICOMx() void setDefaultPrefs(struct TDCMprefs *prefs) { prefs->isVerbose = false; diff --git a/console/nii_dicom.h b/console/nii_dicom.h index c7b2a583..2458f75a 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.20211220" +#define kDCMdate "v1.0.20220720" #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 @@ -177,7 +178,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; int sliceOrder[kMaxSlice2D]; // [7,3,2] means the first slice on disk should be moved to 7th position int gradDynVol[kMaxDTI4D]; //used to parse dimensions of Philips data, e.g. file with multiple dynamics, echoes, phase+magnitude //int fragmentOffset[kMaxDTI4D], fragmentLength[kMaxDTI4D]; //for images with multiple compressed fragments - float frameDuration[kMaxDTI4D], decayFactor[kMaxDTI4D], volumeOnsetTime[kMaxDTI4D], triggerDelayTime[kMaxDTI4D], TE[kMaxDTI4D], RWVScale[kMaxDTI4D], RWVIntercept[kMaxDTI4D], intenScale[kMaxDTI4D], intenIntercept[kMaxDTI4D], intenScalePhilips[kMaxDTI4D]; + float frameReferenceTime[kMaxDTI4D], frameDuration[kMaxDTI4D], decayFactor[kMaxDTI4D], volumeOnsetTime[kMaxDTI4D], triggerDelayTime[kMaxDTI4D], TE[kMaxDTI4D], RWVScale[kMaxDTI4D], RWVIntercept[kMaxDTI4D], intenScale[kMaxDTI4D], intenIntercept[kMaxDTI4D], intenScalePhilips[kMaxDTI4D]; bool isReal[kMaxDTI4D]; bool isImaginary[kMaxDTI4D]; bool isPhase[kMaxDTI4D]; @@ -222,16 +223,16 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; int xyzDim[5]; uint32_t coilCrc, seriesUidCrc, instanceUidCrc; int overlayStart[kMaxOverlay]; - int phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; - float xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; + int postLabelDelay, shimGradientX, shimGradientY, shimGradientZ, phaseNumber, spoiling, mtState, partialFourierDirection, interp3D, aslFlags, durationLabelPulseGE, epiVersionGE, internalepiVersionGE, maxEchoNumGE, rawDataRunNumber, numberOfImagesInGridUIH, numberOfDiffusionDirectionGE, phaseEncodingGE, protocolBlockStartGE, protocolBlockLengthGE, modality, dwellTime, effectiveEchoSpacingGE, phaseEncodingLines, phaseEncodingSteps, echoTrainLength, echoNum, sliceOrient, manufacturer, converted2NII, acquNum, imageNum, imageStart, imageBytes, bitsStored, bitsAllocated, samplesPerPixel,locationsInAcquisition, locationsInAcquisitionConflict, compressionScheme; + float xRayTubeCurrent, exposureTimeMs, numberOfExcitations, numberOfArms, numberOfPointsPerArm, groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, 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 frameDuration, ecat_isotope_halflife, ecat_dosage; - float pixelPaddingValue; // used for both FloatPixelPaddingValue (0028, 0122) and PixelPaddingValue (0028, 0120); NaN if not present. + 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 acquisitionDuration, triggerDelayTime, RWVScale, RWVIntercept, dateTime, acquisitionTime, acquisitionDate, bandwidthPerPixelPhaseEncode; char parallelAcquisitionTechnique[kDICOMStr], radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr]; - char imageOrientationText[kDICOMStr], coilElements[kDICOMStr], coilName[kDICOMStr], phaseEncodingDirectionDisplayedUIH[kDICOMStr], imageBaseName[kDICOMStr], scanOptions[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 institutionAddress[kDICOMStrLarge], imageComments[kDICOMStrLarge]; + 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; bool isRealIsPhaseMapHz, isPrivateCreatorRemap, isHasOverlay, isEPI, isIR, isPartialFourier, isDiffusion, isVectorFromBMatrix, isRawDataStorage, isGrayscaleSoftcopyPresentationState, isStackableSeries, isCoilVaries, isNonParallelSlices, isBVecWorldCoordinates, isSegamiOasis, isXA10A, isScaleOrTEVaries, isScaleVariesEnh, isDerived, isXRay, isMultiEcho, isValid, is3DAcq, is2DAcq, isExplicitVR, isLittleEndian, isPlanarRGB, isSigned, isHasPhase, isHasImaginary, isHasReal, isHasMagnitude,isHasMixed, isFloat, isResampled, isLocalizer; diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 9834bcb0..5cfb3ad9 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -38,6 +38,10 @@ #endif #include "nii_dicom.h" #include "nii_ortho.h" +#ifdef myEnableJNIfTI + #include "base64.h" + #include "cJSON.h" +#endif #include //toupper #include #include @@ -79,11 +83,11 @@ const char kFileSep[2] = "/"; #ifdef USING_R #ifndef max -#define max(a, b) std::max(a, b) +#define max(a, b) (a > b ? a : b) #endif #ifndef min -#define min(a, b) std::min(a, b) +#define min(a, b) (a < b ? a : b) #endif #else @@ -104,6 +108,25 @@ const char kFileSep[2] = "/"; #endif +#ifdef USING_DCM2NIIXFSWRAPPER +// create the struct to save nifti header, image data, TDICOMdata, & TDTI information. +// no .nii, .bval, .bvec are created. +MRIFSSTRUCT mrifsStruct; + +// retrieve the struct +MRIFSSTRUCT* nii_getMrifsStruct() +{ + return &mrifsStruct; +} + +// free the memory used for the image and dti +void nii_clrMrifsStruct() +{ + free(mrifsStruct.imgM); + free(mrifsStruct.tdti); +} +#endif + bool isADCnotDTI(TDTI bvec) { //returns true if bval!=0 but all bvecs == 0 (Philips code for derived ADC image) return ((!isSameFloat(bvec.V[0], 0.0f)) && //not a B-0 image ((isSameFloat(bvec.V[1], 0.0f)) && (isSameFloat(bvec.V[2], 0.0f)) && (isSameFloat(bvec.V[3], 0.0f)))); @@ -336,7 +359,7 @@ void siemensPhilipsCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI //convert DTI vectors from scanner coordinates to image frame of reference //Uses 6 orient values from ImageOrientationPatient (0020,0037) // requires PatientPosition 0018,5100 is HFS (head first supine) - if ((!d->isBVecWorldCoordinates) && (d->manufacturer != kMANUFACTURER_BRUKER) && (d->manufacturer != kMANUFACTURER_TOSHIBA) && (d->manufacturer != kMANUFACTURER_HITACHI) && (d->manufacturer != kMANUFACTURER_UIH) && (d->manufacturer != kMANUFACTURER_SIEMENS) && (d->manufacturer != kMANUFACTURER_PHILIPS)) + if ((!d->isBVecWorldCoordinates) && (d->manufacturer != kMANUFACTURER_MEDISO) && (d->manufacturer != kMANUFACTURER_BRUKER) && (d->manufacturer != kMANUFACTURER_TOSHIBA) && (d->manufacturer != kMANUFACTURER_HITACHI) && (d->manufacturer != kMANUFACTURER_UIH) && (d->manufacturer != kMANUFACTURER_SIEMENS) && (d->manufacturer != kMANUFACTURER_PHILIPS)) return; if (d->CSA.numDti < 1) return; @@ -347,17 +370,15 @@ void siemensPhilipsCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI if (vx[i].V[v] == -0.0f) vx[i].V[v] = 0.0f; //remove sign from values that are virtually zero } -#ifndef USING_R //simple diagnostics for data prior to realignment: useful as first direction is the same for al Philips sequences //for (int i = 0; i < 3; i++) // printf("%g = %g %g %g\n", vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); -#endif return; } //https://github.com/rordenlab/dcm2niix/issues/225 if ((toupper(d->patientOrient[0]) == 'H') && (toupper(d->patientOrient[1]) == 'F') && (toupper(d->patientOrient[2]) == 'S')) ; //participant was head first supine else { - printMessage("Check Siemens/Philips bvecs: expected Patient Position (0018,5100) to be 'HFS' not '%s'\n", d->patientOrient); + printMessage("Check bvecs: expected Patient Position (0018,5100) to be 'HFS' not '%s'\n", d->patientOrient); //return; //see https://github.com/rordenlab/dcm2niix/issues/238 } vec3 read_vector = setVec3(d->orient[1], d->orient[2], d->orient[3]); @@ -631,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; @@ -765,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; @@ -909,7 +936,7 @@ int geProtocolBlock(const char *filename, int geOffset, int geLength, int isVerb uint8_t flags = pCmp[3]; bool isFNAME = ((flags & 0x08) == 0x08); bool isFCOMMENT = ((flags & 0x10) == 0x10); - uint32_t hdrSz = 10; + int hdrSz = 10; if (isFNAME) { //skip null-terminated string FNAME for (; hdrSz < cmpSz; hdrSz++) if (pCmp[hdrSz] == 0) @@ -999,7 +1026,7 @@ void json_Str(FILE *fp, const char *sLabel, char *sVal) { // issue131,425 unsigned char sValEsc[2048] = {""}; unsigned char *iVal = (unsigned char *)sVal; int o = 0; - for (int i = 0; i < strlen(sVal); i++) { + for (int i = 0; i < (int)strlen(sVal); i++) { //escape double quote (") and Backslash if ((sVal[i] == '"') || (sVal[i] == '\\')) { //escape double quotes and back slash sValEsc[o] = '\\'; @@ -1208,10 +1235,10 @@ tse3d: T2*/ fprintf(fp, "\t\"Manufacturer\": \"UIH\",\n"); break; }; - if (d.epiVersionGE == 0) - fprintf(fp, "\t\"PulseSequenceName\": \"epi\",\n"); - if (d.epiVersionGE == 1) - fprintf(fp, "\t\"PulseSequenceName\": \"epiRT\",\n"); + //if (d.epiVersionGE == 0) + // fprintf(fp, "\t\"PulseSequenceName\": \"epi\",\n"); + //if (d.epiVersionGE == 1) + // fprintf(fp, "\t\"PulseSequenceName\": \"epiRT\",\n"); if (d.internalepiVersionGE == 1) fprintf(fp, "\t\"InternalPulseSequenceName\": \"EPI\",\n"); if (d.internalepiVersionGE == 2) @@ -1262,6 +1289,7 @@ tse3d: T2*/ json_Str(fp, "\t\"SequenceVariant\": \"%s\",\n", d.sequenceVariant); json_Str(fp, "\t\"ScanOptions\": \"%s\",\n", d.scanOptions); json_Str(fp, "\t\"SequenceName\": \"%s\",\n", d.sequenceName); + json_Str(fp, "\t\"PulseSequenceName\": \"%s\",\n", d.pulseSequenceName); if (strlen(d.imageType) > 0) { fprintf(fp, "\t\"ImageType\": [\""); bool isSep = false; @@ -1284,6 +1312,27 @@ 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 ((strstr(d.imageTypeText, "_DIS2D") != NULL) || (strstr(d.imageType, "_DIS2D") != NULL) || + (strstr(d.imageTypeText, "_DIS3D") != NULL) || (strstr(d.imageType, "_DIS3D") != NULL)) + fprintf(fp, "\t\"NonlinearGradientCorrection\": true,\n"); + if ((strstr(d.imageTypeText, "_ND") != NULL) || (strstr(d.imageType, "_ND") != NULL) || + (strstr(d.imageTypeText, "_ND") != NULL) || (strstr(d.imageType, "_ND") != NULL)) + 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"); if (d.seriesNum > 0) @@ -1359,7 +1408,7 @@ tse3d: T2*/ } fprintf(fp, "\t],\n"); } - if (dti4D->volumeOnsetTime[0] >= 0.0) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 + if ((h->dim[4] > 1) && (dti4D->volumeOnsetTime[0] >= 0.0)) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 fprintf(fp, "\t\"FrameTimesStart\": [\n"); for (int i = 0; i < h->dim[4]; i++) { if (i != 0) @@ -1370,7 +1419,7 @@ tse3d: T2*/ } fprintf(fp, "\t],\n"); } - if (dti4D->frameDuration[0] >= 0.0) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 + if ((h->dim[4] > 1) && (dti4D->frameDuration[0] >= 0.0)) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 fprintf(fp, "\t\"FrameDuration\": [\n"); for (int i = 0; i < h->dim[4]; i++) { if (i != 0) @@ -1384,6 +1433,23 @@ tse3d: T2*/ } fprintf(fp, "\t],\n"); } + if ((dti4D->frameReferenceTime[0] >= 0.0) && (h->dim[4] > 1)) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 + bool varies = false; + for (int i = 0; i < h->dim[4]; i++) + if (dti4D->frameReferenceTime[i] != dti4D->frameReferenceTime[0]) + varies = true; + if (varies) { + fprintf(fp, "\t\"FrameReferenceTime\": [\n"); + for (int i = 0; i < h->dim[4]; i++) { + if (i != 0) + fprintf(fp, ",\n"); + if (dti4D->frameReferenceTime[i] < 0) + break; + fprintf(fp, "\t\t%g", dti4D->frameReferenceTime[i] / 1000.0); // from 0018,1242 ms -> sec + } + fprintf(fp, "\t],\n"); + } + } //CT parameters json_Float(fp, "\t\"ExposureTime\": %g,\n", d.exposureTimeMs / 1000.0); json_Float(fp, "\t\"XRayTubeCurrent\": %g,\n", d.xRayTubeCurrent); @@ -1400,8 +1466,15 @@ tse3d: T2*/ json_Float(fp, "\t\"NumberOfAverages\": %g,\n", d.numberOfAverages); if ((d.echoNum > 1) || ((d.isMultiEcho) && (d.echoNum > 0))) fprintf(fp, "\t\"EchoNumber\": %d,\n", d.echoNum); - if ((d.TE > 0.0) && (!d.isXRay)) - fprintf(fp, "\t\"EchoTime\": %g,\n", d.TE / 1000.0); + if ((d.TE > 0.0) && (!d.isXRay)) { + if ((d.manufacturer == kMANUFACTURER_GE) && (d.isRealIsPhaseMapHz) && (d.velocityEncodeScaleGE < 0)) { //issue617, only set for GE fieldmaphz + json_Float(fp, "\t\"EchoTime1\": %g,\n", d.TE / 1000.0 ); + json_Float(fp, "\t\"EchoTime2\": %g,\n", d.TE / 1000.0 - 1.0/(2.0 * M_PI * d.velocityEncodeScaleGE)); + // delta TE = -1/(2 * pi * velocityEncodeScaleGE) + } + else + fprintf(fp, "\t\"EchoTime\": %g,\n", d.TE / 1000.0); + } //if ((d.TE2 > 0.0) && (!d.isXRay)) fprintf(fp, "\t\"EchoTime2\": %g,\n", d.TE2 / 1000.0 ); if (dti4D->frameDuration[0] < 0.0) //e.g. PET scans can have variable TR json_Float(fp, "\t\"RepetitionTime\": %g,\n", d.TR / 1000.0); @@ -1432,6 +1505,10 @@ tse3d: T2*/ if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED) fprintf(fp, "\t\"PhaseEncodingPolarityGE\": \"Flipped\",\n"); } + //GE specific fields + if (d.shimGradientX > -33333) + fprintf(fp, "\t\"ShimSetting\": [\n\t\t%d,\n\t\t%d,\n\t\t%d\t],\n", d.shimGradientX, d.shimGradientY, d.shimGradientZ); + json_Str(fp, "\t\"PrescanReuseString\": \"%s\",\n", d.prescanReuseString); float delayTimeInTR = -0.01; float repetitionTimePreparation = 0.0; #ifdef myReadAsciiCsa @@ -1475,6 +1552,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; @@ -1608,6 +1690,10 @@ tse3d: T2*/ fprintf(fp, "\t\"ArterialSpinLabelingType\": \"PCASL\",\n"); if (isPASL) fprintf(fp, "\t\"ArterialSpinLabelingType\": \"PASL\",\n"); + //AcquisitionVoxelSize uses slice thickness (without gap) + // https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#common-metadata-fields-applicable-to-both-pcasl-and-pasl + if (((isPASL) || (isPCASL)) && (csaAscii.interp <= 0)) + fprintf(fp, "\t\"AcquisitionVoxelSize\": [\n\t\t%g,\n\t\t%g,\n\t\t%g\t],\n", d.xyzMM[1], d.xyzMM[2], d.zThick); //general properties if ((csaAscii.partialFourier > 0) && ((d.modality == kMODALITY_MR))) { //check MR, e.g. do not report for Siemens PET //https://github.com/ismrmrd/siemens_to_ismrmrd/blob/master/parameter_maps/IsmrmrdParameterMap_Siemens_EPI_FLASHREF.xsl @@ -1625,8 +1711,6 @@ tse3d: T2*/ interp = true; fprintf(fp, "\t\"Interpolation2D\": %d,\n", interp); } - if (d.interp3D > 1) //through-plane interpolation e.g. GE ZIP2 through-plane http://mriquestions.com/zip.html - fprintf(fp, "\t\"Interpolation3D\": %d,\n", d.interp3D); if (csaAscii.baseResolution > 0) fprintf(fp, "\t\"BaseResolution\": %d,\n", csaAscii.baseResolution); if (shimSetting[0] != 0.0) { @@ -1706,6 +1790,8 @@ tse3d: T2*/ //printf("PhaseLines=%d EchoTrainLength=%d SENSE=%g\n", d.phaseEncodingLines, d.echoTrainLength, d.accelFactPE); //n.b. we can not distinguish pF from SENSE/GRAPPA for UIH } #endif + if (d.interp3D > 1) //through-plane interpolation e.g. GE ZIP2 through-plane http://mriquestions.com/zip.html + fprintf(fp, "\t\"Interpolation3D\": %d,\n", d.interp3D); // https://neurostars.org/t/repetitiontime-parameters-what-are-they-and-where-to-find-them/20020/6 json_Float(fp, "\t\"RepetitionTimePreparation\": %g,\n", repetitionTimePreparation); //Philips ASL specific tags, issue533 @@ -1731,18 +1817,19 @@ tse3d: T2*/ json_Float(fp, "\t\"InitialPostLabelDelay\": %g,\n", dti4D->triggerDelayTime[0] / 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"); + //generic public ASL tags + if (d.postLabelDelay > 0) + json_Float(fp, "\t\"PostLabelDelay\": %g,\n", float(d.postLabelDelay) / 1000.0); + //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\"PostLabelingDelay\": %g,\n", d.TI / 1000.0); //For GE ASL: InversionTime -> Post-label delay + json_Float(fp, "\t\"PostLabelDelay\": %g,\n", d.TI / 1000.0); //For GE ASL: InversionTime -> Post-label delay json_Float(fp, "\t\"NumberOfPointsPerArm\": %g,\n", d.numberOfPointsPerArm); json_Float(fp, "\t\"NumberOfArms\": %g,\n", d.numberOfArms); } @@ -1861,9 +1948,11 @@ tse3d: T2*/ // FSL definition is start of first line until start of last line. // Other than the use of (n-1), the value is basically just 1.0/bandwidthPerPixelPhaseEncode. // https://github.com/rordenlab/dcm2niix/issues/130 - if ((d.manufacturer == kMANUFACTURER_UIH) && (effectiveEchoSpacing <= 0.0)) //issue225, issue531 - json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", d.acquisitionDuration / 1000.0); - else if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0) ) + if (d.manufacturer != kMANUFACTURER_UIH) //issue606 + json_Float(fp, "\t\"AcquisitionDuration\": %g,\n", d.acquisitionDuration); + if ((d.manufacturer == kMANUFACTURER_UIH) && (effectiveEchoSpacing <= 0.0)) //issue225, issue531 + json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", d.acquisitionDuration / 1000.0); + else if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0) ) fprintf(fp, "\t\"TotalReadoutTime\": %g,\n", effectiveEchoSpacing * (reconMatrixPE - 1.0)); json_Float(fp, "\t\"PixelBandwidth\": %g,\n", d.pixelBandwidth); if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.dwellTime > 0)) @@ -1954,7 +2043,11 @@ void swapEndian(struct nifti_1_header *hdr, unsigned char *im, bool isNative) { // must be told which is native to detect datatype and number of voxels // one could also auto-detect: hdr->sizeof_hdr==348 if (!isNative) +#ifdef USING_MGH_NIFTI_IO + swap_nifti_header(hdr, 1); +#else swap_nifti_header(hdr); +#endif int nVox = 1; for (int i = 1; i < 8; i++) if (hdr->dim[i] > 1) @@ -1962,7 +2055,11 @@ void swapEndian(struct nifti_1_header *hdr, unsigned char *im, bool isNative) { int bitpix = hdr->bitpix; int datatype = hdr->datatype; if (isNative) +#ifdef USING_MGH_NIFTI_IO + swap_nifti_header(hdr, 1); +#else swap_nifti_header(hdr); +#endif if (datatype == DT_RGBA32) return; //n.b. do not swap 8-bit, 24-bit RGB, and 32-bit RGBA @@ -2076,6 +2173,8 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st if (opts.isOnlyBIDS) return NULL; uint64_t indx0 = dcmSort[0].indx; //first volume + if (isnan(dcmList[indx0].patientPosition[1])) + return NULL; //issue606 do not save bvec for non-spatial data (e.g. physio) int numDti = dcmList[indx0].CSA.numDti; #ifdef USING_R ImageList *images = (ImageList *)opts.imageList; @@ -2347,10 +2446,16 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st return NULL; } if (!opts.isFlipY) { //!FLIP_Y&& (dcmList[indx0].CSA.mosaicSlices < 2) mosaics are always flipped in the Y direction + //the order of rows is flipped: flip the y-polarity for (int i = 0; i < (numDti); i++) { if (fabs(vx[i].V[2]) > FLT_EPSILON) vx[i].V[2] = -vx[i].V[2]; } //for each direction + //less intuitively: we have now flipped the determinant, so we must swap the x-polarity + for (int i = 0; i < (numDti); i++) { + if (fabs(vx[i].V[1]) > FLT_EPSILON) + vx[i].V[1] = -vx[i].V[1]; + } //for each direction } //if not a mosaic if (opts.isVerbose) { for (int i = 0; i < (numDti); i++) { @@ -2377,9 +2482,16 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st for (int v = 0; v < 4; v++) //for each vector+B-value dti4D->S[i].V[v] = vx[i].V[v]; } +#ifdef USING_DCM2NIIXFSWRAPPER + mrifsStruct.tdti = vx; + mrifsStruct.numDti = numDti; +#else free(vx); +#endif return volOrderIndex; } + +#ifndef USING_DCM2NIIXFSWRAPPER char txtname[2048] = {""}; strcpy(txtname, pathoutname); strcat(txtname, ".bval"); @@ -2398,10 +2510,18 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st } fprintf(fp, "%g\n", vx[numDti - 1].V[0]); fclose(fp); +#endif if (isIsotropic) { //issue 405: ISOTROPIC images have bval but not bvec +#ifdef USING_DCM2NIIXFSWRAPPER + mrifsStruct.tdti = vx; + mrifsStruct.numDti = numDti; +#else free(vx); +#endif return volOrderIndex; } + +#ifndef USING_DCM2NIIXFSWRAPPER strcpy(txtname, pathoutname); if (dcmList[indx0].isVectorFromBMatrix) strcat(txtname, ".mvec"); @@ -2425,7 +2545,14 @@ int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], st } fclose(fp); #endif +#endif + +#ifdef USING_DCM2NIIXFSWRAPPER + mrifsStruct.tdti = vx; + mrifsStruct.numDti = numDti; +#else free(vx); +#endif return volOrderIndex; } // nii_saveDTI() @@ -2608,23 +2735,6 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s int nConvert = d3 * d4; if (d3 < 3) return true; //always consistent - float dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); - bool isConsistent = !isSameFloatGE(dx, 0.0); //slice distance of zero is not consistent with XYZT order (perhaps XYTZ) - bool isAscending1 = (dx > 0); - for (int v = 0; v < d4; v++) { - int volStart = v * d3; - if (!isSameFloatGE(intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[volStart].indx]), 0.0)) - isConsistent = false; //XYZT requires first slice of each volume is at same position - for (int i = 1; i < d3; i++) { - dx = intersliceDistanceSigned(dcmList[dcmSort[volStart + i - 1].indx], dcmList[dcmSort[volStart + i].indx]); - bool isAscending = (dx > 0); - //printf("volume %d slice %d distanceFromSlice1 %g DICOMvolume %d\n", v, i+1, dx, dcmList[dcmSort[volStart + i].indx].rawDataRunNumber); - if (isAscending != isAscending1) - isConsistent = false; //direction reverses - } - } - //if (isConsistent) - // return true; TFloatSort *floatSort = (TFloatSort *)malloc(nConvert * sizeof(TFloatSort)); int minVol = dcmList[dcmSort[0].indx].rawDataRunNumber; int maxVol = minVol; @@ -2643,8 +2753,11 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s maxInstance = max(maxInstance, instance); maxPhase = max(maxPhase, dcmList[dcmSort[i].indx].phaseNumber); } + bool isUseFrameReferenceTimeForVolume = false; + if (dcmList[dcmSort[0].indx].frameReferenceTime >= 0.0) + isUseFrameReferenceTimeForVolume = true; bool isUseInstanceNumberForVolume = false; - if ((d4 > 1) && (maxPhase == 1) && (minVol == maxVolNotADC) && (minInstance < maxInstance)) { + if ((!isUseFrameReferenceTimeForVolume) && (d4 > 1) && (maxPhase == 1) && (minVol == maxVolNotADC) && (minInstance < maxInstance)) { printWarning("Volume number does not vary (0019,10A2; 0020,0100; 2005,1063; 2005,1413), assuming meaningful instance number (0020,0013).\n"); isUseInstanceNumberForVolume = true; } @@ -2666,10 +2779,11 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s int rawvol = vol; int instance = dcmList[dcmSort[i].indx].imageNum; int phase = max(1, dcmList[dcmSort[i].indx].phaseNumber); + int refTime = (int)dcmList[dcmSort[i].indx].frameReferenceTime; //issue577: refTime in ms, so int conversion sufficient if (isUsePhaseForVol) vol = phase; if (isPhaseIsBValNumber) vol += phase * maxVol; int isAslLabel = dcmList[dcmSort[i].indx].aslFlags == kASL_FLAG_PHILIPS_LABEL; - dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); + float dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); if (isASL) { #ifdef myMatchEnhanced00209157 //issue533: make classic DICOMs match enhanced DICOM volume order //disk order: slice < repeat < phase < label/control @@ -2690,6 +2804,8 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s printMessage("%d\t%g\t%d\t%d\t%d\t%d\t%g\n", instance, dx, vol, rawvol, isAslLabel, phase, dcmList[dcmSort[i].indx].triggerDelayTime); if (vol > kMaxDTI4D) //issue529 Philips derived Trace/ADC embedded into DWI vol = maxVol + 1; + if (isUseFrameReferenceTimeForVolume) + vol = refTime; minVolOut = min(minVolOut, vol); maxVolOut = max(maxVolOut, vol); floatSort[i].volume = vol; @@ -2697,9 +2813,10 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s floatSort[i].index = i; } //n.b. should we change dim[3] and dim[4] if number of volumes = dim[3]? - if ((!isPhaseIsBValNumber) && ((maxVolOut-minVolOut+1) != d4)) + if (isUseFrameReferenceTimeForVolume) + printWarning("Reordering volumes based on FrameReferenceTime (0054,1300; issue 577)\n"); + else if ((!isPhaseIsBValNumber) && ((maxVolOut-minVolOut+1) != d4)) printError("Check sorted order: 4D dataset has %d volumes, but volume index ranges from %d..%d\n", d4, minVolOut, maxVolOut); - //printf("dx = %g instance %d %d\n", intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]), dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[1].indx].imageNum); TDCMsort *dcmSortIn = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); for (int i = 0; i < nConvert; i++) dcmSortIn[i] = dcmSort[i]; @@ -2708,11 +2825,9 @@ bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], s dcmSort[i] = dcmSortIn[floatSort[i].index]; free(floatSort); free(dcmSortIn); - //printf("dx = %g instance %d %d\n", intersliceDistanceSigned(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]), dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[1].indx].imageNum); return false; } // ensureSequentialSlicePositions() - void swapDim3Dim4(int d3, int d4, struct TDCMsort dcmSort[]) { //swap space and time: input A0,A1...An,B0,B1...Bn output A0,B0,A1,B1,... int nConvert = d3 * d4; @@ -3180,7 +3295,7 @@ int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts #endif cleanISO8859(outname); //re-insert explicit path separators: -f %t/%s_%p will have folder for time, but will not segment a protocol named "fMRI\bold" - for (int pos = 0; pos < strlen(outname); pos++) { + for (int pos = 0; pos < (int)strlen(outname); pos++) { if (outname[pos] == kTempPathSeparator) outname[pos] = kPathSeparator; //e.g. for Windows, convert "/" to "\" if (outname[pos] < 32) //https://en.wikipedia.org/wiki/ASCII#Control_characters @@ -3286,11 +3401,22 @@ void nii_createDummyFilename(char *niiFilename, struct TDCMopts opts) { nii_createFilename(d, niiFilenameBase, opts); strcpy(niiFilename, "Example output filename: '"); strcat(niiFilename, niiFilenameBase); - if (opts.saveFormat != kSaveFormatNIfTI) { + if (opts.saveFormat == kSaveFormatMGH) { + if (opts.isGz) + strcat(niiFilename, ".mgz'"); + else + strcat(niiFilename, ".mgh'"); + } else if (opts.saveFormat == kSaveFormatNRRD) { if (opts.isGz) strcat(niiFilename, ".nhdr'"); else strcat(niiFilename, ".nrrd'"); + #ifdef myEnableJNIfTI + } else if (opts.saveFormat == kSaveFormatJNII) { + strcat(niiFilename, ".jnii'"); + } else if (opts.saveFormat == kSaveFormatBNII) { + strcat(niiFilename, ".bnii'"); + #endif } else { if (opts.isGz) strcat(niiFilename, ".nii.gz'"); @@ -3663,7 +3789,6 @@ void writeMghGz(char *baseName, Tmgh hdr, TmghFooter footer, unsigned char *src_ free(pCmp); return; } - unsigned char *pHdr; //add header strm.avail_in = (unsigned int)sizeof(hdr); // size of input strm.next_in = (uint8_t *) &hdr.version; @@ -3723,7 +3848,7 @@ void writeMghGz(char *baseName, Tmgh hdr, TmghFooter footer, unsigned char *src_ int nii_saveMGH(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, struct TDTI4D *dti4D, int numDTI) { // FreeeSurfer does not use a permissive license, so we must reverse engineer code // https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat - int n, nDim = hdr.dim[0]; + int nDim = hdr.dim[0]; //printMessage("NRRD writer is experimental\n"); if (nDim < 1) return EXIT_FAILURE; @@ -4085,9 +4210,583 @@ int nii_saveNRRD(char *niiFilename, struct nifti_1_header hdr, unsigned char *im return pigz_File(fname, opts, imgsz); } // nii_saveNRRD() +enum TZipMethod {zmZlib, zmGzip, zmBase64}; + +#ifdef myEnableJNIfTI +#ifdef Z_DEFLATED + +int zmat_run(const size_t inputsize, unsigned char *inputstr, size_t *outputsize, unsigned char **outputbuf, const int zipid, int *ret, const int iscompress){ + z_stream zs; + size_t buflen[2]={0}; + *outputbuf=NULL; + + zs.zalloc = Z_NULL; + zs.zfree = Z_NULL; + zs.opaque = Z_NULL; + + if(inputsize==0) + return -1; + + if(iscompress){ + /** perform compression or encoding */ + if(zipid==zmBase64){ + /** base64 encoding */ + *outputbuf=base64_encode((const unsigned char*)inputstr, inputsize, outputsize); + }else if(zipid==zmZlib || zipid==zmGzip){ + /** zlib (.zip) or gzip (.gz) compression */ + if(zipid==zmZlib){ + if(deflateInit(&zs, (iscompress>0) ? Z_DEFAULT_COMPRESSION : (-iscompress)) != Z_OK) + return -2; + }else{ + if(deflateInit2(&zs, (iscompress>0) ? Z_DEFAULT_COMPRESSION : (-iscompress), Z_DEFLATED, 15|16, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) + return -2; + } + buflen[0] =deflateBound(&zs,inputsize); + *outputbuf=(unsigned char *)malloc(buflen[0]); + zs.avail_in = inputsize; /* size of input, string + terminator*/ + zs.next_in = (Bytef *)inputstr; /* input char array*/ + zs.avail_out = buflen[0]; /* size of output*/ + + zs.next_out = (Bytef *)(*outputbuf); /*(Bytef *)(); // output char array*/ + + *ret=deflate(&zs, Z_FINISH); + *outputsize=zs.total_out; + if(*ret!=Z_STREAM_END && *ret!=Z_OK) + return -3; + deflateEnd(&zs); + }else{ + return -7; + } + }else{ + /** perform decompression or decoding */ + if(zipid==zmBase64){ + /** base64 decoding */ + *outputbuf=base64_decode((const unsigned char*)inputstr, inputsize, outputsize); + }else if(zipid==zmZlib || zipid==zmGzip){ + /** zlib (.zip) or gzip (.gz) decompression */ + int count=1; + if(zipid==zmZlib){ + if(inflateInit(&zs) != Z_OK) + return -2; + }else{ + if(inflateInit2(&zs, 15|32) != Z_OK) + return -2; + } + + buflen[0] =inputsize*20; + *outputbuf=(unsigned char *)malloc(buflen[0]); + + zs.avail_in = inputsize; /* size of input, string + terminator*/ + zs.next_in =inputstr; /* input char array*/ + zs.avail_out = buflen[0]; /* size of output*/ + + zs.next_out = (Bytef *)(*outputbuf); /*(Bytef *)(); // output char array*/ + + while((*ret=inflate(&zs, Z_SYNC_FLUSH))!=Z_STREAM_END && count<=10){ + *outputbuf=(unsigned char *)realloc(*outputbuf, (buflen[0]<1) + dim[ndim++]=jdataelemlen; + + for(int i=0;i0 && output[i][0]!='?'){ // take care all name-tags and constant string values + if(!(slen==1 && output[i][0]=='N')) + fwrite(output[i],1,slen,fp); + if(slen==1 && (output[i][0]=='N' || output[i][0]=='S') && i+20){ + int slotid=0; + if(sscanf(output[i],"\?%d",&slotid)==1 && slotid>0){ + unsigned char *compressed=NULL; + size_t compressedbytes; + int ret=0, status=0; + switch(slotid){ // mapping data to the pre-defined slots in the form of "?number" in the template + case 1: {write_ubjsonint(&hdr.sizeof_hdr,sizeof(hdr.sizeof_hdr),1,fp);break;} + case 2: {fputc(ndim,fp);break;} + case 3: {write_ubjsonint(hdr.dim+1, sizeof(hdr.dim[0]), ndim,fp);break;} + case 4: {write_ubjsonint(&hdr.intent_p1,1,sizeof(hdr.intent_p1),fp);break;} + case 5: {write_ubjsonint(&hdr.intent_p2,1,sizeof(hdr.intent_p2),fp);break;} + case 6: {write_ubjsonint(&hdr.intent_p3,1,sizeof(hdr.intent_p3),fp);break;} + case 7: {unsigned char val=strlen(intent);fputc('U',fp);fputc(val,fp); fwrite(intent,1,val,fp);break;} + case 8: {unsigned char val=strlen(dtype); fputc('U',fp);fputc(val,fp); fwrite(dtype,1,val,fp);break;} + case 9: {fputc(hdr.bitpix,fp);break;} + case 10: {write_ubjsonint(&hdr.slice_start, sizeof(hdr.slice_start),1,fp);break;} + case 11: {fputc(ndim,fp);break;} + case 12: {write_ubjsonint(&hdr.pixdim[1],ndim,sizeof(hdr.pixdim[1]),fp);break;} + case 13: {fputc((int)hdr.pixdim[0] ? 'l' : 'r',fp);break;} + case 14: {write_ubjsonint(&hdr.scl_slope,1,sizeof(hdr.scl_slope),fp);break;} + case 15: {write_ubjsonint(&hdr.scl_inter,1,sizeof(hdr.scl_inter),fp);break;} + case 16: {write_ubjsonint(&hdr.slice_end,sizeof(hdr.slice_end),1,fp);break;} + case 17: {unsigned char val=strlen(slicetype);fputc('U',fp);fputc(val,fp); fwrite(slicetype,1,val,fp);break;} + case 18: {unsigned char val=strlen(lunit);fputc('U',fp);fputc(val,fp); fwrite(lunit,1,val,fp);break;} + case 19: {unsigned char val=strlen(tunit);fputc('U',fp);fputc(val,fp); fwrite(tunit,1,val,fp);break;} + case 20: {write_ubjsonint(&hdr.cal_max,1,sizeof(hdr.cal_max),fp);break;} + case 21: {write_ubjsonint(&hdr.cal_min,1,sizeof(hdr.cal_min),fp);break;} + case 22: {write_ubjsonint(&hdr.slice_duration,1,sizeof(hdr.slice_duration),fp);break;} + case 23: {write_ubjsonint(&hdr.toffset,1,sizeof(hdr.toffset),fp);break;} + case 24: {unsigned char val=strlen(hdr.descrip);fputc('U',fp);fputc(val,fp); fwrite(hdr.descrip,1,val,fp);break;} + case 25: {unsigned char val=strlen(hdr.aux_file);fputc('U',fp);fputc(val,fp); fwrite(hdr.aux_file,1,val,fp);break;} + case 26: {write_ubjsonint(&hdr.qform_code,sizeof(hdr.qform_code),1,fp);break;} + case 27: {write_ubjsonint(&hdr.sform_code,sizeof(hdr.sform_code),1,fp);break;} + case 28: {write_ubjsonint(&hdr.quatern_b,1,sizeof(hdr.quatern_b),fp);break;} + case 29: {write_ubjsonint(&hdr.quatern_c,1,sizeof(hdr.quatern_c),fp);break;} + case 30: {write_ubjsonint(&hdr.quatern_d,1,sizeof(hdr.quatern_d),fp);break;} + case 31: {write_ubjsonint(&hdr.qoffset_x,1,sizeof(hdr.qoffset_x),fp);break;} + case 32: {write_ubjsonint(&hdr.qoffset_y,1,sizeof(hdr.qoffset_y),fp);break;} + case 33: {write_ubjsonint(&hdr.qoffset_z,1,sizeof(hdr.qoffset_z),fp);break;} + case 34: { + write_ubjsonint(&hdr.srow_x[0],4,sizeof(hdr.srow_x[0]),fp); + write_ubjsonint(&hdr.srow_y[0],4,sizeof(hdr.srow_y[0]),fp); + write_ubjsonint(&hdr.srow_z[0],4,sizeof(hdr.srow_z[0]),fp); + break; + } + case 35: {unsigned char val=strlen(hdr.intent_name);fputc('U',fp);fputc(val,fp); fwrite(hdr.intent_name,1,val,fp);break;} + case 36: {unsigned char val=strlen(hdr.magic);fputc('U',fp);fputc(val,fp); fwrite(hdr.magic,1,val,fp);break;} + case 37: {int val=hdr.vox_offset;write_ubjsonint(&val, sizeof(val), 1,fp);break;} + case 38: {unsigned char val=strlen(jdtype);fputc('U',fp);fputc(val,fp); fwrite(jdtype,1,val,fp);break;} + case 39: {fputc(ndim,fp);break;} + case 40: { + write_ubjsonint(dim, sizeof(dim[0]), ndim, fp); + if(!opts.isGz){ + const char *datastub="U\x0b_ArrayData_[$"; + fwrite(datastub,1,strlen(datastub),fp); + fputc(jdatamarker,fp); + fputc('#',fp); + + size_t totalelem=(totalbytes/(hdr.bitpix>>3)); + unsigned int clen=totalelem; + if((size_t)clen==totalelem){ + fputc('l',fp); + write_ubjsonint(&clen, sizeof(clen), 1,fp); + }else{ + fputc('L',fp); + write_ubjsonint(&totalelem, sizeof(totalelem), 1,fp); + } + fwrite(im,1,totalbytes,fp); + fputc('}',fp); // end of NIFTIData + fputc('}',fp); // end of the root object + } + break; + } +#ifdef Z_DEFLATED + case 41: {fputc('U',fp);fputc(4,fp);fwrite("zlib",1,4,fp);break;} + case 42: {unsigned int val=(totalbytes/(hdr.bitpix>>3));write_ubjsonint(&val,sizeof(val), 1,fp);break;} + case 43: + ret=zmat_run(totalbytes, im, &compressedbytes, (unsigned char **)&compressed, zmZlib, &status, -opts.gzLevel); + if(!ret){ + unsigned int clen=compressedbytes; + if((size_t)clen==compressedbytes){ + fputc('l',fp); + write_ubjsonint(&clen, sizeof(clen), 1,fp); + }else{ + fputc('L',fp); + write_ubjsonint(&compressedbytes, sizeof(compressedbytes), 1,fp); + } + fwrite(compressed,1,compressedbytes,fp); + }else{ + printError("Failed to compress data stream, error code=%d, status code=%d\n", ret, status); + return EXIT_FAILURE; + } + if(compressed) + free(compressed); + break; +#endif + } + if(!opts.isGz && slotid==40) + break; + } + } + } + fclose(fp); + return EXIT_SUCCESS; +} + +int nii_savejnii(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, struct TDTI4D *dti4D, int numDTI) { +// JNIfTI is a JSON wrapper to the NIfTI-1/2 format, supports both plain-text (.jnii) and binary (.bnii) formats +// Specification: https://github.com/NeuroJSON/jnifti/blob/master/JNIfTI_specification.md +// jnii is a plain JSON file and can be parsed in nearly all JSON parsers; to decode the +// embedded binary data as strings, the NIFTIData section can be be parsed in MATLAB using +// jnifty (https://github.com/NeuroJSON/jnifty) and Python using jdata (https://github.com/NeuroJSON/pyjdata) + + FILE *fp; + int dim[8]={0}, ndim=0; /* extra dimension in dim to hold the split data from complex voxel types, such as complex64 */ + + cJSON *root=NULL, *info=NULL, *jhdr=NULL, *dat=NULL, *sub=NULL; + char *jsonstr=NULL; + size_t compressedbytes, totalbytes; + unsigned char *compressed=NULL, *buf=NULL; + + /*jnifti convers code-based header fields to human-readable/standardized strings*/ + int datatypeidx; + const char *datatypestr[]={"uint8","int16","int32","single","complex64","double", + "rgb24" ,"int8","uint16","uint32","int64","uint64", + "double128","complex128","complex256","rgba32",""}; + const char *jdatatypestr[]={"uint8","int16","int32","single","double","double", + "uint8" ,"int8","uint16","uint32","int64","uint64", + "uint8","double","uint8","uint8","uint8"}; + const char jdatamarker[]={'U','I','l','d','D','D','U','i','u','m','L','M', + 'U','D','U','U','U'}; + unsigned char jdataelemlen[]={1,1,1,1,2,1,3,1,1,1,1,1,16,2,32,4,1}; + int datatypeid[]={NIFTI_TYPE_UINT8,NIFTI_TYPE_INT16,NIFTI_TYPE_INT32, + NIFTI_TYPE_FLOAT32,NIFTI_TYPE_COMPLEX64,NIFTI_TYPE_FLOAT64, + NIFTI_TYPE_RGB24,NIFTI_TYPE_INT8,NIFTI_TYPE_UINT16, + NIFTI_TYPE_UINT32,NIFTI_TYPE_INT64,NIFTI_TYPE_UINT64, + NIFTI_TYPE_FLOAT128,NIFTI_TYPE_COMPLEX128, + NIFTI_TYPE_COMPLEX256,NIFTI_TYPE_RGBA32}; + + int lunitidx, tunitidx; + const char *unitstr[]={"m","mm","um","s","ms","us","hz","ppm","rad",""}; + int unitid[]={NIFTI_UNITS_METER,NIFTI_UNITS_MM,NIFTI_UNITS_MICRON, + NIFTI_UNITS_SEC,NIFTI_UNITS_MSEC,NIFTI_UNITS_USEC, + NIFTI_UNITS_HZ,NIFTI_UNITS_PPM,NIFTI_UNITS_RADS}; + + int slicetypeidx; + const char *slicetypestr[]={"","seq+","seq-","alt+","alt-","alt2+","alt2-",""}; + int slicetypeid[]={NIFTI_SLICE_UNKNOWN,NIFTI_SLICE_SEQ_INC, + NIFTI_SLICE_SEQ_DEC,NIFTI_SLICE_ALT_INC,NIFTI_SLICE_ALT_DEC, + NIFTI_SLICE_ALT_INC2,NIFTI_SLICE_ALT_DEC2}; + + int intentidx; + const char *intentstr[]={"","corr","ttest","ftest","zscore","chi2","beta","binomial", + "gamma","poisson","normal","ncftest","ncchi2","logistic","laplace", + "uniform","ncttest","weibull","chi","invgauss","extval","pvalue", + "logpvalue","log10pvalue","estimate","label","neuronames","matrix", + "symmatrix","dispvec","vector","point","triangle","quaternion", + "unitless","tseries","elem","rgb","rgba","shape",""}; + int intentid[]={NIFTI_INTENT_NONE,NIFTI_INTENT_CORREL,NIFTI_INTENT_TTEST, + NIFTI_INTENT_FTEST,NIFTI_INTENT_ZSCORE,NIFTI_INTENT_CHISQ, + NIFTI_INTENT_BETA,NIFTI_INTENT_BINOM,NIFTI_INTENT_GAMMA, + NIFTI_INTENT_POISSON,NIFTI_INTENT_NORMAL,NIFTI_INTENT_FTEST_NONC, + NIFTI_INTENT_CHISQ_NONC,NIFTI_INTENT_LOGISTIC,NIFTI_INTENT_LAPLACE, + NIFTI_INTENT_UNIFORM,NIFTI_INTENT_TTEST_NONC,NIFTI_INTENT_WEIBULL, + NIFTI_INTENT_CHI,NIFTI_INTENT_INVGAUSS,NIFTI_INTENT_EXTVAL, + NIFTI_INTENT_PVAL,NIFTI_INTENT_LOGPVAL,NIFTI_INTENT_LOG10PVAL, + NIFTI_INTENT_ESTIMATE,NIFTI_INTENT_LABEL,NIFTI_INTENT_NEURONAME, + NIFTI_INTENT_GENMATRIX,NIFTI_INTENT_SYMMATRIX,NIFTI_INTENT_DISPVECT, + NIFTI_INTENT_VECTOR,NIFTI_INTENT_POINTSET,NIFTI_INTENT_TRIANGLE, + NIFTI_INTENT_QUATERNION,NIFTI_INTENT_DIMLESS,NIFTI_INTENT_TIME_SERIES, + NIFTI_INTENT_NODE_INDEX,NIFTI_INTENT_RGB_VECTOR,NIFTI_INTENT_RGBA_VECTOR, + NIFTI_INTENT_SHAPE}; + char fname[2048] = {""}; + strcpy(fname, niiFilename); + if (opts.saveFormat == kSaveFormatBNII) + strcat(fname, ".bnii"); + else + strcat(fname, ".jnii"); + + /* preprocess nifti header to convert to human-readable jnifti fields */ + totalbytes=nii_ImgBytes(hdr); + ndim=hdr.dim[0]; + for(int i=1;i<8;i++) + dim[i-1]=hdr.dim[i]; + + datatypeidx=jnifti_lookup(datatypeid, sizeof(datatypeid)/sizeof(int), hdr.datatype); + slicetypeidx=jnifti_lookup(slicetypeid, sizeof(slicetypeid)/sizeof(int), hdr.slice_code); + intentidx=jnifti_lookup(intentid, sizeof(intentid)/sizeof(int), hdr.intent_code); + lunitidx=sizeof(unitid)/sizeof(unitid[0]); + tunitidx=sizeof(unitid)/sizeof(unitid[0]); + for(int i=0;i=NIFTI_UNITS_HZ && hdr.xyzt_units==unitid[i]) + cJSON_AddStringToObject(sub, "Special", unitstr[i]); + } + cJSON_AddNumberToObject(jhdr, "MaxIntensity", hdr.cal_max); + cJSON_AddNumberToObject(jhdr, "MinIntensity", hdr.cal_min); + cJSON_AddNumberToObject(jhdr, "SliceTime", hdr.slice_duration); + cJSON_AddNumberToObject(jhdr, "TimeOffset", hdr.toffset); + cJSON_AddStringToObject(jhdr, "Description", hdr.descrip); + cJSON_AddStringToObject(jhdr, "AuxFile", hdr.aux_file); + cJSON_AddNumberToObject(jhdr, "QForm", hdr.qform_code); + cJSON_AddNumberToObject(jhdr, "SForm", hdr.sform_code); + cJSON_AddItemToObject(jhdr, "Quatern", sub=cJSON_CreateObject()); + cJSON_AddNumberToObject(sub, "b", hdr.quatern_b); + cJSON_AddNumberToObject(sub, "c", hdr.quatern_c); + cJSON_AddNumberToObject(sub, "d", hdr.quatern_d); + cJSON_AddItemToObject(jhdr, "QuaternOffset", sub=cJSON_CreateObject()); + cJSON_AddNumberToObject(sub, "x", hdr.qoffset_x); + cJSON_AddNumberToObject(sub, "y", hdr.qoffset_y); + cJSON_AddNumberToObject(sub, "z", hdr.qoffset_z); + cJSON_AddItemToObject(jhdr, "Affine", sub=cJSON_CreateArray()); + cJSON_AddItemToArray(sub, cJSON_CreateFloatArray(hdr.srow_x,4)); + cJSON_AddItemToArray(sub, cJSON_CreateFloatArray(hdr.srow_y,4)); + cJSON_AddItemToArray(sub, cJSON_CreateFloatArray(hdr.srow_z,4)); + cJSON_AddStringToObject(jhdr, "Name", hdr.intent_name); + cJSON_AddStringToObject(jhdr, "NIIFormat", hdr.magic); + + /* save depreciated header flags if non-trival values are provided */ + if(hdr.vox_offset) + cJSON_AddNumberToObject(jhdr, "NIIByteOffset", hdr.vox_offset); + if(strlen(hdr.data_type)) + cJSON_AddStringToObject(jhdr, "A75DataTypeName", hdr.data_type); + if(strlen(hdr.db_name)) + cJSON_AddStringToObject(jhdr, "A75DBName", hdr.db_name); + if(hdr.extents) + cJSON_AddNumberToObject(jhdr, "A75Extends", hdr.extents); + if(hdr.session_error) + cJSON_AddNumberToObject(jhdr, "A75SessionError", hdr.session_error); + if(hdr.regular) + cJSON_AddNumberToObject(jhdr, "A75DataTypeName", hdr.regular); + if(hdr.glmax) + cJSON_AddNumberToObject(jhdr, "A75GlobalMax", hdr.glmax); + if(hdr.glmin) + cJSON_AddNumberToObject(jhdr, "A75GlobalMin", hdr.glmin); + + /* the "NIFTIData" section stores volumetric data */ + cJSON_AddItemToObject(root, "NIFTIData",dat = cJSON_CreateObject()); + cJSON_AddStringToObject(dat,"_ArrayType_",jdatatypestr[datatypeidx]); + if(jdataelemlen[datatypeidx]>1) + dim[ndim++]=jdataelemlen[datatypeidx]; + cJSON_AddItemToObject(dat, "_ArraySize_",cJSON_CreateIntArray(dim,ndim)); + cJSON_AddStringToObject(dat,"_ArrayOrder_","c"); // NIfTI array is column-major + +#ifdef Z_DEFLATED + if(opts.isGz){ + int ret=0, status=0; + cJSON_AddStringToObject(dat, "_ArrayZipType_", "zlib"); + cJSON_AddNumberToObject(dat, "_ArrayZipSize_",totalbytes/(hdr.bitpix>>3)); + + ret=zmat_run(totalbytes, im, &compressedbytes, (unsigned char **)&compressed, zmZlib, &status, -opts.gzLevel); + + if(!ret){ + ret=zmat_run(compressedbytes, compressed, &totalbytes, (unsigned char **)&buf, zmBase64, &status,1); + cJSON_AddStringToObject(dat, "_ArrayZipData_",(const char *)buf); + }else{ + printError("Failed to compress data stream, error code=%d, status code=%d\n", ret, status); + return EXIT_FAILURE; + } + if(compressed) + free(compressed); + }else{ + cJSON_AddStringToObject(dat, "_ArrayZipType_", "base64"); + cJSON_AddNumberToObject(dat, "_ArrayZipSize_",totalbytes/(hdr.bitpix>>3)); + buf=base64_encode(im, totalbytes, &compressedbytes); + cJSON_AddStringToObject(dat,"_ArrayZipData_", (const char*)buf); + } +#else + cJSON_AddStringToObject(dat, "_ArrayZipType_", "base64"); + cJSON_AddNumberToObject(dat, "_ArrayZipSize_",totalbytes/(hdr.bitpix>>3)); + buf=base64_encode(im, totalbytes, &compressedbytes); + cJSON_AddStringToObject(dat,"_ArrayZipData_", (const char*)buf); +#endif + if(buf) + free(buf); + + /* now save JSON to file */ + jsonstr=cJSON_Print(root); + if(jsonstr==NULL){ + printMessage("Error: error when converting to JNIfTI\n"); + return EXIT_FAILURE; + } + + fp=fopen(fname,"wt"); + if(fp==NULL){ + printMessage("Error: error when writing to JNIfTI file\n"); + return EXIT_FAILURE; + } + fprintf(fp,"%s\n",jsonstr); + fclose(fp); + + if(jsonstr) + free(jsonstr); + if(root) + cJSON_Delete(root); + return EXIT_SUCCESS; +} // nii_savejnii() +#endif //#ifdef myEnableJNIfTI + int nii_saveForeign(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, struct TDTI4D *dti4D, int numDTI) { if (opts.saveFormat == kSaveFormatMGH) return nii_saveMGH(niiFilename, hdr, im, opts, d, dti4D, numDTI); + #ifdef myEnableJNIfTI + else if (opts.saveFormat == kSaveFormatJNII || opts.saveFormat == kSaveFormatBNII) + return nii_savejnii(niiFilename, hdr, im, opts, d, dti4D, numDTI); + #endif return nii_saveNRRD(niiFilename, hdr, im, opts, d, dti4D, numDTI); }// nii_saveForeign() @@ -4236,6 +4935,8 @@ int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, return EXIT_SUCCESS; } #endif + +#ifndef USING_DCM2NIIXFSWRAPPER FILE *fp = fopen(fname, "wb"); if (!fp) return EXIT_FAILURE; @@ -4246,6 +4947,8 @@ int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, fwrite(&pad, sizeof(pad), 1, fp); fwrite(&im[0], imgsz, 1, fp); fclose(fp); +#endif + if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) if ((opts.isGz) && (strlen(opts.pigzname) > 0)) { @@ -5042,6 +5745,8 @@ void checkSliceTiming(struct TDICOMdata *d, struct TDICOMdata *d1, int verbose, //modified 20190704: this function now ensures all slice times are in msec if ((d->TR < 0.0) || (d->CSA.sliceTiming[0] < 0.0)) return; //no slice timing + if (d->manufacturer == kMANUFACTURER_PHILIPS) + return; //Philips does not provide slice timing details in DICOM if (d->manufacturer == kMANUFACTURER_GE) return; //compute directly from Protocol Block if (d->modality == kMODALITY_PT) @@ -5211,7 +5916,7 @@ void sliceTimeGE(struct TDICOMdata *d, int mb, int dim3, float TR, bool isInterl if ((mb > 1) && (!is27r3) && ((nExcitations % 2) == 0) ) { //number of slices divided by MB factor should is Even nExcitations ++; //https://osf.io/q4d53/wiki/home/; Figure 3 of https://pubmed.ncbi.nlm.nih.gov/26308571/ } - int nDiscardedSlices = (nExcitations * mb) - dim3; + //int nDiscardedSlices = (nExcitations * mb) - dim3; float secPerSlice = (TR - groupDelaysec) / (nExcitations); if (!isInterleaved) { for (int i = 0; i < nExcitations; i++) @@ -5285,7 +5990,6 @@ void readSoftwareVersionsGE(char softwareVersionsGE[], int verbose, char geVersi char *versionString = (char *)malloc(sizeof(char) * len); versionString[len - 1] = 0; memcpy(versionString, sepStart, len); - int ver1, ver2, ver3; char c1, c2, c3, c4; // RX27.0_R02_ or MR29.1_EA_2 sscanf(versionString, "%c%c%d.%d_%c%c%d", &c1, &c2, geMajorVersionInt, geMinorVersionInt, &c3, &c4, geReleaseVersionInt); @@ -5719,13 +6423,15 @@ void loadOverlay(char *imgname, unsigned char *img, int offset, int x, int y, in } fseek(file, 0, SEEK_END); long fileLen = ftell(file); - if (fileLen < (imgszRead + offset)) { + if (fileLen < (int)(imgszRead + offset)) { printWarning("File not large enough to store overlay: %s\n", imgname); return; } fseek(file, (long)offset, SEEK_SET); unsigned char *bImg = (unsigned char *)malloc(imgszRead); size_t sz = fread(bImg, 1, imgszRead, file); + if (sz < imgszRead) + printWarning("loadOverlay fread error."); //static unsigned char mask[] = {128, 64, 32, 16, 8, 4, 2, 1}; static unsigned char mask[] = {1, 2, 4, 8, 16, 32, 64, 128}; for (int i = 0; i < nvox; i++) { @@ -5739,16 +6445,34 @@ void loadOverlay(char *imgname, unsigned char *img, int offset, int x, int y, in } //loadOverlay() int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts, struct TDTI4D *dti4D, int segVol) { +#ifdef USING_DCM2NIIXFSWRAPPER + double seriesNum = (double) dcmList[dcmSort[0].indx].seriesUidCrc; + int segVolEcho = segVol; + if ((dcmList[dcmSort[0].indx].echoNum > 1) && (segVolEcho <= 0)) + segVolEcho = dcmList[dcmSort[0].indx].echoNum + 1; + if (segVolEcho > 0) + seriesNum = seriesNum + ((double)segVolEcho - 1.0) / 10.0; + + if (!isSameDouble(opts.seriesNumber[0], seriesNum)) + return EXIT_SUCCESS; +#endif + bool iVaries = intensityScaleVaries(nConvert, dcmSort, dcmList); float *sliceMMarray = NULL; //only used if slices are not equidistant uint64_t indx = dcmSort[0].indx; uint64_t indx0 = dcmSort[0].indx; - uint64_t indx1 = indx0; - if (nConvert > 1) - indx1 = dcmSort[1].indx; + //uint64_t indx1 = indx0; + //if (nConvert > 1) + // indx1 = dcmSort[1].indx; uint64_t indxEnd = dcmSort[nConvert - 1].indx; dti4D->repetitionTimeInversion = 0.0; //only set for Siemens and GE 3D T1 "TR" dti4D->repetitionTimeExcitation = 0.0; //only set for Philips 3D T1 "TR" + if (nConvert > 0) { //issue 616: not enhanced DICOMs: infer these arrays from multiple volumes + dti4D->volumeOnsetTime[0] = -1; + dti4D->decayFactor[0] = -1; + dti4D->frameDuration[0] = -1; + dti4D->frameReferenceTime[0] = -1; + } #ifdef newTilt //see issue 254 if (((nConvert > 1) || (dcmList[indx0].xyzDim[3] > 1)) && ((dcmList[indx0].modality == kMODALITY_CT) || (dcmList[indx0].isXRay) || (dcmList[indx0].gantryTilt > 0.0))) { //issue372: enhanced DICOMs can also have gantry tilt dcmList[indx0].gantryTilt = computeGantryTiltPrecise(dcmList[indx0], dcmList[indxEnd], opts.isVerbose); @@ -5791,6 +6515,11 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d #else bool saveAs3D = false; #endif + +#ifdef USING_DCM2NIIXFSWRAPPER + mrifsStruct.tdicomData = dcmList[indx]; // first in sorted list dcmSort +#endif + struct nifti_1_header hdr0; unsigned char *img = nii_loadImgXL(nameList->str[indx], &hdr0, dcmList[indx], iVaries, opts.compressFlag, opts.isVerbose, dti4D); if (strlen(opts.imageComments) > 0) { @@ -5806,6 +6535,11 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d unsigned char *imgM = (unsigned char *)malloc(imgsz * (uint64_t)nConvert); memcpy(&imgM[0], &img[0], imgsz); free(img); + +#ifdef USING_DCM2NIIXFSWRAPPER + printMessage("load Image %s\n", nameList->str[indx]); +#endif + //printMessage(" %d %d %d %d %lu\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], (unsigned long)[imgM length]); bool isHasOverlay = dcmList[indx0].isHasOverlay; if (nConvert > 1) { @@ -5846,12 +6580,24 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d else printMessage("DICOM images may be missing, expected %d spatial locations per volume, but found %d slices.\n", dcmList[indx0].locationsInAcquisition, nConvert); } - if (nAcq < 2) { - nAcq = 0; - for (int i = 0; i < nConvert; i++) - if (isSamePosition(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx])) - nAcq++; + //end validate number of spatial volumes: check that number of times a spatial position is repeated matches number of 3D volumes in 4D dataset + int nSamePos = 0; + for (int i = 0; i < nConvert; i++) + if (isSamePosition(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx])) + nSamePos++; + if ((nAcq < 2) && (nSamePos > 0)) nAcq = nSamePos; + if (nAcq > nSamePos) { //issue556 + if ((dcmList[indx0].manufacturer == kMANUFACTURER_GE) && (dcmList[indx0].zThick > dcmList[indx0].xyzMM[3])) { + int zipFactor = (int)roundf(dcmList[indx0].zThick / dcmList[indx0].xyzMM[3]); + if (zipFactor > 1) { + nAcq = nSamePos; + dcmList[dcmSort[0].indx].interp3D = zipFactor; + } + } } + if (nAcq != nSamePos) + printWarning("Expected %d volumes but found spatial position repeats %d times.\n", nAcq, nSamePos); + //end validate number of spatial volumes if ((nAcq > 1) && ((nConvert / nAcq) > 1) && ((nConvert % nAcq) == 0)) { hdr0.dim[3] = nConvert / nAcq; hdr0.dim[4] = nAcq; @@ -5894,7 +6640,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d //next: detect variable inter-volume time https://github.com/rordenlab/dcm2niix/issues/184 //if ((nConvert > 1) && ((dcmList[indx0].modality == kMODALITY_PT)|| (opts.isForceOnsetTimes))) { if ((nConvert > 1) && ((dcmList[indx0].modality == kMODALITY_PT) || ((opts.isForceOnsetTimes) && (dcmList[indx0].manufacturer != kMANUFACTURER_GE)))) { - if (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_PHILIPS) { + if (((dcmList[indx0].modality == kMODALITY_PT) && (dcmList[indx0].frameReferenceTime >= 0.0)) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_PHILIPS)) { //PT: issue 577 ensureSequentialSlicePositions(hdr0.dim[3], hdr0.dim[4], dcmSort, dcmList, opts.isVerbose); //issue529 indx0 = dcmSort[0].indx; } @@ -5905,6 +6651,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d for (int i = 0; i < nConvert; i++) if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { dti4D->frameDuration[nTR] = dcmList[dcmSort[i].indx].frameDuration; + dti4D->frameReferenceTime[nTR] = dcmList[dcmSort[i].indx].frameReferenceTime; nTR += 1; if (nTR >= kMaxDTI4D) break; @@ -5937,12 +6684,18 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d if ((nVol > 1) && (volumeTimeStartFirstStartLast > 0.0)) { tr = volumeTimeStartFirstStartLast / (nVol - 1.0); if (fabs(tr - hdr0.pixdim[4]) > toleranceSec) { - if ((dcmList[indx0].isIR) && (dcmList[indx0].manufacturer != kMANUFACTURER_PHILIPS)) - dti4D->repetitionTimeInversion = hdr0.pixdim[4]; - else - dti4D->repetitionTimeExcitation = hdr0.pixdim[4]; - hdr0.pixdim[4] = tr; - dcmList[indx0].TR = tr * 1000.0; //as msec + if (dcmList[indx0].numberOfAverages > 1.0) //e.g. Mediso will save averaged data + tr = tr / dcmList[indx0].numberOfAverages; + if (fabs(tr - hdr0.pixdim[4]) > toleranceSec) { + if (hdr0.pixdim[4] > 0.0) + printWarning("Discrepancy between reported (%gs) and estimated (%gs) repetition time (issue 560).\n", hdr0.pixdim[4], tr); + if ((dcmList[indx0].isIR) && (dcmList[indx0].manufacturer != kMANUFACTURER_PHILIPS)) + dti4D->repetitionTimeInversion = hdr0.pixdim[4]; + else + dti4D->repetitionTimeExcitation = hdr0.pixdim[4]; + hdr0.pixdim[4] = tr; + dcmList[indx0].TR = tr * 1000.0; //as msec + } } } if (dcmList[indx0].aslFlags != kASL_FLAG_NONE) { //issue533 @@ -5998,8 +6751,8 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d if (!ensureSequentialSlicePositions(hdr0.dim[3], hdr0.dim[4], dcmSort, dcmList, opts.isVerbose)) dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); indx0 = dcmSort[0].indx; - if (nConvert > 1) - indx1 = dcmSort[1].indx; + //if (nConvert > 1) + // indx1 = dcmSort[1].indx; #endif bool dxVaries = false; for (int i = 1; i < nConvert; i++) @@ -6046,8 +6799,8 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d isInconsistenSliceDir = false; //code below duplicates prior code, could be written as modular function(s) indx0 = dcmSort[0].indx; - if (nConvert > 1) - indx1 = dcmSort[1].indx; + //if (nConvert > 1) + // indx1 = dcmSort[1].indx; dxVaries = false; dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); for (int i = 1; i < nConvert; i++) @@ -6059,7 +6812,7 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d //for (int i = 1; i < nConvert; i++) // printf("%g ", intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[i].indx]) ); //printf("\n"); - bool isInconsistenSliceDir = false; + isInconsistenSliceDir = false; int slicePositionRepeats = 1; //how many times is first position repeated if (nConvert > 2) { float dxPrev = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); @@ -6159,6 +6912,11 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d } memcpy(&imgM[(uint64_t)i * imgsz], &img[0], imgsz); free(img); + +#ifdef USING_DCM2NIIXFSWRAPPER + if (opts.isVerbose) + printMessage("load Image #%d %s\n", i, nameList->str[indx]); +#endif } } //skip if we are only creating BIDS if (hdr0.dim[4] > 1) //for 4d datasets, last volume should be acquired before first @@ -6208,7 +6966,9 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d nii_SaveBIDSX(pathoutname, dcmList[dcmSort[0].indx], opts, &hdr0, nameList->str[dcmSort[0].indx], dti4D); if (opts.isOnlyBIDS) { //note we waste time loading every image, however this ensures hdr0 matches actual output +#ifndef USING_DCM2NIIXFSWRAPPER free(imgM); +#endif return EXIT_SUCCESS; } if ((segVol >= 0) && (hdr0.dim[4] > 1)) { @@ -6262,9 +7022,13 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d struct nifti_1_header hdrrx = hdr0; bool isFlipZ = false; if (sliceDir < 0) { +#ifdef USING_DCM2NIIXFSWRAPPER // freesurfer fix dcm/261000-10-6?.dcm + printMessage("***USING_DCM2NIIXFSWRAPPER***: skip nii_flipZ() when sliceDir < 0 (%s:%s:%d)\n", __FILE__, __func__, __LINE__); +#else isFlipZ = true; imgM = nii_flipZ(imgM, &hdr0); sliceDir = abs(sliceDir); //change this, we have flipped the image so GE DTI bvecs no longer need to be flipped! +#endif } nii_saveText(pathoutname, dcmList[dcmSort[0].indx], opts, &hdr0, nameList->str[indx]); int numADC = 0; @@ -6280,19 +7044,21 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d nii_scale16bitUnsigned(imgM, &hdr0, opts.isVerbose); //allow UINT16 to use full dynamic range } else if ((opts.isMaximize16BitRange == kMaximize16BitRange_False) && (hdr0.datatype == DT_UINT16) && (!dcmList[dcmSort[0].indx].isSigned)) nii_check16bitUnsigned(imgM, &hdr0, opts.isVerbose); //save UINT16 as INT16 if we can do this losslessly - //if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert < 2)) //20211220: XA30 appears robust - // printWarning("Siemens XA DICOM inadequate for robust conversion (issue 236)\n"); - if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert > 1)) + if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert > 1) && (nConvert == (hdr0.dim[3] * hdr0.dim[4])) ) printWarning("Siemens XA exported as classic not enhanced DICOM (issue 236)\n"); +#ifndef USING_DCM2NIIXFSWRAPPER printMessage("Convert %d DICOM as %s (%dx%dx%dx%d)\n", nConvert, pathoutname, hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4]); +#else + printMessage( "Convert %d DICOM (%dx%dx%dx%d)\n", nConvert, hdr0.dim[1],hdr0.dim[2],hdr0.dim[3],hdr0.dim[4]); +#endif #ifndef USING_R fflush(stdout); //show immediately if run from MRIcroGL GUI #endif //~ if (!dcmList[dcmSort[0].indx].isSlicesSpatiallySequentialPhilips) //~ nii_reorderSlices(imgM, &hdr0, dti4D); //hdr0.pixdim[3] = dxNoTilt; - if (hdr0.dim[3] < 2) - printWarning("Check that 2D images are not mirrored.\n"); + if ((hdr0.dim[3] < 2) && (!isnan(dcmList[dcmSort[0].indx].patientPosition[0]))) + printWarning("Check that 2D images are not mirrored.\n"); // isnan does not warn for non-spatial images (physio) #ifndef USING_R else fflush(stdout); //GUI buffers printf, display all results @@ -6470,7 +7236,16 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata d if (returnCode == EXIT_SUCCESS) nii_saveAttributes(dcmList[dcmSort[0].indx], hdr0, opts, nameList->str[dcmSort[0].indx]); #endif + +#ifdef USING_DCM2NIIXFSWRAPPER + hdr0.vox_offset = 352; + + mrifsStruct.hdr0 = hdr0; + mrifsStruct.imgsz = nii_ImgBytes(hdr0); + mrifsStruct.imgM = imgM; +#else free(imgM); +#endif if (dcmList[dcmSort[0].indx].xyzDim[0] > 1) returnCode = kEXIT_INCOMPLETE_VOLUMES_FOUND; //issue515 return returnCode; //EXIT_SUCCESS; @@ -6486,6 +7261,8 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmLi printError("Unexpected error for image with varying echo time or intensity scaling\n"); return EXIT_FAILURE; } + if ((nConvert == 1) && (dcmList[indx].manufacturer == kMANUFACTURER_PHILIPS)) + printWarning("Philips enhanced DICOMs (hint: export as classic DICOM)\n"); int ret = EXIT_SUCCESS; //check for repeated echoes - count unique number of echoes //code below checks for multi-echoes - not required if maxNumberOfEchoes reported in PARREC @@ -6510,10 +7287,14 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmLi for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) dti4D->gradDynVol[i] = 0; dti4D->gradDynVol[0] = 1; + int dim3 = dcmList[indx].xyzDim[3];//vol2slice(int vol, int dim3) for (int i = 1; i < dcmList[indx].xyzDim[4]; i++) { - for (int j = 0; j < i; j++) - if (( (dcmList[indx].aslFlags != kASL_FLAG_NONE) || isSameFloatGE(dti4D->triggerDelayTime[i], dti4D->triggerDelayTime[j])) && (dti4D->intenIntercept[i] == dti4D->intenIntercept[j]) && (dti4D->intenScale[i] == dti4D->intenScale[j]) && (dti4D->isReal[i] == dti4D->isReal[j]) && (dti4D->isImaginary[i] == dti4D->isImaginary[j]) && (dti4D->isPhase[i] == dti4D->isPhase[j]) && (dti4D->TE[i] == dti4D->TE[j])) + int iv = (i * dim3); //intenIntercept and intenScale can vary within a volume + for (int j = 0; j < i; j++) { + int jv = (j * dim3); + if (( (dcmList[indx].aslFlags != kASL_FLAG_NONE) || isSameFloatGE(dti4D->triggerDelayTime[i], dti4D->triggerDelayTime[j])) && (dti4D->intenIntercept[iv] == dti4D->intenIntercept[jv]) && (dti4D->intenScale[iv] == dti4D->intenScale[jv]) && (dti4D->isReal[i] == dti4D->isReal[j]) && (dti4D->isImaginary[i] == dti4D->isImaginary[j]) && (dti4D->isPhase[i] == dti4D->isPhase[j]) && (dti4D->TE[i] == dti4D->TE[j])) dti4D->gradDynVol[i] = dti4D->gradDynVol[j]; + } if (dti4D->gradDynVol[i] == 0) { series++; dti4D->gradDynVol[i] = series; @@ -6794,6 +7575,9 @@ bool isSameSet(struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts *opts //if (((!(isSameFloat(d1.TE, d2.TE))) || (d1.echoNum != d2.echoNum)) && (!d1.isXRay)) { // *isMultiEcho = true; //} +#ifdef USING_DCM2NIIXFSWRAPPER + printf("isForceStackSameSeries = true, seriesNum %ld, %ld, seriesInstanceUidCrc %d, %d\n", d1.seriesNum, d2.seriesNum, d1.seriesUidCrc, d2.seriesUidCrc); +#endif return true; //we will stack these images, even if they differ in the following attributes } if ((d1.isHasImaginary != d2.isHasImaginary) || (d1.isHasPhase != d2.isHasPhase) || (d1.isHasReal != d2.isHasReal)) { @@ -6806,12 +7590,8 @@ bool isSameSet(struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts *opts if ((!(isSameFloat(d1.TE, d2.TE))) || (d1.echoNum != d2.echoNum)) { if ((!warnings->echoVaries) && (d1.isXRay)) //for CT/XRay we check DICOM tag 0018,1152 (XRayExposure) printMessage("Slices not stacked: X-Ray Exposure varies (exposure %g, %g; number %d, %d). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE, d1.echoNum, d2.echoNum); - if ((!warnings->echoVaries) && (!d1.isXRay)) {//for MRI - if (d1.echoNum == d2.echoNum) - printMessage("Slices not stacked: echo varies (TE %g, %g). No echo number (0018,0086; issue 568). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE); - else - printMessage("Slices not stacked: echo varies (TE %g, %g; echo %d, %d). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE, d1.echoNum, d2.echoNum); - } + if ((!warnings->echoVaries) && (!d1.isXRay)) //for MRI + printMessage("Slices not stacked: echo varies (TE %g, %g; echo %d, %d). Use 'merge 2D slices' option to force stacking\n", d1.TE, d2.TE, d1.echoNum, d2.echoNum); warnings->echoVaries = true; *isMultiEcho = true; return false; @@ -7266,6 +8046,10 @@ int reportProgress(int progressPct, float frac) { #endif int nii_loadDirCore(char *indir, struct TDCMopts *opts) { +#ifdef USING_DCM2NIIXFSWRAPPER + memset(&mrifsStruct, 0, sizeof(mrifsStruct)); +#endif + struct TSearchList nameList; int nConvertTotal = 0; #if defined(_WIN64) || defined(_WIN32) || defined(USING_R) @@ -7511,6 +8295,15 @@ int nii_loadDirCore(char *indir, struct TDCMopts *opts) { continue; if (!dcmList[ii].isValid) continue; + +#ifdef USING_DCM2NIIXFSWRAPPER + if (opts->numSeries > 0) { + double seriesNum = (double) dcmList[ii].seriesUidCrc; + if (!isSameDouble(opts->seriesNumber[0], seriesNum)) + continue; // we convert one series at a time, skip the ones that we are not interested in + } +#endif + int nConvert = 0; bool isMultiEcho = false; bool isNonParallelSlices = false; @@ -7894,7 +8687,8 @@ void readFindPigz(struct TDCMopts *opts, const char *argv[]) { void setDefaultOpts(struct TDCMopts *opts, const char *argv[]) { //either "setDefaultOpts(opts,NULL)" or "setDefaultOpts(opts,argv)" where argv[0] is path to search strcpy(opts->pigzname, ""); #ifndef USING_R - readFindPigz(opts, argv); + if (argv != NULL) + readFindPigz(opts, argv); #endif #ifdef myEnableJasper opts->compressFlag = kCompressYes; //JASPER for JPEG2000 diff --git a/console/nii_dicom_batch.h b/console/nii_dicom_batch.h index b3423c81..80902c0d 100644 --- a/console/nii_dicom_batch.h +++ b/console/nii_dicom_batch.h @@ -23,6 +23,22 @@ extern "C" { }; #endif +typedef struct +{ + struct nifti_1_header hdr0; + + size_t imgsz; + unsigned char *imgM; + + struct TDICOMdata tdicomData; + + struct TDTI *tdti; + int numDti; +} MRIFSSTRUCT; + +MRIFSSTRUCT* nii_getMrifsStruct(); +void nii_clrMrifsStruct(); + #define kNAME_CONFLICT_SKIP 0 //0 = write nothing for a file that exists with desired name #define kNAME_CONFLICT_OVERWRITE 1 //1 = overwrite existing file with same name #define kNAME_CONFLICT_ADD_SUFFIX 2 //default 2 = write with new suffix as a new file @@ -34,6 +50,8 @@ extern "C" { #define kSaveFormatNIfTI 0 #define kSaveFormatNRRD 1 #define kSaveFormatMGH 2 +#define kSaveFormatJNII 3 +#define kSaveFormatBNII 4 #define MAX_NUM_SERIES 16 @@ -59,6 +77,7 @@ extern "C" { void readIniFile (struct TDCMopts *opts, const char * argv[]); int nii_saveNIIx(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts); int nii_loadDir(struct TDCMopts *opts); + int nii_loadDirCore(char *indir, struct TDCMopts* opts); void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char * filename); int nii_createFilename(struct TDICOMdata dcm, char * niiFilename, struct TDCMopts opts); void nii_createDummyFilename(char * niiFilename, struct TDCMopts opts); diff --git a/dcm_qa b/dcm_qa index 2f59a065..bb457d98 160000 --- a/dcm_qa +++ b/dcm_qa @@ -1 +1 @@ -Subproject commit 2f59a065686f1bdab52f8c4c529e1e2e7e4c133f +Subproject commit bb457d98bdb320af3b9f29fad1c706788490dab8 diff --git a/dcm_qa_nih b/dcm_qa_nih index 0a0aa943..aa82e560 160000 --- a/dcm_qa_nih +++ b/dcm_qa_nih @@ -1 +1 @@ -Subproject commit 0a0aa943b5e5263645a33359e52dedf147c6a5a7 +Subproject commit aa82e560d5471b53f0d0332c4de33d88bf179157 diff --git a/dcm_qa_uih b/dcm_qa_uih index 78f21294..4c0e2b37 160000 --- a/dcm_qa_uih +++ b/dcm_qa_uih @@ -1 +1 @@ -Subproject commit 78f212941ca7adc13bd3810667084b48a0047484 +Subproject commit 4c0e2b37430fa566a501bb65f35a9ad4089e71a9 diff --git a/docs/source/dcm2niibatch.rst b/docs/source/dcm2niibatch.rst index ffbc3a0f..574d5a46 100644 --- a/docs/source/dcm2niibatch.rst +++ b/docs/source/dcm2niibatch.rst @@ -41,9 +41,9 @@ The configuration file should be in yaml format as shown in example *batch_confi Options: isGz: false - isFlipY: true + isFlipY: false isVerbose: false - isCreateBIDS: true + isCreateBIDS: false isOnlySingleFile: false Files: -