pax_global_header00006660000000000000000000000064141276150330014514gustar00rootroot0000000000000052 comment=003f0d19f1e57b0129c9dcf3e653f51ca3559028 dcm2niix-1.0.20211006/000077500000000000000000000000001412761503300137635ustar00rootroot00000000000000dcm2niix-1.0.20211006/.github/000077500000000000000000000000001412761503300153235ustar00rootroot00000000000000dcm2niix-1.0.20211006/.github/ISSUE_TEMPLATE/000077500000000000000000000000001412761503300175065ustar00rootroot00000000000000dcm2niix-1.0.20211006/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000031411412761503300221770ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To reproduce** Steps to reproduce the behavior: 1. Run the command 'dcm2niix ...' 2. See error ... **Expected behavior** A clear and concise description of what you expected to happen. **Output log** If applicable, output generated converting data. **Version** Please report the complete version string: - dcm2niix version string, e.g. `dcm2niiX version v1.0.20201207 Clang12.0.0 ARM (64-bit MacOS)` The version string is always the first line generated when dcm2niix is run. **Troubleshooting** Please try the following steps to resolve your issue: - Is this the [latest stable release](https://github.com/rordenlab/dcm2niix/releases)? If not, does the latest stable release resolve your issue? - If the latest stable version fails, and you are using Windows. Does the latest commit on the development branch resolve your issue? You can get a pre-compiled version from [AppVeyor](https://ci.appveyor.com/project/neurolabusc/dcm2niix) (click on the Artifacts button). - If the latest stable version fails, and you are using macOS or Linux. Does the latest commit on the development branch resolve your issue? You can build this using the recipe below: ``` git clone --branch development https://github.com/rordenlab/dcm2niix.git cd dcm2niix/console g++ -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 ./dcm2niix .... ``` dcm2niix-1.0.20211006/.gitignore000066400000000000000000000000521412761503300157500ustar00rootroot00000000000000.DS_Store /build/ /bin/ /console/dcm2niix dcm2niix-1.0.20211006/.gitmodules000066400000000000000000000004271412761503300161430ustar00rootroot00000000000000[submodule "dcm_qa"] path = dcm_qa url = https://github.com/neurolabusc/dcm_qa.git [submodule "dcm_qa_nih"] path = dcm_qa_nih url = https://github.com/neurolabusc/dcm_qa_nih.git [submodule "dcm_qa_uih"] path = dcm_qa_uih url = https://github.com/neurolabusc/dcm_qa_uih.git dcm2niix-1.0.20211006/.travis.yml000066400000000000000000000026141412761503300160770ustar00rootroot00000000000000language: cpp git: depth: 1 matrix: include: - os: linux dist: trusty addons: apt: sources: - ubuntu-toolchain-r-test packages: - g++-5 # support c++14 env: - MATRIX_EVAL="CC=gcc-5 && CXX=g++-5" - TARGET=lnx - os: osx osx_image: xcode8.3 # Tracvis default: OS X 10.12.6 and Xcode 8.3.3 env: TARGET=mac before_install: - eval "${MATRIX_EVAL}" - git submodule update --init --remote --depth=3 script: # - mkdir build && cd build && cmake -DBATCH_VERSION=ON -DUSE_OPENJPEG=ON -DUSE_JPEGLS=true -DZLIB_IMPLEMENTATION=Cloudflare .. && make && cd - - mkdir build && cd build && cmake -DBATCH_VERSION=OFF -DUSE_OPENJPEG=ON -DUSE_JPEGLS=true -DZLIB_IMPLEMENTATION=Cloudflare .. && make && cd - - export PATH=$PWD/build/bin:$PATH - cd dcm_qa && ./batch.sh && cd - - cd dcm_qa_nih && ./batch.sh && cd - - cd dcm_qa_uih && ./batch.sh && cd - before_deploy: - zip -j dcm2niix_${TARGET}.zip build/bin/* - sleep 300 # make sure appveyor deployment is done, thus proper release name is set deploy: provider: releases api_key: secure: sVIYRakcEQdMPEdGSSePtMVCMQvaohqV7NNzEErAgZ+b/4ofv2aPpJb5kNTv3JRl2FrPy7iXJ8lOUQ/95pqvimX6jv5ztksTNXtSMnHZNbjjWwIc99enPY+mSdWMO2lb9vGBWQ9GNfXjmk7MgtDHPjjygbuZfUw9fmGy4ocxkws= file_glob: true file: dcm2niix*.zip skip_cleanup: true on: tags: true dcm2niix-1.0.20211006/BATCH.md000066400000000000000000000014651412761503300151340ustar00rootroot00000000000000**Optional batch processing version:** Perform a batch conversion of multiple dicoms using the configurations specified in a yaml file. ```bash dcm2niibatch batch_config.yml ``` The configuration file should be in yaml format as shown in example `batch_config.yaml` ```yaml Options: isGz: false isFlipY: false isVerbose: false isCreateBIDS: false isOnlySingleFile: false Files: - in_dir: /path/to/first/folder out_dir: /path/to/output/folder filename: dcemri - in_dir: /path/to/second/folder out_dir: /path/to/output/folder filename: fa3 ``` You can add as many files as you want to convert as long as this structure stays consistent. Note that a dash must separate each file. dcm2niix-1.0.20211006/BIDS/000077500000000000000000000000001412761503300145045ustar00rootroot00000000000000dcm2niix-1.0.20211006/BIDS/README.md000066400000000000000000001221051412761503300157640ustar00rootroot00000000000000## About dcm2niix is designed to convert complicated DICOM images in to simple NIfTI images. However, the NIfTI images are unable to store much metadata that might be useful for analyses. Therefore, dcm2niix can create Brain Imaging Data Structure ([BIDS](https://bids.neuroimaging.io)) files that retain useful information. These are simple human-readable text files in the [JSON](https://en.wikipedia.org/wiki/JSON) format. The tables below provide details regarding the JSON fields generated by dcm2niix. Some of these are defined by the BIDS standard, while others are unique to dcm2niix. Note that dcm2niix cannot provide information that is not in the DICOM header. Common reasons for absent fields are: - Irrelevance to the scan type, e.g. MagneticFieldStrength for CT - It was removed from the DICOM during anonymization, possibly by accident or overzealousness - Difficulty interpreting the storage format used by the manufacturer - The manufacturer simply neglected to write it ### Formatting Note The units listed in this document can be extracted into JSON format using ```./extract_units.py```, as long as they are in tables where the first two columns are Field and Unit. It can also optionally extract only the units that are used in a given BIDS sidecar - e.g. if you have a BIDS sidecar file named ```my_series.json``` from dcm2niix, you can use ```extract_units.py``` to get ```my_series_units.json```. Help on using ```extract_units.py``` is available from running ```extract_units.py -h```. ### Glossary The Defined By column uses: - B : The [BIDS](https://bids.neuroimaging.io) standard - D : dcm2niix (often, but not always, these come directly from the DICOM header without transformation) The Unit column uses: - deg : degrees - f : fraction - kg : Kilogram - list : list of text strings - MBq : megabecquerel - MHz : megahertz - mA : milliampere - mm : millimeter - s : second - T : tesla - V : volt ## Global Fields These fields are present regardless of modality (e.g. MR, CT, PET). ### Global Constants These fields should be the same for all images acquired on a specific scanner. | Field | Unit | Comments | Defined By | |-----------------------------|------|----------------------|------------| | Manufacturer | | DICOM tag 0008,0070 | B | | DeviceSerialNumber | | DICOM tag 0018,1000 | B | | StationName | | DICOM tag 0008,1010 | B | | SoftwareVersions | | DICOM tag 0018,1020 | B | | Modality | | DICOM tag 0008,1060 | D | | ManufacturersModelName | | DICOM tag 0008,1090 | B | | InstitutionName | | DICOM tag 0008,0080 | B | | InstitutionalDepartmentName | | DICOM tag 0008,1040 | B | | InstitutionAddress | | DICOM tag 0008,0081 | B | | DeviceSerialNumber | | DICOM tag 0018,1000 | B | | StationName | | DICOM tag 0008,1010 | B | | ConversionSoftware | | e.g. `dcm2niix` | D | | ConversionSoftwareVersion | | e.g. `v1.0.20210317` | D | ### Global Series Information These fields are present regardless of modality (e.g. MR, CT, PET). | Field | Unit | Comments | Defined By | |--------------------------|------|---------------------|------------| | BodyPartExamined | | DICOM tag 0018,0015 | D | | PatientPosition | | DICOM tag 0020,0032 | D | | ProcedureStepDescription | | DICOM tag 0040,0254 | D | | SoftwareVersions | | DICOM tag 0020,1020 | B | | SeriesDescription | | DICOM tag 0008,103E | D | | ProtocolName | | DICOM tag 0018,1030 | D | | ScanningSequence | | DICOM tag 0018,0020 | B | | SequenceVariant | | DICOM tag 0018,0021 | B | | ScanOptions | | DICOM tag 0018,0022 | B | | SequenceName | | DICOM tag 0018,0024 | B | | ImageType | list | DICOM tag 0008,0008 | D | | AcquisitionTime | | DICOM tag 0008,0032 | D | | AcquisitionNumber | | DICOM tag 0020,0012 | D | | ImageComments | | DICOM tag 0020,4000 | D | ### Global Private Information These fields contain personally identifiable information. By default dcm2niix will create anonymized files without these fields. The option `-ba n` will retain private information. | Field | Unit | Comments | Defined By | |------------------------|------|---------------------|------------| | SeriesInstanceUID | | DICOM tag 0020,000E | D | | StudyInstanceUID | | DICOM tag 0020,000D | D | | ReferringPhysicianName | | DICOM tag 0008,0090 | D | | StudyID | | DICOM tag 0020,0010 | D | | PatientName | | DICOM tag 0010,0010 | D | | PatientID | | DICOM tag 0010,0020 | D | | AccessionNumber | | DICOM tag 0008,0050 | D | | PatientBirthDate | | DICOM tag 0010,0030 | D | | PatientWeight | kg | DICOM tag 0010,1030 | D | | AcquisitionDateTime | | DICOM tag 0008,002A | D | ## Modality Fields These fields are specific to modality (e.g. MR, CT, PET). ### Modality Computerized Tomography Fields specific to CT scans. | Field | Unit | Comments | Defined By | |-----------------|------|---------------------|------------| | ExposureTime | s | DICOM tag 0018,1150 | D | | XRayTubeCurrent | mA | DICOM tag 0018,1151 | D | | XRayExposure | mAs | DICOM tag 0018,1152 | D | ### Modality Magnetic Resonance Imaging Fields specific to MRI scans. | Field | Unit | Comments | Defined By | |------------------------------------|------|-----------------------------------------------------------------------------------------|------------| | AcquisitionMatrixPE | | DICOM tag 0018,9231 (aka PhaseEncodingLines) | D | | DerivedVendorReportedEchoSpacing | s | | D | | EchoNumber | | Only multi-echo series | D | | EchoTime | s | DICOM tag 0018,0081 | B | | EchoTrainLength | | DICOM tag 0018,0091 | D | | EffectiveEchoSpacing | s | | D | | EstimatedEffectiveEchoSpacing | s | | D | | EstimatedTotalReadoutTime | s | | D | | FlipAngle | deg | DICOM tag 0018,1314 | B | | ImageOrientationPatientDICOM | | DICOM tag 0020,0037 | D | | ImagingFrequency | MHz | DICOM tag 0018,0084 | D | | InPlanePhaseEncodingDirectionDICOM | | DICOM tag 0018,1312 | D | | InversionTime | s | DICOM tag 0018,0082 | B | | MagneticFieldStrength | T | DICOM tag 0018,0087 | B | | MRAcquisitionType | | DICOM tag 0018,0023 | B | | MTState | | 0018,9020 | B | | MultibandAccelerationFactor | | aka `SMS`, `HyperBand` | B | | NumberOfAverages | | DICOM tag 0018,0083 | D | | ParallelAcquisitionTechnique | | DICOM tag 0018, 9078, aka `SENSE`, `GRAPPA` | B | | ParallelReductionFactorInPlane | | DICOM tag 0018,9069 | B | | ParallelReductionOutOfPlane | | DICOM tag 0018,9155 | D | | PartialFourierDirection | | DICOM tag 0018,9036 | B | | PercentPhaseFOV | | DICOM tag 0018,0094 | D | | PercentSampling | | DICOM tag 0018,0093 | D | | PhaseEncodingAxis | | When polarity unknown | B | | PhaseEncodingDirection | | When polarity known | B | | PhaseEncodingSteps | | DICOM tag 0018,0089 | D | | PixelBandwidth | Hz | DICOM tag 0018,0095 | D | | ReceiveCoilName | | DICOM tag 0018,1250 | B | | RepetitionTime | s | DICOM tag 0018,0080 | B | | RepetitionTimeExcitation | s | DICOM tag 0018, 0080 for some manufacturers | B | | RepetitionTimeInversion | s | | D | | SAR | | DICOM tag 0018,1316 | D | | SliceThickness | mm | [nb](http://dclunie.blogspot.com/2013/10/how-thick-am-i-sad-story-of-lonely-slice.html) | D | | SliceTiming | s | | B | | SpacingBetweenSlices | mm | [nb](http://dclunie.blogspot.com/2013/10/how-thick-am-i-sad-story-of-lonely-slice.html) | D | | SpoilingState | | 0018,9016 or 0018,0021 | B | | SpoilingType | | 0018,9016 | B | | TotalReadoutTime | s | [FSL definition](https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=fsl;d2c47aa4.1606) | B | | Units | | `Hz`,`rad` (field maps) | B | ### Modality Positron Emission Tomography PET fields extracted from [DICOM tags](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.9.2.html). See BIDS [BEP009 Positron Emission Tomography](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). Be aware that many of the fields required by BEP009 are not stored in DICOM or ECAT files. Since dcm2niix is limited to the information provided by the input files, it will generate JSON fields that reflect the meta data stored in the source images. The term ECAT in the comments suggests that values are defined by the [ECAT7](http://www.turkupetcentre.net/petanalysis/format_image_ecat.html) format. Therefore, these fields will not be populated for DICOM data. | Field | Unit | Comments | Defined By | |------------------------------|------|-----------------------------|------------| | Radiopharmaceutical | | DICOM tag 0018,0031 or ECAT | D | | RadionuclidePositronFraction | f | DICOM tag 0018,1076 | D | | RadionuclideTotalDose | MBq | DICOM tag 0018,1074 | D | | RadionuclideHalfLife | s | DICOM tag 0018,1075 | D | | DoseCalibrationFactor | | DICOM tag 0054,1322 | D | | IsotopeHalfLife | | ECAT | D | | Dosage | | ECAT | D | | ConvolutionKernel | | DICOM tag 0018,1210 | D | | Units | | DICOM tag 0054,1001 | D | | DecayCorrection | | DICOM tag 0054,1102 | D | | AttenuationCorrectionMethod | | DICOM tag 0054,1101 | D | | ReconstructionMethod | | DICOM tag 0054,1103 | D | | DecayFactor | | DICOM tag 0054,1321 | D | | FrameTimesStart | s | DICOM tags 0008,0022 | D | | FrameDuration | s | DICOM tag 0018,1242 | D | ## Manufacturer Fields These fields are specific to manufacturer (e.g. GE, Philips, Siemens). For further manufacturer details see: - [Canon (Toshiba)](https://github.com/rordenlab/dcm2niix/tree/master/Canon) - [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE) - [Philips](https://github.com/rordenlab/dcm2niix/tree/master/Philips) - [Siemens](https://github.com/rordenlab/dcm2niix/tree/master/Siemens) - [UIH](https://github.com/rordenlab/dcm2niix/tree/master/UIH) ### Manufacturer General Electric Data unique to [GE](https://github.com/rordenlab/dcm2niix/tree/master/GE). Determining these fields from a DICOM image requires decoding the compressed Protocol Data Block (0025,101B). | Field | Unit | Comments | Defined By | |--------------------------------|------|--------------------------|------------| | 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 | | NumberOfPointsPerArm | | DICOM tag 0027,1060 | D | | NumberOfArms | | DICOM tag 0027,1061 | D | | NumberOfExcitations | | DICOM tag 0027,1062 | D | | ParallelReductionFactorInPlane | | DICOM tag 0043,1083 | B | | PostLabelingDelay | s | DICOM tag 0018,0082 (TI) | B | ### Manufacturer Philips Data unique to Philips, including [custom intensity scaling](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3998685/) | Field | Unit | Comments | Defined By | |------------------------------------|------|----------------------------------|------------| | TriggerDelayTime | s | DICOM tag 0020,9153 or 0018,1060 | D | | PhilipsRWVSlope | | DICOM tag 0040,9225 | D | | PhilipsRWVIntercept | | DICOM tag 0040,9224 | D | | PhilipsRescaleSlope | | DICOM tag 0028,1053 | D | | PhilipsRescaleIntercept | | DICOM tag 0028,1052 | D | | PhilipsScaleSlope | | DICOM tag 2005,100E | D | | UsePhilipsFloatNotDisplayScaling | | dcm2niix option `-p y` or `-p n` | D | | PartialFourierEnabled | | DICOM tag 0018,9081, `YES` | D | | PhaseEncodingStepsNoPartialFourier | | DICOM tag 0018,9231 | D | | WaterFatShift | | DICOM tag 2001,1022 | D | ### Manufacturer Siemens (Arterial Spin Labeling) See BIDS [BEP005 Arterial Spin Labeling](https://bids-specification.readthedocs.io/en/v1.2.1/06-extensions.html). All these values are specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). Note that [many of the fields listed by BIDS](https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#_asljson-file ) do not have DICOM equivalents. | Field | Unit | Comments | Defined By | |---------------------------------|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| | ArterialSpinLabelingType | | `PASL` or `PCASL` | B | | LabelOffset | | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | D | | PostLabelDelay | s | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org), [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | D | | PostInversionDelay | s | 2D [FAIREST](https://pubmed.ncbi.nlm.nih.gov/11746944/) (`ep2d_fairest`) | D | | NumRFBlocks | | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | D | | RFGap | | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | D | | MeanGzx10 | | 2,3D pCASL [ep2d_pcasl, tgse_pcasl](http://www.loft-lab.org) | D | | PhiAdjust | | 2D pCASL [ep2d_pcasl](http://www.loft-lab.org) | D | | InversionTime | s | 2D,3D PASL product (`ep2d_pasl`, `tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | | BolusDuration | s | 2D,3D PASL product (`ep2d_pasl`, `tgse_pasl`), [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | | MeanGzx10 | | 3D pCASL [tgse_pcasl](http://www.loft-lab.org) | D | | TagRFFlipAngle | deg | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | D | | TagRFDuration | s | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | D | | TagRFSeparation | | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | D | | MeanTagGradient | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | | TagGradientAmplitude | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | | TagDuration | s | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | | MaximumT1Opt | | 2,3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`, `jw_tgse_VEPCASL`) | D | | InitialPostLabelDelay | s | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | | sWipMemBlockAdFree* | multiple values | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | | TagPlaneDThickness | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | | TagPlaneUlShape | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | | TagPlaneSPositionDTra | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | | TagPlaneSNormalDTra | | 2D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`to_ep2d_VEPCASL`) | D | | Tag* (if not explicitly listed) | One value for each | [Post-label delays](https://fsl.fmrib.ox.ac.uk/fslcourse/lectures/practicals/ASLpractical/index.html), 3D [VEPCASL](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3824178/) (`jw_tgse_VEPCASL`) | D | ### Manufacturer Siemens Magnetic Resonance Imaging (V*) Fields specific to Siemens V*-series (e.g. VB, VE) MRI systems (e.g. Verio, Trio, Skyra, Prisma) from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). | Field | Unit | Comments | Defined By | |--------------------------------|-------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| | EchoTime1 | s | For Fieldmaps created with two echo times | B | | EchoTime2 | s | For Fieldmaps created with two echo times | B | | PartialFourier | f | Partial fourier fraction, e.g. image with 6/8 pF will report `0.75`. Different tags depending on manufacturer | B | | Interpolation2D | | If present, slices interpolated within plane | D | | Interpolation3D | | If present, image interpolated in all spatial dimensions | D | | BaseResolution | integer | # of acquisition lines? | D | | ShimSetting | DAC for 1st order terms, mA for 2nd order terms | The 1st 3 values are the 1st order shims from sGRADSPEC.lOffset{X,Y,Z}. The next 5 are 2nd order shims from sGRADSPEC.alShimCurrent[0-4]. Converting to the first order terms to uT/m may be possible using the "gradient sensitivities". Converting the second order terms to uT/m^2 requires information not stored in the DICOM header, but they are fixed for a given shim hardware configuration. | D | | DiffusionScheme | | `Monopolar` or `Bipolar` | D | | DelayTime | s | Pause between EPI volumes, where TR is longer than required by TA (`sparse` imaging) | D | | TxRefAmp | V | | D | | ParallelReductionFactorInPlane | | DICOM tag 0021,1009 | B | | PhaseResolution | f | | D | | PhaseOversampling | | | D | | MultibandAccelerationFactor | | DICOM tag 0021,1009 | B | | VendorReportedEchoSpacing | | | B | | ReceiveCoilActiveElements | | Given as groups of channels, instead of individual ones, e.g. "HC1-7" for HeadNeck\_64. | B | | CoilString | | May or may not match `ReceiveCoilName` | D | | PulseSequenceDetails | | pulse sequence label, e.g. "%SiemensSeq%\\\\tgse\_pasl" | B | | FmriExternalInfo | | | D | | WipMemBlock | | | D | | AveragesDouble | | CSA `dAveragesDouble`, fractions possible, independent of DICOM `NumberOfAverages` (0018,0083) | D | | AccelFact3D | | 3D Acquisitions (Parallel Reduction Factor Across Slices) | D | | ProtocolName | | Check SeriesDescription - they might be switched around | D | | RefLinesPE | | # of reference lines in the phase encoding direction for acceleration (GRAPPA) | D | | ConsistencyInfo | | The more complete software version, e.g. VE11C or VE11E instead of just VE11. | D | | CoilCombinationMethod | | Detects `Sum of Squares` and `Adaptive Combine` | B | | MatrixCoilMode | | Detects `SENSE` and `GRAPPA` | B | | DwellTime | | DICOM tag 0019,1018 | B | | BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0019,1028 | D | | ImageOrientationText | | DICOM tag 0051,100E | D | ### 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 | ### Manufacturer UIH Fields specific to United Imaging Healthcare systems (e.g. uMR 770). | Field | Unit | Comments | Defined By | |--------------------------------|------|--------------------------|------------| | BandwidthPerPixelPhaseEncode | Hz | DICOM tag 0019,1028 | B | | ParallelReductionFactorInPlane | | DICOM tag 0065,100D | B | dcm2niix-1.0.20211006/BIDS/extract_units.py000077500000000000000000000053531412761503300177630ustar00rootroot00000000000000#!/usr/bin/env python """extract_units.py - extract BIDS/README.md's units as json Usage: extract_units.py [-e EXISTING -o OUT] [MD] extract_units.py (-h|--help|--version) Arguments: MD: A Markdown input file including tables with Field and Unit as the first two columns. [default: README.md] Options: -h --help Show this message and exit. --version Show version and exit. -e EXISTING --ex=EXISTING Extract units for all the fields, and only the fields in EXISTING, a BIDS file, and write the output to EXISTING.replace('.json', '_units.json') instead of stdout. -o OUT --out=OUT If given, save the output to this filename. (Overrides the implicit destination of -e.) """ from __future__ import print_function try: import json_tricks as json except ImportError: try: import simplejson as json except ImportError: # Not really compatible with json_tricks because it does not support # primitives=True import json import sys # Please use semantic versioning (API.feature.bugfix), http://semver.org/ __version__ = '0.0.0' def extract_units(mdfn): units = {} intable = False with open(mdfn) as md: for line in md: if line.startswith('|'): parts = [s.strip() for s in line.split('|')[1:-1]] if parts[:2] == ['Field', 'Unit']: intable = True elif intable and parts[1] and not parts[1].startswith('-'): units[parts[0]] = parts[1] else: intable = False return units def main(mdfn, existing=None, outfn=None): units = extract_units(mdfn) if existing: with open(existing) as f: # Can't count on having json_tricks. used = json.load(f) units = {k: v for k, v in units.items() if k in used} if not outfn: outfn = existing.replace('.json', '_units.json') outtext = json.dumps(units, indent=2, sort_keys=True, separators=(', ', ': ')) if outfn: with open(outfn, 'w') as outf: outf.write(outtext + "\n") else: print(outtext) return units if __name__ == '__main__': from docopt import docopt args = docopt(__doc__, version=__version__) # https://github.com/docopt/docopt/issues/214 has been open for # almost 7 years, so it looks like docopt isn't getting default # positional args. output = main(args['MD'] or 'README.md', args.get('--ex'), args.get('--out')) sys.exit(0) dcm2niix-1.0.20211006/CMakeLists.txt000066400000000000000000000002701412761503300165220ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.11) if(COMMAND CMAKE_POLICY) CMAKE_POLICY(SET CMP0003 NEW) endif() project(dcm2niix) include(${CMAKE_SOURCE_DIR}/SuperBuild/SuperBuild.cmake) dcm2niix-1.0.20211006/COMPILE.md000066400000000000000000000304711412761503300154020ustar00rootroot00000000000000## About The README.md file describes the typical compilation of the software, using the `make` command to build the software. This document describes advanced methods for compiling and tuning the software. Beyond the complexity of compiling the software, the only downside to adding optional modules is that the dcm2niix executable size will require a tiny bit more disk space. For example, on MacOS the stripped basic executable is 238kb, miniz (GZip support) adds 18kb, NanoJPEG (lossy JPEG support) adds 13kb, CharLS (JPEG-LS support) adds 271kb, and OpenJPEG (JPEG2000 support) adds 192kb. So with all these features installed the executable weighs in at 732kb. ## Choosing your compiler The text below generally describes how to build dcm2niix using the [GCC](https://gcc.gnu.org) compiler using the `g++` command. However, the code is portable and you can use different compilers. For [clang/llvm](https://clang.llvm.org) compile using `clang++`. If you have the [Intel C compiler](https://software.intel.com/en-us/c-compilers), you can substitute the `icc` command. The code is compatible with Microsoft's VS 2015 or later. For [Microsoft's C compiler](http://landinghub.visualstudio.com/visual-cpp-build-tools) you would use the `cl` command. In theory, the code should support other compilers, but this has not been tested. Be aware that if you do not have gcc installed the `g++` command may use a default to a compiler (e.g. clang). To check what compiler was used, run the dcm2niix software: it always reports the version and the compiler used for the build. ## Building the command line version without cmake You can also build the software without C-make. The easiest way to do this is to run the function "make" from the "console" folder. Note that this only creates the default version of dcm2niix, not the optional batch version described above. The make command simply calls the g++ compiler, and if you want you can tune this for your build. In essence, the make function simply calls ``` g++ -dead_strip -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG ``` The following sub-sections list how you can modify this basic recipe for your needs. ## Trouble Shooting Some [Centos/Redhat](https://github.com/rordenlab/dcm2niix/issues/137) may report "/usr/bin/ld: cannot find -lstdc++". This can be resolved by installing static versions of libstdc++: `yum install libstdc++-static`. To compile with debugging symbols, use ``` cmake -DUSE_OPENJPEG=ON -DCMAKE_CXX_FLAGS=-g .. && make ``` ##### ZLIB BUILD If we have zlib, we can use it (-lz) and disable [miniz](https://code.google.com/p/miniz/) (-myDisableMiniZ) ``` g++ -O3 -DmyDisableOpenJPEG -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -dead_strip -o dcm2niix -lz -DmyDisableMiniZ ``` ##### MINGW BUILD If you use the (obsolete) compiler MinGW on Windows you will want to include the rare libgcc libraries with your executable so others can use it. Here I also demonstrate the optional "-DmyDisableZLib" to remove zip support. ``` g++ -O3 -s -DmyDisableOpenJPEG -DmyDisableZLib -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -static-libgcc ``` ##### DISABLING CLASSIC JPEG DICOM images can be stored as either raw data or compressed using one of many formats as described by the [transfer syntaxes](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Transfer_Syntaxes_and_Compressed_Images). One of the compressed formats is the lossy classic JPEG format (which is separate from and predates the lossy JPEG 2000 format). This software comes with the [NanoJPEG](http://keyj.emphy.de/nanojpeg/) library to handle these images. However, you can use the `myDisableClassicJPEG` compiler switch to remove this dependency. The resulting executable will be smaller but will not be able to convert images stored with this format. ``` g++ -dead_strip -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableClassicJPEG -DmyDisableOpenJPEG ``` ##### USING LIBJPEG-TURBO TO DECODE CLASSIC JPEG By default, classic JPEG images will be decoded using the [compact NanoJPEG decoder](http://keyj.emphy.de/nanojpeg/). However, the compiler directive `myTurboJPEG` will create an executable based on the [libjpeg-turbo](http://www.libjpeg-turbo.org) library. This library is a faster decoder and is the standard for many Linux distributions. On the other hand, the lossy classic JPEG is rarely used for DICOM images, so this compilation has extra dependencies and can result in a larger executable size (for static builds). ``` g++ -dead_strip -O3 -I. main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -o dcm2niix -DmyDisableOpenJPEG -DmyTurboJPEG -I/opt/libjpeg-turbo/include /opt/libjpeg-turbo/lib/libturbojpeg.a ``` ##### JPEG-LS BUILD You can compile dcm2niix to convert DICOM images compressed with the [JPEG-LS](https://en.wikipedia.org/wiki/JPEG_2000) [transfer syntaxes 1.2.840.10008.1.2.4.80 and 1.2.840.10008.1.2.4.81](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Transfer_Syntaxes_and_Compressed_Images). Decoding this format is handled by the [CharLS library](https://github.com/team-charls/charls), which is included with dcm2niix in the `charls` folder. The included code was downloaded from the CharLS website on 6 June 2018. To enable support you will need to include the `myEnableJPEGLS` compiler flag as well as a few file sin the `charls` folder. Therefore, a minimal compile (with just JPEG-LS and without JPEG2000) should look like this: `g++ -I. -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp 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` Alternatively, you can decompress an image in JPEG-LS to an uncompressed DICOM using [gdcmconv](https://github.com/malaterre/GDCM)(e.g. `gdcmconv -w 3691459 3691459.dcm`). Or you can use gdcmconv compress a DICOM to JPEG-LS (e.g. `gdcmconv -L 3691459 3691459.dcm`). Alternatively, the DCMTK tool [dcmcjpls](https://support.dcmtk.org/docs/dcmcjpls.html) provides JPEG-LS support. ##### JPEG2000 BUILD You can compile dcm2niix to convert DICOM images compressed with the [JPEG2000](https://en.wikipedia.org/wiki/JPEG_2000) [transfer syntaxes 1.2.840.10008.1.2.4.90 and 1.2.840.10008.1.2.4.91](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Transfer_Syntaxes_and_Compressed_Images). This is optional, as JPEG2000 is very rare in DICOMs (usually only created by the proprietary DCMJP2K or OsiriX). Due to the challenges discussed below this is a poor choice for archiving DICOM data. Rather than support conversion with dcm2niix, a better solution would be to use DCMJP2K to do a DICOM-to-DICOM conversion to a more widely supported transfer syntax. Unfortunately, JPEG2000 saw poor adoption as a general image format. This situation is unlikely to change, as JPEG2000 only offered incremental benefits over the simpler classic JPEG, and is outperformed by the more recent [HEIF](https://en.wikipedia.org/wiki/High_Efficiency_Image_File_Format). This has implications for DICOM, as there is little active development on libraries to decode JPEG2000. Indeed, the two popular open-source libraries that decode JPEG2000 have serious limitations for processing these images. Some JPEG2000 DICOM images can not be decoded by the default compilation of OpenJPEG library after version [2.1.0](https://github.com/uclouvain/openjpeg/issues/962). On the other hand, the Jasper library does not handle lossy [16-bit](https://en.wikipedia.org/wiki/JPEG_2000) images with good precision. You can build dcm2niix with JPEG2000 decompression support using OpenJPEG 2.1.0. You will need to have the OpenJPEG library installed (use the package manager of your Linux distribution, Homebrew for macOS, or see [here](https://github.com/uclouvain/openjpeg/blob/master/INSTALL.md) if you want to build it yourself). If you want to use a more recent version of OpenJPEG, it must be custom-compiled with `-DOPJ_DISABLE_TPSOT_FIX` compiler flag. I suggest building static libraries where you would [download the code](https://github.com/uclouvain/openjpeg) and run ``` cmake -DBUILD_SHARED_LIBS:bool=off -DOPJ_DISABLE_TPSOT_FIX:bool=on . make sudo make install ``` You should then be able to run: ``` g++ -O3 -dead_strip -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -lopenjp2 ``` But in my experience this works best if you explicitly tell the software how to find the libraries, so your compile will probably look like one of these options: ``` #for MacOS g++ -O3 -dead_strip -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -I/usr/local/include/openjpeg-2.1 /usr/local/lib/libopenjp2.a ``` ``` #For older Linux g++ -O3 -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -o dcm2niix -I/usr/local/lib /usr/local/lib/libopenjp2.a ``` ``` #For modern Linux g++ -O3 -s -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -lpthread -o dcm2niix -I/usr/local/include/openjpeg-2.2 ~/openjpeg-master/build/bin/libopenjp2.a ``` If you want to build this with JPEG2000 decompression support using Jasper: You will need to have the Jasper (http://www.ece.uvic.ca/~frodo/jasper/) and libjpeg (http://www.ijg.org) libraries installed which for Linux users may be as easy as running 'sudo apt-get install libjasper-dev' (otherwise, see http://www.ece.uvic.ca/~frodo/jasper/#doc). You can then run: ``` g++ -O3 -DmyDisableOpenJPEG -DmyEnableJasper -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -s -o dcm2niix -ljasper -ljpeg ``` ##### VISUAL STUDIO BUILD This software can be compiled with VisualStudio 2015. This example assumes the compiler is in your path. ``` vcvarsall amd64 cl /EHsc main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp nii_foreign.cpp -DmyDisableOpenJPEG /o dcm2niix ``` ##### OSX BUILD WITH BOTH 32 AND 64-BIT SUPPORT Building command line version universal binary from OSX 64 bit system: This requires a C compiler. With a terminal, change directory to the 'conosle' folder and run the following: ``` g++ -O3 -DmyDisableOpenJPEG -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -dead_strip -arch i386 -o dcm2niix32 ``` ``` g++ -O3 -DmyDisableOpenJPEG -I. main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp jpg_0XC3.cpp ujpeg.cpp nii_foreign.cpp -dead_strip -o dcm2niix64 ``` ``` lipo -create dcm2niix32 dcm2niix64 -o dcm2niix ``` To validate that the resulting executable supports both architectures type ``` file ./dcm2niix ``` ##### CMAKE INSTALLATION While most of this page describes how to use `make` to compile dcm2niix, `cmake` can automatically aid complex builds. The [home page](https://github.com/rordenlab/dcm2niix) describes typical cmake options. The cmake command will attempt to pull additional code from git as needed for zlib, OpenJPEG etc. If you get the following error: ``` fatal: unable to connect to github.com: github.com[0: 140.82.121.4]: errno=Connection timed out ``` This suggests git is unable to connect using ssh. You have two options, first you can disable the cmake option USE_GIT_PROTOCOL (which is on by default). Alternatively, to use https instead using the following lines prior to running cmake: ``` git config --global url."https://github.com/".insteadOf git@github.com: git config --global url."https://".insteadOf git:// ``` Once the installation is completed, you can revert these changes: ``` git config --global --unset-all url.https://github.com/.insteadof git config --global --unset-all url.https://.insteadof ``` dcm2niix-1.0.20211006/CONTRIBUTE.md000066400000000000000000000043551412761503300157720ustar00rootroot00000000000000#### Introduction dcm2niix is a community effort Like the [Brain Imaging Data Structure](https://bids.neuroimaging.io/get_involved.html), which it supports, dcm2niix is developed by the community for the community and everybody can become a part of the community. The easiest way to contribute to dcm2niix is to ask questions you have by [generating Github issues](https://github.com/rordenlab/dcm2niix/issues) or [asking a question on the NITRC forum](https://www.nitrc.org/forum/?group_id=880). The code is open source, and you can share your improvements by [creating a pull request](https://github.com/rordenlab/dcm2niix/pulls). dcm2niix is a community project that has benefitted from many [contrbutors](https://github.com/rordenlab/dcm2niix/graphs/contributors). The INCF suggests indicating who is responsible for maintaining software for [stability and support](https://incf.org/incf-standards-review-criteria-v20). Therefore, below we indicate several active contributors and their primary domain of expertise. However, this list is not comprehensive, and it is noted that the project has been supported by contributions from many users. This list does not reflect magnitude of prior contributions, rather it is a non-exhaustive list of members who are actively maintaining the project. - Jon Clayden: (@jonclayden): [R Deployment](https://github.com/jonclayden/divest) - Ningfei Li : (@ningfei) CMake, AppVeyor, Travis - Yaroslav O. Halchenko: (@yarikoptic) Debian distributions - Taylor Hanayik (@hanayik): FSL integration - Michael Harms (@mharms): Advanced modalities - Roger D Newman-Norlund (@rogiedodgie): User support - Rob Reid (@captainnova): Clinical modalities - Chris Rorden (@neurolabusc): General development, user support #### Style Guide dcm2niix is written in C. Different programmers prefer different styles of indentation. Feel free to contribute code without being concerned about matching the style of the rest of the code. Once in a while, the code base will be automatically reformatted to make it appear more consistent for all users. This is done automatically with clang-format: ``` clang-format -i -style="{BasedOnStyle: LLVM, IndentWidth: 4, IndentCaseLabels: false, TabWidth: 4, UseTab: Always, ColumnLimit: 0}" *.cpp *.h ``` dcm2niix-1.0.20211006/Canon/000077500000000000000000000000001412761503300150215ustar00rootroot00000000000000dcm2niix-1.0.20211006/Canon/README.md000066400000000000000000000067031412761503300163060ustar00rootroot00000000000000## About dcm2niix can convert Canon (né Toshiba) DICOM format images to NIfTI. This page notes vendor specific conversion details. ## Avoid Classic DICOM 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). ## 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: ``` (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: ``` (0018,9087) FD 1500 # 8, 1 DiffusionBValue (0018,9089) FD 0.29387456178665161\-0.95365142822265625\-0.064700603485107422 # 24, 3 DiffusionGradientOrientation ``` ## Unknown Properties The [BIDS format](https://bids.neuroimaging.io) can record several sequence properties that are useful for processing MRI data. The DICOM headers created by Toshiba scanners are very clean and minimalistic, and do not report several of these advanced properties. Therefore, dcm2niix is unable to populate these properties of the JSON file. This reflects a limitation of the DICOM images, not of dcm2niix. - SliceTiming is not recorded. This can be useful for slice time correction. - Phase encoding polarity is not record. This is useful for undistortion with [TOPUP](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup). ## Sample Datasets - [Toshiba Aquilion](https://www.aliza-dicom-viewer.com/download/datasets). - [Toshiba 3T Galan Diffusion Dataset](https://github.com/neurolabusc/dcm_qa_toshiba). - [Canon 3T Galan Diffusion Dataset](https://github.com/neurolabusc/dcm_qa_canon). dcm2niix-1.0.20211006/Dockerfile000066400000000000000000000010561412761503300157570ustar00rootroot00000000000000FROM ubuntu:trusty MAINTAINER # feel free to change/adopt # Install Dependencies RUN apt-get update && apt-get upgrade -y && \ apt-get install -y build-essential pkg-config cmake git pigz && \ apt-get clean -y && apt-get autoclean -y && apt-get autoremove -y # Get dcm2niix from github and compile RUN cd /tmp && \ git clone https://github.com/rordenlab/dcm2niix.git && \ cd dcm2niix && mkdir build && cd build && \ cmake -DBATCH_VERSION=ON -DUSE_OPENJPEG=ON .. && \ make && make install ENTRYPOINT ["/usr/local/bin/dcm2niix"] dcm2niix-1.0.20211006/ERRORS.md000066400000000000000000000032421412761503300153220ustar00rootroot00000000000000## About dcm2niix will return an exit status to allow scripts to determine if a conversion was successful. Following Unix convention, the value value 0 is used for [EXIT_SUCCESS](https://www.gnu.org/software/libc/manual/html_node/Exit-Status.html). In contrast, any non-zero result suggests an error. In the Unix terminal and with shell scripts, the variable `$?` reports the most recent exit status. Here is an example of a successful conversion: ``` >dcm2niix ~/dcm Chris Rorden's dcm2niiX version v1.0.20200331 Clang11.0.0 (64-bit MacOS) Found 2 DICOM file(s) Convert 2 DICOM as ~/dcm/dcm_ax_asc_6 (64x64x35x2) Conversion required 0.015866 seconds (0.012676 for core code). >echo $? 0 ``` Below is a list of possible return values from running dcm2niix. | Exit Status | Meaning | | ----------- | ----------------------------------------------------------- | | 0 | Success | | 1 | Unspecified error (see console output for details) | | 2 | No DICOM images found in input folder | | 3 | Exit from report version (result of `dcm2niix -v`) | | 4 | Corrupt DICOM file (Irrecoverable error during conversion) | | 5 | Input folder invalid | | 6 | Output folder invalid | | 7 | Unable to write to output folder (check file permissions) | | 8 | Converted some but not all of the input DICOMs | | 9 | Unable to rename files (result of `dcm2niix -r y ~/in`) | dcm2niix-1.0.20211006/FILENAMING.md000066400000000000000000000240041412761503300157160ustar00rootroot00000000000000## About DICOM files tend to have bizarre file names, for example based on the instance UID, e.g. `MR.1.3.12.2.1107.5.2.32.35131.2014031013003871821190579`. In addition, DICOM images are often 2D slices or 3D volumes that we will combine into a single unified NIfTI file. On the other hand, some enhanced DICOM images save different reconstructions (e.g. phase and magnitude) of the same image that we will want to save as separate NIfTI files. Therefore, dcm2niix attempts to provide a sensible file naming scheme. ## Basics You request the output file name with the `-f` argument. For example, consider you convert files with `dcm2niix -f %s_%p`: in this case an image from series 3 with the protocol name `T1` will be saved as `3_T1.nii`. Here are the available parameters for file names: - %a=antenna (coil) name (from Siemens 0051,100F) - %b=basename (file name of first DICOM) - %c=comments (from 0020,4000) - %d=description (from 0008,103E) - %e=echo number (from 0018,0086) - %f=folder name (name of folder containing first DICOM) - %g=accession number (0008,0050) - %i=ID of patient (from 0010,0020) - %j=series instance UID (from 0020,000E) - %k=study instance UID (from 0020,000D) - %l=local procedure step description (from 0040,0254) - %m=manufacturer short name (from 0008,0070: GE, Ph, Si, To, UI, NA) - %n=name of patient (from 0010,0010) - %o=mediaObjectInstanceUID (0002,0003)* - %p=protocol name (from 0018,1030). If 0018,1030 is empty, the SequenceName (0018,0024) is used. - %r=instance number (from 0020,0013)* - %s=series number (from 0020,0011) - %t=time of study (from 0008,0020 and 0008,0030) - %u=acquisition number (from 0020,0012). This option is recommended for [DICOM renaming](RENAMING.md). It is [not recommended](https://github.com/rordenlab/dcm2niix/issues/526) for DICOM to NIfTI conversion (as [BIDS compatible](https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html) 4D datasets collapses multiple acquisitions to a single file). - %v=vendor long name (from 0008,0070: GE, Philips, Siemens, Toshiba, UIH, NA) - %x=study ID (from 0020,0010) - %y=youth in series: GE RawDataRunNumber ([0019,10A2](https://github.com/rordenlab/dcm2niix/issues/359)) else TemporalPosition ([0020,0100](https://github.com/rordenlab/dcm2niix/issues/357))* - %z=sequence name (from 0018,0024) * Attributes listed above with an asterisk (*) are likely to vary within a series, and are typically not useful for DICOM to NIfTI conversion (where all images from a series are stacked together). These attributes can be useful for [renaming](RENAMING.md) DICOM images ## File Name Post-fixes: Image Disambiguation In general dcm2niix creates images with 3D dimensions, or 4 dimensions when the 4th dimension is time (fMRI) or gradient number (DWI). However, DICOM images can include additional dimensions, e.g. a multiple-echo sequence would generate separate images for each echo. By default dcm2niix will use the following extensions to the file names in order to disambiguate additional dimensions from the same series: - _cNx.._cNz where C* refers to the coil name (typically only seen for uncombined data, where a separate image is generated for each antenna) - _e1..eN echo number for multi-echo sequences - _Eq is commonly seen in [CT scans](https://github.com/neurolabusc/dcm_qa_ct). For example, CT scans of the brain often have many slices closely packed near the brain stem and only a few slices spread far apart near the top of the head. Variable between-slice spacing is rarer in MRI, and if you see this from a MRI sequence you should ensure that [all of the acquired slices have been provided to dcm2niix](https://neurostars.org/t/field-mapping-siemens-scanners-dcm2niix-output-2-bids/2075/7). NIfTI assumes all 2D slices that form a 3D stack are equidistant. Therefore, dcm2niix reslices the input data to generate an equidistant volume. - _ph phase map - _iN appended image number for non-parallel slices - _imaginary imaginary component of complex image - _MoCo is appended to the ProtocolName if Image Type (0008,0008) includes the term 'MOCO'. This helps disambiguate Siemens fMRI runs where both motion corrected and raw data is stored for a single session. - _real real component of complex image - _phMag rare case where phase and magnitude are saved as the 4th dimension - _t If the trigger delay time (0020,9153) or trigger time (0018,1060) is non-zero, it will be recorded in the file name. For example, the files "T1_t178.nii" and "T1_t511" suggests that the T1 scan was acquired with two cardiac trigger delays (178 and 511ms after the last R-peak). - _Tilt is specific to [CT scans](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Computed_Tomography_.28CT.2C_CAT.29). These scans can be acquired with a gantry tilt that causes a skew that can not be stored in a NIfTI qForm. Therefore, the slices are resampled to remove the effect of tilt. Some post-fixes are specific to Philips DICOMs - _ADC Philips specific case. A DWI image where derived isotropic, ADC or trace volume was appended to the series. Since this image will disrupt subsequent processing, and because subsequent processing (dwidenoise, topup, eddy) will yield better derived images, dcm2niix will also create an additional image without this volume. Therefore, the _ADC file should typically be discarded. If you want dcm2niix to discard these useless derived images, use the ignore feature ('-i y'). - _fieldmaphz unwrapped B0 field map (in Hz) generated by a Philips scanner. Suggests Image Type (0008,0008) includes the terms 'B0' and 'MAP'. - _Raw Philips XX_* DICOMs (Raw Data Storage). - _PS Philips PS_* DICOMs (Grayscale Softcopy Presentation State). If you do not want post-fixes, run dcm2niix in the terse mode (`--terse`). In this mode, most post-fixes will be omitted. Beware that this mode can have name clashes, and images from a series may over write each other. ## Overlays DICOM images can have up to [16](https://www.medicalconnections.co.uk/kb/Number-Of-Overlays-In-Image/) binary (black or white) overlays as described by the [Overlay Plane Module](http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.9.html). dcm2niix will save these regions of interest with the post-fix "_ROIn" where N is the overlay number (1..16). ## File Name Conflicts dcm2niix will attempt to write your image using the naming scheme you specify with the '-f' parameter. However, if an image already exists with the specified output name, dcm2niix will append a letter (e.g. 'a') to the end of a file name to avoid overwriting existing images. Consider a situation where dcm2niix is run with '-f %t'. This will name images based on the study time. If a single study has multiple series (for example, a T1 sequence and a fMRI scan, the reulting file names will conflict with each other. In order to avoid overwriting images, dcm2niix will resort to adding the post fix 'a', 'b', etc. There are a few solutions to avoiding these situations. You may want to consider using both of these: - Make sure you specify a naming scheme that can discriminate between your images. For example '-f %t' will not disambiguate different series acquired in the same session. However, '-f %t_%s' will discriminate between series. - Localizer (scout) images are the first scans acquired for any scanning session, and are used to plan the location for subsequent images. Localizers are not used in subsequent analyses (due to resolution, artefacts, etc). Localizers are often acquired with three orthogonal image planes (sagittal, coronal and axial). The NIfTI format requires that all slices in a volume are co-planar, so these localizers will generate naming conflicts. The solution is to use '-i y' which will ignore (not convert) localizers (it will also ignore derived images and 2D slices). This command helps exclude images that are not required for subsequent analyses. - Be aware that if you run dcm2niix twice with the same parameters on the same data, you will encounter file naming conflicts. ## Special Characters [Some characters are not permitted](https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names) in file names. The following characters will be replaced with underscorces (`_`). Note that the forbidden characters vary between operating systems (Linux only forbids the forward slash, MacOS forbids forward slash and colon, while Windows forbids any of the characters listed below). To ensure that files can be easily copied between file systems, [dcm2niix restricts file names to characters allowed by Windows](https://github.com/rordenlab/dcm2niix/issues/237). While technically legal in all filesystems, the semicolon can wreak havoc in [Windows](https://stackoverflow.com/questions/3869594/semi-colons-in-windows-filenames) and [Linux](https://forums.plex.tv/t/linux-hates-semicolons-in-file-names/49098/2). ### List of Forbidden Characters ``` < (less than) > (greater than) : (colon - sometimes works, but is actually NTFS Alternate Data Streams) " (double quote) / (forward slash) \ (backslash) | (vertical bar or pipe) ? (question mark) * (asterisk) ; (semicolon) ``` [Control characters](https://en.wikipedia.org/wiki/ASCII#Control_characters) like backspace and tab are also forbidden. Be warned that dcm2niix will copy all allowed characters verbatim, which can cause problems for some other tools. Consider this [sample dataset](https://github.com/neurolabusc/dcm_qa_nih/tree/master/In/20180918GE/mr_0004) where the DICOM Protocol Name (0018,1030) is 'Axial_EPI-FMRI_(Interleaved_I_to_S)'. The parentheses ("round brackets") may cause other tools issues. Consider converting this series with the command 'dcm2niix -f %s_%p ~/DICOM' to create the file '4_Axial_EPI-FMRI_(Interleaved_I_to_S).nii'.If you now run the command 'fslhd 4_Axial_EPI-FMRI_(Interleaved_I_to_S).nii' you will get the error '-bash: syntax error near unexpected token `(''. Therefore, it is often a good idea to use double quotes to specify the names of files. In this example 'fslhd "4_Axial_EPI-FMRI_(Interleaved_I_to_S).nii"' will work correctly. dcm2niix-1.0.20211006/GE/000077500000000000000000000000001412761503300142565ustar00rootroot00000000000000dcm2niix-1.0.20211006/GE/README.md000066400000000000000000000410131412761503300155340ustar00rootroot00000000000000## About dcm2niix attempts to convert GE DICOM format images to NIfTI. The current generation DICOM files generated by GE equipment is quite impoverished relative to other vendors. Therefore, the amount of information dcm2niix is able to extract is relatively limited. Hopefully, in the future GE will provide more details that are critical for brain scientists. ## Arterial Spin Labeling As noted by David Shin (GE), the GE product ASL sequence sequence produces two 3D volumes. The Perfusion Weighted (PW) first pass acquires ASL tag/control spirals in interleaved fashion over many volumes (TRs), and does the subtraction and averaging in k-space. Therefore, the result is a single 3D volume. The second pass acquires a Proton Density (PD) reference volume. The PW and PD images can be combined offline to generate quantified Cerebral Blood Flow(CBF) map. [Stanford](https://cni.stanford.edu/wiki/Data_Processing) includes useful notes on this sequence. Note that Number of Excitations (NEX) is needed for CBF quantification. The sequence specific details are listed in the table. | DICOM Tag | Pass 1 (PW) | Pass 2 (PD) | |-----------|------------------------------------|------------------------------------| | 0043,10A3 | PSEUDOCONTINUOUS | CONTINUOUS | | 0043,10A4 | 3D pulsed continuous ASL technique | 3D continuous ASL technique | | 0043,10A5 | Label Duration (ms) | Label Duration (ms) | | 0018,0082 | Post Label Delay (ms) | NA | | 0027,1060 | Number of Points per Arm | Number of Points per Arm | | 0027,1061 | Number of Arms | Number of Arms | | 0027,1062 | Number of Excitations | Number of Excitations | The GE product ASL sequence is optimized for clinical diagnosis with emphasis on 3D acquisition and multi-shot interleaving for high spatial resolution. For 4D type acquisition (time resolved single-shot acquisition), GE researchers can leverage the [multi-band ASL/BOLD sequence](https://journals.plos.org/plosone/article/authors?id=10.1371/journal.pone.0190427). ## Diffusion Tensor Notes The [NA-MIC Wiki](https://www.na-mic.org/wiki/NAMIC_Wiki:DTI:DICOM_for_DWI_and_DTI#Private_vendor:_GE) provides a nice description of the GE diffusion tags. In brief, the B-value is stored as the first element in the array of 0043,1039. The DICOM elements 0019,10bb, 0019,10bc and 0019,10bd provide the gradient direction relative to the frequency, phase and slice. As noted by Jaemin Shin (GE), the GE convention for reported diffusion gradient direction has always been in “MR physics” logical coordinate, i.e Freq (X), Phase (Y), Slice (Z). Note that this is neither “with reference to the scanner bore” (like Siemens or Philips) nor “with reference to the imaging plane” (as expected by FSL tools). This is the main source of confusion. This explains why the dcm2niix function geCorrectBvecs() checks whether the DICOM tag In-plane Phase Encoding Direction (0018,1312) is 'ROW' or 'COL'. In addition, it will generate the warning 'reorienting for ROW phase-encoding untested' if you acquire DTI data with the phase encoding in the ROW direction. If you do test this feature, please report your findings as a Github issue. Assuming you have COL phase encoding, dcm2niix should provide [FSL format](http://justinblaber.org/brief-introduction-to-dwmri/) [bvec files](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FDT/FAQ#What_conventions_do_the_bvecs_use.3F). ## Slice Timing Knowing the relative timing of the acquisition for each 2D slice in a 3D volume is useful for [slice time correction](https://www.mccauslandcenter.sc.edu/crnl/tools/stc) of both fMRI and DTI data. Unfortunately, current GE software does not provide a consistent way to record this. [Some sequences](https://afni.nimh.nih.gov/afni/community/board/read.php?1,154006) encode the RTIA Timer (0021,105E) element. For example, [this DV24 dataset](https://github.com/nikadon/cc-dcm2bids-wrapper/tree/master/dicom-qa-examples/ge-mr750-slice-timing) includes timing data, while [this DV26 dataset does not](https://github.com/neurolabusc/dcm_qa_nih). Be aware that different versions of GE software appear to use different units for 0021,105E. The DV24 example is reported in seconds, while [14.0 uses 1/10000 seconds](https://github.com/rordenlab/dcm2niix/issues/286). An example of the latter format can be found [here](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Archival_MRI). Even with the sequences that do encode the RTIA Timer, there is some debate regarding the accuracy of this element. In the example listed, the slice times are clearly wrong in the first volume. Therefore, dcm2niix always estimates slice times based on the 2nd volume in a time series. In general, fMRI acquired using GE product sequence (PSD) “epi” with the multiphase option will store slice timing in the Trigger Time (DICOM 0018,1060) element. In contrast, the popular PSD “epiRT” (BrainWave RT, fMRI/DTI package provided by Medical Numerics) does not save this tag (though in some cases it saves the RTIA Timer). Examples are [available](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Slice_timing_correction) for both the “epiRT” and “epi” sequences. The “epiRT” sequences also allow the user to specify the `Group Delay`, which is 0 msec by default. Increasing this value will create a pause at the end of each volume, and this value is recorded in the DICOM header(as 0043,107C, reported in seconds). This option can be used for sparse designs, where one wants a pause after each value. Be aware that the `RepetitionTime` (0018,0080) reported in this header omits the group delay. So a study with a TR of 2000ms and a Group Delay of 55ms will report the values (0018,0080 = 2000, 0043,107C = 0.055), while the actual sampling rate will be 2055ms. This is unintuitive, the TR with respect to tissue contrast is 2055ms, not the reported 2000. If neither Trigger Time (DICOM 0018,1060) or RTIA Timer (0021,105E) store slice timing information, a final option is to decode the GE Protocol Data Block as described below. At best, this block only reports whether the acquisition was interleaved or sequential. As long as one assumes the acquisition was continuous (with no temporal gap between volumes, e.g. sparse images) on can use this value, the number of slices in the volume and the repetition time to infer slice times. Due to these various methods, recent releases of dcm2niix read the Protocol Data Block to determine the multi-bandFactor, number of samples, sampling rate (TR), interleaved or sequential slices, group delay and software version. This information is used to estimate the slice timing directly, without requiring the previously described tags. The [dcm_qa_ge](https://github.com/neurolabusc/dcm_qa_ge) provides validation images and more details. Note that this slice timing approach does not support GE's diffusion weighted imaging sequences. ## User Define Data GE (0043,102A) This private element of the DICOM header is used to determine the phase encoding polarity. Specifically, we need to know the "Ky traversal direction" (top-down, or bottom up) and the phase encoding polarity. Unfortunately, this data is stored in a complicated, proprietary structure, that has changed with different releases of GE software. [Click here to see the definition for this structure](https://github.com/ScottHaileRobertson/GE-MRI-Tools/blob/master/GePackage/%2BGE/%2BPfile/%2BHeader/%2BRDB15/rdbm.h). ## Multi-Echo EPI Sequences Current GE software (DV26.0_R03_1831.b) running research multi-echo sequences create invalid DICOM images. The required public [EchoTime (0018,0081)](https://dicom.innolitics.com/ciods/mr-image/mr-image/00180081) attribute lists the shortest echo time for the series, rather than the actual echo time for the given DICOM image. The public tag [EchoNumber (0018,0086)](https://dicom.innolitics.com/ciods/mr-image/mr-image/00180086) reports `1` for all echoes. These limitations in GE's DICOM images disrupt dcm2niix's image conversion. Hopefully future product sequences will generate valid DICOM data. In the meantime, [issue 359](https://github.com/rordenlab/dcm2niix/issues/359) provides a kludge for image conversion. ## Image Interpolation Some sequences allow the user to interpolate images in plane (e.g. saving a 2D 64x64 EPI image as 128x128) or between slices (e.g. saving a 126 slice T1-weighted image as 252 images). The resulting files require much more disk space, add no new information, are slower to process and can [disrupt some tools](https://mrtrix.readthedocs.io/en/latest/reference/commands/mrdegibbs.html). Users are strongly discouraged from interpolating raw data. However, dcm2niix should correctly detect this interpolation, resolving apparent discrepancies between tags (0020,1002; 0021,104F; 0054,0081). [Issue 355](https://github.com/rordenlab/dcm2niix/issues/355) provides details. ## Total Readout Time One often wants to determine [echo spacing, bandwidth](https://support.brainvoyager.com/brainvoyager/functional-analysis-preparation/29-pre-processing/78-epi-distortion-correction-echo-spacing-and-bandwidth) and total read-out time for EPI data so they can be undistorted. Specifically, we are interested in FSL's definition of total read-out time, which may differ from the actual read-out time. FSL expects “the time from the middle of the first echo to the middle of the last echo, as it would have been had partial k-space not been used”. So total read-out time is influenced by parallel acceleration factor, bandwidth, number of EPI lines, but not partial Fourier. For GE data we can use the Acquisition Matrix (0018,1310) in the phase-encoding direction, the in-plane acceleration ASSET R factor (the reciprocal of this is stored as the first element of 0043,1083) and the Effective Echo Spacing (0043,102C). While GE does not tell us the [partial Fourier fraction](https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html), is does reveal if it is present with the ScanOptions (0018,1022) reporting [PFF](http://dicomlookup.com/lookup.asp?sw=Ttable&q=C.8-4) (in my experience, GE does not populate [(0018,9081)](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,9081))). While partial Fourier does not impact FSL's totalReadoutTime directly, it can interact with the number of lines acquired when combined with parallel imaging (the `Round_factor` 2 (Full Fourier) or 4 (Partial Fourier)). The formula for FSL's definition of TotalReadoutTime (in seconds) is: ``` TotalReadoutTime = ( ( ceil ((1/Round_factor) * PE_AcquisitionMatrix / Asset_R_factor ) * Round_factor) - 1 ] * EchoSpacing * 0.000001 EffectiveEchoSpacing = TotalReadoutTime/ (reconMatrixPE - 1) ``` Consider an example: ``` (0018,1310) US 128\0\0\128 # 8, 4 AcquisitionMatrix (0018,0022) CS [SAT_GEMS\MP_GEMS\EPI_GEMS\ACC_GEMS\PFF\FS] # 42, 6 ScanOptions (0043,102c) SS 636 # 2, 1 EchoSpacing (0043,1083) DS [0.666667\1] # 10, 2 Acceleration ``` From this we can derive: ``` ASSET= 1.5 PE_AcquisitionMatrix= 128 EchoSpacing= 636 Round_Factor= 4 TotalReadoutTime= 0.055332 ``` ## Image Acceleration The BIDS [ParallelReductionFactorInPlane](https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#in-plane-spatial-encoding) can be determined from the reciprocal of the first element of the private tag `Asset R Factors` (0043,1083). The first value is for acceleration in the phase direction equivalent to the public tag 0018,9069), the second for the slice direction (equivalent to the public tag 0018,9155). For 2D EPI scans, the second value will always be 1, whereas for 3D acquisitions [acceleration can take place in both the phase-encoding and the slice-encoding directions](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4459721/). For example, a scan using a acceleration factor of 1.5 would be reported as `(0043,1083) DS [0.666667\1]`. The BIDS [MultibandAccelerationFactor](https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#rf-contrast) can be determined from the private tag `Multiband Parameters` (0043,10B6). This is an array with at least three values, the first is the Multiband (aka HyperBand) factor, the second is the Slice FOV Shift Factor, and the final is the Calibration method. For example, a scan using a multiband factor of 2 could be reported as `(0043,10b6) LO [2\4\19\\\\]`. ## Missing Information While GE DICOMs will report if the image uses partial Fourier, it will not reveal the partial Fourier fraction. ## Phase-Encoding Polarity All EPI scans have spatial distortion, particularly those with longer readout times. Tools like [FSL topup](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup/TopupUsersGuide) can leverage data where two spin-echo images are acquired that are identical except for using opposite phase-encoding polarity (e.g. one uses A>P, the other P>A). Each image is distorted with the same magnitude, but in the opposite direction. GE's Rx27 software version and later populate the [Rectilinear Phase Encode Reordering (0018,9034)](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,9034)) tag which for EPI is set to either LINEAR or REVERSE_LINEAR. ## GE Protocol Data Block In addition to the public DICOM tags, previous versions of dcm2niix attempted to decode the proprietary GE Protocol Data Block (0025,101B). This is essentially a [GZip format](http://www.onicos.com/staff/iz/formats/gzip.html) file embedded inside the DICOM header. Unfortunately, this data seems to be [unreliable](https://github.com/rordenlab/dcm2niix/issues/163) and therefore this strategy is not used anymore. The notes below regarding the usage of this data block are provided for historical purposes. - The VIEWORDER tag is used to set the polarity of the BIDS tag PhaseEncodingDirection, with VIEWORDER of 1 suggesting bottom up phase encoding. Unfortunately, users can separately reverse the phase encoding direction making this tag unreliable. Thankfully, recent scanners provide 0018,9034. - The SLICEORDER tag could be used to set the SliceTiming for the BIDS tag PhaseEncodingDirection, with a SLICEORDER of 1 suggesting interleaved acquisition. - There are reports that newer versions of GE equipement (e.g. DISCOVERY MR750 / 24\MX\MR Software release:DV24.0_R01_1344.a) are now storing an [XML](https://groups.google.com/forum/#!msg/comp.protocols.dicom/mxnCkv8A-i4/W_uc6SxLwHQJ) file within the Protocol Data Block (compressed). In theory this might also provide useful information. ## Complex Image Component Most vendors store the complex image component (magnitude, phase, real or imaginary) in the [Complex Image Component (0008,9208)](http://dicom.nema.org/medical/dicom/2016e/output/chtml/part03/sect_C.8.13.3.html) tag or in the [Image Type (0008,0008)](http://incenter.medical.philips.com/doclib/enc/fetch/2000/4504/577242/577256/588723/5144873/5144488/5144982/DICOM_Conformance_Statement_Intera_R7%2c_R8_and_R9.pdf%3fnodeid%3d5147977%26vernum%3d-2) tag. GE stores this information as a signed short of the Private Image Type(0043,102F) tag. The values 0, 1, 2, 3 correspond to magnitude, phase, real, and imaginary (respectively). ## Detecting Anatomical Localizers Anatomical localizers (e.g. scout images) are quick-and-dirty scans used to position subsequent slower but higher quality images. These scans are typically discarded in subsequent analyses. The dcm2niix argument `-i y` will ignore these scans. For GE, these sequences are detected based on the SeriesPlane (0019,1017) tag, which is of type [SS](http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html) and can report the values 2 (Axial), 4 (Sagittal), 8 (Coronal), 16 (Oblique) or 256 (3plane). The 3plane value is consistent with an anatomical localizer. ## Sample Datasets - [A validation dataset for dcm2niix commits](https://github.com/neurolabusc/dcm_qa_nih). - [Slice Timing and Phase Encoding examples](https://github.com/jannikadon/cc-dcm2bids-wrapper/tree/main/dicom-qa-examples) - [Slice timing validation](https://github.com/neurolabusc/dcm_qa_stc) for different varieties of GE EPI sequences. - [Examples of phase encoding polarity, slice timing and diffusion gradients](https://github.com/neurolabusc/dcm_qa_ge). - The dcm2niix [wiki](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage) includes examples of diffusion data, slice timing, and other variations. dcm2niix-1.0.20211006/Mediso/000077500000000000000000000000001412761503300152035ustar00rootroot00000000000000dcm2niix-1.0.20211006/Mediso/README.md000066400000000000000000000011311412761503300164560ustar00rootroot00000000000000## About dcm2niix attempts to convert all DICOM images to NIfTI. However, different manufacturers handle the format differently. [Mediso](https://mediso.com/usa/en/) is a manufacturer that supports preclinical tools for PET, MRI, SPECT and CT. In general, this manufacturer uses public tags and generates simple DICOM headers. While these files do not contain the rich meta data available from other manufacturers, they are simple to parse. ## Sample Datasets - The [ftp://medical.nema.org/MEDICAL](ftp://medical.nema.org/MEDICAL) server provides reference images in Dicom/Datasets/WG30/Mediso dcm2niix-1.0.20211006/PARREC/000077500000000000000000000000001412761503300147375ustar00rootroot00000000000000dcm2niix-1.0.20211006/PARREC/README.md000066400000000000000000000073701412761503300162250ustar00rootroot00000000000000## About dcm2niix attempts to convert Philips PAR/REC format images to NIfTI. While this format remains popular with users, it is slowly being superseded by Philips enhanced DICOM format, an XML/REC format as well as the direct NIfTI export. Note that dcm2niix does not support the XML/REC format. According to [Matthew Clemence](https://www.nitrc.org/forum/forum.php?thread_id=9319&forum_id=4703) DICOM (classic and enhanced) and XML/REC are supported in the base product, NIFTI forms part of a Neuroscience commercial option from release 5 onwards. PAR/REC requires a research agreement to obtain. For the two formats XML/REC and PAR/REC, the "REC" part is identical but instead of a plain text file of the "par" format, the same information is now available as an XML file. This decision has been taken to allow the information to be more easily extended as the PAR file was getting increasingly limited. ## Detecting, Reporting and Fixing the V4 Image offcentre Bug The PAR header contains a field 'image offcentre (ap,fh,rl in mm )' that we use to detect the spatial position of slices (e.g. for an axial scan is the first slice inferior or superior to the final slice). However, it appears that in some V4 images the values in these columns are actually stored in the order "rl,ap,fh". This has never been reported in V3, V4.1 and V4.2 images. A nice example of this is the ['philips_1_5T_intera' dataset provided with Rosetta Bit](https://www.nitrc.org/projects/rosetta/)(actually from a 3T MRI). This sample includes both DICOM and V4 PAR/REC data. Note the 'Off Centre midslice(ap,fh,rl) [mm]' field gives the volume center in the correct order. However, the subsequent 'image offcentre' fields are swizzled. The latest versions of dcm2niix will detect, report and correct this error. If you do see an error like the one below, please report it on Github as an issue, so we can have a better understanding of its prevalence. ``` Order of 'image offcentre (ap,fh,rl in mm )' appears incorrect (assuming rl,ap,fh) err[ap,fh,rl]= 12.7279 (-7.05 -4.55 2.95) err[rl,ap,fh]= 0.0223597 (-4.55 2.95 -7.05) ``` ## File naming You can specify the preferred name for your output file with dcm2niix. You do this by passing an argument string (default is '%f_%p_%t_%s'). For example, If you run "dcm2niix -f %p_%s ~/myParDir" the output name will be based on the protocol name and the series name. Here is a list of the possible argument you can use with Philips, and the tag from the header that is used for this value: - %c: comment : "Examination name" - %d: description : "Series Type" - %e: echo number : "echo number" Column - %f: folder name - %i: ID : "Technique" - %m: manufacturer : always 'Ph' for Philips - %n: name : "Patient name" - %p: protocol name : "Protocol name" - %s: series number : "Acquisition nr" - %t: time : "Examination date/time" - %u: acquisition number : "Acquisition nr" - %v: vendor : always 'Philips' for Philips Note that for Philips (unlike DICOM) the For PAR/REC the acquisition (%u) and series (%s) numbers are the same. Also note that there are several arguments that might be useful with DICOM but will always be unused for PAR/REC: - %a: antenna - %j: seriesInstanceUID - %k: studyInstanceUID - %x: study ID - %z: sequence name ## dcm2niix Limitations Be aware that dcm2niix assumes that the data is stored in complete 3D volumes. It will not convert datasets where the scan is interrupted mid-volume (e.g. where the number of 2D slices is not divisible by the number of slices in a volume). This can occur if the user aborts a sequence part way through acquisition. If dcm2niix detects this situation it will suggest you use [dicm2nii](https://www.mathworks.com/matlabcentral/fileexchange/42997-dicom-to-nifti-converter--nifti-tool-and-viewer) which can handle these files. dcm2niix-1.0.20211006/Philips/000077500000000000000000000000001412761503300153735ustar00rootroot00000000000000dcm2niix-1.0.20211006/Philips/README.md000066400000000000000000000572701412761503300166650ustar00rootroot00000000000000## About dcm2niix attempts to convert all DICOM images to NIfTI. The Philips enhanced DICOM images are elegantly able to save all images from a series as a single file. However, this format is necessarily complex. The usage of this format has evolved over time, and can become further complicated when DICOM are modified by DICOM tools (for example, anonymization, mangled by a [dcm4che/AGFA PACS](https://github.com/neurolabusc/dcm_qa_agfa), conversion of explicit VRs to implicit VRs, etc.). This web page describes some of the strategies handle these images. However, users should be vigilant when handling these datasets. If you encounter problems using dcm2niix you can explore [alternative DICOM to NIfTI converters](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Alternatives) or [report an issue](https://github.com/rordenlab/dcm2niix). ## Image Patient Position The Image Patient Position (0020,0032) tag is required to determine the position of the slices with respect to each other (and with respect to the scanner bore). Philips scans often report two conflicting IPPs for each single slice: with one stored in the private sequence (SQ) 2005,140F while the other is in the public sequence. This is unusual, but is [legal](ftp://medical.nema.org/medical/dicom/final/cp758_ft.pdf). In practice, this complication has several major implications. First, not all software translate private SQs well. One potential problem is when the implicit VRs are saved as implicit VRs. This can obscure the fact that 2005,140F is an SQ. Indeed, some tools will convert the private SQ type as a "UN" unknown type and add another public sequence. This can confuse reading software. Furthermore, in the real world there are many Philips DICOM images that ONLY contain IPP inside SQ 2005,140F. These situations appear to reflect modifications applied by a PACS to the DICOM data or attempts to anonymize the DICOM images (e.g. using simple Matlab scripts). Note that the private IPP differs from the public one by half a voxel. Therefore, in theory if one only has the private IPP, one can infer the public IPP location. Current versions of dcm2niix do not do this: the error is assumed small enough that it will not impact image processing steps such as coregistration. In general it is recommended that you archive and convert DICOM images as they are created from the scanner. If one does use an export tool such as the excellent dcmtk, it is recommended that you preserve the explicit VR, as implicit VR has the potential of obscuring private sequence (SQ) tags. Be aware that subsequent processing of DICOM data can disrupt data conversion. Therefore, dcm2niix will ignore the IPP enclosed in 2005,140F unless no alternative exists. ## Image Scaling How data is represented in DICOM for MR has several challenges and the technology and standard has evolved over the years to accommodate new uses. Unlike CT, where the signal is naturally displayed in Hounsfield units, MR has no natural signal units and the magnitude is influenced by the electronics and the software processing required to bring this to the final image. Secondly most of the original DICOM implementations used small bit number integers to store the underlying images for economy of storage. As a result it is necessary to apply scaling from the internal DICOM storage to a form suitable for radiographic display or quantitative measurement. There remain several challenges with this process, ensuring that the mapping to the integer values makes best use of the available bit depth for images with large dynamic range, or large changes between images, without clipping the data while also preserving the appearance of the noise field which is demanded by the needs of radiographic visual review. Note that for most MRI modalities these concerns do not impact analyses: the intensity is assumed arbitrary, the statistics treat signal offset and scaling as nuisance regressors when fitting models, and calculations are computed with high precision floating point numbers. However, there are some situations such as arterial spin labeling where image scaling is important. In these situations, scaling is a crucial aspect to be aware of for quantitative methods and which representation is used depends upon your needs. At its simplest image scaling requires a rescale slope and intercept defined by the DICOM standard tags [0028,1053](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0028,1053)) and [0028,1052](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0028,1053)). Whether these values are the same for all images, or image specific depends upon the implementation and potentially the location of these tags within the DICOM tag structure. For manufacturers other than Philips, these are the only intensity scaling values provided, so there is no concern regarding which scaling values should be used. However, the DICOM standard introduced the concept of [`real world units`](http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_A.46.html). This allows the storage of one or more mappings to allow selective viewing of the data mapped into different value ranges (which may also be non-linear mappings). Philips thinks in terms of three different representations (using the terminology of the documentation available to Philips collaborators): | Name | ID | Description| | ---------------- | ------------- | ------------- | | Stored Value | SV | Raw data stored in DICOM tag PIXEL DATA tag (7FE0,0010)| | Displayed Value | DV | The value which is shown to the user when using scanner interface, ROIS, measurements etc. | | Floating Point | FP | An internal value at a point earlier in the reconstruction chain before the conversion to DICOM/integer for image presentation. | | Real World Value | WV | DICOM defined real world units| In general SV should not be used for quantitative measurements as it is an integer format. In practice, if the Rescale values are the same for all images (the typical case, but not guaranteed) SV can be used to compare signal intensities between images from the same scan. Note that the NIfTI format only provides a single `scl_slope` and `scl_inter` for the entire file, whereas in DICOM rescale values can in theory differ across 2D slices. Therefore, in situations where the rescale values do differ across slices, dcm2niix will apply the requested rescale to each slice and save the scaled data as the 32-bit float NIfTI dataset. This preserves the variability reported by the rescale tags, at the cost of disk space. DV can be used for quantitative comparison of signal intensities between images in the same scan as long as the relevant rescale values are taken into account. These rescale values may come from the tags standard tags 0028,1053 and 0028,1052 or from a relevant RealWorld block if present. If the DV is derived from a RealWorld block with defined units (tag (0008,0104) such as Hz or ms rather than “no units”) or a RescaleType (0028,1054) with a non-US type (not defined by the standard), then the DV is already quantitative and cross scan comparison may be done. However, in general DV is not sufficient to compare images from different scans, especially if the signal intensity varies a lot (eg multiple inversion recovery scans) in which case the FP value may be used as this may be compared (with some caveats) across scans and across timescales. This scaling requires an additional scale factor on top of the DV value, the Scale Slope (private tag (2005,100E)) As long as rescale values are identical across all DICOM slices, dcm2niix losslessly copies the raw pixel data from the DICOM tag (7FE0,0010) to NIfTI image. These values are typically stored as 16-bit integers in the range -32768..32767. Both the DICOM and NIfTI formats describe how scaling intercept and slope values can be used to convert these raw values into calibrated values. For example, with an intercept of 0 and slope of 0.01 the raw value of 50 would be converted to 0.5. The [NIfTI](https://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1.h) header provides the `scl_slope` and `scl_inter` fields so each voxel value in the dataset is scaled as: ``` I = scl_slope * SV + scl_inter ``` where `SV` is the raw stored value and `I` is the "true" transformed voxel intensity. Philips has three possible intensity transforms for their DICOM images (world (`W`), display (`D`), precise (`P`)). All of these transforms might be provided in a single DICOM image, while the [NIfTI](https://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1.h) header only designates a single `scl_slope` and `scl_inter` for each image. dcm2niix will attempt to retain the stored values (`SV`) and sets the NIfTI `scl_inter` and `scl_slope values` for the desired intensity transform. dcm2niix will use `FP` if possible. If this is not possibleor the user specifies `-p n` dcm2niix will use the transforms for `DV`. The formulas are provided below. The DICOM tags are in brackets (e.g. `(0040,9225)`) and the BIDS tag is in double quotes (e.g. `"PhilipsRWVSlope"`). Since all the scaling values are stored in the BIDS sidecar, you can always use these to later your preferred intensity transform (assume all slices used the same scaling values). ``` Inputs: SV = stored value of DICOM PIXEL DATA without scaling WS = RealWorldValue slope (0040,9225) "PhilipsRWVSlope" WI = RealWorldValue intercept (0040,9224) "PhilipsRWVIntercept" RS = rescale slope (0028,1053) "PhilipsRescaleSlope" RI = rescale intercept (0028,1052) "PhilipsRescaleIntercept" SS = scale slope (2005,100E) "PhilipsScaleSlope" Outputs: WV = real world value FP = precise value DV = displayed value Formulas: WV = SV * WS + WI DV = SV * RS + RI FP = DV / (RS * SS) ``` ## Volume Ordering The DICOM standard does not require that the [Instance Number (0020,0013)](http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0020,0013)) be sequential or even unique. As a convention, most manufacturers provide instance numbers that are sequential with the temporal or spatial order. However, Philips often generates these values in a random order. This can lead to images appearing in a jumbled order when displayed with many DICOM viewers (e.g. [Horos](https://horosproject.org)). dcm2niix will attempt to automatically resolve this. Hopefully, fMRI volumes will be ordered temporally, diffusion data will be ordered by gradient number (with derived TRACE/ADC maps being made the final volume), and ASL scans will follow the order hierarchical order of [repeat, phase, label/control](https://github.com/neurolabusc/dcm_qa_philips_asl)). dcm2niix makes assumptions about the volume based on assumptions observed in previous Philips data. These heuristics may not be robust for future Philips data or for DICOM images that have been manipulated (e.g. anonymized, dcuncat, touched by an AGFA/dcmche PACS). Therefore, users need to use caution when dealing with Philips data converted by dcm2niix. ## Arterial Spin Labelling Details and sample datasets for Philips Arterial Spin Labeling (ASL) are provided with the [dcm_qa_philips_asl](https://github.com/neurolabusc/dcm_qa_philips_asl) (classic DICOM) and [dcm_qa_philips_asl_enh](https://github.com/neurolabusc/dcm_qa_philips_asl) (enhanced DICOM) repositories. dcm2niix v1.0.20210819 and later will attempt to store volumes in [temporal order](https://github.com/rordenlab/dcm2niix/issues/533) regardless of whether the data is acquired as classic or enhanced DICOM. The [BIDS BEP005](https://bids.neuroimaging.io/get_involved) requires ASL sequences to report `PostLabelingDelay` with respect to the first slice of the volume. Curiously, Philips reports label delay independently for each slice (using DICOM tags 0020,9153; 0018,1060). In theory, this might allow a method to infer slice timing (though details like descending acquisitions may complicate these methods). ## Derived parametric maps stored with raw diffusion data Some Philips diffusion DICOM images include derived image(s) along with the images. Other manufacturers save these derived images as a separate series number, and the DICOM standard seems ambiguous on whether it is allowable to mix raw and derived data in the same series (see PS 3.3-2008, C.7.6.1.1.2-3). In practice, many Philips diffusion images append [derived parametric maps](http://www.revisemri.com/blog/2008/diffusion-tensor-imaging/) with the original data. With Philips, appending the derived isotropic image is optional - it is only created for the 'clinical' DTI schemes for radiography analysis and is triggered if the first three vectors in the gradient table are the unit X,Y and Z vectors. For conventional DWI, the result is the conventional mean of the ADC X,Y,Z for DTI it the conventional mean of the 3 principle Eigen vectors. As scientists, we want to discard these derived images, as they will disrupt data processing and we can generate better parametric maps after we have applied undistortion methods such as [Eddy and Topup](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/eddy/UsersGuide). The current version of dcm2niix uses the Diffusion Directionality (0018,9075) tag to detect B=0 unweighted ("NONE"), B-weighted ("DIRECTIONAL"), and derived ("ISOTROPIC") images. Note that the Dimension Index Values (0020,9157) tag provides an alternative approach to discriminate these images. Here are sample tags from a Philips enhanced image that includes and derived map (3rd dimension is "1" while the other images set this to "2"). ``` (0018,9075) CS [DIRECTIONAL] (0018,9089) FD 1\0\0 (0018,9087) FD 1000 (0020,9157) UL 1\1\2\32 ... (0018,9075) CS [ISOTROPIC] (0018,9087) FD 1000 (0020,9157) UL 1\2\1\33 ... (0018,9075) CS [NONE] (0018,9087) FD 0 (0020,9157) UL 1\1\2\33 ``` ## Diffusion Direction Proper Philips enhanced scans use tag 0018,9089 to report the 3 gradient directions. However, in the wild, other files from Philips (presumably using older versions of Philips software) use the tags 2005,10b0, 2005,10b1, 2005,10b2. In general, dcm2niix will use the values that most closely precede the Dimension Index Values (0020,9157). Public Tags ``` (0018,9089) FD 1\0\0 (0018,9087) FD 1000 ``` Private Tags ``` (2001,1003) FL 1000 (2005,10b0) FL 1.0 (2005,10b1) FL 1.0 (2005,10b2) FL 0.0 ``` For modern Philips DICOMs, the current version of dcm2niix uses Dimension Index Values (0020,9157) to determine gradient number, which can also be found in (2005,1413). However, while 2005,1413 is always indexed from one, this is not necessarily the case for 0020,9157. For example, the ADNI DWI dataset for participant 018_S_4868 has values of 2005,1413 that range from 1..36 for the 36 directions, while 0020,9157 ranges from 2..37. The current version of dcm2niix compensates for this by re-indexing the values of 0020,9157 after all the volumes have been read. For acquiring DWI data, you can adjust your setup with from the Philips console. Specifically, in Contrast one selects Diffusion mode to DTI and adjusts the directional resolution. Options for directional resolution are `Low` which acquires 6 directions (P,M,S, plus 3 oblique), `Medium` (15 directions), `High` (32 directions) and `Opt x` where x is a number from 6 - 128 directions. DTI Elite users can also select `From File`. This will import the text file named E:\Export\dti_vectors_input.txt. This text file has a simple format. The first line is optional and is the name of the scheme - this line should not begin with a number. If the file contains a b=0 vector, it must be the first line of the file. The following lines specify the direction and bvalues. If you want to acquire more than one b=0 volume, each must specify a unique direction. One can only process these custom files with Philips FiberTrak if the same directions have been obtained for all b-values. Here is an example file that obeys these rules: ``` MyCustomDirections 0.000 0.000 1.000 0 0.049 -0.919 -0.391 1000 0.726 0.301 -0.618 1000 -0.683 0.255 -0.684 1000 0.845 -0.502 -0.186 1000 -0.73 -0.619 -0.288 1000 -0.051 0.039 0.998 1000 -0.018 0.871 -0.491 1000 -0.444 0.494 0.747 1000 -0.989 -0.086 -0.116 1000 1.000 0.000 0.000 0 ``` Important tags for Philips DICOM include b-value index (2005,1412) and gradient direction number (2005,1413). Knowing both 2005,1412 nd 2005,1413 uniquely identifies a volume in a series (two volumes can share either b-value or gradient direction, but can not be identical in both dimensions). With software release R5.6 and later there are additional useful tags: DIFFUSION2_KDTI (2005, 1595, Y/N) specifies whether acquisition ordering is enabled for a series. NR_OF_DIFFUSION_ORDER (2005,1599) specifies the number of vectors in a series. DIFFUSION_ORDER (2005,1596) specifies the acquisition ordering of a series. ## Missing Information Philips DICOMs do not contain all the information desired by many neuroscientists. Due to this, the [BIDS](http://bids.neuroimaging.io/) files created by dcm2niix are impoverished relative to data from other vendors. This reflects a limitation in the Philips DICOMs, not dcm2niix. Research users may want to explore the direct NIfTI export provided by Philips. This tool may have access to sequence information not provided in the DICOM export. However, this [manufacturer provided NIfTI export](https://github.com/rordenlab/dcm2niix/issues/529) is limited to certain image types and sequences and does not support features like FSL format diffusion bvec/bvals or BIDS sidecars. The BIDS tag [PartialFourier](https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#in-plane-spatial-encoding) expects a fractional value. For example, an acquisition with 5/8 partial Fourier should be reported as "0.625". However, Philips DICOM only reports if partial Fourier is enabled or not, and does not reveal the fraction. 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). 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). Another value desirable for TOPUP is the "TotalReadoutTime". Again, one can not confidently calculate this from Philips DICOMs (though on can [approximate it if you make a few assumptions](https://github.com/nipreps/sdcflows/issues/5)). If you do decide to calculate this using values from the MRI console, be aware that the [FSL definition](https://github.com/rordenlab/dcm2niix/issues/130) is not intuitive for scans with interpolation, partial Fourier, parallel imaging, etc. However, it should be pointed out that the "TotalReadoutTime" only influences TOPUP's calibrated validation images that are typically ignored. The data used in subsequent steps will not be influenced by this value. ## Partial Volumes NIfTI expects all 3D volumes of a 4D series to have the same number of series (e.g. a time series of 3D fMRI volumes, or a diffusion set with 3D volumes with different gradients applied). If a fMRI sequence is aborted part way through, it is possible that a Philips scanner will only save part of the final volume. An example would be where the total slices (9970) does not equal Dynamics (290) x slices (35) = 10150. Current versions of dcm2niix expect complete volumes. You can repair your data using the console or a Python script, as discussed in [issue 357](https://github.com/rordenlab/dcm2niix/issues/357). To resolve this situation by hand you could also [rename](RENAMING.md) your DICOM files with a call like `./dcm2niix -r y -f %t/%s_%p_%4y_%2r.dcm ~/out 0020,0100`. In this example, the [`%4y`](FILENAMING.md) parameter adds the volume (Temporal Position, 0020,0100) to the filename, allowing you to identify volumes with missing slices. ## Non-Image DICOMs NIfTI is an image format, while DICOM is a multi-purpose format that can store videos (MPEG) or other data. Specifically, some Philips systems save Exam Cards and other non-image data as DICOM files. In these case, dcm2niix should skip these files, as they can not be represented in NIfTI. You can discriminate these files by reading the [MediaStorageSOPClassUID (0002,0002)](https://github.com/rordenlab/dcm2niix/issues/328). - MR Image Storage = 1.2.840.10008.5.1.4.1.1.4 - Enhanced MR Image Storage = 1.2.840.10008.5.1.4.1.1.4.1 - MR Spectroscopy Storage = 1.2.840.10008.5.1.4.1.1.4.2 - Secondary Capture Image Storage = 1.2.840.10008.5.1.4.1.1.7 - Grayscale Softcopy Presentation State = 1.2.840.10008.5.1.4.1.1.11.1 - Raw Data Storage = 1.2.840.10008.5.1.4.1.1.66 - (Old) Private MR Spectrum Storage = 1.3.46.670589.11.0.0.12.1 - (Old) Private MR Series Data Storage = 1.3.46.670589.11.0.0.12.2 - (Old) Private MR Examcard Storage = 1.3.46.670589.11.0.0.12.4 ## General variations Prior versions of dcm2niix used different methods to sort images. However, these have proved unreliable The undocumented tags SliceNumberMrPhilips (2001,100A). In theory, InStackPositionNumber (0020,9057) should be present in all enhanced files, but has not proved reliable (perhaps not in older Philips images or DICOM images that were modified after leaving the scanner). MRImageGradientOrientationNumber (2005,1413) is complicated by the inclusion of derived images. Therefore, current versions of dcm2niix do not generally depend on any of these. ## Sample Datasets - [National Alliance for Medical Image Computing (NAMIC) samples](http://www.insight-journal.org/midas/collection/view/194) - [Unusual Philips Examples (e.g. multi-echo)](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Unusual_MRI) - [Archival samples](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Archival_MRI) - [Diffusion Examples](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging) - [Additional Diffusion Examples](https://github.com/neurolabusc/dcm_qa_philips) - Classic and enhanced [ASL Examples](https://github.com/neurolabusc/dcm_qa_philips_asl) - [Enhanced DICOMs](https://github.com/neurolabusc/dcm_qa_enh) dcm2niix-1.0.20211006/README.md000066400000000000000000000476761412761503300152660ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/rordenlab/dcm2niix.svg?branch=master)](https://travis-ci.org/rordenlab/dcm2niix) [![Build status](https://ci.appveyor.com/api/projects/status/7o0xp2fgbhadkgn1?svg=true)](https://ci.appveyor.com/project/neurolabusc/dcm2niix) ## About 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. ## License This software is open source. The bulk of the code is covered by the BSD license. Some units are either public domain (nifti*.*, miniz.c) or use the MIT license (ujpeg.cpp). See the license.txt file for more details. ## Dependencies This software should run on macOS, Linux and Windows typically without requiring any other software. However, if you use dcm2niix to create gz-compressed images it will be faster if you have [pigz](https://github.com/madler/pigz) installed. You can get a version of both dcm2niix and pigz compiled for your operating system by downloading [MRIcroGL](https://www.nitrc.org/projects/mricrogl/). ## Image Conversion and Compression DICOM provides many ways to store/compress image data, known as [transfer syntaxes](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#DICOM_Transfer_Syntaxes_and_Compressed_Images). The [COMPILE.md file describes details](./COMPILE.md) on how to enable different options to provide support for more formats. - The base code includes support for raw, run-length encoded, and classic JPEG lossless decoding. - Lossy JPEG is handled by the included [NanoJPEG](https://keyj.emphy.de/nanojpeg/). This support is modular: you can compile for [libjpeg-turbo](https://github.com/chris-allan/libjpeg-turbo) or disable it altogether. - JPEG-LS lossless support is optional, and can be provided by using [CharLS](https://github.com/team-charls/charls). - JPEG2000 lossy and lossless support is optional, and can be provided using [OpenJPEG](https://github.com/uclouvain/openjpeg) or [Jasper](https://www.ece.uvic.ca/~frodo/jasper/). - GZ compression (e.g. creating .nii.gz images) is optional, and can be provided using either the included [miniz](https://github.com/richgel999/miniz) or the popular zlib. Of particular note, the [Cloudflare zlib](https://github.com/cloudflare/zlib) exploits modern hardware (available since 2008) for very rapid compression. Alternatively, you can compile dcm2niix without a gzip compressor. Regardless of how you compile dcm2niix, it can use the external program [pigz](https://github.com/madler/pigz) for parallel compression. ## Versions [See releases](https://github.com/rordenlab/dcm2niix/releases) for recent release notes. [See the VERSIONS.md file for details on earlier releases](./VERSIONS.md). ## Contribute dcm2niix is developed by the community for the community and everybody can become a part of the [community](./CONTRIBUTE.md). ## Running Command line usage is described in the [NITRC wiki](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#General_Usage). The minimal command line call would be `dcm2niix /path/to/dicom/folder`. However, you may want to invoke additional options, for example the call `dcm2niix -z y -f %p_%t_%s -o /path/output /path/to/dicom/folder` will save data as gzip compressed, with the filename based on the protocol name (%p) acquisition time (%t) and DICOM series number (%s), with all files saved to the folder "output". For more help see help: `dcm2niix -h`. [See the BATCH.md file for instructions on using the batch processing version](./BATCH.md). ## Install There are a couple ways to install dcm2niix - [Github Releases](https://github.com/rordenlab/dcm2niix/releases) provides the latest compiled executables. This is an excellent option for MacOS and Windows users. However, the provided Linux executable requires a recent version of Linux (e.g. Ubuntu 14.04 or later), so the provided Unix executable is not suitable for very old distributions. Specifically, it requires Glibc 2.19 (from 2014) or later. Users of older systems can compile their own copy of dcm2niix or download the compiled version included with MRIcroGL Glibc 2.12 (from 2011, see below). - Run the following command to get the latest version for Linux, Macintosh or Windows: * `curl -fLO https://github.com/rordenlab/dcm2niix/releases/latest/download/dcm2niix_lnx.zip` * `curl -fLO https://github.com/rordenlab/dcm2niix/releases/latest/download/dcm2niix_mac.zip` * `curl -fLO https://github.com/rordenlab/dcm2niix/releases/latest/download/dcm2niix_mac_arm.pkg` * `curl -fLO https://github.com/rordenlab/dcm2niix/releases/latest/download/dcm2niix_win.zip` - [MRIcroGL (NITRC)](https://www.nitrc.org/projects/mricrogl) or [MRIcroGL (GitHub)](https://github.com/rordenlab/MRIcroGL12/releases) includes dcm2niix that can be run from the command line or from the graphical user interface (select the Import menu item). The Linux version of dcm2niix is compiled on a [holy build box](https://github.com/phusion/holy-build-box), so it should run on any Linux distribution. - If you have a MacOS computer with Homebrew or MacPorts you can run `brew install dcm2niix` or `sudo port install dcm2niix`, respectively. - If you have Conda, [`conda install -c conda-forge dcm2niix`](https://anaconda.org/conda-forge/dcm2niix) on Linux, MacOS or Windows. - On Debian Linux computers you can run `sudo apt-get install dcm2niix`. ## Build from source It is often easier to download and install a precompiled version. However, you can also build from source. ### Build command line version with cmake (Linux, MacOS, Windows) `cmake` and `pkg-config` (optional) can be installed as follows: Ubuntu: `sudo apt-get install cmake pkg-config` MacOS: `brew install cmake pkg-config` or `sudo port install cmake pkgconfig` **Basic build:** ```bash git clone https://github.com/rordenlab/dcm2niix.git cd dcm2niix mkdir build && cd build cmake .. make ``` `dcm2niix` will be created in the `bin` subfolder. To install on the system run `make install` instead of `make` - this will copy the executable to your path so you do not have to provide the full path to the executable. In rare case if cmake fails with the message like `"Generator: execution of make failed"`, it could be fixed by ``sudo ln -s `which make` /usr/bin/gmake``. **Advanced build:** As noted in the `Image Conversion and Compression Support` section, the software provides many optional modules with enhanced features. A common choice might be to include support for JPEG2000, [JPEG-LS](https://github.com/team-charls/charls) (this option requires a c++14 compiler), as well as using the high performance Cloudflare zlib library (this option requires a CPU built after 2008). To build with these options simply request them when configuring cmake: ```bash git clone https://github.com/rordenlab/dcm2niix.git cd dcm2niix mkdir build && cd build cmake -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_JPEGLS=ON -DUSE_OPENJPEG=ON .. make ``` **optional batch processing version:** The batch processing binary `dcm2niibatch` is optional. To build `dcm2niibatch` as well change the cmake command to `cmake -DBATCH_VERSION=ON ..`. This requires a compiler that supports c++11. ### Building the command line version without cmake If you have any problems with the cmake build script described above or want to customize the software see the [COMPILE.md file for details on manual compilation](./COMPILE.md). ## Referencing - Li X, Morgan PS, Ashburner J, Smith J, Rorden C (2016) The first step for neuroimaging data analysis: DICOM to NIfTI conversion. J Neurosci Methods. 264:47-56. doi: 10.1016/j.jneumeth.2016.03.001. [PMID: 26945974](https://www.ncbi.nlm.nih.gov/pubmed/26945974) ## Alternatives - [BIDS-converter](https://github.com/openneuropet/BIDS-converter) hosts Matlab and Python scripts for PET images, supporting DICOM and ECAT (ecat2nii) formats. - [dcm2nii](https://people.cas.sc.edu/rorden/mricron/dcm2nii.html) is the predecessor of dcm2niix. It is deprecated for modern images, but does handle image formats that predate DICOM (proprietary Elscint, GE and Siemens formats). - Python [dcmstack](https://github.com/moloney/dcmstack) DICOM to Nifti conversion with meta data preservation. - [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/). - [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. - [mri_convert](https://surfer.nmr.mgh.harvard.edu/pub/docs/html/mri_convert.help.xml.html) is part of the popular FreeSurfer package. In my limited experience this tool works well for GE and Siemens data, but fails with Philips 4D datasets. - [MRtrix mrconvert](http://mrtrix.readthedocs.io/en/latest/reference/commands/mrconvert.html) is a useful general purpose image converter and handles DTI data well. It is an outstanding tool for modern Philips enhanced images. - [nanconvert](https://github.com/spinicist/nanconvert) uses the ITK library to convert DICOM from GE and proprietary Bruker to standard formats like DICOM. - [PET CT viewer](http://petctviewer.org/index.php/feature/results-exports/nifti-export) for [Fiji](https://fiji.sc) can load DICOM images and export as NIfTI. - [Plastimatch](https://www.plastimatch.org/) is a Swiss Army knife - it computes registration, image processing, statistics and it has a basic image format converter that can convert some DICOM images to NIfTI or NRRD. - [Simple Dicom Reader 2 (Sdr2)](http://ogles.sourceforge.net/sdr2-doc/index.html) uses [dcmtk](https://dicom.offis.de/dcmtk.php.en) to read DICOM images and convert them to the NIfTI format. - [SlicerHeart extension](https://github.com/SlicerHeart/SlicerHeart) is specifically designed to help 3D Slicer support ultra sound (US) images stored as DICOM. - [spec2nii](https://github.com/wexeee/spec2nii) converts MR spectroscopy to NIFTI. - [SPM12](http://www.fil.ion.ucl.ac.uk/spm/software/spm12/) is one of the most popular tools in the field. It includes DICOM to NIfTI conversion. Being based on Matlab it is easy to script. ## Links 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). - [clinica](https://github.com/aramis-lab/clinica) is a software platform for clinical neuroimaging studies that uses dcm2niix to convert DICOM images. - [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. - [boutiques-dcm2niix](https://github.com/lalet/boutiques-dcm2niix) is a dockerfile for installing and validating dcm2niix. - [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. - [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. - [DICOM2BIDS](https://github.com/klsea/DICOM2BIDS) is a Python 2 script for creating BIDS files. - [dicom2bids](https://github.com/Jolinda/lcnimodules) includes python modules for converting dicom files to nifti in a bids-compatible file structure that use dcm2niix. - [dcmwrangle](https://github.com/jbteves/dcmwrangle) a Python interactive and static tool for organizing dicoms. - [dicom2nifti_batch](https://github.com/scanUCLA/dicom2nifti_batch) is a Matlab script for automating 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. - [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. - [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. - [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. - [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. - [nipype](https://github.com/nipy/nipype) can use dcm2niix to convert 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). - [pydra-dcm2niix](https://github.com/nipype/pydra-dcm2niix) is a contains Pydra task interface for dcm2niix. - [qsm](https://github.com/CAIsr/qsm) Quantitative Susceptibility Mapping software. - [reproin](https://github.com/ReproNim/reproin) is a setup for automatic generation of shareable, version-controlled BIDS datasets from MR scanners. - [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. - [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). dcm2niix-1.0.20211006/RENAMING.md000066400000000000000000000076611412761503300155170ustar00rootroot00000000000000## About dcm2niix is primarily designed to convert DICOM images into NIfTI images. However, it does include a primitive ability to organize and rename DICOM images without converting them. This can be useful, as DICOM images are often stored with random file names that make it difficult to recognize the images. ## Limitation dcm2niix renames and copies the DICOM images, but the current version does not copy or create a new [DICOMDIR](http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_F.2.2.2.html) file. Most users ignore these files. However, you should not use this featire if you wish to preserve your DICOMDIR files. Note that this feature only copies your DICOM images with a new filename. It does not modify the contents of the DICOM header. This means it will not compress or anonymize your files. Free tools for these functions include [dcmcjpeg](https://dicom.offis.de/dcmtk.php.en), [gdcmanon](http://gdcm.sourceforge.net/html/gdcmanon.html) and [gdcmconv](http://gdcm.sourceforge.net/html/gdcmconv.html). In addition, this tool assumes that the DICOM images can be uniquely identified by the filenaming argument you provide. ## Usage The command line argument `-r y` instructs dcm2niix to rename your DICOM files rather than convert them. It does not delete your DICOM images, but rather creates a copy with the organization specified by the [filenaming argument `-f`](FILENAMING.md). Here is an example where the DICOM images will be sorted into folders, with the folder name reflecting the study time (`%t`) and series number (`%s`), each DICOM image will be named based on the image number (`%r`) which will be padded with zeros to fill 5 characters. - `dcm2niix -r y -f %t_%s/%5r.dcm -o ~/out ~/in` Therefore, the 9th DICOM image from series 3 acquired on 4 February 2012 would be saved as ~/out/20120204084424_3/00009.dcm. It is very important that your file naming disambiguates all your images. For example, consider a naming scheme that only used the image number (`-f %r.dcm`) and was applied to multiple series (each which had an image number 1,2,...). When there are naming conflicts, dcm2niix will terminate with an error message, e.g. `Error: File naming conflict. Existing file /home/c/dcm/1.dcm`. A special situation is the fieldmaps generated by Siemens scanners. Users often acquire gradient-echo fieldmaps so they can undistort EPI images. These fieldmaps acquire two (or more) echoes. Unfortunately, Siemens will give each of these echoes an identical series and image number. DICOM tools that are unaware of this often [overwrite](https://neurostars.org/t/field-mapping-siemens-scanners-dcm2niix-output-2-bids/2075/7) some of the images from each echo. To combat this situation, dcm2niix will add the post-fix `_e2` to the second echo. Therefore, if you converted a series with `-f %s_%4r` your fieldmap might generate files named `5_0001.dcm` and `5_0001_e2.dcm`. Note you could also explicitly number each echo (`-f %s_%4r_%e`), though in this case all your series (not just the fieldmaps) will have the echo appended. ## Alternatives An advantage of using dcm2niix is simplicity: it is a free, single file executable that you can [download](https://github.com/rordenlab/dcm2niix/releases) that is available for MacOS, Linux and Windows that you can run from the command line. On the other hand, this simplicity means it is fairly inflexible. You may want to consider a DICOM renamer built in your favorite scripting language. - [dicomsort is a Python script](https://github.com/pieper/dicomsort). - [rename_dir is a Matlab script that requires the proprietary Image Processing Toolbox](https://gist.github.com/htygithub/ad3597577e1de004e9f5). - [dicm2nii includes the Matlab script rename_dicm that does not require any additional toolboxes](https://github.com/xiangruili/dicm2nii). - [DICOMSort provides a nice graphical interface]https://dicomsort.com/downloads.html), providing a nice alternative for scripting and command line solutions. dcm2niix-1.0.20211006/Siemens/000077500000000000000000000000001412761503300153665ustar00rootroot00000000000000dcm2niix-1.0.20211006/Siemens/README.md000066400000000000000000000226441412761503300166550ustar00rootroot00000000000000## About dcm2niix attempts to convert Siemens DICOM format images to NIfTI. This page describes some vendor-specific details. ## Siemens X-Series Siemens MR is named by Series, Generation, Major Version and Minor Version. Prior to the Siemens Vida, all contemporary Siemens MRI systems (Trio, Prisma, Skyra, etc) were part of the V series. So a Trio might be on VB17, and a Prisma on VE11 (series 'V', generation 'E', major version '1', minor version '1'). The 3T Vida and 1.5T Sola introduce the X-series (XA10, XA11, XA20). Since the V-series was dominant for so long, most users simply omit the series, e.g. referring to a system as `B19`. However, Siemens has recently introduced a new X-series. The DICOM images exported by the X-series is radically different than the V-series. The images lack the proprietary CSA header with its rich meta data. 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). 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 ``` Private Tags ``` (0019,100c) IS 1000 #SiemensDiffusionBValue (0019,100e) FD -0.20\-0.51\-0.83 #SiemensDiffusionGradientOrientation ``` 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. ## 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. ## Slice Timing See the [dcm_qa_stc](https://github.com/neurolabusc/dcm_qa_stc) repository with sample data that exhibits different methods used by Siemens to record slice timing. Older software (e.g. A25 through B13) sometimes populates the tag sSliceArray.ucMode in the [CSA Series Header (0029, 1020)](https://nipy.org/nibabel/dicom/siemens_csa.html) where the values [1, 2, and 4](https://github.com/xiangruili/dicm2nii/issues/18) correspond to Ascending, Descending and Interleaved acquisitions. For software versions B15 through E11 where all slices of a volume are stored as a single mosaic file, the proprietary [CSA Image Header (0029,1010)](https://nipy.org/nibabel/dicom/siemens_csa.html) contains the array MosaicRefAcqTimes that provides [slice timing](https://www.mccauslandcenter.sc.edu/crnl/tools/stc). For volumes where each 2D slice is saved as a separate DICOM file, one can infer slice order from the DICOM tag Acquisition Time (0008,0032). The prior section describes Vida slice timing issues seen with the XA software series. In brief, dcm2niix will use Frame Acquisition Time (0018,9074) to determine slice times. Some Siemens DICOMs store slice timings in the private tag [0019,1029](https://github.com/rordenlab/dcm2niix/issues/296). In theory, this could be used when the CSA header is missing. For archival studies, be aware that some sequences [incorrectly reported slice timing](https://github.com/rordenlab/dcm2niix/issues/126). The [SPM slice timing wiki](https://en.wikibooks.org/w/index.php?title=SPM/Slice_Timing&stable=0#Siemens_scanners) provides further information on Siemens slice timing. ## Total Readout Time One often wants to determine [echo spacing, bandwidth, ](https://support.brainvoyager.com/brainvoyager/functional-analysis-preparation/29-pre-processing/78-epi-distortion-correction-echo-spacing-and-bandwidth) and total read-out time for EPI data so they can be undistorted. The [Siemens validation dataset](https://github.com/neurolabusc/dcm_qa/tree/master/In/TotalReadoutTime) demonstrates that dcm2niix can accurately report these parameters - the included notes and spreadsheet describe this in more detail. ## Diffusion Tensor Notes Diffusion specific parameters are described on the [NA-MIC](https://www.na-mic.org/wiki/NAMIC_Wiki:DTI:DICOM_for_DWI_and_DTI#Private_vendor:_Siemens) website. Gradient vectors are reported with respect to the scanner bore, and dcm2niix will attempt to re-orient these to [FSL format](http://justinblaber.org/brief-introduction-to-dwmri/) [bvec files](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FDT/FAQ#What_conventions_do_the_bvecs_use.3F). For Siemens V-series systems from the B-generation onward (around 2005), the most reliable way to read diffusion gradients is from the [CSA header](https://nipy.org/nibabel/dicom/siemens_csa.html). Specially, the CSA's 'DiffusionGradientDirection' and 'B_value' tags. For the X-series, the private DICOM tags B_value (0019,100c) and DiffusionGradientDirection (0019,100e) are used. ## Arterial Spin Labeling 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). ## 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). - [A validation dataset for dcm2niix commits](https://github.com/neurolabusc/dcm_qa). - [A mixture of GE and Siemens data](https://github.com/neurolabusc/dcm_qa_nih). - [DTI examples](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging). - [Archival (old) examples](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Archival_MRI). - [Unusual examples](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Unusual_MRI). dcm2niix-1.0.20211006/SuperBuild/000077500000000000000000000000001412761503300160415ustar00rootroot00000000000000dcm2niix-1.0.20211006/SuperBuild/External-CLOUDFLARE-ZLIB.cmake000066400000000000000000000006601412761503300230030ustar00rootroot00000000000000set(CLOUDFLARE_BRANCH gcc.amd64) # Cloudflare zlib branch ExternalProject_Add(zlib GIT_REPOSITORY "${git_protocol}://github.com/ningfei/zlib.git" GIT_TAG "${CLOUDFLARE_BRANCH}" SOURCE_DIR cloudflare-zlib BINARY_DIR cloudflare-zlib-build CMAKE_ARGS -Wno-dev -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=${DEP_INSTALL_DIR} ) set(ZLIB_ROOT ${DEP_INSTALL_DIR}) dcm2niix-1.0.20211006/SuperBuild/External-OPENJPEG.cmake000066400000000000000000000007211412761503300220320ustar00rootroot00000000000000set(OPENJPEG_TAG v2.1-static) # version v2.1-static ExternalProject_Add(openjpeg GIT_REPOSITORY "${git_protocol}://github.com/ningfei/openjpeg.git" GIT_TAG "${OPENJPEG_TAG}" SOURCE_DIR openjpeg BINARY_DIR openjpeg-build CMAKE_ARGS -Wno-dev --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=${DEP_INSTALL_DIR} ) set(OpenJPEG_DIR ${DEP_INSTALL_DIR}/lib/openjpeg-2.1) dcm2niix-1.0.20211006/SuperBuild/External-YAML-CPP.cmake000066400000000000000000000007301412761503300220050ustar00rootroot00000000000000set(YAML-CPP_TAG yaml-cpp-0.5.3) # version yaml-cpp-0.5.3 ExternalProject_Add(yaml-cpp GIT_REPOSITORY "${git_protocol}://github.com/ningfei/yaml-cpp.git" GIT_TAG "${YAML-CPP_TAG}" SOURCE_DIR yaml-cpp BINARY_DIR yaml-cpp-build CMAKE_ARGS -Wno-dev --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=${DEP_INSTALL_DIR} ) set(YAML-CPP_DIR ${DEP_INSTALL_DIR}/lib/cmake/yaml-cpp) dcm2niix-1.0.20211006/SuperBuild/SuperBuild.cmake000066400000000000000000000135611412761503300211270ustar00rootroot00000000000000# Check if git exists find_package(Git) if(NOT GIT_FOUND) message(FATAL_ERROR "Cannot find Git. Git is required for Superbuild") endif() # Use git protocol or not option(USE_GIT_PROTOCOL "If behind a firewall turn this off to use http instead." ON) if(USE_GIT_PROTOCOL) set(git_protocol "git") else() set(git_protocol "https") endif() # Basic CMake build settings if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;RelWithDebInfo;MinSizeRel") endif() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) option(USE_STATIC_RUNTIME "Use static runtime" ON) if(USE_STATIC_RUNTIME) if(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") find_file(STATIC_LIBCXX "libstdc++.a" ${CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES}) mark_as_advanced(STATIC_LIBCXX) if(NOT STATIC_LIBCXX) unset(STATIC_LIBCXX CACHE) # Only on some Centos/Redhat systems message(FATAL_ERROR "\"USE_STATIC_RUNTIME\" set to ON but \"libstdc++.a\" not found! Set it to OFF or \ \"yum install libstdc++-static\" to resolve the error.") endif() endif() endif() 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) include(ExternalProject) set(DEPENDENCIES) option(INSTALL_DEPENDENCIES "Optionally install built dependent libraries (OpenJPEG and yaml-cpp) for future use." OFF) if(INSTALL_DEPENDENCIES) set(DEP_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}) else() set(DEP_INSTALL_DIR ${CMAKE_BINARY_DIR}) endif() if(USE_OPENJPEG) message("-- Build with OpenJPEG: ${USE_OPENJPEG}") if(OpenJPEG_DIR) set(OpenJPEG_DIR "${OpenJPEG_DIR}" CACHE PATH "Path to OpenJPEG configuration file" FORCE) message("-- Using OpenJPEG library from ${OpenJPEG_DIR}") else() find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(OPENJPEG libopenjp2) endif() if(OPENJPEG_FOUND AND NOT ${OPENJPEG_INCLUDE_DIRS} MATCHES "gdcmopenjpeg") set(OpenJPEG_DIR ${OPENJPEG_LIBDIR}/openjpeg-${OPENJPEG_VERSION} CACHE PATH "Path to OpenJPEG configuration file" FORCE) message("-- Using OpenJPEG library from ${OpenJPEG_DIR}") else() if(${OPENJPEG_INCLUDE_DIRS} MATCHES "gdcmopenjpeg") message("-- Unable to use GDCM's internal OpenJPEG") endif() include(${CMAKE_SOURCE_DIR}/SuperBuild/External-OPENJPEG.cmake) list(APPEND DEPENDENCIES openjpeg) set(BUILD_OPENJPEG TRUE) message("-- Will build OpenJPEG library from github") endif() endif() endif() if(BATCH_VERSION) message("-- Build dcm2niibatch: ${BATCH_VERSION}") if(YAML-CPP_DIR) set(YAML-CPP_DIR ${YAML-CPP_DIR} CACHE PATH "Path to yaml-cpp configuration file" FORCE) message("-- Using yaml-cpp library from ${YAML-CPP_DIR}") else() find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(YAML-CPP yaml-cpp) endif() # Build from github if not found or version < 0.5.3 if(YAML-CPP_FOUND AND NOT (YAML-CPP_VERSION VERSION_LESS "0.5.3")) set(YAML-CPP_DIR ${YAML-CPP_LIBDIR}/cmake/yaml-cpp CACHE PATH "Path to yaml-cpp configuration file" FORCE) message("-- Using yaml-cpp library from ${YAML-CPP_DIR}") else() include(${CMAKE_SOURCE_DIR}/SuperBuild/External-YAML-CPP.cmake) list(APPEND DEPENDENCIES yaml-cpp) set(BUILD_YAML-CPP TRUE) message("-- Will build yaml-cpp library from github") endif() endif() endif() set(ZLIB_IMPLEMENTATION "Miniz" CACHE STRING "Choose zlib implementation.") set_property(CACHE ZLIB_IMPLEMENTATION PROPERTY STRINGS "Miniz;System;Cloudflare;Custom") if(${ZLIB_IMPLEMENTATION} STREQUAL "Cloudflare") message("-- Build with Cloudflare zlib: ON") include(${CMAKE_SOURCE_DIR}/SuperBuild/External-CLOUDFLARE-ZLIB.cmake) list(APPEND DEPENDENCIES zlib) set(BUILD_CLOUDFLARE-ZLIB TRUE) message("-- Will build Cloudflare zlib from github") elseif(${ZLIB_IMPLEMENTATION} STREQUAL "Custom") set(ZLIB_ROOT ${ZLIB_ROOT} CACHE PATH "Specify custom zlib root directory.") if(NOT ZLIB_ROOT) message(FATAL_ERROR "ZLIB_ROOT needs to be set to locate custom zlib!") endif() endif() ExternalProject_Add(console DEPENDS ${DEPENDENCIES} DOWNLOAD_COMMAND "" SOURCE_DIR ${CMAKE_SOURCE_DIR}/console BINARY_DIR console-build CMAKE_ARGS -Wno-dev --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR} -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS} -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS} -DCMAKE_VERBOSE_MAKEFILE:BOOL=${CMAKE_VERBOSE_MAKEFILE} -DUSE_STATIC_RUNTIME:BOOL=${USE_STATIC_RUNTIME} -DUSE_TURBOJPEG:BOOL=${USE_TURBOJPEG} -DUSE_JASPER:BOOL=${USE_JASPER} -DUSE_JPEGLS:BOOL=${USE_JPEGLS} -DZLIB_IMPLEMENTATION:STRING=${ZLIB_IMPLEMENTATION} -DZLIB_ROOT:PATH=${ZLIB_ROOT} # OpenJPEG -DUSE_OPENJPEG:BOOL=${USE_OPENJPEG} -DOpenJPEG_DIR:PATH=${OpenJPEG_DIR} # yaml-cpp -DBATCH_VERSION:BOOL=${BATCH_VERSION} -DYAML-CPP_DIR:PATH=${YAML-CPP_DIR} ) install(DIRECTORY ${CMAKE_BINARY_DIR}/bin/ DESTINATION bin USE_SOURCE_PERMISSIONS) option(BUILD_DOCS "Build documentation (manpages)" OFF) if(BUILD_DOCS) add_subdirectory(docs) endif() dcm2niix-1.0.20211006/Troubleshooting/000077500000000000000000000000001412761503300171525ustar00rootroot00000000000000dcm2niix-1.0.20211006/Troubleshooting/README.md000066400000000000000000000107661412761503300204430ustar00rootroot00000000000000## About The DICOM standard has become the dominant imaging format in medicine. However, it is necessarily complex. The relative simplicity of NIfTI makes it popular with many scientific tools.Further, to protect participant privacy scientists often wish to anonymize the datasets, removing [protected health information](https://www.hipaajournal.com/considered-phi-hipaa/). It is much easier to ensure a simple format is completely anonymized relative to a complex format.The role of dcm2niix is to convert these images into the simpler NIfTI standard. A challenge is that the DICOM standard is implemented differently by different vendors, and is evolving. Indeed, the complexity of the standard means that many DICOM images do not perfectly conform to the standard. To thoroughly read these images, one needs to develop an understanding of how each vendor has interpreted the DICOM standard. Due to these factors, dcm2niix may not always create the results you expect. This page explains situations where dcm2niix may fail or generate impoverished results. If this page is unable to resolve your problem, you may want to consider creating a [new issue report on the Github web page](https://github.com/rordenlab/dcm2niix/issues). ## Common DICOM Problems The DICOM standard is very complex. In addition, dcm2niix attempts to extract a rich amount of meta data. For example, knowing about diffusion direction, phase encoding polarity and slice timing are all important details for scientists. Yet many of these details are not described by public DICOM tags. Therefore, dcm2niix must determine the vendor that generated the images. This allows dcm2niix to decipher the private tags used by the vendors. While the community has reversed-engineered data from the major vendors, data from unfamiliar vendors can cause issues. Further, tools that attempt to anonymize or otherwise modify DICOM data often inadvertently remove vital meta data or corrupt the DICOM images. If you have a problem with a DICOM scan, your first step should be to inspect the DICOM header understand the providence of the image. The DICOM images created by the scanner are often saved in slightly altered forms by other tools that attempt to anonymize or process the images. It is typically these tools that effectively corrupt the images. In particular, the recent Enhanced DICOM images generated by Siemens Vida scanners and Philips scanners use a recent variation of DICOM. Many older DICOM tools are not familiar with this dialect, and corrupt every enhanced DICOM image they receive. Therefore, if you have a problem, it is worthwhile to first get an image direct from your scanner. If this raw data converts correctly, it suggests a subsequent tool is responsible for tampering with the data. In general, you should be very cautious to use any tool to manipulate your raw DICOM data. The complexity of DICOM makes them very fragile. Attempts to change the transfer syntax (e.g. internally compress), anonymize or change the value representation from Explicit to Implicit can all corrupt some forms of DICOM data. Archival quality data should be tampered with as little as possible. If you are unsure of the providence of your image, you can inspect the DICOM header. MacOS users can use the free [Horos](https://horosproject.org/) application to inspect the meta data using a graphical interface. The command line tools [dcmdump](https://support.dcmtk.org/docs/dcmdump.html) and [gdcmdump](http://gdcm.sourceforge.net/html/gdcmdump.html). For example, typing 'dcmconv myDICOM.dcm > log.txt' will generate text file of your complete DICOM header. The first public tag to check is the Manufacturer (0008,0070): the major vendors are Siemens, Philips and GE. Data from other vendors is likely to have more issues. The next tag to check is the Implementation Version Name and the SourceApplicationEntityTitle (0002,0016) - these is the tool that created your image. For images direct off the scanner this will usually be named after the scanner model (e.g. a Siemens E11C Prisma might report "MR_VE11C", a Philips Examination Workstation might report "EWS R2.5'). In general, the most popular free tools have been used by many users, and generate good DICOM images. For example, "dcm4che","GDCM" and "OFFIS DCMTK" all refer to popular and robust tools. On the other hand, "MATLAB IPT 9.4" suggests that the file was manipulated by a Matlab script, and since it is so easy to generate Matlab scripts it is possible that the user did not consider all the possible unintended consequences of manipulating the image. dcm2niix-1.0.20211006/UIH/000077500000000000000000000000001412761503300144105ustar00rootroot00000000000000dcm2niix-1.0.20211006/UIH/README.md000066400000000000000000000107361412761503300156760ustar00rootroot00000000000000## About dcm2niix attempts to convert UIH DICOM format images to NIfTI. ## Notes Shan C Young provided the [following information](https://github.com/rordenlab/dcm2niix/issues/225), which is used by dcm2niix to generate NIfTI and BIDS format files. UIH supports two ways of archiving the DWI/DTI and fMRI data. One way is one DICOM file per slice and the other is one dicom file per volume (UIH refers to this as GRID format, similar to the Siemens Mosaic format). The private tags used in the images are shown in the following table. | Tag ID | Tag Name | VR | VM | Description | Sample | |-----------|-----------|----|-----|--------------|---------| |0019,1028 | Bandwidth PerPixel Phase Encode | FD | 1 | Useful for TotalReadoutTime | 37.6| |0061,1002 | Generate Private | US | 1 | Flag to generate private format file | 1| |0061,4002 | FOV | SH | 1 | FOV(mm) | 224*224| |0065,1000 | MeasurmentUID | UL | 1 | Measurement UID of Protocol | 12547865| |0065,1002 | ImageOrientationDisplayed | SH | 1 | Image Orientation Displayed | Sag or Sag>Cor| |0065,1003 | ReceiveCoil | LO | 1 | Receive Coil Information | H 8| |0065,1004 | Interpolation | SH | 1 | Interpolation | I| |0065,1005 | PE Direction Displayed | SH | 1 | Phase encoding diretion displayed | A->P or H->F| |0065,1006 | Slice Group ID | IS | 1 | Slice Group ID | 1| |0065,1007 | Uprotocol | OB | 1 | Uprotocol value |  | |0065,1009 | BActualValue | FD | 1 | Actual B-Value from sequence | 1000.0| |0065,100A | BUserValue | FD | 1 | User Choose B-Value from UI | 1000.0| |0065,100B | Block Size | DS | 1 | Size of the paradigm/block | 10| |0065,100C | Experimental status | SH | 1 | fMRI | rest/active| |0065,100D | Parallel Information | SH | 1 | ratio of parallel acquisition and acceleration |  | |0065,100F | Slice Position | SH | 1 | Slice location displayed on the screen | H23.4| |0065,1011 | Sections | SH | 1 |   |  | |0065,1013 | InPlaneRotAngle | FD(°) | 1 | Rotation angle in the plane | -0.5936| |0065,1014 | SliceNormalVector | DS | 3 | Normal vector of the slice | 0\0\1| |0065,1015 | SliceCenterPosition | DS | 3 | Center position of the slice | 0\0\0| |0065,1016 | PixelRotateModel | UL | 1 | Pixel Rotation Model | 4| |0065,1017 | SAR Model | LO | 1 | Calculation model of SAR value | Normal:WHBST| |0065,1018 | dB/dt Model | LO | 1 | Calculation model of dB/dt | Normal| |0065,1023 | TablePosition | LO | 1 | Table Position | 0| |0065,1025 | Slice Gap | DS | 1 | Slice Gap | 0.0| |0065,1029 | AcquisitionDuration | SH | 1 | Acquisition Duration | 0.03| |0065,102B | ApplicationCategory | LT | 1 | Application names available | DTI\Func| |0065,102C | RepeatitionIndex | IS | 1 |   | 0| |0065,102D | SequenceDisplayName | ST | 1 | Sequence display name | Epi_dti_b0| |0065,102E | NoiseDecovarFlag | LO | 1 | Noise decorrelation flag | PreWhite| |0065,102F | ScaleFactor | FL | 1 | scale factor | 2.125| |0065,1031 | MRSequenceVariant | SH | 1 | SequenceVariant |  | |0065,1032 | MRKSpaceFilter | SH | 1 | K space filter |  | |0065,1033 | MRTableMode | SH | 1 | Table mode | Fix| |0065,1036 | MRDiscoParameter | OB | 1 |   |  | |0065,1037 | MRDiffusionGradOrientation | FD | 3 | Diffusion gradient orientation | 0\0\0| |0065,1038 | MRPerfusionNoiseLevel | FD | 1 | epi_dwi/perfusion noise level | 40| |0065,1039 | MRGradRange | SH | 6 | linear range of gradient | 0.0\157\0.0\157\0.0\125| |0065,1050 | MR Number Of Slice In Volume | DS | 1 | Number Of Frames In a Volume,Columns of each frame: cols =ceil(sqrt(total)) ; Rows of each frame: rows =ceil(total/cols) ; appeared when image type (00080008) has VFRAME | 27| |0065,1051 | MR VFrame Sequence | SQ | 1 | 1 |  | | :0008,0022 | Acquisition Date | DA | 1 |   |  | | :0008,0032 | Acquisition Time | TM | 1 |   |  | | :0008,002A | Acquisition DateTime | DT | 1 |   |  | | :0020,0032 | ImagePosition(Patient) | DS | 3 |   |  | | :0020,1041 | Slice Location | DS | 1 |   |  | | :0018,9073 | Acquisition Duration | FD | 1 |   |  | | :0065,100C | MRExperimental Status | SH | 1 |   | rest/active| The tag BandwidthPerPixelPhaseEncode (0019,1028) can be used to determine the [FSL definition for TotalReadoutTime](https://github.com/rordenlab/dcm2niix/issues/531). Specifically, TotalReadoutTime = effectiveEchoSpacing * (reconMatrixPE-1.0)). ## Sample Datasets - [UIH has provided a reference dataset](https://1drv.ms/f/s!Avf7THyflzj1gnO37GL2I8Hk-0MV). - [A validation dataset for dcm2niix commits](https://github.com/neurolabusc/dcm_qa_uih).dcm2niix-1.0.20211006/VERSIONS.md000066400000000000000000000213031412761503300155540ustar00rootroot00000000000000## Versions 14-November-2018 - [GE images provide more BIDS tags](https://github.com/rordenlab/dcm2niix/issues/163). - [Bruker enhanced DICOM support](https://github.com/rordenlab/dcm2niix/issues/241). - [Siemens Vida XA10 support](https://github.com/rordenlab/dcm2niix/issues/240). Note that Vida DICOM data is crippled [if the user exports as mosaics or anonymized/reduced](https://github.com/rordenlab/dcm2niix/issues/236). - [UIH enhanced DICOM support](https://github.com/rordenlab/dcm2niix/issues/225). - New DICOM [renaming](RENAMING.md) feature. 22-June-2018 - [Fix issues where 6-June-2018 release could save Enhanced DICOM Philips bvec/bval with different order than .nii images](https://github.com/rordenlab/dcm2niix/issues/201). 6-June-2018 - [Improved Philips PAR/REC support](https://github.com/rordenlab/dcm2niix/issues/171) - [Improved Philips Enhanced DICOM support](https://github.com/rordenlab/dcm2niix/issues/170) including saving different [real, imaginary, magnitude and phase images in a single DICOM file](https://github.com/rordenlab/dcm2niix/issues/189). - GE and Philips data now report [PhaseEncodingAxis](https://github.com/rordenlab/dcm2niix/issues/163) instead of PhaseEncodingDirection (these DICOMs store the dimension, but not the polarity). - Experimental detection of [phase encoding direction for GE](https://github.com/rordenlab/dcm2niix/issues/163). To enable compile with "MY_DEBUG_GE" flag. - Support for Philips Private RLE (1.3.46.670589.33.1.4.1) transfer syntax. - Optional support for JPEG-LS (1.2.840.10008.1.2.4.80/1.2.840.10008.1.2.4.81) transfer syntaxes (using [CharLS](https://github.com/team-charls/charls)). Requires c++14. - [Improved GE support](https://github.com/rordenlab/dcm2niix/issues/163) - Optional [lossless integer scaling](https://github.com/rordenlab/dcm2niix/issues/198) for INT16 and UINT16 DICOM images that only use a fraction of the possible range. 15-Dec-2017 - Support [Siemens XA10 images](https://github.com/rordenlab/dcm2niix/pull/145). - [Ability to select specific series to convert](https://github.com/rordenlab/dcm2niix/pull/146). 4-Dec-2017 - Handle implicit VR DICOMs where [critical values nested in sequence groups (SQ)](https://github.com/rordenlab/dcm2niix/commit/7f5649c6fe6ed366d07776aa54397b50f6641aff) - Better support for [PAR/REC files with segmented 3D EPI](https://github.com/rordenlab/dcm2niix/commit/66cdf2dcc60d55a6ef37f5a6db8d500d3eeb7c88). - Allow Protocol Name to be [empty](https://github.com/rordenlab/dcm2niix/commit/94f3129898ba83bf310c9ff28e994f29feb13068). 17-Oct-2017 - Swap [phase-encoding direction polarity](https://github.com/rordenlab/dcm2niix/issues/125) for Siemens images where PE is in the Column direction. - Sort diffusion volumes by [B-value amplitude](https://www.nitrc.org/forum/forum.php?thread_id=8396&forum_id=4703) (use "-d n"/"-d y" to turn the feature off/on). - BIDS tag [TotalReadoutTime](https://github.com/rordenlab/dcm2niix/issues/130) handles partial fourier, Phase Resolution, etc (Michael Harms). - Additional [json fields](https://github.com/rordenlab/dcm2niix/issues/127). 18-Aug-2017 - Better BVec extraction for [PAR/REC 4.1](https://www.nitrc.org/forum/forum.php?thread_id=8387&forum_id=4703). - Support for [Segami Cerescan volumes](https://www.nitrc.org/forum/forum.php?thread_id=8076&forum_id=4703). 24-July-2017 - Compiles with recent releases of [OpenJPEG](https://github.com/neurolabusc/dcm_qa/issues/5#issuecomment-317443179) for JPEG2000 support. 23-June-2017 - [Ensure slice timing always reported for Siemens EPI](https://github.com/neurolabusc/dcm_qa/issues/4#issuecomment-310707906) - [Integrates validation](https://github.com/neurolabusc/dcm_qa) - JSON fix (InstitutionName -> InstitutionAddress) 21-June-2017 - Read DICOM header in 1Mb segments rather than loading whole file : reduces ram usage and [faster for systems with slow io](https://github.com/rordenlab/dcm2niix/issues/104). - Report [TotalReadoutTime](https://github.com/rordenlab/dcm2niix/issues/98). - Fix JPEG2000 support in [Superbuild](https://github.com/rordenlab/dcm2niix/issues/105). 28-May-2017 - Remove all derived images from [Philips DTI series](http://www.nitrc.org/forum/message.php?msg_id=21025). - Provide some [Siemens EPI sequence details](https://github.com/rordenlab/dcm2niix/issues). 28-April-2017 - Experimental [ECAT support](https://github.com/rordenlab/dcm2niix/issues/95). - Updated cmake to make JPEG2000 support easier with improved Travis and AppVeyor support [Ningfei Li](https://github.com/ningfei). - Supports Data/Time for images that report Data/Time (0008,002A) but not separate Date and Time (0008,0022 and 0008,0032). - [BIDS reports SliceTiming correctly](http://www.nitrc.org/forum/message.php?msg_id=20852). - Options -1..-9 to control [gz compression level](https://github.com/rordenlab/dcm2niix/issues/90). - Includes some [PET details in the BIDS JSON sidecar](https://github.com/rordenlab/dcm2niix/issues/87). - Better detection of image order for Philips 4D DICOM (reported by Jason McMorrow and Stephen Wilson). - [Include StudyInstanceUID and SeriesInstanceUID in filename](https://github.com/rordenlab/dcm2niix/issues/94). 7-Feb-2017 - Can be compiled to use either Philips [Float or Display](http://www.nitrc.org/forum/message.php?msg_id=20213) intensity intercept and slope values. - Handle 3D Philips DICOM and [PAR/REC](https://www.nitrc.org/forum/forum.php?thread_id=7707&forum_id=4703) files where images are not stored in a spatially contiguous order. - Handle DICOM violations where icon is uncompressed but image data is compressed. - Best guess matrix for 2D slices (similar to dcm2nii, SPM and MRIconvert). - Linux (case sensitive filenames) now handles par/rec as well as PAR/REC. - Images with unknown phase encoding do not generate [BIDS entry](https://github.com/rordenlab/dcm2niix/issues/79). - Unified printMessage/printWarning/printError aids embedding in other projects, such as [divest](https://github.com/jonclayden/divest). 1-Nov-2016 - AppVeyor Support (Ningfei Li & Chris Filo Gorgolewski) - Swap 3rd/4th dimensions for GE sequential multi-phase acquisitions (Niels Janssen). 10-Oct-2016 - Restores/improves building for the Windows operating system using MinGW. 30-Sept-2016 - Save ImageType (0x0008,0x0008) to BIDS. - Separate CT scans with different exposures. - Fixed issues where some compilers would generate erratic filenames for zero-padded series (e.g. "-f %3s"). 21-Sept-2016 - Reduce verbosity (reduce number of repeated warnings, less scary warnings for derived rather than raw images). - Re-enable custom output directory "-o" option broken by 30-Apr-2016 version. - Deal with mis-behaved GE CT images where slice direction across images is not consistent. - Add new BIDS fields (field strength, manufacturer, etc). - Philips PAR/REC conversion now reports inconsistent requested vs measured TR (due to prospect. motion corr.?). - GE: Locations In Acquisition (0054, 0081) is inaccurate if slices are interpolated, use Images In Acquisition (0020,1002) if available. - New filename options %d Series description (0008,103E), %z Sequence Name (0018,0024). - New filename options %a antenna (coil) number, %e echo number. - Initialize unused portions of NIfTI header to zero so multiple runs always produce identical results. - Supports 3D lossless JPEG saved as [multiple fragments](http://www.nitrc.org/forum/forum.php?thread_id=5872&forum_id=4703). 5-May-2016 - Crop 3D T1 acquisitions (e.g. ./dcm2niix -x y ~/DICOM). 30-Apr-2016 - Convert multiple files/folders with single command line invocation (e.g. ./dcm2niix -b y ~/tst ~/tst2). 22-Apr-2016 - Detect Siemens Phase maps (phase image names end with "_ph"). - Use current working directory if file name not specified. 12-Apr-2016 - Provide override (command line option "-m y") to stack images of the same series even if they differ in study date/time, echo/coil number, or slice orientation. This mechanism allows users to concatenate images that break strict DICOM compliance. 22-Mar-2016 - Experimental support for [DICOM datasets without DICOM file meta information](http://dicom.nema.org/dicom/2013/output/chtml/part10/chapter_7.html). 12-Dec-2015 - Support PAR/REC FP values when possible (see PMC3998685). 11-Nov-2015 - Minor refinements. 12-June-2015 - Uses less memory (helpful for large datasets). 2-Feb-2015 - Support for Visual Studio. - Remove dependency on zlib (now uses miniz). 1-Jan-2015 - Images separated based on TE (fieldmaps). - Support for JPEG2000 using OpenJPEG or Jasper libraries. - Support for JPEG using NanoJPEG library. - Support for lossless JPEG using custom library. 24-Nov-2014 - Support for CT scans with gantry tilt and varying distance between slices. 11-Oct-2014 - Initial public release. dcm2niix-1.0.20211006/appveyor.yml000066400000000000000000000025051412761503300163550ustar00rootroot00000000000000version: build-{build} image: Visual Studio 2015 configuration: Release platform: x64 clone_depth: 1 clone_folder: c:\projects\dcm2niix init: - ps: >- $env:DATE = $(Get-Date -Format d-MMM-yyyy) $githash = $env:APPVEYOR_REPO_COMMIT.Substring(0, 7) $gittag = if ($env:APPVEYOR_REPO_TAG -eq $True) {"_$($env:APPVEYOR_REPO_TAG_NAME)"} else {""} Update-AppveyorBuild -Version "$($env:DATE)_g${githash}${gittag}" $env:release_version = $(Get-Date -Format d-MMMM-yyyy) before_build: - cmd: >- echo "Running cmake" mkdir c:\projects\dcm2niix\build cd c:\projects\dcm2niix\build cmake -Wno-dev -G "Visual Studio 14 2015 Win64" -DBATCH_VERSION=ON -DUSE_OPENJPEG=ON -DUSE_JPEGLS=true ..\ build: project: c:\projects\dcm2niix\build\dcm2niix.sln verbosity: normal after_build: - ps: >- cd c:\projects\dcm2niix 7z a dcm2niix_win.zip c:\projects\dcm2niix\build\bin\* >$null artifacts: - path: dcm2niix*.zip name: dcm2niix deploy: - provider: GitHub tag: $(appveyor_repo_tag_name) release: version $(release_version) ($(appveyor_repo_tag_name)) description: "" auth_token: secure: gCltVLQEWsjSTRlsi8qw7FGP54ujBq60apjXkWTV954b65bOHl95hXMxxkQ734L4 artifact: dcm2niix draft: false prerelease: false on: branch: master appveyor_repo_tag: true dcm2niix-1.0.20211006/batch_config.yml000066400000000000000000000006251412761503300171170ustar00rootroot00000000000000Options: isGz: false isFlipY: false isVerbose: false isCreateBIDS: false isOnlySingleFile: false Files: - in_dir: /path/to/first/folder out_dir: /path/to/output/folder filename: dcemri - in_dir: /path/to/second/folder out_dir: /path/to/output/folder filename: fa3 dcm2niix-1.0.20211006/console/000077500000000000000000000000001412761503300154255ustar00rootroot00000000000000dcm2niix-1.0.20211006/console/CMakeLists.txt000066400000000000000000000173661412761503300202020ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.11) project(console) # Option Choose whether to use static runtime include(ucm.cmake) option(USE_STATIC_RUNTIME "Use static runtime" ON) if(USE_STATIC_RUNTIME) ucm_set_runtime(STATIC) else() ucm_set_runtime(DYNAMIC) endif() # Basic CMake build settings if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;RelWithDebInfo;MinSizeRel") endif() if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") # using Clang add_definitions(-fno-caret-diagnostics) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip") elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") # using GCC if(NOT (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 7.1.0)) add_definitions(-Wno-format-overflow) # available since GCC 7.1.0 endif() if(NOT (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 4.5.0)) add_definitions(-Wno-unused-result) # available since GCC 4.5.0 endif() if(NOT (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 4.8.0)) add_definitions(-fno-diagnostics-show-caret) # available since GCC 4.8.0 endif() elseif(MSVC) # using Visual Studio C++ add_definitions(-D_CRT_SECURE_NO_DEPRECATE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4018") # '<': signed/unsigned mismatch set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4068") # unknown pragma set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4101") # unreferenced local variable set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4244") # 'initializing': conversion from 'double' to 'int', possible loss of data set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267") # 'initializing': conversion from 'size_t' to 'int', possible loss of data set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4305") # 'argument': truncation from 'double' to 'float' set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4308") # negative integral constant converted to unsigned type set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4334") # '<<': result of 32-bit shift implicitly converted to 64 bits set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800") # 'uint32_t' : forcing value to bool 'true' or 'false' set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819") # The file contains a character that cannot be represented in the current code page set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4996") # 'access': The POSIX name for this item is deprecated set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:8388608") # set "Stack Reserve Size" to 8MB (default value is 1MB) endif() # Compiler dependent flags include (CheckCXXCompilerFlag) if(UNIX) check_cxx_compiler_flag(-march=armv8-a+crc ARM_CRC) if(ARM_CRC) # wrong answer for Apple Silicon: check_cxx_compiler_flag(-msse2 HAS_SSE2) else() check_cxx_compiler_flag(-msse2 HAS_SSE2) if(HAS_SSE2) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2 -mfpmath=sse") endif() endif() endif() set(PROGRAMS dcm2niix) set(DCM2NIIX_SRCS main_console.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_foreign.cpp nii_ortho.cpp nii_dicom_batch.cpp) option(USE_JPEGLS "Build with JPEG-LS support using CharLS" OFF) if(USE_JPEGLS) add_definitions(-DmyEnableJPEGLS) if(MSVC) add_definitions(-DCHARLS_STATIC) endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") set(CHARLS_SRCS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp) add_executable(dcm2niix ${DCM2NIIX_SRCS} ${CHARLS_SRCS}) else() add_executable(dcm2niix ${DCM2NIIX_SRCS}) endif() set(ZLIB_IMPLEMENTATION "Miniz" CACHE STRING "Choose zlib implementation.") set_property(CACHE ZLIB_IMPLEMENTATION PROPERTY STRINGS "Miniz;System;Custom") if(NOT ${ZLIB_IMPLEMENTATION} STREQUAL "Miniz") if(NOT ${ZLIB_IMPLEMENTATION} STREQUAL "System") set(ZLIB_ROOT ${ZLIB_ROOT} CACHE PATH "Specify custom zlib root directory.") if(NOT ZLIB_ROOT) message(FATAL_ERROR "ZLIB_ROOT needs to be set to locate custom zlib!") endif() endif() find_package(ZLIB REQUIRED) add_definitions(-DmyDisableMiniZ) target_include_directories(dcm2niix PRIVATE ${ZLIB_INCLUDE_DIRS}) 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) add_definitions(-DmyTurboJPEG) target_include_directories(dcm2niix PRIVATE ${TURBOJPEG_INCLUDEDIR}) 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) target_include_directories(dcm2niix PRIVATE ${JASPER_INCLUDE_DIR}) 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) find_package(OpenJPEG REQUIRED) if(WIN32) if(BUILD_SHARED_LIBS) add_definitions(-DOPJ_EXPORTS) else() add_definitions(-DOPJ_STATIC) endif() endif() target_include_directories(dcm2niix PRIVATE ${OPENJPEG_INCLUDE_DIRS}) target_link_libraries(dcm2niix ${OPENJPEG_LIBRARIES}) else () add_definitions(-DmyDisableOpenJPEG) endif() option(BATCH_VERSION "Build dcm2niibatch for multiple conversions" OFF) if(BATCH_VERSION) set(DCM2NIIBATCH_SRCS main_console_batch.cpp nii_dicom.cpp jpg_0XC3.cpp ujpeg.cpp nifti1_io_core.cpp nii_foreign.cpp nii_ortho.cpp nii_dicom_batch.cpp) if(USE_JPEGLS) add_executable(dcm2niibatch ${DCM2NIIBATCH_SRCS} ${CHARLS_SRCS}) else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") add_executable(dcm2niibatch ${DCM2NIIBATCH_SRCS}) endif() set(YAML-CPP_DIR ${YAML-CPP_DIR} CACHE PATH "Path to yaml-cpp configuration file" FORCE) find_package(YAML-CPP REQUIRED) target_include_directories(dcm2niibatch PRIVATE ${YAML_CPP_INCLUDE_DIR}) target_link_libraries(dcm2niibatch ${YAML_CPP_LIBRARIES}) if(ZLIB_FOUND) target_include_directories(dcm2niibatch PRIVATE ${ZLIB_INCLUDE_DIRS}) target_link_libraries(dcm2niibatch ${ZLIB_LIBRARIES}) endif() if(TURBOJPEG_FOUND) target_include_directories(dcm2niibatch PRIVATE ${TURBOJPEG_INCLUDEDIR}) target_link_libraries(dcm2niibatch ${TURBOJPEG_LIBRARIES}) endif() if(JASPER_FOUND) target_include_directories(dcm2niibatch PRIVATE ${JASPER_INCLUDE_DIR}) target_link_libraries(dcm2niibatch ${JASPER_LIBRARIES}) endif() if(OPENJPEG_FOUND) target_include_directories(dcm2niibatch PRIVATE ${OPENJPEG_INCLUDE_DIRS}) target_link_libraries(dcm2niibatch ${OPENJPEG_LIBRARIES}) endif() list(APPEND PROGRAMS dcm2niibatch) endif() if(APPLE) message("-- Adding Apple plist") set_target_properties(dcm2niix PROPERTIES LINK_FLAGS "-Wl,-sectcreate,__TEXT,__info_plist,${CMAKE_SOURCE_DIR}/Info.plist") #Apple notarization requires a Info.plist # For .app bundles, the Info.plist is a separate file, for executables it is appended as a section #you can check that the Info.plist section has been inserted with either of these commands # otool -l ./dcm2niix | grep info_plist -B1 -A10 # launchctl plist ./dcm2niix endif() install(TARGETS ${PROGRAMS} DESTINATION bin) dcm2niix-1.0.20211006/console/Info.plist000066400000000000000000000012771412761503300174040ustar00rootroot00000000000000 CFBundleExecutable dcm2niix CFBundleIdentifier com.mricro.dcm2niix CFBundleInfoDictionaryVersion 6.0 CFBundleName dcm2niix CFBundleShortVersionString 1.0 CFBundleVersion 1 CFBundleSupportedPlatforms MacOSX CFBundlePackageType APPL dcm2niix-1.0.20211006/console/charls/000077500000000000000000000000001412761503300167015ustar00rootroot00000000000000dcm2niix-1.0.20211006/console/charls/License.txt000066400000000000000000000030211412761503300210200ustar00rootroot00000000000000https://github.com/team-charls/charls Copyright (c) 2007-2010, Jan de Vaan All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of my employer, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. dcm2niix-1.0.20211006/console/charls/README.md000066400000000000000000000220071412761503300201610ustar00rootroot00000000000000[![Build status](https://ci.appveyor.com/api/projects/status/yq0naf3v2m8nfa8r/branch/master?svg=true)](https://ci.appveyor.com/project/vbaderks/charls/branch/master) [![Build Status](https://travis-ci.org/team-charls/charls.svg?branch=master)](https://travis-ci.org/team-charls/charls) # CharLS CharLS is a C++ implementation of the JPEG-LS standard for lossless and near-lossless image compression and decompression. JPEG-LS is a low-complexity image compression standard that matches JPEG 2000 compression ratios. # CharLS and dcm2niix [CharLS](https://github.com/team-charls/charls) is an optional module for dcm2niix. If included, it allows dcm2niix to handle the [JPEG-LS transfer syntaxes](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#DICOM_Transfer_Syntaxes_and_Compressed_Images). The included code was downloaded from the CharLS website on 6 June 2018. It is worth noting that DICOM can specify three different [lossless forms of the JPEG](http://www.mccauslandcenter.sc.edu/crnl/tools/jpeg-formats) image standard. CharLS is used only for the JPEG-LS form (transfer syntaxes 1.2.840.10008.1.2.4.80/81; ISO/IEC 14495-1:1999 ITU-T.87). In contrast, dcm2niix uses bespoke code to handle the older JPEG-Lossless (1.2.840.10008.1.2.4.57/70; ISO/IEC 10918-1:1994 ITU-T.81). Finally, dcm2niix handles the very complex JPEG-2000 lossless (1.2.840.10008.1.2.4.90/91; ISO/IEC 15444-1:2004 ITU-T.800) using the [OpenJPEG](https://github.com/uclouvain/openjpeg) library. To enable support you will need to include the `myEnableJPEGLS` compiler flag as well as a few file sin the `charls` folder. You will also need to specify `-std=c++14` and use a compiler that supports c++14 or later. Therefore, a minimal compile should look like this: `g++ -I. -std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp 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` The option `myEnableJPEGLS` specifies the latest version of CharLS (currently version 2). Alternatively, you can specify `myEnableJPEGLS1` to compile for CharLS version 1. This older code is not included with dcm2niix, but you can [download it from Github](https://github.com/team-charls/charls/tree/1.x-master). Note that CharLS version 1 is designed for c++03: `g++ -I. -std=c++03 -DmyEnableJPEGLS1 charls1/header.cpp charls1/jpegls.cpp charls1/jpegmarkersegment.cpp charls1/interface.cpp charls1/jpegstreamwriter.cpp 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` Note that in these examples we have disabled OpenJPEG's JPEG2000 support. In reality, you will probably want to support both JPEG2000 and JPEG-LS, this will allow you to convert a broader range of images, and JPEG2000 includes lossy variations that do not have analogues for JPEG-LS. For details on adding the JPEG2000 module see this [compile page](https://github.com/rordenlab/dcm2niix/blob/master/COMPILE.md). # JPEG-LS versus other lossless JPEG codecs You can use gdcmconv to compare the performance of the ancient JPEG-lossless (gdcmconv -J; default mode for dcmcjpeg), JPEG-LS (gdcmconv -L) and JPEG2000-lossess (gdcmconv -K). Below is a sample test looking at 800 DICOM CT scans - with a raw size of 425mb which dcm2niix can convert in 1.6 seconds. The table shows that JPEG-LS reduces the file sizes to 137mb (0.39 original size), but that decompression takes 7.2 times longer. In contrast, the complicated JPEG2000 achieves only slightly better compression but is much slower to decompress. | CT | Size | Speed | | ----------------------------------------- | -----:| -----:| | Raw 1.2.840.10008.1.2.1 | 1.00 | 1.0 | | JPEG-lossless 1.2.840.10008.1.2.4.70 | 0.37 | 6.4 | | JPEG-LS 1.2.840.10008.1.2.4.80 | 0.32 | 7.2 | | JPEG2000 lossless 1.2.840.10008.1.2.4.90 | 0.31 | 60.1 | Below is a sample test looking at 1092 DICOM MRI scans with a Siemens Prisma with 16-bit output (many older systems use 12-bit output which would presumably provide more compression potential) - with a raw size of 425mb which dcm2niix can convert in 4.7 seconds. Note that the MRI scans show poorer compression for all techniques. | MRI | Size | Speed | | ----------------------------------------- | -----:| -----:| | Raw 1.2.840.10008.1.2.1 | 1.00 | 1.0 | | JPEG-lossless 1.2.840.10008.1.2.4.70 | 0.65 | 5.0 | | JPEG-LS 1.2.840.10008.1.2.4.80 | 0.60 | 7.7 | | JPEG2000 lossless 1.2.840.10008.1.2.4.90 | 0.61 | 71.6 | The tables above describe illustrate the speed for decompression. With respect to compression, the MRI images take 54 seconds to compress as JPEG-lossless, 72 seconds to compress as JPEG-LS and 212 seconds for JPEG2000 lossless. These tests support the notion that JPEG-LS provides similar compression to JPEG2000 lossless with much faster compression and decompression. In fairness, it should be noted that all these tests use open source OpenJPEG library for JPEG2000 compression and decompression. This library is known to be robust but [slow compared to proprietary libraries](https://blog.hexagongeospatial.com/jpeg2000-quirks/). ## Features * C++14 library implementation with a binary C interface for maximum interoperability.
Note: a C++03 compatible implementation is maintained in the 1.x-master branch. * Supports Windows, Linux and Solaris in 32 bit and 64 bit. * Includes an adapter assembly for .NET based languages. * Excellent compression and decompression performance. ## About JPEG-LS JPEG-LS (ISO/IEC 14495-1:1999 / ITU-T.87) is an image compression standard derived from the Hewlett Packard LOCO algorithm. JPEG-LS has low complexity (meaning fast compression) and high compression ratios, similar to the JPEG 2000 lossless ratios. JPEG-LS is more similar to the old Lossless JPEG than to JPEG 2000, but interestingly the two different techniques result in vastly different performance characteristics. Wikipedia on lossless JPEG and JPEG-LS: Tip: the ITU makes their version of the JPEG-LS standard (ITU-T.87) freely available for download, the text is identical with the ISO version. ## About this software This project's goal is to provide a full implementation of the ISO/IEC 14495-1:1999, "Lossless and near-lossless compression of continuous-tone still images: Baseline" standard. This library is written from scratch in portable C++. The master branch uses modern C++14. The 1.x branch is maintained in C++03. All mainstream JPEG-LS features are implemented by this library. According to preliminary test results published on http://imagecompression.info/gralic, CharLS is about *twice as fast* as the original HP code, and beats both JPEG-XR and JPEG 2000 by a factor 3. ### Limitations * No support for (optional) JPEG restart markers (RST). These markers are rarely used in practice. * No support for the SPIFF file header. * No support for oversize image dimension. Maximum supported image dimensions are [1, 65535] by [1, 65535]. * After releasing the original baseline standrd 14495-1:1999, ISO released an extension to the JPEG-LS standard called ISO/IEC 14495-2:2003: "Lossless and near-lossless compression of continuous-tone still images: Extensions". CharLS doesn't support these extensions. ## Supported platforms The code is regularly compiled/tested on Windows and 64 bit Linux. Additionally, the code has been successfully tested on Linux Intel/AMD 32/64 bit (slackware, debian, gentoo), Solaris SPARC systems, Intel based Macs and Windows CE (ARM CPU, emulated), where the less common compilers may require minor code edits. It leverages C++ language features (templates, traits) to create optimized code, which generally perform best with recent compilers. If you compile with GCC, 64 bit code performs substantially better. ## Users & Acknowledgements CharLS is being used by [GDCM DICOM toolkit](http://sourceforge.net/projects/gdcm/), thanks for [Mathieu Malaterre](http://sourceforge.net/users/malat) for getting CharLS started on Linux. [Kato Kanryu](http://knivez.homelinux.org/) wrote an initial version of the color transfroms and the DIB output format code, for an [irfanview](http://www.irfanview.com) plugin using CharLS. Thanks to Uli Schlachter, CharLS now finally runs correctly on big-endian architectures like Sun SPARC. ## Legal The code in this project is available through a BSD style license, allowing use of the code in commercial closed source applications if you wish. **All** the code in this project is written from scratch, and not based on other JPEG-LS implementations. Be aware that Hewlett Packard claims to own patents that apply to JPEG-LS implementations, but they license it for free for conformant JPEG-LS implementations. Read more at before you use this if you use this code for commercial purposes. dcm2niix-1.0.20211006/console/charls/charls.h000066400000000000000000000111211412761503300203220ustar00rootroot00000000000000/* (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. */ #ifndef CHARLS_CHARLS #define CHARLS_CHARLS #include "publictypes.h" // Windows and building CharLS DLL itself. #if defined(WIN32) && defined(CHARLS_DLL_BUILD) #define CHARLS_IMEXPORT(returntype) __declspec(dllexport) returntype __stdcall // NOLINT #endif // Non-windows (static linking) #if !defined(CHARLS_IMEXPORT) && !defined(_WIN32) #define CHARLS_IMEXPORT(returntype) returntype #endif // Windows static linking #if !defined(CHARLS_IMEXPORT) && defined(CHARLS_STATIC) #define CHARLS_IMEXPORT(returntype) returntype #endif // Windows DLL #if !defined(CHARLS_IMEXPORT) && defined(CHARLS_DLL) #define CHARLS_IMEXPORT(returntype) __declspec(dllimport) returntype __stdcall #endif #if !defined(CHARLS_IMEXPORT) #error Please #define CHARLS_STATIC or CHARLS_DLL before including "charls.h" to indicate if CharLS is built as a static library or as a dll. #endif #ifdef __cplusplus extern "C" { #else #include #endif /// /// Encodes a byte array with pixel data to a JPEG-LS encoded (compressed) byte array. /// /// Byte array that holds the encoded bytes when the function returns. /// Length of the array in bytes. If the array is too small the function will return an error. /// This parameter will hold the number of bytes written to the destination byte array. Cannot be NULL. /// Byte array that holds the pixels that should be encoded. /// Length of the array in bytes. /// Parameter object that describes the pixel data and how to encode it. /// Character array of at least 256 characters or NULL. Hold the error message when a failure occurs, empty otherwise. CHARLS_IMEXPORT(CharlsApiResultType) JpegLsEncode(void* destination, size_t destinationLength, size_t* bytesWritten, const void* source, size_t sourceLength, const struct JlsParameters* params, char* errorMessage); /// /// Retrieves the JPEG-LS header. This info can be used to pre-allocate the uncompressed output buffer. /// /// Byte array that holds the JPEG-LS encoded data of which the header should be extracted. /// Length of the array in bytes. /// Parameter object that describes how the pixel data is encoded. /// Character array of at least 256 characters or NULL. Hold the error message when a failure occurs, empty otherwise. CHARLS_IMEXPORT(CharlsApiResultType) JpegLsReadHeader(const void* compressedData, size_t compressedLength, struct JlsParameters* params, char* errorMessage); /// /// Encodes a JPEG-LS encoded byte array to uncompressed pixel data byte array. /// /// Byte array that holds the uncompressed pixel data bytes when the function returns. /// Length of the array in bytes. If the array is too small the function will return an error. /// Byte array that holds the JPEG-LS encoded data that should be decoded. /// Length of the array in bytes. /// Parameter object that describes the pixel data and how to decode it. /// Character array of at least 256 characters or NULL. Hold the error message when a failure occurs, empty otherwise. CHARLS_IMEXPORT(CharlsApiResultType) JpegLsDecode(void* destination, size_t destinationLength, const void* source, size_t sourceLength, const struct JlsParameters* params, char* errorMessage); CHARLS_IMEXPORT(CharlsApiResultType) JpegLsDecodeRect(void* uncompressedData, size_t uncompressedLength, const void* compressedData, size_t compressedLength, struct JlsRect roi, const struct JlsParameters* info, char* errorMessage); #ifdef __cplusplus } CHARLS_IMEXPORT(CharlsApiResultType) JpegLsEncodeStream(ByteStreamInfo compressedStreamInfo, size_t& pcbyteWritten, ByteStreamInfo rawStreamInfo, const JlsParameters& params, char* errorMessage); CHARLS_IMEXPORT(CharlsApiResultType) JpegLsDecodeStream(ByteStreamInfo rawStream, ByteStreamInfo compressedStream, const JlsParameters* info, char* errorMessage); CHARLS_IMEXPORT(CharlsApiResultType) JpegLsReadHeaderStream(ByteStreamInfo rawStreamInfo, JlsParameters* params, char* errorMessage); #endif #endif dcm2niix-1.0.20211006/console/charls/colortransform.h000066400000000000000000000133601412761503300221270ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_COLORTRANSFORM #define CHARLS_COLORTRANSFORM #include "util.h" // This file defines simple classes that define (lossless) color transforms. // They are invoked in processline.h to convert between decoded values and the internal line buffers. // Color transforms work best for computer generated images, but are outside the official JPEG-LS specifications. template struct TransformNoneImpl { static_assert(std::is_integral::value, "Integral required."); using size_type = T; FORCE_INLINE Triplet operator()(int v1, int v2, int v3) const noexcept { return Triplet(v1, v2, v3); } }; template struct TransformNone : TransformNoneImpl { static_assert(std::is_integral::value, "Integral required."); using Inverse = TransformNoneImpl; }; template struct TransformHp1 { static_assert(std::is_integral::value, "Integral required."); using size_type = T; struct Inverse { explicit Inverse(const TransformHp1&) noexcept { } FORCE_INLINE Triplet operator()(int v1, int v2, int v3) const noexcept { return Triplet(v1 + v2 - Range / 2, v2, v3 + v2 - Range / 2); } }; FORCE_INLINE Triplet operator()(int red, int green, int blue) const noexcept { Triplet hp1; hp1.v2 = static_cast(green); hp1.v1 = static_cast(red - green + Range / 2); hp1.v3 = static_cast(blue - green + Range / 2); return hp1; } private: static constexpr size_t Range = 1 << (sizeof(T) * 8); }; template struct TransformHp2 { static_assert(std::is_integral::value, "Integral required."); using size_type = T; struct Inverse { explicit Inverse(const TransformHp2&) noexcept { } FORCE_INLINE Triplet operator()(int v1, int v2, int v3) const noexcept { Triplet rgb; rgb.R = static_cast(v1 + v2 - Range / 2); // new R rgb.G = static_cast(v2); // new G rgb.B = static_cast(v3 + ((rgb.R + rgb.G) >> 1) - Range / 2); // new B return rgb; } }; FORCE_INLINE Triplet operator()(int red, int green, int blue) const noexcept { return Triplet(red - green + Range / 2, green, blue - ((red + green) >> 1) - Range / 2); } private: static constexpr size_t Range = 1 << (sizeof(T) * 8); }; template struct TransformHp3 { static_assert(std::is_integral::value, "Integral required."); using size_type = T; struct Inverse { explicit Inverse(const TransformHp3&) noexcept { } FORCE_INLINE Triplet operator()(int v1, int v2, int v3) const noexcept { const int G = v1 - ((v3 + v2) >> 2) + Range / 4; Triplet rgb; rgb.R = static_cast(v3 + G - Range / 2); // new R rgb.G = static_cast(G); // new G rgb.B = static_cast(v2 + G - Range / 2); // new B return rgb; } }; FORCE_INLINE Triplet operator()(int red, int green, int blue) const noexcept { Triplet hp3; hp3.v2 = static_cast(blue - green + Range / 2); hp3.v3 = static_cast(red - green + Range / 2); hp3.v1 = static_cast(green + ((hp3.v2 + hp3.v3) >> 2)) - Range / 4; return hp3; } private: static constexpr size_t Range = 1 << (sizeof(T) * 8); }; // Transform class that shifts bits towards the high bit when bit count is not 8 or 16 // needed to make the HP color transformations work correctly. template struct TransformShifted { using size_type = typename Transform::size_type; struct Inverse { explicit Inverse(const TransformShifted& transform) noexcept : _shift(transform._shift), _inverseTransform(transform._colortransform) { } FORCE_INLINE Triplet operator()(int v1, int v2, int v3) noexcept { const Triplet result = _inverseTransform(v1 << _shift, v2 << _shift, v3 << _shift); return Triplet(result.R >> _shift, result.G >> _shift, result.B >> _shift); } FORCE_INLINE Quad operator()(int v1, int v2, int v3, int v4) { Triplet result = _inverseTransform(v1 << _shift, v2 << _shift, v3 << _shift); return Quad(result.R >> _shift, result.G >> _shift, result.B >> _shift, v4); } private: int _shift; typename Transform::Inverse _inverseTransform; }; explicit TransformShifted(int shift) noexcept : _shift(shift) { } FORCE_INLINE Triplet operator()(int red, int green, int blue) noexcept { const Triplet result = _colortransform(red << _shift, green << _shift, blue << _shift); return Triplet(result.R >> _shift, result.G >> _shift, result.B >> _shift); } FORCE_INLINE Quad operator()(int red, int green, int blue, int alpha) { Triplet result = _colortransform(red << _shift, green << _shift, blue << _shift); return Quad(result.R >> _shift, result.G >> _shift, result.B >> _shift, alpha); } private: int _shift; Transform _colortransform; }; #endif dcm2niix-1.0.20211006/console/charls/constants.h000066400000000000000000000011541412761503300210670ustar00rootroot00000000000000// // Copyright CharLS Team, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_CONSTANTS #define CHARLS_CONSTANTS // Default threshold values for JPEG-LS statistical modeling as defined in ISO/IEC 14495-1, Table C.3 // for the case MAXVAL = 255 and NEAR = 0. // Can be overridden at compression time, however this is rarely done. const int DefaultThreshold1 = 3; // BASIC_T1 const int DefaultThreshold2 = 7; // BASIC_T2 const int DefaultThreshold3 = 21; // BASIC_T3 const int DefaultResetValue = 64; // Default RESET value as defined in ISO/IEC 14495-1, table C.2 #endif dcm2niix-1.0.20211006/console/charls/context.h000066400000000000000000000043711412761503300205430ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_CONTEXT #define CHARLS_CONTEXT #include // // Purpose: a JPEG-LS context with it's current statistics. // struct JlsContext { int32_t A; int32_t B; int16_t C; int16_t N; JlsContext() noexcept : A(), B(), C(), N(1) { } explicit JlsContext(int32_t a) noexcept : A(a), B(0), C(0), N(1) { } FORCE_INLINE int32_t GetErrorCorrection(int32_t k) const noexcept { if (k != 0) return 0; return BitWiseSign(2 * B + N - 1); } FORCE_INLINE void UpdateVariables(int32_t errorValue, int32_t NEAR, int32_t NRESET) noexcept { ASSERT(N != 0); // For performance work on copies of A,B,N (compiler will use registers). int a = A + std::abs(errorValue); int b = B + errorValue * (2 * NEAR + 1); int n = N; ASSERT(a < 65536 * 256); ASSERT(std::abs(b) < 65536 * 256); if (n == NRESET) { a = a >> 1; b = b >> 1; n = n >> 1; } A = a; n = n + 1; N = static_cast(n); if (b + n <= 0) { b = b + n; if (b <= -n) { b = -n + 1; } C = C - (C > -128); } else if (b > 0) { b = b - n; if (b > 0) { b = 0; } C = C + (C < 127); } B = b; ASSERT(N != 0); } FORCE_INLINE int32_t GetGolomb() const noexcept { const int32_t Ntest = N; const int32_t Atest = A; if (Ntest >= Atest) return 0; if (Ntest << 1 >= Atest) return 1; if (Ntest << 2 >= Atest) return 2; if (Ntest << 3 >= Atest) return 3; if (Ntest << 4 >= Atest) return 4; int32_t k = 5; for(; (Ntest << k) < Atest; k++) { ASSERT(k <= 32); } return k; } }; #endif dcm2niix-1.0.20211006/console/charls/contextrunmode.h000066400000000000000000000046241412761503300221360ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_CONTEXTRUNMODE #define CHARLS_CONTEXTRUNMODE #include // Implements statistical modeling for the run mode context. // Computes model dependent parameters like the Golomb code lengths struct CContextRunMode { // Note: members are sorted based on their size. int32_t A; int32_t _nRItype; uint8_t _nReset; uint8_t N; uint8_t Nn; CContextRunMode() noexcept : A(), _nRItype(), _nReset(), N(), Nn() { } CContextRunMode(int32_t a, int32_t nRItype, int32_t nReset) noexcept : A(a), _nRItype(nRItype), _nReset(static_cast(nReset)), N(1), Nn(0) { } FORCE_INLINE int32_t GetGolomb() const noexcept { const int32_t TEMP = A + (N >> 1) * _nRItype; int32_t Ntest = N; int32_t k = 0; for (; Ntest < TEMP; k++) { Ntest <<= 1; ASSERT(k <= 32); } return k; } void UpdateVariables(int32_t Errval, int32_t EMErrval) noexcept { if (Errval < 0) { Nn = Nn + 1; } A = A + ((EMErrval + 1 - _nRItype) >> 1); if (N == _nReset) { A = A >> 1; N = N >> 1; Nn = Nn >> 1; } N = N + 1; } FORCE_INLINE int32_t ComputeErrVal(int32_t temp, int32_t k) const noexcept { const bool map = temp & 1; const int32_t errvalabs = (temp + static_cast(map)) / 2; if ((k != 0 || (2 * Nn >= N)) == map) { ASSERT(map == ComputeMap(-errvalabs, k)); return -errvalabs; } ASSERT(map == ComputeMap(errvalabs, k)); return errvalabs; } bool ComputeMap(int32_t Errval, int32_t k) const noexcept { if ((k == 0) && (Errval > 0) && (2 * Nn < N)) return true; if ((Errval < 0) && (2 * Nn >= N)) return true; if ((Errval < 0) && (k != 0)) return true; return false; } FORCE_INLINE bool ComputeMapNegativeE(int32_t k) const noexcept { return k != 0 || (2 * Nn >= N); } }; #endif dcm2niix-1.0.20211006/console/charls/decoderstrategy.h000066400000000000000000000203351412761503300222450ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_DECODERSTATEGY #define CHARLS_DECODERSTATEGY #include "util.h" #include "processline.h" #include // Purpose: Implements encoding to stream of bits. In encoding mode JpegLsCodec inherits from EncoderStrategy class DecoderStrategy { public: explicit DecoderStrategy(const JlsParameters& params) : _params(params), _byteStream(nullptr), _readCache(0), _validBits(0), _position(nullptr), _nextFFPosition(nullptr), _endPosition(nullptr) { } virtual ~DecoderStrategy() = default; DecoderStrategy(const DecoderStrategy&) = delete; DecoderStrategy(DecoderStrategy&&) = delete; DecoderStrategy& operator=(const DecoderStrategy&) = delete; DecoderStrategy& operator=(DecoderStrategy&&) = delete; virtual std::unique_ptr CreateProcess(ByteStreamInfo rawStreamInfo) = 0; virtual void SetPresets(const JpegLSPresetCodingParameters& presets) = 0; virtual void DecodeScan(std::unique_ptr outputData, const JlsRect& size, ByteStreamInfo& compressedData) = 0; void Init(ByteStreamInfo& compressedStream) { _validBits = 0; _readCache = 0; if (compressedStream.rawStream) { _buffer.resize(40000); _position = _buffer.data(); _endPosition = _position; _byteStream = compressedStream.rawStream; AddBytesFromStream(); } else { _byteStream = nullptr; _position = compressedStream.rawData; _endPosition = _position + compressedStream.count; } _nextFFPosition = FindNextFF(); MakeValid(); } void AddBytesFromStream() { if (!_byteStream || _byteStream->sgetc() == std::char_traits::eof()) return; const std::size_t count = _endPosition - _position; if (count > 64) return; for (std::size_t i = 0; i < count; ++i) { _buffer[i] = _position[i]; } const std::size_t offset = _buffer.data() - _position; _position += offset; _endPosition += offset; _nextFFPosition += offset; const std::streamsize readbytes = _byteStream->sgetn(reinterpret_cast(_endPosition), _buffer.size() - count); _endPosition += readbytes; } FORCE_INLINE void Skip(int32_t length) noexcept { _validBits -= length; _readCache = _readCache << length; } static void OnLineBegin(int32_t /*cpixel*/, void* /*ptypeBuffer*/, int32_t /*pixelStride*/) noexcept { } void OnLineEnd(int32_t pixelCount, const void* ptypeBuffer, int32_t pixelStride) const { _processLine->NewLineDecoded(ptypeBuffer, pixelCount, pixelStride); } void EndScan() { if ((*_position) != 0xFF) { ReadBit(); if ((*_position) != 0xFF) throw charls_error(charls::ApiResult::TooMuchCompressedData); } if (_readCache != 0) throw charls_error(charls::ApiResult::TooMuchCompressedData); } FORCE_INLINE bool OptimizedRead() noexcept { // Easy & fast: if there is no 0xFF byte in sight, we can read without bit stuffing if (_position < _nextFFPosition - (sizeof(bufType)-1)) { _readCache |= FromBigEndian::Read(_position) >> _validBits; const int bytesToRead = (bufType_bit_count - _validBits) >> 3; _position += bytesToRead; _validBits += bytesToRead * 8; ASSERT(static_cast(_validBits) >= bufType_bit_count - 8); return true; } return false; } void MakeValid() { ASSERT(static_cast(_validBits) <=bufType_bit_count - 8); if (OptimizedRead()) return; AddBytesFromStream(); do { if (_position >= _endPosition) { if (_validBits <= 0) throw charls_error(charls::ApiResult::InvalidCompressedData); return; } const bufType valnew = _position[0]; if (valnew == 0xFF) { // JPEG bit stream rule: no FF may be followed by 0x80 or higher if (_position == _endPosition - 1 || (_position[1] & 0x80) != 0) { if (_validBits <= 0) throw charls_error(charls::ApiResult::InvalidCompressedData); return; } } _readCache |= valnew << (bufType_bit_count - 8 - _validBits); _position += 1; _validBits += 8; if (valnew == 0xFF) { _validBits--; } } while (static_cast(_validBits) < bufType_bit_count - 8); _nextFFPosition = FindNextFF(); } uint8_t* FindNextFF() const noexcept { auto positionNextFF = _position; while (positionNextFF < _endPosition) { if (*positionNextFF == 0xFF) break; positionNextFF++; } return positionNextFF; } uint8_t* GetCurBytePos() const noexcept { int32_t validBits = _validBits; uint8_t* compressedBytes = _position; for (;;) { const int32_t cbitLast = compressedBytes[-1] == 0xFF ? 7 : 8; if (validBits < cbitLast) return compressedBytes; validBits -= cbitLast; compressedBytes--; } } FORCE_INLINE int32_t ReadValue(int32_t length) { if (_validBits < length) { MakeValid(); if (_validBits < length) throw charls_error(charls::ApiResult::InvalidCompressedData); } ASSERT(length != 0 && length <= _validBits); ASSERT(length < 32); const auto result = static_cast(_readCache >> (bufType_bit_count - length)); Skip(length); return result; } FORCE_INLINE int32_t PeekByte() { if (_validBits < 8) { MakeValid(); } return static_cast(_readCache >> (bufType_bit_count - 8)); } FORCE_INLINE bool ReadBit() { if (_validBits <= 0) { MakeValid(); } const bool bSet = (_readCache & (static_cast(1) << (bufType_bit_count - 1))) != 0; Skip(1); return bSet; } FORCE_INLINE int32_t Peek0Bits() { if (_validBits < 16) { MakeValid(); } bufType valTest = _readCache; for (int32_t count = 0; count < 16; count++) { if ((valTest & (static_cast(1) << (bufType_bit_count - 1))) != 0) return count; valTest <<= 1; } return -1; } FORCE_INLINE int32_t ReadHighbits() { const int32_t count = Peek0Bits(); if (count >= 0) { Skip(count + 1); return count; } Skip(15); for (int32_t highbits = 15; ; highbits++) { if (ReadBit()) return highbits; } } int32_t ReadLongValue(int32_t length) { if (length <= 24) return ReadValue(length); return (ReadValue(length - 24) << 24) + ReadValue(24); } protected: JlsParameters _params; std::unique_ptr _processLine; private: using bufType = std::size_t; static constexpr size_t bufType_bit_count = sizeof(bufType) * 8; std::vector _buffer; std::basic_streambuf* _byteStream; // decoding bufType _readCache; int32_t _validBits; uint8_t* _position; uint8_t* _nextFFPosition; uint8_t* _endPosition; }; #endif dcm2niix-1.0.20211006/console/charls/defaulttraits.h000066400000000000000000000076451412761503300217410ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_DEFAULTTRAITS #define CHARLS_DEFAULTTRAITS #include "util.h" #include "constants.h" #include #include // Default traits that support all JPEG LS parameters: custom limit, near, maxval (not power of 2) // This traits class is used to initialize a coder/decoder. // The coder/decoder also delegates some functions to the traits class. // This is to allow the traits class to replace the default implementation here with optimized specific implementations. // This is done for lossless coding/decoding: see losslesstraits.h WARNING_SUPPRESS(26432) template struct DefaultTraits { using SAMPLE = sample; using PIXEL = pixel; int32_t MAXVAL; const int32_t RANGE; const int32_t NEAR; const int32_t qbpp; const int32_t bpp; const int32_t LIMIT; const int32_t RESET; DefaultTraits(int32_t max, int32_t near, int32_t reset = DefaultResetValue) noexcept : MAXVAL(max), RANGE((max + 2 * near) / (2 * near + 1) + 1), NEAR(near), qbpp(log_2(RANGE)), bpp(log_2(max)), LIMIT(2 * (bpp + std::max(8, bpp))), RESET(reset) { } DefaultTraits(const DefaultTraits& other) noexcept : MAXVAL(other.MAXVAL), RANGE(other.RANGE), NEAR(other.NEAR), qbpp(other.qbpp), bpp(other.bpp), LIMIT(other.LIMIT), RESET(other.RESET) { } DefaultTraits() = delete; DefaultTraits(DefaultTraits&&) = default; DefaultTraits& operator=(const DefaultTraits&) = delete; DefaultTraits& operator=(DefaultTraits&&) = delete; FORCE_INLINE int32_t ComputeErrVal(int32_t e) const noexcept { return ModuloRange(Quantize(e)); } FORCE_INLINE SAMPLE ComputeReconstructedSample(int32_t Px, int32_t ErrVal) const noexcept { return FixReconstructedValue(Px + DeQuantize(ErrVal)); } FORCE_INLINE bool IsNear(int32_t lhs, int32_t rhs) const noexcept { return std::abs(lhs - rhs) <= NEAR; } bool IsNear(Triplet lhs, Triplet rhs) const noexcept { return std::abs(lhs.v1 - rhs.v1) <= NEAR && std::abs(lhs.v2 - rhs.v2) <= NEAR && std::abs(lhs.v3 - rhs.v3) <= NEAR; } FORCE_INLINE int32_t CorrectPrediction(int32_t Pxc) const noexcept { if ((Pxc & MAXVAL) == Pxc) return Pxc; return (~(Pxc >> (int32_t_bit_count-1))) & MAXVAL; } /// /// Returns the value of errorValue modulo RANGE. ITU.T.87, A.4.5 (code segment A.9) /// FORCE_INLINE int32_t ModuloRange(int32_t errorValue) const noexcept { ASSERT(std::abs(errorValue) <= RANGE); if (errorValue < 0) { errorValue += RANGE; } if (errorValue >= (RANGE + 1) / 2) { errorValue -= RANGE; } ASSERT(-RANGE / 2 <= errorValue && errorValue <= (RANGE / 2) - 1); return errorValue; } private: int32_t Quantize(int32_t Errval) const noexcept { if (Errval > 0) return (Errval + NEAR) / (2 * NEAR + 1); return - (NEAR - Errval) / (2 * NEAR + 1); } FORCE_INLINE int32_t DeQuantize(int32_t Errval) const noexcept { return Errval * (2 * NEAR + 1); } FORCE_INLINE SAMPLE FixReconstructedValue(int32_t val) const noexcept { if (val < -NEAR) { val = val + RANGE * (2 * NEAR + 1); } else if (val > MAXVAL + NEAR) { val = val - RANGE * (2 * NEAR + 1); } return static_cast(CorrectPrediction(val)); } }; WARNING_UNSUPPRESS() #endif dcm2niix-1.0.20211006/console/charls/encoderstrategy.h000066400000000000000000000133311412761503300222550ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_ENCODERSTRATEGY #define CHARLS_ENCODERSTRATEGY #include "processline.h" #include "decoderstrategy.h" // Purpose: Implements encoding to stream of bits. In encoding mode JpegLsCodec inherits from EncoderStrategy class EncoderStrategy { public: explicit EncoderStrategy(const JlsParameters& params) : _params(params), _bitBuffer(0), _freeBitCount(sizeof(_bitBuffer) * 8), _compressedLength(0), _position(nullptr), _isFFWritten(false), _bytesWritten(0), _compressedStream(nullptr) { } virtual ~EncoderStrategy() = default; EncoderStrategy(const EncoderStrategy&) = delete; EncoderStrategy(EncoderStrategy&&) = delete; EncoderStrategy& operator=(const EncoderStrategy&) = delete; EncoderStrategy& operator=(EncoderStrategy&&) = delete; virtual std::unique_ptr CreateProcess(ByteStreamInfo rawStreamInfo) = 0; virtual void SetPresets(const JpegLSPresetCodingParameters& presets) = 0; virtual std::size_t EncodeScan(std::unique_ptr rawData, ByteStreamInfo& compressedData) = 0; int32_t PeekByte(); void OnLineBegin(int32_t cpixel, void* ptypeBuffer, int32_t pixelStride) const { _processLine->NewLineRequested(ptypeBuffer, cpixel, pixelStride); } static void OnLineEnd(int32_t /*cpixel*/, void* /*ptypeBuffer*/, int32_t /*pixelStride*/) noexcept { } protected: void Init(ByteStreamInfo& compressedStream) { _freeBitCount = sizeof(_bitBuffer) * 8; _bitBuffer = 0; if (compressedStream.rawStream) { _compressedStream = compressedStream.rawStream; _buffer.resize(4000); _position = _buffer.data(); _compressedLength = _buffer.size(); } else { _position = compressedStream.rawData; _compressedLength = compressedStream.count; } } void AppendToBitStream(int32_t bits, int32_t bitCount) { ASSERT(bitCount < 32 && bitCount >= 0); ASSERT((!_qdecoder) || (bitCount == 0 && bits == 0) ||( _qdecoder->ReadLongValue(bitCount) == bits)); #ifndef NDEBUG const int mask = (1u << (bitCount)) - 1; ASSERT((bits | mask) == mask); // Not used bits must be set to zero. #endif _freeBitCount -= bitCount; if (_freeBitCount >= 0) { _bitBuffer |= bits << _freeBitCount; } else { // Add as much bits in the remaining space as possible and flush. _bitBuffer |= bits >> -_freeBitCount; Flush(); // A second flush may be required if extra marker detect bits were needed and not all bits could be written. if (_freeBitCount < 0) { _bitBuffer |= bits >> -_freeBitCount; Flush(); } ASSERT(_freeBitCount >= 0); _bitBuffer |= bits << _freeBitCount; } } void EndScan() { Flush(); // if a 0xff was written, Flush() will force one unset bit anyway if (_isFFWritten) AppendToBitStream(0, (_freeBitCount - 1) % 8); else AppendToBitStream(0, _freeBitCount % 8); Flush(); ASSERT(_freeBitCount == 0x20); if (_compressedStream) { OverFlow(); } } void OverFlow() { if (!_compressedStream) throw charls_error(charls::ApiResult::CompressedBufferTooSmall); const std::size_t bytesCount = _position - _buffer.data(); const auto bytesWritten = static_cast(_compressedStream->sputn(reinterpret_cast(_buffer.data()), _position - _buffer.data())); if (bytesWritten != bytesCount) throw charls_error(charls::ApiResult::CompressedBufferTooSmall); _position = _buffer.data(); _compressedLength = _buffer.size(); } void Flush() { if (_compressedLength < 4) { OverFlow(); } for (int i = 0; i < 4; ++i) { if (_freeBitCount >= 32) break; if (_isFFWritten) { // JPEG-LS requirement (T.87, A.1) to detect markers: after a xFF value a single 0 bit needs to be inserted. *_position = static_cast(_bitBuffer >> 25); _bitBuffer = _bitBuffer << 7; _freeBitCount += 7; } else { *_position = static_cast(_bitBuffer >> 24); _bitBuffer = _bitBuffer << 8; _freeBitCount += 8; } _isFFWritten = *_position == 0xFF; _position++; _compressedLength--; _bytesWritten++; } } std::size_t GetLength() const noexcept { return _bytesWritten - (_freeBitCount - 32) / 8; } FORCE_INLINE void AppendOnesToBitStream(int32_t length) { AppendToBitStream((1 << length) - 1, length); } std::unique_ptr _qdecoder; JlsParameters _params; std::unique_ptr _processLine; private: unsigned int _bitBuffer; int32_t _freeBitCount; std::size_t _compressedLength; // encoding uint8_t* _position; bool _isFFWritten; std::size_t _bytesWritten; std::vector _buffer; std::basic_streambuf* _compressedStream; }; #endif dcm2niix-1.0.20211006/console/charls/interface.cpp000066400000000000000000000203161412761503300213470ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #include "charls.h" #include "util.h" #include "jpegstreamreader.h" #include "jpegstreamwriter.h" #include "jpegmarkersegment.h" #include using namespace charls; namespace { void VerifyInput(const ByteStreamInfo& uncompressedStream, const JlsParameters& parameters) { if (!uncompressedStream.rawStream && !uncompressedStream.rawData) throw charls_error(ApiResult::InvalidJlsParameters, "rawStream or rawData needs to reference to something"); if (parameters.width < 1 || parameters.width > 65535) throw charls_error(ApiResult::InvalidJlsParameters, "width needs to be in the range [1, 65535]"); if (parameters.height < 1 || parameters.height > 65535) throw charls_error(ApiResult::InvalidJlsParameters, "height needs to be in the range [1, 65535]"); if (parameters.bitsPerSample < 2 || parameters.bitsPerSample > 16) throw charls_error(ApiResult::InvalidJlsParameters, "bitspersample needs to be in the range [2, 16]"); if (!(parameters.interleaveMode == InterleaveMode::None || parameters.interleaveMode == InterleaveMode::Sample || parameters.interleaveMode == InterleaveMode::Line)) throw charls_error(ApiResult::InvalidJlsParameters, "interleaveMode needs to be set to a value of {None, Sample, Line}"); if (parameters.components < 1 || parameters.components > 255) throw charls_error(ApiResult::InvalidJlsParameters, "components needs to be in the range [1, 255]"); if (uncompressedStream.rawData) { if (uncompressedStream.count < static_cast(parameters.height) * parameters.width * parameters.components * (parameters.bitsPerSample > 8 ? 2 : 1)) throw charls_error(ApiResult::InvalidJlsParameters, "uncompressed size does not match with the other parameters"); } switch (parameters.components) { case 3: break; case 4: if (parameters.interleaveMode == InterleaveMode::Sample) throw charls_error(ApiResult::InvalidJlsParameters, "interleaveMode cannot be set to Sample in combination with components = 4"); break; default: if (parameters.interleaveMode != InterleaveMode::None) throw charls_error(ApiResult::InvalidJlsParameters, "interleaveMode can only be set to None in combination with components = 1"); break; } } ApiResult ResultAndErrorMessage(ApiResult result, char* errorMessage) noexcept { if (errorMessage) { errorMessage[0] = 0; } return result; } ApiResult ResultAndErrorMessageFromException(char* errorMessage) { try { // re-trow the exception. throw; } catch (const charls_error& error) { if (errorMessage) { ASSERT(strlen(error.what()) < ErrorMessageSize); strcpy(errorMessage, error.what()); } return static_cast(error.code().value()); } catch (...) { return ResultAndErrorMessage(ApiResult::UnexpectedFailure, errorMessage); } } } // namespace CHARLS_IMEXPORT(ApiResult) JpegLsEncodeStream(ByteStreamInfo compressedStreamInfo, size_t& pcbyteWritten, ByteStreamInfo rawStreamInfo, const struct JlsParameters& params, char* errorMessage) { try { VerifyInput(rawStreamInfo, params); JlsParameters info = params; if (info.stride == 0) { info.stride = info.width * ((info.bitsPerSample + 7)/8); if (info.interleaveMode != InterleaveMode::None) { info.stride *= info.components; } } JpegStreamWriter writer; if (info.jfif.version) { writer.AddSegment(JpegMarkerSegment::CreateJpegFileInterchangeFormatSegment(info.jfif)); } writer.AddSegment(JpegMarkerSegment::CreateStartOfFrameSegment(info.width, info.height, info.bitsPerSample, info.components)); if (info.colorTransformation != ColorTransformation::None) { writer.AddColorTransform(info.colorTransformation); } if (info.interleaveMode == InterleaveMode::None) { const int32_t cbyteComp = info.width * info.height * ((info.bitsPerSample + 7) / 8); for (int32_t component = 0; component < info.components; ++component) { writer.AddScan(rawStreamInfo, info); SkipBytes(rawStreamInfo, cbyteComp); } } else { writer.AddScan(rawStreamInfo, info); } writer.Write(compressedStreamInfo); pcbyteWritten = writer.GetBytesWritten(); return ResultAndErrorMessage(ApiResult::OK, errorMessage); } catch (...) { return ResultAndErrorMessageFromException(errorMessage); } } CHARLS_IMEXPORT(ApiResult) JpegLsDecodeStream(ByteStreamInfo rawStream, ByteStreamInfo compressedStream, const JlsParameters* info, char* errorMessage) { try { JpegStreamReader reader(compressedStream); if (info) { reader.SetInfo(*info); } reader.Read(rawStream); return ResultAndErrorMessage(ApiResult::OK, errorMessage); } catch (...) { return ResultAndErrorMessageFromException(errorMessage); } } CHARLS_IMEXPORT(ApiResult) JpegLsReadHeaderStream(ByteStreamInfo rawStreamInfo, JlsParameters* params, char* errorMessage) { try { JpegStreamReader reader(rawStreamInfo); reader.ReadHeader(); reader.ReadStartOfScan(true); *params = reader.GetMetadata(); return ResultAndErrorMessage(ApiResult::OK, errorMessage); } catch (...) { return ResultAndErrorMessageFromException(errorMessage); } } extern "C" { CHARLS_IMEXPORT(ApiResult) JpegLsEncode(void* destination, size_t destinationLength, size_t* bytesWritten, const void* source, size_t sourceLength, const struct JlsParameters* params, char* errorMessage) { if (!destination || !bytesWritten || !source || !params) return ApiResult::InvalidJlsParameters; const ByteStreamInfo rawStreamInfo = FromByteArrayConst(source, sourceLength); const ByteStreamInfo compressedStreamInfo = FromByteArray(destination, destinationLength); return JpegLsEncodeStream(compressedStreamInfo, *bytesWritten, rawStreamInfo, *params, errorMessage); } CHARLS_IMEXPORT(ApiResult) JpegLsReadHeader(const void* compressedData, size_t compressedLength, JlsParameters* params, char* errorMessage) { return JpegLsReadHeaderStream(FromByteArrayConst(compressedData, compressedLength), params, errorMessage); } CHARLS_IMEXPORT(ApiResult) JpegLsDecode(void* destination, size_t destinationLength, const void* source, size_t sourceLength, const struct JlsParameters* params, char* errorMessage) { const ByteStreamInfo compressedStream = FromByteArrayConst(source, sourceLength); const ByteStreamInfo rawStreamInfo = FromByteArray(destination, destinationLength); return JpegLsDecodeStream(rawStreamInfo, compressedStream, params, errorMessage); } CHARLS_IMEXPORT(ApiResult) JpegLsDecodeRect(void* uncompressedData, size_t uncompressedLength, const void* compressedData, size_t compressedLength, JlsRect roi, const JlsParameters* info, char* errorMessage) { try { const ByteStreamInfo compressedStream = FromByteArrayConst(compressedData, compressedLength); JpegStreamReader reader(compressedStream); const ByteStreamInfo rawStreamInfo = FromByteArray(uncompressedData, uncompressedLength); if (info) { reader.SetInfo(*info); } reader.SetRect(roi); reader.Read(rawStreamInfo); return ResultAndErrorMessage(ApiResult::OK, errorMessage); } catch (...) { return ResultAndErrorMessageFromException(errorMessage); } } } dcm2niix-1.0.20211006/console/charls/jlscodecfactory.h000066400000000000000000000010171412761503300222270ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_JLSCODECFACTORY #define CHARLS_JLSCODECFACTORY #include struct JlsParameters; struct JpegLSPresetCodingParameters; template class JlsCodecFactory { public: std::unique_ptr CreateCodec(const JlsParameters& params, const JpegLSPresetCodingParameters& presets); private: std::unique_ptr CreateOptimizedCodec(const JlsParameters& params); }; #endif dcm2niix-1.0.20211006/console/charls/jpegimagedatasegment.h000066400000000000000000000012561412761503300232230ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_JPEGIMAGEDATASEGMENT #define CHARLS_JPEGIMAGEDATASEGMENT #include "jpegsegment.h" #include "jpegstreamwriter.h" class JpegImageDataSegment : public JpegSegment { public: JpegImageDataSegment(ByteStreamInfo rawStream, const JlsParameters& params, int componentCount) noexcept : _componentCount(componentCount), _rawStreamInfo(rawStream), _params(params) { } void Serialize(JpegStreamWriter& streamWriter) override; private: int _componentCount; ByteStreamInfo _rawStreamInfo; JlsParameters _params; }; #endif dcm2niix-1.0.20211006/console/charls/jpegls.cpp000066400000000000000000000143061412761503300206750ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #include "util.h" #include "decoderstrategy.h" #include "encoderstrategy.h" #include "lookuptable.h" #include "losslesstraits.h" #include "defaulttraits.h" #include "jlscodecfactory.h" #include "jpegstreamreader.h" #include using namespace charls; // As defined in the JPEG-LS standard // used to determine how large runs should be encoded at a time. const int J[32] = {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 10, 11, 12, 13, 14, 15}; #include "scan.h" namespace { signed char QuantizeGratientOrg(const JpegLSPresetCodingParameters& preset, int32_t NEAR, int32_t Di) noexcept { if (Di <= -preset.Threshold3) return -4; if (Di <= -preset.Threshold2) return -3; if (Di <= -preset.Threshold1) return -2; if (Di < -NEAR) return -1; if (Di <= NEAR) return 0; if (Di < preset.Threshold1) return 1; if (Di < preset.Threshold2) return 2; if (Di < preset.Threshold3) return 3; return 4; } std::vector CreateQLutLossless(int32_t cbit) { const JpegLSPresetCodingParameters preset = ComputeDefault((1u << static_cast(cbit)) - 1, 0); const int32_t range = preset.MaximumSampleValue + 1; std::vector lut(static_cast(range) * 2); for (int32_t diff = -range; diff < range; diff++) { lut[static_cast(range) + diff] = QuantizeGratientOrg(preset, 0,diff); } return lut; } template std::unique_ptr create_codec(const Traits& traits, const JlsParameters& params) { return std::make_unique>(traits, params); } } // namespace class charls_category : public std::error_category { public: const char* name() const noexcept override { return "charls"; } std::string message(int /* errval */) const override { return "CharLS error"; } }; const std::error_category& charls_error::CharLSCategoryInstance() noexcept { static charls_category instance; return instance; } // Lookup tables to replace code with lookup tables. // To avoid threading issues, all tables are created when the program is loaded. // Lookup table: decode symbols that are smaller or equal to 8 bit (16 tables for each value of k) CTable decodingTables[16] = { InitTable(0), InitTable(1), InitTable(2), InitTable(3), InitTable(4), InitTable(5), InitTable(6), InitTable(7), InitTable(8), InitTable(9), InitTable(10), InitTable(11), InitTable(12), InitTable(13), InitTable(14),InitTable(15) }; // Lookup tables: sample differences to bin indexes. std::vector rgquant8Ll = CreateQLutLossless(8); std::vector rgquant10Ll = CreateQLutLossless(10); std::vector rgquant12Ll = CreateQLutLossless(12); std::vector rgquant16Ll = CreateQLutLossless(16); template std::unique_ptr JlsCodecFactory::CreateCodec(const JlsParameters& params, const JpegLSPresetCodingParameters& presets) { std::unique_ptr codec; if (presets.ResetValue == 0 || presets.ResetValue == DefaultResetValue) { codec = CreateOptimizedCodec(params); } if (!codec) { if (params.bitsPerSample <= 8) { DefaultTraits traits((1 << params.bitsPerSample) - 1, params.allowedLossyError, presets.ResetValue); traits.MAXVAL = presets.MaximumSampleValue; codec = std::make_unique, Strategy>>(traits, params); } else { DefaultTraits traits((1 << params.bitsPerSample) - 1, params.allowedLossyError, presets.ResetValue); traits.MAXVAL = presets.MaximumSampleValue; codec = std::make_unique, Strategy>>(traits, params); } } codec->SetPresets(presets); return codec; } template std::unique_ptr JlsCodecFactory::CreateOptimizedCodec(const JlsParameters& params) { if (params.interleaveMode == InterleaveMode::Sample && params.components != 3) return nullptr; #ifndef DISABLE_SPECIALIZATIONS // optimized lossless versions common formats if (params.allowedLossyError == 0) { if (params.interleaveMode == InterleaveMode::Sample) { if (params.bitsPerSample == 8) return create_codec(LosslessTraits, 8>(), params); } else { switch (params.bitsPerSample) { case 8: return create_codec(LosslessTraits(), params); case 12: return create_codec(LosslessTraits(), params); case 16: return create_codec(LosslessTraits(), params); default: break; } } } #endif const int maxval = (1u << static_cast(params.bitsPerSample)) - 1; if (params.bitsPerSample <= 8) { if (params.interleaveMode == InterleaveMode::Sample) return create_codec(DefaultTraits >(maxval, params.allowedLossyError), params); return create_codec(DefaultTraits((1u << params.bitsPerSample) - 1, params.allowedLossyError), params); } if (params.bitsPerSample <= 16) { if (params.interleaveMode == InterleaveMode::Sample) return create_codec(DefaultTraits >(maxval, params.allowedLossyError), params); return create_codec(DefaultTraits(maxval, params.allowedLossyError), params); } return nullptr; } template class JlsCodecFactory; template class JlsCodecFactory; dcm2niix-1.0.20211006/console/charls/jpegmarkercode.h000066400000000000000000000050461412761503300220410ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #pragma once #include // JPEG Marker codes have the pattern 0xFFaa in a JPEG byte stream. // The valid 'aa' options are defined by several ITU / IEC standards: // 0x00, 0x01, 0xFE, 0xC0-0xDF are defined in ITU T.81/IEC 10918-1 // 0xF0 - 0xF6 are defined in ITU T.84/IEC 10918-3 JPEG extensions // 0xF7 - 0xF8 are defined in ITU T.87/IEC 14495-1 JPEG LS // 0x4F - 0x6F, 0x90 - 0x93 are defined in JPEG 2000 IEC 15444-1 enum class JpegMarkerCode : uint8_t { StartOfImage = 0xD8, // SOI: Marks the start of an image. EndOfImage = 0xD9, // EOI: Marks the end of an image. StartOfScan = 0xDA, // SOS: Marks the start of scan. // The following markers are defined in ITU T.81 | ISO IEC 10918-1. StartOfFrameBaselineJpeg = 0xC0, // SOF_0: Marks the start of a baseline jpeg encoded frame. StartOfFrameExtendedSequential = 0xC1, // SOF_1: Marks the start of a extended sequential Huffman encoded frame. StartOfFrameProgressive = 0xC2, // SOF_2: Marks the start of a progressive Huffman encoded frame. StartOfFrameLossless = 0xC3, // SOF_3: Marks the start of a lossless Huffman encoded frame. StartOfFrameDifferentialSequential = 0xC5, // SOF_5: Marks the start of a differential sequential Huffman encoded frame. StartOfFrameDifferentialProgressive = 0xC6, // SOF_6: Marks the start of a differential progressive Huffman encoded frame. StartOfFrameDifferentialLossless = 0xC7, // SOF_7: Marks the start of a differential lossless Huffman encoded frame. StartOfFrameExtendedArithemtic = 0xC9, // SOF_9: Marks the start of a extended sequential arithmetic encoded frame. StartOfFrameProgressiveArithemtic = 0xCA, // SOF_10: Marks the start of a progressive arithmetic encoded frame. StartOfFrameLosslessArithemtic = 0xCB, // SOF_11: Marks the start of a lossless arithmetic encoded frame. StartOfFrameJpegLS = 0xF7, // SOF_55: Marks the start of a JPEG-LS encoded frame. JpegLSPresetParameters = 0xF8, // LSE: Marks the start of a JPEG-LS preset parameters segment. ApplicationData0 = 0xE0, // APP0: Application data 0: used for JFIF header. ApplicationData7 = 0xE7, // APP7: Application data 7: color-space. ApplicationData8 = 0xE8, // APP8: Application data 8: colorXForm. Comment = 0xFE // COM: Comment block. }; dcm2niix-1.0.20211006/console/charls/jpegmarkersegment.cpp000066400000000000000000000117171412761503300231260ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #include "jpegmarkersegment.h" #include "jpegmarkercode.h" #include "util.h" #include #include using namespace charls; std::unique_ptr JpegMarkerSegment::CreateStartOfFrameSegment(int width, int height, int bitsPerSample, int componentCount) { ASSERT(width >= 0 && width <= UINT16_MAX); ASSERT(height >= 0 && height <= UINT16_MAX); ASSERT(bitsPerSample > 0 && bitsPerSample <= UINT8_MAX); ASSERT(componentCount > 0 && componentCount <= (UINT8_MAX - 1)); // Create a Frame Header as defined in T.87, C.2.2 and T.81, B.2.2 std::vector content; content.push_back(static_cast(bitsPerSample)); // P = Sample precision push_back(content, static_cast(height)); // Y = Number of lines push_back(content, static_cast(width)); // X = Number of samples per line // Components content.push_back(static_cast(componentCount)); // Nf = Number of image components in frame for (auto component = 0; component < componentCount; ++component) { // Component Specification parameters content.push_back(static_cast(component + 1)); // Ci = Component identifier content.push_back(0x11); // Hi + Vi = Horizontal sampling factor + Vertical sampling factor content.push_back(0); // Tqi = Quantization table destination selector (reserved for JPEG-LS, should be set to 0) } return std::make_unique(JpegMarkerCode::StartOfFrameJpegLS, move(content)); } std::unique_ptr JpegMarkerSegment::CreateJpegFileInterchangeFormatSegment(const JfifParameters& params) { ASSERT(params.units == 0 || params.units == 1 || params.units == 2); ASSERT(params.Xdensity > 0); ASSERT(params.Ydensity > 0); ASSERT(params.Xthumbnail >= 0 && params.Xthumbnail < 256); ASSERT(params.Ythumbnail >= 0 && params.Ythumbnail < 256); // Create a JPEG APP0 segment in the JPEG File Interchange Format (JFIF), v1.02 std::vector content { 'J', 'F', 'I', 'F', '\0' }; push_back(content, static_cast(params.version)); content.push_back(static_cast(params.units)); push_back(content, static_cast(params.Xdensity)); push_back(content, static_cast(params.Ydensity)); // thumbnail content.push_back(static_cast(params.Xthumbnail)); content.push_back(static_cast(params.Ythumbnail)); if (params.Xthumbnail > 0) { if (params.thumbnail) throw charls_error(ApiResult::InvalidJlsParameters, "params.Xthumbnail is > 0 but params.thumbnail == null_ptr"); content.insert(content.end(), static_cast(params.thumbnail), static_cast(params.thumbnail) + static_cast(3) * params.Xthumbnail * params.Ythumbnail); } return std::make_unique(JpegMarkerCode::ApplicationData0, move(content)); } std::unique_ptr JpegMarkerSegment::CreateJpegLSPresetParametersSegment(const JpegLSPresetCodingParameters& params) { std::vector content; // Parameter ID. 0x01 = JPEG-LS preset coding parameters. content.push_back(1); push_back(content, static_cast(params.MaximumSampleValue)); push_back(content, static_cast(params.Threshold1)); push_back(content, static_cast(params.Threshold2)); push_back(content, static_cast(params.Threshold3)); push_back(content, static_cast(params.ResetValue)); return std::make_unique(JpegMarkerCode::JpegLSPresetParameters, move(content)); } std::unique_ptr JpegMarkerSegment::CreateColorTransformSegment(ColorTransformation transformation) { return std::make_unique( JpegMarkerCode::ApplicationData8, std::vector { 'm', 'r', 'f', 'x', static_cast(transformation) }); } std::unique_ptr JpegMarkerSegment::CreateStartOfScanSegment(int componentIndex, int componentCount, int allowedLossyError, InterleaveMode interleaveMode) { ASSERT(componentIndex >= 0); ASSERT(componentCount > 0); // Create a Scan Header as defined in T.87, C.2.3 and T.81, B.2.3 std::vector content; content.push_back(static_cast(componentCount)); for (auto i = 0; i < componentCount; ++i) { content.push_back(static_cast(componentIndex + i)); content.push_back(0); // Mapping table selector (0 = no table) } content.push_back(static_cast(allowedLossyError)); // NEAR parameter content.push_back(static_cast(interleaveMode)); // ILV parameter content.push_back(0); // transformation return std::make_unique(JpegMarkerCode::StartOfScan, move(content)); } dcm2niix-1.0.20211006/console/charls/jpegmarkersegment.h000066400000000000000000000057101412761503300225670ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_JPEGMARKERSEGMENT #define CHARLS_JPEGMARKERSEGMENT #include "jpegsegment.h" #include "jpegstreamwriter.h" #include #include #include enum class JpegMarkerCode : uint8_t; class JpegMarkerSegment : public JpegSegment { public: /// /// Creates a JPEG-LS Start Of Frame (SOF-55) segment. /// /// The width of the frame. /// The height of the frame. /// The bits per sample. /// The component count. static std::unique_ptr CreateStartOfFrameSegment(int width, int height, int bitsPerSample, int componentCount); /// /// Creates a JPEG File Interchange (APP1 + jfif) segment. /// /// Parameters to write into the JFIF segment. static std::unique_ptr CreateJpegFileInterchangeFormatSegment(const JfifParameters& params); /// /// Creates a JPEG-LS preset parameters (LSE) segment. /// /// Parameters to write into the JPEG-LS preset segment. static std::unique_ptr CreateJpegLSPresetParametersSegment(const JpegLSPresetCodingParameters& params); /// /// Creates a color transformation (APP8) segment. /// /// Parameters to write into the JFIF segment. static std::unique_ptr CreateColorTransformSegment(charls::ColorTransformation transformation); /// /// Creates a JPEG-LS Start Of Scan (SOS) segment. /// /// The component index of the scan segment or the start index if component count > 1. /// The number of components in the scan segment. Can only be > 1 when the components are interleaved. /// The allowed lossy error. 0 means lossless. /// The interleave mode of the components. static std::unique_ptr CreateStartOfScanSegment(int componentIndex, int componentCount, int allowedLossyError, charls::InterleaveMode interleaveMode); JpegMarkerSegment(JpegMarkerCode markerCode, std::vector&& content) : _markerCode(markerCode), _content(content) { } void Serialize(JpegStreamWriter& streamWriter) override { streamWriter.WriteByte(0xFF); streamWriter.WriteByte(static_cast(_markerCode)); streamWriter.WriteWord(static_cast(_content.size() + 2)); streamWriter.WriteBytes(_content); } private: JpegMarkerCode _markerCode; std::vector _content; }; #endif dcm2niix-1.0.20211006/console/charls/jpegsegment.h000066400000000000000000000012041412761503300213570ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_JPEGSEGMENT #define CHARLS_JPEGSEGMENT class JpegStreamWriter; // // Purpose: base class for segments that can be written to JPEG streams. // class JpegSegment { public: virtual ~JpegSegment() = default; virtual void Serialize(JpegStreamWriter& streamWriter) = 0; JpegSegment(const JpegSegment&) = delete; JpegSegment(JpegSegment&&) = delete; JpegSegment& operator=(const JpegSegment&) = delete; JpegSegment& operator=(JpegSegment&&) = delete; protected: JpegSegment() = default; }; #endif dcm2niix-1.0.20211006/console/charls/jpegstreamreader.cpp000066400000000000000000000315021412761503300227320ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #include "jpegstreamreader.h" #include "util.h" #include "jpegstreamwriter.h" #include "jpegimagedatasegment.h" #include "jpegmarkercode.h" #include "decoderstrategy.h" #include "encoderstrategy.h" #include "jlscodecfactory.h" #include "constants.h" #include #include #include using namespace charls; extern template class JlsCodecFactory; extern template class JlsCodecFactory; namespace { // JFIF\0 uint8_t jfifID[] = { 'J', 'F', 'I', 'F', '\0' }; /// Clamping function as defined by ISO/IEC 14495-1, Figure C.3 int32_t clamp(int32_t i, int32_t j, int32_t maximumSampleValue) noexcept { if (i > maximumSampleValue || i < j) return j; return i; } ApiResult CheckParameterCoherent(const JlsParameters& params) noexcept { if (params.bitsPerSample < 2 || params.bitsPerSample > 16) return ApiResult::ParameterValueNotSupported; if (params.interleaveMode < InterleaveMode::None || params.interleaveMode > InterleaveMode::Sample) return ApiResult::InvalidCompressedData; switch (params.components) { case 4: return params.interleaveMode == InterleaveMode::Sample ? ApiResult::ParameterValueNotSupported : ApiResult::OK; case 3: return ApiResult::OK; case 0: return ApiResult::InvalidJlsParameters; default: return params.interleaveMode != InterleaveMode::None ? ApiResult::ParameterValueNotSupported : ApiResult::OK; } } } // namespace JpegLSPresetCodingParameters ComputeDefault(int32_t maximumSampleValue, int32_t allowedLossyError) noexcept { JpegLSPresetCodingParameters preset; const int32_t factor = (std::min(maximumSampleValue, 4095) + 128) / 256; const int threshold1 = clamp(factor * (DefaultThreshold1 - 2) + 2 + 3 * allowedLossyError, allowedLossyError + 1, maximumSampleValue); const int threshold2 = clamp(factor * (DefaultThreshold2 - 3) + 3 + 5 * allowedLossyError, threshold1, maximumSampleValue); //-V537 preset.Threshold1 = threshold1; preset.Threshold2 = threshold2; preset.Threshold3 = clamp(factor * (DefaultThreshold3 - 4) + 4 + 7 * allowedLossyError, threshold2, maximumSampleValue); preset.MaximumSampleValue = maximumSampleValue; preset.ResetValue = DefaultResetValue; return preset; } void JpegImageDataSegment::Serialize(JpegStreamWriter& streamWriter) { JlsParameters info = _params; info.components = _componentCount; auto codec = JlsCodecFactory().CreateCodec(info, _params.custom); std::unique_ptr processLine(codec->CreateProcess(_rawStreamInfo)); ByteStreamInfo compressedData = streamWriter.OutputStream(); const size_t cbyteWritten = codec->EncodeScan(move(processLine), compressedData); streamWriter.Seek(cbyteWritten); } JpegStreamReader::JpegStreamReader(ByteStreamInfo byteStreamInfo) noexcept : _byteStream(byteStreamInfo), _params(), _rect() { } void JpegStreamReader::Read(ByteStreamInfo rawPixels) { ReadHeader(); const auto result = CheckParameterCoherent(_params); if (result != ApiResult::OK) throw charls_error(result); if (_rect.Width <= 0) { _rect.Width = _params.width; _rect.Height = _params.height; } const int64_t bytesPerPlane = static_cast(_rect.Width) * _rect.Height * ((_params.bitsPerSample + 7)/8); if (rawPixels.rawData && static_cast(rawPixels.count) < bytesPerPlane * _params.components) throw charls_error(ApiResult::UncompressedBufferTooSmall); int componentIndex = 0; while (componentIndex < _params.components) { ReadStartOfScan(componentIndex == 0); std::unique_ptr qcodec = JlsCodecFactory().CreateCodec(_params, _params.custom); std::unique_ptr processLine(qcodec->CreateProcess(rawPixels)); qcodec->DecodeScan(move(processLine), _rect, _byteStream); SkipBytes(rawPixels, static_cast(bytesPerPlane)); if (_params.interleaveMode != InterleaveMode::None) return; componentIndex += 1; } } void JpegStreamReader::ReadNBytes(std::vector& dst, int byteCount) { for (int i = 0; i < byteCount; ++i) { dst.push_back(static_cast(ReadByte())); } } void JpegStreamReader::ReadHeader() { if (ReadNextMarker() != JpegMarkerCode::StartOfImage) throw charls_error(ApiResult::InvalidCompressedData); for (;;) { const JpegMarkerCode marker = ReadNextMarker(); if (marker == JpegMarkerCode::StartOfScan) return; const int32_t cbyteMarker = ReadWord(); const int bytesRead = ReadMarker(marker) + 2; const int paddingToRead = cbyteMarker - bytesRead; if (paddingToRead < 0) throw charls_error(ApiResult::InvalidCompressedData); for (int i = 0; i < paddingToRead; ++i) { ReadByte(); } } } JpegMarkerCode JpegStreamReader::ReadNextMarker() { auto byte = ReadByte(); if (byte != 0xFF) { std::ostringstream message; message << std::setfill('0'); message << "Expected JPEG Marker start byte 0xFF but the byte value was 0x" << std::hex << std::uppercase << std::setw(2) << static_cast(byte); throw charls_error(ApiResult::MissingJpegMarkerStart, message.str()); } // Read all preceding 0xFF fill values until a non 0xFF value has been found. (see T.81, B.1.1.2) do { byte = ReadByte(); } while (byte == 0xFF); return static_cast(byte); } int JpegStreamReader::ReadMarker(JpegMarkerCode marker) { // ISO/IEC 14495-1, ITU-T Recommendation T.87, C.1.1. defines the following markers valid for a JPEG-LS byte stream: // SOF55, LSE, SOI, EOI, SOS, DNL, DRI, RSTm, APPn, COM. // All other markers shall not be present. switch (marker) { case JpegMarkerCode::StartOfFrameJpegLS: return ReadStartOfFrame(); case JpegMarkerCode::Comment: return ReadComment(); case JpegMarkerCode::JpegLSPresetParameters: return ReadPresetParameters(); case JpegMarkerCode::ApplicationData0: return 0; case JpegMarkerCode::ApplicationData7: return ReadColorSpace(); case JpegMarkerCode::ApplicationData8: return ReadColorXForm(); case JpegMarkerCode::StartOfFrameBaselineJpeg: case JpegMarkerCode::StartOfFrameExtendedSequential: case JpegMarkerCode::StartOfFrameProgressive: case JpegMarkerCode::StartOfFrameLossless: case JpegMarkerCode::StartOfFrameDifferentialSequential: case JpegMarkerCode::StartOfFrameDifferentialProgressive: case JpegMarkerCode::StartOfFrameDifferentialLossless: case JpegMarkerCode::StartOfFrameExtendedArithemtic: case JpegMarkerCode::StartOfFrameProgressiveArithemtic: case JpegMarkerCode::StartOfFrameLosslessArithemtic: { std::ostringstream message; message << "JPEG encoding with marker " << static_cast(marker) << " is not supported."; throw charls_error(ApiResult::UnsupportedEncoding, message.str()); } // Other tags not supported (among which DNL DRI) default: { std::ostringstream message; message << "Unknown JPEG marker " << static_cast(marker) << " encountered."; throw charls_error(ApiResult::UnknownJpegMarker, message.str()); } } } int JpegStreamReader::ReadPresetParameters() { const int type = ReadByte(); switch (type) { case 1: { _params.custom.MaximumSampleValue = ReadWord(); _params.custom.Threshold1 = ReadWord(); _params.custom.Threshold2 = ReadWord(); _params.custom.Threshold3 = ReadWord(); _params.custom.ResetValue = ReadWord(); return 11; } case 2: // mapping table specification case 3: // mapping table continuation case 4: // X and Y parameters greater than 16 bits are defined. { std::ostringstream message; message << "JPEG-LS preset parameters with type " << static_cast(type) << " are not supported."; throw charls_error(ApiResult::UnsupportedEncoding, message.str()); } default: { std::ostringstream message; message << "JPEG-LS preset parameters with invalid type " << static_cast(type) << " encountered."; throw charls_error(ApiResult::InvalidJlsParameters, message.str()); } } } void JpegStreamReader::ReadStartOfScan(bool firstComponent) { if (!firstComponent) { if (ReadByte() != 0xFF) throw charls_error(ApiResult::MissingJpegMarkerStart); if (static_cast(ReadByte()) != JpegMarkerCode::StartOfScan) throw charls_error(ApiResult::InvalidCompressedData);// TODO: throw more specific error code. } int length = ReadByte(); length = length * 256 + ReadByte(); // TODO: do something with 'length' or remove it. const int componentCount = ReadByte(); if (componentCount != 1 && componentCount != _params.components) throw charls_error(ApiResult::ParameterValueNotSupported); for (int i = 0; i < componentCount; ++i) { ReadByte(); ReadByte(); } _params.allowedLossyError = ReadByte(); _params.interleaveMode = static_cast(ReadByte()); if (!(_params.interleaveMode == InterleaveMode::None || _params.interleaveMode == InterleaveMode::Line || _params.interleaveMode == InterleaveMode::Sample)) throw charls_error(ApiResult::InvalidCompressedData);// TODO: throw more specific error code. if (ReadByte() != 0) throw charls_error(ApiResult::InvalidCompressedData);// TODO: throw more specific error code. if(_params.stride == 0) { const int width = _rect.Width != 0 ? _rect.Width : _params.width; const int components = _params.interleaveMode == InterleaveMode::None ? 1 : _params.components; _params.stride = components * width * ((_params.bitsPerSample + 7) / 8); } } int JpegStreamReader::ReadComment() noexcept { return 0; } void JpegStreamReader::ReadJfif() { for(int i = 0; i < static_cast(sizeof(jfifID)); i++) { if(jfifID[i] != ReadByte()) return; } _params.jfif.version = ReadWord(); // DPI or DPcm _params.jfif.units = ReadByte(); _params.jfif.Xdensity = ReadWord(); _params.jfif.Ydensity = ReadWord(); // thumbnail _params.jfif.Xthumbnail = ReadByte(); _params.jfif.Ythumbnail = ReadByte(); if(_params.jfif.Xthumbnail > 0 && _params.jfif.thumbnail) { std::vector tempbuff(static_cast(_params.jfif.thumbnail), static_cast(_params.jfif.thumbnail) + static_cast(3) * _params.jfif.Xthumbnail * _params.jfif.Ythumbnail); ReadNBytes(tempbuff, 3*_params.jfif.Xthumbnail*_params.jfif.Ythumbnail); } } int JpegStreamReader::ReadStartOfFrame() { _params.bitsPerSample = ReadByte(); _params.height = ReadWord(); _params.width = ReadWord(); _params.components= ReadByte(); return 6; } uint8_t JpegStreamReader::ReadByte() { if (_byteStream.rawStream) return static_cast(_byteStream.rawStream->sbumpc()); if (_byteStream.count == 0) throw charls_error(ApiResult::CompressedBufferTooSmall); const uint8_t value = _byteStream.rawData[0]; SkipBytes(_byteStream, 1); return value; } int JpegStreamReader::ReadWord() { const int i = ReadByte() * 256; return i + ReadByte(); } int JpegStreamReader::ReadColorSpace() const noexcept { return 0; } int JpegStreamReader::ReadColorXForm() { std::vector sourceTag; ReadNBytes(sourceTag, 4); if (strncmp(sourceTag.data(), "mrfx", 4) != 0) return 4; const auto xform = ReadByte(); switch (xform) { case static_cast(ColorTransformation::None): case static_cast(ColorTransformation::HP1): case static_cast(ColorTransformation::HP2): case static_cast(ColorTransformation::HP3): _params.colorTransformation = static_cast(xform); return 5; case 4: // RgbAsYuvLossy (The standard lossy RGB to YCbCr transform used in JPEG.) case 5: // Matrix (transformation is controlled using a matrix that is also stored in the segment. throw charls_error(ApiResult::ImageTypeNotSupported); default: throw charls_error(ApiResult::InvalidCompressedData); } } dcm2niix-1.0.20211006/console/charls/jpegstreamreader.h000066400000000000000000000032001412761503300223710ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_JPEGSTREAMREADER #define CHARLS_JPEGSTREAMREADER #include "publictypes.h" #include #include enum class JpegMarkerCode : uint8_t; struct JlsParameters; class JpegCustomParameters; JpegLSPresetCodingParameters ComputeDefault(int32_t maximumSampleValue, int32_t allowedLossyError) noexcept; // // JpegStreamReader: minimal implementation to read a JPEG byte stream. // class JpegStreamReader { public: explicit JpegStreamReader(ByteStreamInfo byteStreamInfo) noexcept; const JlsParameters& GetMetadata() const noexcept { return _params; } const JpegLSPresetCodingParameters& GetCustomPreset() const noexcept { return _params.custom; } void Read(ByteStreamInfo rawPixels); void ReadHeader(); void SetInfo(const JlsParameters& params) noexcept { _params = params; } void SetRect(const JlsRect& rect) noexcept { _rect = rect; } void ReadStartOfScan(bool firstComponent); uint8_t ReadByte(); private: JpegMarkerCode ReadNextMarker(); int ReadPresetParameters(); static int ReadComment() noexcept; int ReadStartOfFrame(); int ReadWord(); void ReadNBytes(std::vector& dst, int byteCount); int ReadMarker(JpegMarkerCode marker); void ReadJfif(); // Color Transform Application Markers & Code Stream (HP extension) int ReadColorSpace() const noexcept; int ReadColorXForm(); ByteStreamInfo _byteStream; JlsParameters _params; JlsRect _rect; }; #endif dcm2niix-1.0.20211006/console/charls/jpegstreamwriter.cpp000066400000000000000000000042741412761503300230120ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #include "jpegstreamwriter.h" #include "jpegimagedatasegment.h" #include "jpegmarkercode.h" #include "jpegmarkersegment.h" #include "jpegstreamreader.h" #include "util.h" #include using namespace charls; namespace { bool IsDefault(const JpegLSPresetCodingParameters& custom) noexcept { if (custom.MaximumSampleValue != 0) return false; if (custom.Threshold1 != 0) return false; if (custom.Threshold2 != 0) return false; if (custom.Threshold3 != 0) return false; if (custom.ResetValue != 0) return false; return true; } } JpegStreamWriter::JpegStreamWriter() noexcept : _data(), _byteOffset(0), _lastCompenentIndex(0) { } void JpegStreamWriter::AddColorTransform(ColorTransformation transformation) { AddSegment(JpegMarkerSegment::CreateColorTransformSegment(transformation)); } size_t JpegStreamWriter::Write(const ByteStreamInfo& info) { _data = info; WriteMarker(JpegMarkerCode::StartOfImage); for (size_t i = 0; i < _segments.size(); ++i) { _segments[i]->Serialize(*this); } WriteMarker(JpegMarkerCode::EndOfImage); return _byteOffset; } void JpegStreamWriter::AddScan(const ByteStreamInfo& info, const JlsParameters& params) { if (!IsDefault(params.custom)) { AddSegment(JpegMarkerSegment::CreateJpegLSPresetParametersSegment(params.custom)); } else if (params.bitsPerSample > 12) { const JpegLSPresetCodingParameters preset = ComputeDefault((1 << params.bitsPerSample) - 1, params.allowedLossyError); AddSegment(JpegMarkerSegment::CreateJpegLSPresetParametersSegment(preset)); } // Note: it is a common practice to start to count components by index 1. _lastCompenentIndex += 1; const int componentCount = params.interleaveMode == InterleaveMode::None ? 1 : params.components; AddSegment(JpegMarkerSegment::CreateStartOfScanSegment(_lastCompenentIndex, componentCount, params.allowedLossyError, params.interleaveMode)); AddSegment(std::make_unique(info, params, componentCount)); } dcm2niix-1.0.20211006/console/charls/jpegstreamwriter.h000066400000000000000000000045621412761503300224570ustar00rootroot00000000000000// // (C) CharLS Team 2014, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_JPEGSTREAMWRITER #define CHARLS_JPEGSTREAMWRITER #include "util.h" #include "jpegsegment.h" #include #include enum class JpegMarkerCode : uint8_t; // // Purpose: 'Writer' class that can generate JPEG-LS file streams. // class JpegStreamWriter { friend class JpegMarkerSegment; friend class JpegImageDataSegment; public: JpegStreamWriter() noexcept; void AddSegment(std::unique_ptr segment) { _segments.push_back(std::move(segment)); } void AddScan(const ByteStreamInfo& info, const JlsParameters& params); void AddColorTransform(charls::ColorTransformation transformation); std::size_t GetBytesWritten() const noexcept { return _byteOffset; } std::size_t GetLength() const noexcept { return _data.count - _byteOffset; } std::size_t Write(const ByteStreamInfo& info); private: uint8_t* GetPos() const noexcept { return _data.rawData + _byteOffset; } ByteStreamInfo OutputStream() const noexcept { ByteStreamInfo data = _data; data.count -= _byteOffset; data.rawData += _byteOffset; return data; } void WriteByte(uint8_t val) { if (_data.rawStream) { _data.rawStream->sputc(val); } else { if (_byteOffset >= _data.count) throw charls_error(charls::ApiResult::CompressedBufferTooSmall); _data.rawData[_byteOffset++] = val; } } void WriteBytes(const std::vector& bytes) { for (std::size_t i = 0; i < bytes.size(); ++i) { WriteByte(bytes[i]); } } void WriteWord(uint16_t value) { WriteByte(static_cast(value / 0x100)); WriteByte(static_cast(value % 0x100)); } void WriteMarker(JpegMarkerCode marker) { WriteByte(0xFF); WriteByte(static_cast(marker)); } void Seek(std::size_t byteCount) noexcept { if (_data.rawStream) return; _byteOffset += byteCount; } ByteStreamInfo _data; std::size_t _byteOffset; int32_t _lastCompenentIndex; std::vector> _segments; }; #endif dcm2niix-1.0.20211006/console/charls/lookuptable.h000066400000000000000000000027011412761503300213730ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_LOOKUPTABLE #define CHARLS_LOOKUPTABLE #include // Tables for fast decoding of short Golomb Codes. struct Code { Code() noexcept : _value(), _length() { } Code(int32_t value, int32_t length) noexcept : _value(value), _length(length) { } int32_t GetValue() const noexcept { return _value; } int32_t GetLength() const noexcept { return _length; } int32_t _value; int32_t _length; }; class CTable { public: static constexpr size_t byte_bit_count = 8; CTable() noexcept { std::memset(_rgtype, 0, sizeof(_rgtype)); } void AddEntry(uint8_t bvalue, Code c) noexcept { const int32_t length = c.GetLength(); ASSERT(static_cast(length) <= byte_bit_count); for (int32_t i = 0; i < static_cast(1) << (byte_bit_count - length); ++i) { ASSERT(_rgtype[(bvalue << (byte_bit_count - length)) + i].GetLength() == 0); _rgtype[(bvalue << (byte_bit_count - length)) + i] = c; } } FORCE_INLINE const Code& Get(int32_t value) const noexcept { return _rgtype[value]; } private: Code _rgtype[1 << byte_bit_count]; }; #endif dcm2niix-1.0.20211006/console/charls/losslesstraits.h000066400000000000000000000070731412761503300221570ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_LOSSLESSTRAITS #define CHARLS_LOSSLESSTRAITS #include "constants.h" // Optimized trait classes for lossless compression of 8 bit color and 8/16 bit monochrome images. // This class assumes MaximumSampleValue correspond to a whole number of bits, and no custom ResetValue is set when encoding. // The point of this is to have the most optimized code for the most common and most demanding scenario. template struct LosslessTraitsImpl { using SAMPLE = sample; enum { NEAR = 0, bpp = bitsperpixel, qbpp = bitsperpixel, RANGE = (1 << bpp), MAXVAL= (1 << bpp) - 1, LIMIT = 2 * (bitsperpixel + std::max(8, bitsperpixel)), RESET = DefaultResetValue }; static FORCE_INLINE int32_t ComputeErrVal(int32_t d) noexcept { return ModuloRange(d); } static FORCE_INLINE bool IsNear(int32_t lhs, int32_t rhs) noexcept { return lhs == rhs; } // The following optimization is implementation-dependent (works on x86 and ARM, see charlstest). #if defined(__clang__) __attribute__((no_sanitize("shift"))) #endif static FORCE_INLINE int32_t ModuloRange(int32_t errorValue) noexcept { return static_cast(errorValue << (int32_t_bit_count - bpp)) >> (int32_t_bit_count - bpp); } static FORCE_INLINE SAMPLE ComputeReconstructedSample(int32_t Px, int32_t ErrVal) noexcept { return static_cast(MAXVAL & (Px + ErrVal)); } static FORCE_INLINE int32_t CorrectPrediction(int32_t Pxc) noexcept { if ((Pxc & MAXVAL) == Pxc) return Pxc; return (~(Pxc >> (int32_t_bit_count-1))) & MAXVAL; } }; template struct LosslessTraits : LosslessTraitsImpl { using PIXEL = T; }; template<> struct LosslessTraits : LosslessTraitsImpl { using PIXEL = SAMPLE; static FORCE_INLINE signed char ModRange(int32_t Errval) noexcept { return static_cast(Errval); } static FORCE_INLINE int32_t ComputeErrVal(int32_t d) noexcept { return static_cast(d); } static FORCE_INLINE uint8_t ComputeReconstructedSample(int32_t Px, int32_t ErrVal) noexcept { return static_cast(Px + ErrVal); } }; template<> struct LosslessTraits : LosslessTraitsImpl { using PIXEL = SAMPLE; static FORCE_INLINE short ModRange(int32_t Errval) noexcept { return static_cast(Errval); } static FORCE_INLINE int32_t ComputeErrVal(int32_t d) noexcept { return static_cast(d); } static FORCE_INLINE SAMPLE ComputeReconstructedSample(int32_t Px, int32_t ErrVal) noexcept { return static_cast(Px + ErrVal); } }; template struct LosslessTraits, bpp> : LosslessTraitsImpl { using PIXEL = Triplet; static FORCE_INLINE bool IsNear(int32_t lhs, int32_t rhs) noexcept { return lhs == rhs; } static FORCE_INLINE bool IsNear(PIXEL lhs, PIXEL rhs) noexcept { return lhs == rhs; } static FORCE_INLINE T ComputeReconstructedSample(int32_t Px, int32_t ErrVal) noexcept { return static_cast(Px + ErrVal); } }; #endif dcm2niix-1.0.20211006/console/charls/processline.h000066400000000000000000000303171412761503300214040ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_PROCESSLINE #define CHARLS_PROCESSLINE #include "util.h" #include "publictypes.h" #include #include #include #include // // This file defines the ProcessLine base class, its derivatives and helper functions. // During coding/decoding, CharLS process one line at a time. The different Processline implementations // convert the uncompressed format to and from the internal format for encoding. // Conversions include color transforms, line interleaved vs sample interleaved, masking out unused bits, // accounting for line padding etc. // This mechanism could be used to encode/decode images as they are received. // class ProcessLine { public: virtual ~ProcessLine() = default; ProcessLine(const ProcessLine&) = delete; ProcessLine(ProcessLine&&) = delete; ProcessLine& operator=(const ProcessLine&) = delete; ProcessLine& operator=(ProcessLine&&) = delete; virtual void NewLineDecoded(const void* pSrc, int pixelCount, int sourceStride) = 0; virtual void NewLineRequested(void* pDest, int pixelCount, int destStride) = 0; protected: ProcessLine() = default; }; class PostProcesSingleComponent : public ProcessLine { public: PostProcesSingleComponent(void* rawData, const JlsParameters& params, size_t bytesPerPixel) noexcept : _rawData(static_cast(rawData)), _bytesPerPixel(bytesPerPixel), _bytesPerLine(params.stride) { } WARNING_SUPPRESS(26440) void NewLineRequested(void* dest, int pixelCount, int /*byteStride*/) override { std::memcpy(dest, _rawData, pixelCount * _bytesPerPixel); _rawData += _bytesPerLine; } void NewLineDecoded(const void* pSrc, int pixelCount, int /*sourceStride*/) override { std::memcpy(_rawData, pSrc, pixelCount * _bytesPerPixel); _rawData += _bytesPerLine; } WARNING_UNSUPPRESS() private: uint8_t* _rawData; size_t _bytesPerPixel; size_t _bytesPerLine; }; inline void ByteSwap(unsigned char* data, int count) { if (static_cast(count) & 1u) { std::ostringstream message; message << "An odd number of bytes (" << count << ") cannot be swapped."; throw charls_error(charls::ApiResult::InvalidJlsParameters, message.str()); } const auto data32 = reinterpret_cast(data); for(auto i = 0; i < count / 4; i++) { const auto value = data32[i]; data32[i] = ((value >> 8u) & 0x00FF00FFu) | ((value & 0x00FF00FFu) << 8u); } if ((count % 4) != 0) { std::swap(data[count-2], data[count-1]); } } class PostProcesSingleStream : public ProcessLine { public: PostProcesSingleStream(std::basic_streambuf* rawData, const JlsParameters& params, size_t bytesPerPixel) noexcept : _rawData(rawData), _bytesPerPixel(bytesPerPixel), _bytesPerLine(params.stride) { } void NewLineRequested(void* dest, int pixelCount, int /*destStride*/) override { auto bytesToRead = pixelCount * _bytesPerPixel; while (bytesToRead != 0) { const auto bytesRead = _rawData->sgetn(static_cast(dest), bytesToRead); if (bytesRead == 0) throw charls_error(charls::ApiResult::UncompressedBufferTooSmall); bytesToRead = static_cast(bytesToRead - bytesRead); } if (_bytesPerPixel == 2) { ByteSwap(static_cast(dest), 2 * pixelCount); } if (_bytesPerLine - pixelCount * _bytesPerPixel > 0) { _rawData->pubseekoff(static_cast(_bytesPerLine - bytesToRead), std::ios_base::cur); } } void NewLineDecoded(const void* pSrc, int pixelCount, int /*sourceStride*/) override { const auto bytesToWrite = pixelCount * _bytesPerPixel; const auto bytesWritten = static_cast(_rawData->sputn(static_cast(pSrc), bytesToWrite)); if (bytesWritten != bytesToWrite) throw charls_error(charls::ApiResult::UncompressedBufferTooSmall); } private: std::basic_streambuf* _rawData; size_t _bytesPerPixel; size_t _bytesPerLine; }; template void TransformLineToQuad(const T* ptypeInput, int32_t pixelStrideIn, Quad* pbyteBuffer, int32_t pixelStride, TRANSFORM& transform) noexcept { const int cpixel = std::min(pixelStride, pixelStrideIn); Quad* ptypeBuffer = pbyteBuffer; for (auto x = 0; x < cpixel; ++x) { const Quad pixel(transform(ptypeInput[x], ptypeInput[x + pixelStrideIn], ptypeInput[x + 2*pixelStrideIn]), ptypeInput[x + 3 * pixelStrideIn]); ptypeBuffer[x] = pixel; } } template void TransformQuadToLine(const Quad* pbyteInput, int32_t pixelStrideIn, T* ptypeBuffer, int32_t pixelStride, TRANSFORM& transform) noexcept { const auto cpixel = std::min(pixelStride, pixelStrideIn); const Quad* ptypeBufferIn = pbyteInput; for (auto x = 0; x < cpixel; ++x) { const Quad color = ptypeBufferIn[x]; const Quad colorTranformed(transform(color.v1, color.v2, color.v3), color.v4); ptypeBuffer[x] = colorTranformed.v1; ptypeBuffer[x + pixelStride] = colorTranformed.v2; ptypeBuffer[x + 2 * pixelStride] = colorTranformed.v3; ptypeBuffer[x + 3 * pixelStride] = colorTranformed.v4; } } template void TransformRgbToBgr(T* pDest, int samplesPerPixel, int pixelCount) noexcept { for (auto i = 0; i < pixelCount; ++i) { std::swap(pDest[0], pDest[2]); pDest += samplesPerPixel; } } template void TransformLine(Triplet* pDest, const Triplet* pSrc, int pixelCount, TRANSFORM& transform) noexcept { for (auto i = 0; i < pixelCount; ++i) { pDest[i] = transform(pSrc[i].v1, pSrc[i].v2, pSrc[i].v3); } } template void TransformLineToTriplet(const T* ptypeInput, int32_t pixelStrideIn, Triplet* pbyteBuffer, int32_t pixelStride, TRANSFORM& transform) noexcept { const auto cpixel = std::min(pixelStride, pixelStrideIn); Triplet* ptypeBuffer = pbyteBuffer; for (auto x = 0; x < cpixel; ++x) { ptypeBuffer[x] = transform(ptypeInput[x], ptypeInput[x + pixelStrideIn], ptypeInput[x + 2*pixelStrideIn]); } } template void TransformTripletToLine(const Triplet* pbyteInput, int32_t pixelStrideIn, T* ptypeBuffer, int32_t pixelStride, TRANSFORM& transform) noexcept { const auto cpixel = std::min(pixelStride, pixelStrideIn); const Triplet* ptypeBufferIn = pbyteInput; for (auto x = 0; x < cpixel; ++x) { const Triplet color = ptypeBufferIn[x]; const Triplet colorTranformed = transform(color.v1, color.v2, color.v3); ptypeBuffer[x] = colorTranformed.v1; ptypeBuffer[x + pixelStride] = colorTranformed.v2; ptypeBuffer[x + 2 *pixelStride] = colorTranformed.v3; } } template class ProcessTransformed : public ProcessLine { public: ProcessTransformed(ByteStreamInfo rawStream, const JlsParameters& info, TRANSFORM transform) : _params(info), _templine(static_cast(info.width) * info.components), _buffer(static_cast(info.width) * info.components * sizeof(size_type)), _transform(transform), _inverseTransform(transform), _rawPixels(rawStream) { } void NewLineRequested(void* dest, int pixelCount, int destStride) override { if (!_rawPixels.rawStream) { Transform(_rawPixels.rawData, dest, pixelCount, destStride); _rawPixels.rawData += _params.stride; return; } Transform(_rawPixels.rawStream, dest, pixelCount, destStride); } void Transform(std::basic_streambuf* rawStream, void* dest, int pixelCount, int destStride) { std::streamsize bytesToRead = static_cast(pixelCount) * _params.components * sizeof(size_type); while (bytesToRead != 0) { const auto read = rawStream->sgetn(reinterpret_cast(_buffer.data()), bytesToRead); if (read == 0) { std::ostringstream message; message << "No more bytes available in input buffer, still needing " << read; throw charls_error(charls::ApiResult::UncompressedBufferTooSmall, message.str()); } bytesToRead -= read; } Transform(_buffer.data(), dest, pixelCount, destStride); } void Transform(const void* source, void* dest, int pixelCount, int destStride) noexcept { if (_params.outputBgr) { memcpy(_templine.data(), source, sizeof(Triplet) * pixelCount); TransformRgbToBgr(_templine.data(), _params.components, pixelCount); source = _templine.data(); } if (_params.components == 3) { if (_params.interleaveMode == charls::InterleaveMode::Sample) { TransformLine(static_cast*>(dest), static_cast*>(source), pixelCount, _transform); } else { TransformTripletToLine(static_cast*>(source), pixelCount, static_cast(dest), destStride, _transform); } } else if (_params.components == 4 && _params.interleaveMode == charls::InterleaveMode::Line) { TransformQuadToLine(static_cast*>(source), pixelCount, static_cast(dest), destStride, _transform); } } void DecodeTransform(const void* pSrc, void* rawData, int pixelCount, int byteStride) noexcept { if (_params.components == 3) { if (_params.interleaveMode == charls::InterleaveMode::Sample) { TransformLine(static_cast*>(rawData), static_cast*>(pSrc), pixelCount, _inverseTransform); } else { TransformLineToTriplet(static_cast(pSrc), byteStride, static_cast*>(rawData), pixelCount, _inverseTransform); } } else if (_params.components == 4 && _params.interleaveMode == charls::InterleaveMode::Line) { TransformLineToQuad(static_cast(pSrc), byteStride, static_cast*>(rawData), pixelCount, _inverseTransform); } if (_params.outputBgr) { TransformRgbToBgr(static_cast(rawData), _params.components, pixelCount); } } void NewLineDecoded(const void* pSrc, int pixelCount, int sourceStride) override { if (_rawPixels.rawStream) { const std::streamsize bytesToWrite = static_cast(pixelCount) * _params.components * sizeof(size_type); DecodeTransform(pSrc, _buffer.data(), pixelCount, sourceStride); const auto bytesWritten = _rawPixels.rawStream->sputn(reinterpret_cast(_buffer.data()), bytesToWrite); if (bytesWritten != bytesToWrite) throw charls_error(charls::ApiResult::UncompressedBufferTooSmall); } else { DecodeTransform(pSrc, _rawPixels.rawData, pixelCount, sourceStride); _rawPixels.rawData += _params.stride; } } private: using size_type = typename TRANSFORM::size_type; const JlsParameters& _params; std::vector _templine; std::vector _buffer; TRANSFORM _transform; typename TRANSFORM::Inverse _inverseTransform; ByteStreamInfo _rawPixels; }; #endif dcm2niix-1.0.20211006/console/charls/publictypes.h000066400000000000000000000317571412761503300214320ustar00rootroot00000000000000/* (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. */ #ifndef CHARLS_PUBLICTYPES #define CHARLS_PUBLICTYPES #ifdef __cplusplus #include #include namespace charls { /// /// Defines the result values that are returned by the CharLS API functions. /// enum class ApiResult { OK = 0, // The operation completed without errors. InvalidJlsParameters = 1, // One of the JLS parameters is invalid. ParameterValueNotSupported = 2, // The parameter value not supported. UncompressedBufferTooSmall = 3, // The uncompressed buffer is too small to hold all the output. CompressedBufferTooSmall = 4, // The compressed buffer too small, more input data was expected. InvalidCompressedData = 5, // This error is returned when the encoded bit stream contains a general structural problem. TooMuchCompressedData = 6, // Too much compressed data.The decoding process is ready but the input buffer still contains encoded data. ImageTypeNotSupported = 7, // This error is returned when the bit stream is encoded with an option that is not supported by this implementation. UnsupportedBitDepthForTransform = 8, // The bit depth for transformation is not supported. UnsupportedColorTransform = 9, // The color transformation is not supported. UnsupportedEncoding = 10, // This error is returned when an encoded frame is found that is not encoded with the JPEG-LS algorithm. UnknownJpegMarker = 11, // This error is returned when an unknown JPEG marker code is detected in the encoded bit stream. MissingJpegMarkerStart = 12, // This error is returned when the algorithm expect a 0xFF code (indicates start of a JPEG marker) but none was found. UnspecifiedFailure = 13, // This error is returned when the implementation detected a failure, but no specific error is available. UnexpectedFailure = 14 // This error is returned when the implementation encountered a failure it didn't expect. No guarantees can be given for the state after this error. }; /// /// Defines the interleave mode for multi-component (color) pixel data. /// enum class InterleaveMode { /// /// The data is encoded and stored as component for component: RRRGGGBBB. /// None = 0, /// /// The interleave mode is by line. A full line of each component is encoded before moving to the next line. /// Line = 1, /// /// The data is encoded and stored by sample. For color images this is the format like RGBRGBRGB. /// Sample = 2 }; /// /// Defines color space transformations as defined and implemented by the JPEG-LS library of HP Labs. /// These color space transformation decrease the correlation between the 3 color components, resulting in better encoding ratio. /// These options are only implemented for backwards compatibility and NOT part of the JPEG-LS standard. /// The JPEG-LS ISO/IEC 14495-1:1999 standard provides no capabilities to transport which color space transformation was used. /// enum class ColorTransformation { /// /// No color space transformation has been applied. /// None = 0, /// /// Defines the reversible lossless color transformation: /// G = G /// R = R - G /// B = B - G /// HP1 = 1, /// /// Defines the reversible lossless color transformation: /// G = G /// B = B - (R + G) / 2 /// R = R - G /// HP2 = 2, /// /// Defines the reversible lossless color transformation of Y-Cb-Cr): /// R = R - G /// B = B - G /// G = G + (R + B) / 4 /// HP3 = 3, }; } using CharlsApiResultType = charls::ApiResult; using CharlsInterleaveModeType = charls::InterleaveMode; using CharlsColorTransformationType = charls::ColorTransformation; // Defines the size of the char buffer that should be passed to the CharLS API to get the error message text. const std::size_t ErrorMessageSize = 256; #else #include enum CharlsApiResult { CHARLS_API_RESULT_OK = 0, // The operation completed without errors. CHARLS_API_RESULT_INVALID_JLS_PARAMETERS = 1, // One of the JLS parameters is invalid. CHARLS_API_RESULT_PARAMETER_VALUE_NOT_SUPPORTED = 2, // The parameter value not supported. CHARLS_API_RESULT_UNCOMPRESSED_BUFFER_TOO_SMALL = 3, // The uncompressed buffer is too small to hold all the output. CHARLS_API_RESULT_COMPRESSED_BUFFER_TOO_SMALL = 4, // The compressed buffer too small, more input data was expected. CHARLS_API_RESULT_INVALID_COMPRESSED_DATA = 5, // This error is returned when the encoded bit stream contains a general structural problem. CHARLS_API_RESULT_TOO_MUCH_COMPRESSED_DATA = 6, // Too much compressed data.The decoding process is ready but the input buffer still contains encoded data. CHARLS_API_RESULT_IMAGE_TYPE_NOT_SUPPORTED = 7, // This error is returned when the bit stream is encoded with an option that is not supported by this implementation. CHARLS_API_RESULT_UNSUPPORTED_BIT_DEPTH_FOR_TRANSFORM = 8, // The bit depth for transformation is not supported. CHARLS_API_RESULT_UNSUPPORTED_COLOR_TRANSFORM = 9, // The color transformation is not supported. CHARLS_API_RESULT_UNSUPPORTED_ENCODING = 10, // This error is returned when an encoded frame is found that is not encoded with the JPEG-LS algorithm. CHARLS_API_RESULT_UNKNOWN_JPEG_MARKER = 11, // This error is returned when an unknown JPEG marker code is detected in the encoded bit stream. CHARLS_API_RESULT_MISSING_JPEG_MARKER_START = 12, // This error is returned when the algorithm expect a 0xFF code (indicates start of a JPEG marker) but none was found. CHARLS_API_RESULT_UNSPECIFIED_FAILURE = 13, // This error is returned when the implementation detected a failure, but no specific error is available. CHARLS_API_RESULT_UNEXPECTED_FAILURE = 14, // This error is returned when the implementation encountered a failure it didn't expect. No guarantees can be given for the state after this error. }; enum CharlsInterleaveMode { CHARLS_IM_NONE = 0, CHARLS_IM_LINE = 1, CHARLS_IM_SAMPLE = 2 }; enum CharlsColorTransformation { CHARLS_COLOR_TRANSFORMATION_NONE = 0, CHARLS_COLOR_TRANSFORMATION_HP1 = 1, CHARLS_COLOR_TRANSFORMATION_HP2 = 2, CHARLS_COLOR_TRANSFORMATION_HP3 = 3, }; typedef enum CharlsApiResult CharlsApiResultType; typedef enum CharlsInterleaveMode CharlsInterleaveModeType; typedef enum CharlsColorTransformation CharlsColorTransformationType; // Defines the size of the char buffer that should be passed to the CharLS API to get the error message text. #define CHARLS_ERROR_MESSAGE_SIZE 256 #endif /// /// Defines the JPEG-LS preset coding parameters as defined in ISO/IEC 14495-1, C.2.4.1.1. /// JPEG-LS defines a default set of parameters, but custom parameters can be used. /// When used these parameters are written into the encoded bit stream as they are needed for the decoding process. /// struct JpegLSPresetCodingParameters { /// /// Maximum possible value for any image sample in a scan. /// This must be greater than or equal to the actual maximum value for the components in a scan. /// int MaximumSampleValue; /// /// First quantization threshold value for the local gradients. /// int Threshold1; /// /// Second quantization threshold value for the local gradients. /// int Threshold2; /// /// Third quantization threshold value for the local gradients. /// int Threshold3; /// /// Value at which the counters A, B, and N are halved. /// int ResetValue; }; struct JlsRect { int X; int Y; int Width; int Height; }; /// /// Defines the parameters for the JPEG File Interchange Format. /// The format is defined in the JPEG File Interchange Format v1.02 document by Eric Hamilton. /// /// /// The JPEG File Interchange Format is the de-facto standard JPEG interchange format. /// struct JfifParameters { /// /// Version of the JPEG File Interchange Format. /// Should be set to zero to not write a JFIF header or to 1.02, encoded as: (1 * 256) + 2. /// int32_t version; /// /// Defines the units for the X and Y densities. /// 0: no units, X and Y specify the pixel aspect ratio. /// 1: X and Y are dots per inch. /// 2: X and Y are dots per cm. /// int32_t units; /// /// Horizontal pixel density /// int32_t Xdensity; /// /// Vertical pixel density /// int32_t Ydensity; /// /// Thumbnail horizontal pixel count. /// int32_t Xthumbnail; /// /// Thumbnail vertical pixel count. /// int32_t Ythumbnail; /// /// Reference to a buffer with thumbnail pixels of size Xthumbnail * Ythumbnail * 3(RGB). /// This parameter is only used when creating JPEG-LS encoded images. /// void* thumbnail; }; struct JlsParameters { /// /// Width of the image in pixels. /// int width; /// /// Height of the image in pixels. /// int height; /// /// The number of valid bits per sample to encode. /// Valid range 2 - 16. When greater than 8, pixels are assumed to stored as two bytes per sample, otherwise one byte per sample is assumed. /// int bitsPerSample; /// /// The stride is the number of bytes from one row of pixels in memory to the next row of pixels in memory. /// Stride is sometimes called pitch. If padding bytes are present, the stride is wider than the width of the image. /// int stride; /// /// The number of components. /// Typical 1 for monochrome images and 3 for color images or 4 if alpha channel is present. /// int components; /// /// Defines the allowed lossy error. Value 0 defines lossless. /// int allowedLossyError; /// /// Determines the order of the color components in the compressed stream. /// CharlsInterleaveModeType interleaveMode; /// /// Color transformation used in the compressed stream. The color transformations are all lossless and /// are an HP proprietary extension of the standard. Do not use the color transformations unless /// you know the decoder is capable of decoding it. Color transform typically improve compression ratios only /// for synthetic images (non - photo-realistic computer generated images). /// CharlsColorTransformationType colorTransformation; /// /// If set to true RGB images will be decoded to BGR. BGR is the standard ordering in MS Windows bitmaps. /// char outputBgr; struct JpegLSPresetCodingParameters custom; struct JfifParameters jfif; }; #ifdef __cplusplus #include // // ByteStreamInfo & FromByteArray helper function // // ByteStreamInfo describes the stream: either set rawStream to a valid stream, or rawData/count, not both. // it's possible to decode to memory streams, but using rawData will always be faster. // // Example use: // ByteStreamInfo streamInfo = { fileStream.rdbuf() }; // or // ByteStreamInfo streamInfo = FromByteArray( bytePtr, byteCount); // struct ByteStreamInfo { std::basic_streambuf* rawStream; uint8_t* rawData; std::size_t count; }; inline ByteStreamInfo FromByteArray(void* bytes, std::size_t count) noexcept { return { nullptr, static_cast(bytes), count }; } inline ByteStreamInfo FromByteArrayConst(const void* bytes, std::size_t count) noexcept { return FromByteArray(const_cast(bytes), count); } #endif #endif dcm2niix-1.0.20211006/console/charls/scan.h000066400000000000000000000707501412761503300200070ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_SCAN #define CHARLS_SCAN #include "lookuptable.h" #include "contextrunmode.h" #include "context.h" #include "colortransform.h" #include "processline.h" #include // This file contains the code for handling a "scan". Usually an image is encoded as a single scan. #ifdef _MSC_VER #pragma warning (disable: 4127) // conditional expression is constant (caused by some template methods that are not fully specialized) [VS2017] #endif extern CTable decodingTables[16]; extern std::vector rgquant8Ll; extern std::vector rgquant10Ll; extern std::vector rgquant12Ll; extern std::vector rgquant16Ll; inline int32_t ApplySign(int32_t i, int32_t sign) noexcept { return (sign ^ i) - sign; } // Two alternatives for GetPredictedValue() (second is slightly faster due to reduced branching) #if 0 inline int32_t GetPredictedValue(int32_t Ra, int32_t Rb, int32_t Rc) { if (Ra < Rb) { if (Rc < Ra) return Rb; if (Rc > Rb) return Ra; } else { if (Rc < Rb) return Ra; if (Rc > Ra) return Rb; } return Ra + Rb - Rc; } #else inline int32_t GetPredictedValue(int32_t Ra, int32_t Rb, int32_t Rc) noexcept { // sign trick reduces the number of if statements (branches) const int32_t sgn = BitWiseSign(Rb - Ra); // is Ra between Rc and Rb? if ((sgn ^ (Rc - Ra)) < 0) { return Rb; } if ((sgn ^ (Rb - Rc)) < 0) { return Ra; } // default case, valid if Rc element of [Ra,Rb] return Ra + Rb - Rc; } #endif inline int32_t UnMapErrVal(int32_t mappedError) noexcept { const int32_t sign = static_cast(mappedError << (int32_t_bit_count-1)) >> (int32_t_bit_count-1); return sign ^ (mappedError >> 1); } inline int32_t GetMappedErrVal(int32_t Errval) noexcept { const int32_t mappedError = (Errval >> (int32_t_bit_count-2)) ^ (2 * Errval); return mappedError; } inline int32_t ComputeContextID(int32_t Q1, int32_t Q2, int32_t Q3) noexcept { return (Q1 * 9 + Q2) * 9 + Q3; } template class JlsCodec : public Strategy { public: using PIXEL = typename Traits::PIXEL; using SAMPLE = typename Traits::SAMPLE; WARNING_SUPPRESS(26495) // false warning that _contextRunmode is unintialized JlsCodec(const Traits& inTraits, const JlsParameters& params) : Strategy(params), traits(inTraits), _rect(), _width(params.width), T1(0), T2(0), T3(0), _RUNindex(0), _previousLine(), _currentLine(), _pquant(nullptr) { if (Info().interleaveMode == InterleaveMode::None) { Info().components = 1; } } WARNING_UNSUPPRESS() void SetPresets(const JpegLSPresetCodingParameters& presets) override { const JpegLSPresetCodingParameters presetDefault = ComputeDefault(traits.MAXVAL, traits.NEAR); InitParams(presets.Threshold1 != 0 ? presets.Threshold1 : presetDefault.Threshold1, presets.Threshold2 != 0 ? presets.Threshold2 : presetDefault.Threshold2, presets.Threshold3 != 0 ? presets.Threshold3 : presetDefault.Threshold3, presets.ResetValue != 0 ? presets.ResetValue : presetDefault.ResetValue); } std::unique_ptr CreateProcess(ByteStreamInfo info) override; bool IsInterleaved() noexcept { if (Info().interleaveMode == InterleaveMode::None) return false; if (Info().components == 1) return false; return true; } JlsParameters& Info() noexcept { return Strategy::_params; } signed char QuantizeGratientOrg(int32_t Di) const noexcept; FORCE_INLINE int32_t QuantizeGratient(int32_t Di) const noexcept { ASSERT(QuantizeGratientOrg(Di) == *(_pquant + Di)); return *(_pquant + Di); } void InitQuantizationLUT(); int32_t DecodeValue(int32_t k, int32_t limit, int32_t qbpp); FORCE_INLINE void EncodeMappedValue(int32_t k, int32_t mappedError, int32_t limit); void IncrementRunIndex() noexcept { _RUNindex = std::min(31, _RUNindex + 1); } void DecrementRunIndex() noexcept { _RUNindex = std::max(0, _RUNindex - 1); } int32_t DecodeRIError(CContextRunMode& ctx); Triplet DecodeRIPixel(Triplet Ra, Triplet Rb); SAMPLE DecodeRIPixel(int32_t Ra, int32_t Rb); int32_t DecodeRunPixels(PIXEL Ra, PIXEL* startPos, int32_t cpixelMac); int32_t DoRunMode(int32_t startIndex, DecoderStrategy*); void EncodeRIError(CContextRunMode& ctx, int32_t Errval); SAMPLE EncodeRIPixel(int32_t x, int32_t Ra, int32_t Rb); Triplet EncodeRIPixel(Triplet x, Triplet Ra, Triplet Rb); void EncodeRunPixels(int32_t runLength, bool endOfLine); int32_t DoRunMode(int32_t index, EncoderStrategy*); FORCE_INLINE SAMPLE DoRegular(int32_t Qs, int32_t, int32_t pred, DecoderStrategy*); FORCE_INLINE SAMPLE DoRegular(int32_t Qs, int32_t x, int32_t pred, EncoderStrategy*); void DoLine(SAMPLE* pdummy); void DoLine(Triplet* pdummy); void DoScan(); void InitParams(int32_t t1, int32_t t2, int32_t t3, int32_t nReset); #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winconsistent-missing-override" #endif // Note: depending on the base class EncodeScan OR DecodeScan will be virtual and abstract, cannot use override in all cases. size_t EncodeScan(std::unique_ptr processLine, ByteStreamInfo& compressedData); void DecodeScan(std::unique_ptr processLine, const JlsRect& rect, ByteStreamInfo& compressedData); #if defined(__clang__) #pragma clang diagnostic pop #endif protected: // codec parameters Traits traits; JlsRect _rect; int _width; int32_t T1; int32_t T2; int32_t T3; // compression context JlsContext _contexts[365]; CContextRunMode _contextRunmode[2]; int32_t _RUNindex; PIXEL* _previousLine; PIXEL* _currentLine; // quantization lookup table signed char* _pquant; std::vector _rgquant; }; // Encode/decode a single sample. Performance wise the #1 important functions template typename Traits::SAMPLE JlsCodec::DoRegular(int32_t Qs, int32_t, int32_t pred, DecoderStrategy*) { const int32_t sign = BitWiseSign(Qs); JlsContext& ctx = _contexts[ApplySign(Qs, sign)]; const int32_t k = ctx.GetGolomb(); const int32_t Px = traits.CorrectPrediction(pred + ApplySign(ctx.C, sign)); int32_t ErrVal; const Code& code = decodingTables[k].Get(Strategy::PeekByte()); if (code.GetLength() != 0) { Strategy::Skip(code.GetLength()); ErrVal = code.GetValue(); ASSERT(std::abs(ErrVal) < 65535); } else { ErrVal = UnMapErrVal(DecodeValue(k, traits.LIMIT, traits.qbpp)); if (std::abs(ErrVal) > 65535) throw charls_error(charls::ApiResult::InvalidCompressedData); } if (k == 0) { ErrVal = ErrVal ^ ctx.GetErrorCorrection(traits.NEAR); } ctx.UpdateVariables(ErrVal, traits.NEAR, traits.RESET); ErrVal = ApplySign(ErrVal, sign); return traits.ComputeReconstructedSample(Px, ErrVal); } template typename Traits::SAMPLE JlsCodec::DoRegular(int32_t Qs, int32_t x, int32_t pred, EncoderStrategy*) { const int32_t sign = BitWiseSign(Qs); JlsContext& ctx = _contexts[ApplySign(Qs, sign)]; const int32_t k = ctx.GetGolomb(); const int32_t Px = traits.CorrectPrediction(pred + ApplySign(ctx.C, sign)); const int32_t ErrVal = traits.ComputeErrVal(ApplySign(x - Px, sign)); EncodeMappedValue(k, GetMappedErrVal(ctx.GetErrorCorrection(k | traits.NEAR) ^ ErrVal), traits.LIMIT); ctx.UpdateVariables(ErrVal, traits.NEAR, traits.RESET); ASSERT(traits.IsNear(traits.ComputeReconstructedSample(Px, ApplySign(ErrVal, sign)), x)); return static_cast(traits.ComputeReconstructedSample(Px, ApplySign(ErrVal, sign))); } // Functions to build tables used to decode short Golomb codes. inline std::pair CreateEncodedValue(int32_t k, int32_t mappedError) noexcept { const int32_t highbits = mappedError >> k; return std::make_pair(highbits + k + 1, (static_cast(1) << k) | (mappedError & ((static_cast(1) << k) - 1))); } inline CTable InitTable(int32_t k) noexcept { CTable table; for (short nerr = 0; ; nerr++) { // Q is not used when k != 0 const int32_t merrval = GetMappedErrVal(nerr); const std::pair paircode = CreateEncodedValue(k, merrval); if (static_cast(paircode.first) > CTable::byte_bit_count) break; const Code code(nerr, static_cast(paircode.first)); table.AddEntry(static_cast(paircode.second), code); } for (short nerr = -1; ; nerr--) { // Q is not used when k != 0 const int32_t merrval = GetMappedErrVal(nerr); const std::pair paircode = CreateEncodedValue(k, merrval); if (static_cast(paircode.first) > CTable::byte_bit_count) break; const Code code = Code(nerr, static_cast(paircode.first)); table.AddEntry(static_cast(paircode.second), code); } return table; } // Encoding/decoding of Golomb codes template int32_t JlsCodec::DecodeValue(int32_t k, int32_t limit, int32_t qbpp) { const int32_t highbits = Strategy::ReadHighbits(); if (highbits >= limit - (qbpp + 1)) return Strategy::ReadValue(qbpp) + 1; if (k == 0) return highbits; return (highbits << k) + Strategy::ReadValue(k); } template FORCE_INLINE void JlsCodec::EncodeMappedValue(int32_t k, int32_t mappedError, int32_t limit) { int32_t highbits = mappedError >> k; if (highbits < limit - traits.qbpp - 1) { if (highbits + 1 > 31) { Strategy::AppendToBitStream(0, highbits / 2); highbits = highbits - highbits / 2; } Strategy::AppendToBitStream(1, highbits + 1); Strategy::AppendToBitStream((mappedError & ((1 << k) - 1)), k); return; } if (limit - traits.qbpp > 31) { Strategy::AppendToBitStream(0, 31); Strategy::AppendToBitStream(1, limit - traits.qbpp - 31); } else { Strategy::AppendToBitStream(1, limit - traits.qbpp); } Strategy::AppendToBitStream((mappedError - 1) & ((1 << traits.qbpp) - 1), traits.qbpp); } // Disable the Microsoft Static Analyzer warning: Potential comparison of a constant with another constant. (false warning, triggered by template construction) #ifdef _PREFAST_ #pragma warning(push) #pragma warning(disable:6326) #endif // Sets up a lookup table to "Quantize" sample difference. template void JlsCodec::InitQuantizationLUT() { // for lossless mode with default parameters, we have precomputed the look up table for bit counts 8, 10, 12 and 16. if (traits.NEAR == 0 && traits.MAXVAL == (1 << traits.bpp) - 1) { const JpegLSPresetCodingParameters presets = ComputeDefault(traits.MAXVAL, traits.NEAR); if (presets.Threshold1 == T1 && presets.Threshold2 == T2 && presets.Threshold3 == T3) { if (traits.bpp == 8) { _pquant = &rgquant8Ll[rgquant8Ll.size() / 2]; return; } if (traits.bpp == 10) { _pquant = &rgquant10Ll[rgquant10Ll.size() / 2]; return; } if (traits.bpp == 12) { _pquant = &rgquant12Ll[rgquant12Ll.size() / 2]; return; } if (traits.bpp == 16) { _pquant = &rgquant16Ll[rgquant16Ll.size() / 2]; return; } } } const int32_t RANGE = 1 << traits.bpp; _rgquant.resize(static_cast(RANGE) * 2); _pquant = &_rgquant[RANGE]; for (int32_t i = -RANGE; i < RANGE; ++i) { _pquant[i] = QuantizeGratientOrg(i); } } #ifdef _PREFAST_ #pragma warning(pop) #endif template signed char JlsCodec::QuantizeGratientOrg(int32_t Di) const noexcept { if (Di <= -T3) return -4; if (Di <= -T2) return -3; if (Di <= -T1) return -2; if (Di < -traits.NEAR) return -1; if (Di <= traits.NEAR) return 0; if (Di < T1) return 1; if (Di < T2) return 2; if (Di < T3) return 3; return 4; } // RI = Run interruption: functions that handle the sample terminating a run. template int32_t JlsCodec::DecodeRIError(CContextRunMode& ctx) { const int32_t k = ctx.GetGolomb(); const int32_t EMErrval = DecodeValue(k, traits.LIMIT - J[_RUNindex]-1, traits.qbpp); const int32_t Errval = ctx.ComputeErrVal(EMErrval + ctx._nRItype, k); ctx.UpdateVariables(Errval, EMErrval); return Errval; } template void JlsCodec::EncodeRIError(CContextRunMode& ctx, int32_t Errval) { const int32_t k = ctx.GetGolomb(); const bool map = ctx.ComputeMap(Errval, k); const int32_t EMErrval = 2 * std::abs(Errval) - ctx._nRItype - static_cast(map); ASSERT(Errval == ctx.ComputeErrVal(EMErrval + ctx._nRItype, k)); EncodeMappedValue(k, EMErrval, traits.LIMIT-J[_RUNindex]-1); ctx.UpdateVariables(Errval, EMErrval); } template Triplet JlsCodec::DecodeRIPixel(Triplet Ra, Triplet Rb) { const int32_t Errval1 = DecodeRIError(_contextRunmode[0]); const int32_t Errval2 = DecodeRIError(_contextRunmode[0]); const int32_t Errval3 = DecodeRIError(_contextRunmode[0]); return Triplet(traits.ComputeReconstructedSample(Rb.v1, Errval1 * Sign(Rb.v1 - Ra.v1)), traits.ComputeReconstructedSample(Rb.v2, Errval2 * Sign(Rb.v2 - Ra.v2)), traits.ComputeReconstructedSample(Rb.v3, Errval3 * Sign(Rb.v3 - Ra.v3))); } template Triplet JlsCodec::EncodeRIPixel(Triplet x, Triplet Ra, Triplet Rb) { const int32_t errval1 = traits.ComputeErrVal(Sign(Rb.v1 - Ra.v1) * (x.v1 - Rb.v1)); EncodeRIError(_contextRunmode[0], errval1); const int32_t errval2 = traits.ComputeErrVal(Sign(Rb.v2 - Ra.v2) * (x.v2 - Rb.v2)); EncodeRIError(_contextRunmode[0], errval2); const int32_t errval3 = traits.ComputeErrVal(Sign(Rb.v3 - Ra.v3) * (x.v3 - Rb.v3)); EncodeRIError(_contextRunmode[0], errval3); return Triplet(traits.ComputeReconstructedSample(Rb.v1, errval1 * Sign(Rb.v1 - Ra.v1)), traits.ComputeReconstructedSample(Rb.v2, errval2 * Sign(Rb.v2 - Ra.v2)), traits.ComputeReconstructedSample(Rb.v3, errval3 * Sign(Rb.v3 - Ra.v3))); } template typename Traits::SAMPLE JlsCodec::DecodeRIPixel(int32_t Ra, int32_t Rb) { if (std::abs(Ra - Rb) <= traits.NEAR) { const int32_t ErrVal = DecodeRIError(_contextRunmode[1]); return static_cast(traits.ComputeReconstructedSample(Ra, ErrVal)); } const int32_t ErrVal = DecodeRIError(_contextRunmode[0]); return static_cast(traits.ComputeReconstructedSample(Rb, ErrVal * Sign(Rb - Ra))); } template typename Traits::SAMPLE JlsCodec::EncodeRIPixel(int32_t x, int32_t Ra, int32_t Rb) { if (std::abs(Ra - Rb) <= traits.NEAR) { const int32_t ErrVal = traits.ComputeErrVal(x - Ra); EncodeRIError(_contextRunmode[1], ErrVal); return static_cast(traits.ComputeReconstructedSample(Ra, ErrVal)); } const int32_t ErrVal = traits.ComputeErrVal((x - Rb) * Sign(Rb - Ra)); EncodeRIError(_contextRunmode[0], ErrVal); return static_cast(traits.ComputeReconstructedSample(Rb, ErrVal * Sign(Rb - Ra))); } // RunMode: Functions that handle run-length encoding template void JlsCodec::EncodeRunPixels(int32_t runLength, bool endOfLine) { while (runLength >= static_cast(1 << J[_RUNindex])) { Strategy::AppendOnesToBitStream(1); runLength = runLength - static_cast(1 << J[_RUNindex]); IncrementRunIndex(); } if (endOfLine) { if (runLength != 0) { Strategy::AppendOnesToBitStream(1); } } else { Strategy::AppendToBitStream(runLength, J[_RUNindex] + 1); // leading 0 + actual remaining length } } template int32_t JlsCodec::DecodeRunPixels(PIXEL Ra, PIXEL* startPos, int32_t cpixelMac) { int32_t index = 0; while (Strategy::ReadBit()) { const int count = std::min(1 << J[_RUNindex], int(cpixelMac - index)); index += count; ASSERT(index <= cpixelMac); if (count == (1 << J[_RUNindex])) { IncrementRunIndex(); } if (index == cpixelMac) break; } if (index != cpixelMac) { // incomplete run. index += (J[_RUNindex] > 0) ? Strategy::ReadValue(J[_RUNindex]) : 0; } if (index > cpixelMac) throw charls_error(charls::ApiResult::InvalidCompressedData); for (int32_t i = 0; i < index; ++i) { startPos[i] = Ra; } return index; } template int32_t JlsCodec::DoRunMode(int32_t index, EncoderStrategy*) { const int32_t ctypeRem = _width - index; PIXEL* ptypeCurX = _currentLine + index; const PIXEL* ptypePrevX = _previousLine + index; const PIXEL Ra = ptypeCurX[-1]; int32_t runLength = 0; while (traits.IsNear(ptypeCurX[runLength],Ra)) { ptypeCurX[runLength] = Ra; runLength++; if (runLength == ctypeRem) break; } EncodeRunPixels(runLength, runLength == ctypeRem); if (runLength == ctypeRem) return runLength; ptypeCurX[runLength] = EncodeRIPixel(ptypeCurX[runLength], Ra, ptypePrevX[runLength]); DecrementRunIndex(); return runLength + 1; } template int32_t JlsCodec::DoRunMode(int32_t startIndex, DecoderStrategy*) { const PIXEL Ra = _currentLine[startIndex-1]; const int32_t runLength = DecodeRunPixels(Ra, _currentLine + startIndex, _width - startIndex); const int32_t endIndex = startIndex + runLength; if (endIndex == _width) return endIndex - startIndex; // run interruption const PIXEL Rb = _previousLine[endIndex]; _currentLine[endIndex] = DecodeRIPixel(Ra, Rb); DecrementRunIndex(); return endIndex - startIndex + 1; } /// Encodes/Decodes a scan line of samples template void JlsCodec::DoLine(SAMPLE*) { int32_t index = 0; int32_t Rb = _previousLine[index-1]; int32_t Rd = _previousLine[index]; while (index < _width) { const int32_t Ra = _currentLine[index -1]; const int32_t Rc = Rb; Rb = Rd; Rd = _previousLine[index + 1]; const int32_t Qs = ComputeContextID(QuantizeGratient(Rd - Rb), QuantizeGratient(Rb - Rc), QuantizeGratient(Rc - Ra)); if (Qs != 0) { _currentLine[index] = DoRegular(Qs, _currentLine[index], GetPredictedValue(Ra, Rb, Rc), static_cast(nullptr)); index++; } else { index += DoRunMode(index, static_cast(nullptr)); Rb = _previousLine[index - 1]; Rd = _previousLine[index]; } } } /// Encodes/Decodes a scan line of triplets in ILV_SAMPLE mode template void JlsCodec::DoLine(Triplet*) { int32_t index = 0; while(index < _width) { const Triplet Ra = _currentLine[index - 1]; const Triplet Rc = _previousLine[index - 1]; const Triplet Rb = _previousLine[index]; const Triplet Rd = _previousLine[index + 1]; const int32_t Qs1 = ComputeContextID(QuantizeGratient(Rd.v1 - Rb.v1), QuantizeGratient(Rb.v1 - Rc.v1), QuantizeGratient(Rc.v1 - Ra.v1)); const int32_t Qs2 = ComputeContextID(QuantizeGratient(Rd.v2 - Rb.v2), QuantizeGratient(Rb.v2 - Rc.v2), QuantizeGratient(Rc.v2 - Ra.v2)); const int32_t Qs3 = ComputeContextID(QuantizeGratient(Rd.v3 - Rb.v3), QuantizeGratient(Rb.v3 - Rc.v3), QuantizeGratient(Rc.v3 - Ra.v3)); if (Qs1 == 0 && Qs2 == 0 && Qs3 == 0) { index += DoRunMode(index, static_cast(nullptr)); } else { Triplet Rx; Rx.v1 = DoRegular(Qs1, _currentLine[index].v1, GetPredictedValue(Ra.v1, Rb.v1, Rc.v1), static_cast(nullptr)); Rx.v2 = DoRegular(Qs2, _currentLine[index].v2, GetPredictedValue(Ra.v2, Rb.v2, Rc.v2), static_cast(nullptr)); Rx.v3 = DoRegular(Qs3, _currentLine[index].v3, GetPredictedValue(Ra.v3, Rb.v3, Rc.v3), static_cast(nullptr)); _currentLine[index] = Rx; index++; } } } // DoScan: Encodes or decodes a scan. // In ILV_SAMPLE mode, multiple components are handled in DoLine // In ILV_LINE mode, a call do DoLine is made for every component // In ILV_NONE mode, DoScan is called for each component template void JlsCodec::DoScan() { const int32_t pixelstride = _width + 4; const int components = Info().interleaveMode == charls::InterleaveMode::Line ? Info().components : 1; std::vector vectmp(static_cast(2) * components * pixelstride); std::vector rgRUNindex(components); for (int32_t line = 0; line < Info().height; ++line) { _previousLine = &vectmp[1]; _currentLine = &vectmp[1 + static_cast(components) * pixelstride]; if ((line & 1) == 1) { std::swap(_previousLine, _currentLine); } Strategy::OnLineBegin(_width, _currentLine, pixelstride); for (int component = 0; component < components; ++component) { _RUNindex = rgRUNindex[component]; // initialize edge pixels used for prediction _previousLine[_width] = _previousLine[_width - 1]; _currentLine[-1] = _previousLine[0]; DoLine(static_cast(nullptr)); // dummy argument for overload resolution rgRUNindex[component] = _RUNindex; _previousLine += pixelstride; _currentLine += pixelstride; } if (_rect.Y <= line && line < _rect.Y + _rect.Height) { Strategy::OnLineEnd(_rect.Width, _currentLine + _rect.X - (static_cast(components) * pixelstride), pixelstride); } } Strategy::EndScan(); } // Factory function for ProcessLine objects to copy/transform un encoded pixels to/from our scan line buffers. template std::unique_ptr JlsCodec::CreateProcess(ByteStreamInfo info) { if (!IsInterleaved()) { return info.rawData ? std::unique_ptr(std::make_unique(info.rawData, Info(), sizeof(typename Traits::PIXEL))) : std::unique_ptr(std::make_unique(info.rawStream, Info(), sizeof(typename Traits::PIXEL))); } if (Info().colorTransformation == ColorTransformation::None) return std::make_unique>>(info, Info(), TransformNone()); if (Info().bitsPerSample == sizeof(SAMPLE) * 8) { switch (Info().colorTransformation) { case ColorTransformation::HP1: return std::make_unique>>(info, Info(), TransformHp1()); case ColorTransformation::HP2: return std::make_unique>>(info, Info(), TransformHp2()); case ColorTransformation::HP3: return std::make_unique>>(info, Info(), TransformHp3()); default: std::ostringstream message; message << "Color transformation " << Info().colorTransformation << " is not supported."; throw charls_error(ApiResult::UnsupportedColorTransform, message.str()); } } if (Info().bitsPerSample > 8) { const int shift = 16 - Info().bitsPerSample; switch (Info().colorTransformation) { case ColorTransformation::HP1: return std::make_unique>>>(info, Info(), TransformShifted>(shift)); case ColorTransformation::HP2: return std::make_unique>>>(info, Info(), TransformShifted>(shift)); case ColorTransformation::HP3: return std::make_unique>>>(info, Info(), TransformShifted>(shift)); default: std::ostringstream message; message << "Color transformation " << Info().colorTransformation << " is not supported."; throw charls_error(ApiResult::UnsupportedColorTransform, message.str()); } } throw charls_error(ApiResult::UnsupportedBitDepthForTransform); } // Setup codec for encoding and calls DoScan WARNING_SUPPRESS(26433) template size_t JlsCodec::EncodeScan(std::unique_ptr processLine, ByteStreamInfo& compressedData) { Strategy::_processLine = std::move(processLine); Strategy::Init(compressedData); DoScan(); return Strategy::GetLength(); } // Setup codec for decoding and calls DoScan template void JlsCodec::DecodeScan(std::unique_ptr processLine, const JlsRect& rect, ByteStreamInfo& compressedData) { Strategy::_processLine = std::move(processLine); const uint8_t* compressedBytes = compressedData.rawData; _rect = rect; Strategy::Init(compressedData); DoScan(); SkipBytes(compressedData, Strategy::GetCurBytePos() - compressedBytes); } WARNING_UNSUPPRESS() // Initialize the codec data structures. Depends on JPEG-LS parameters like Threshold1-Threshold3. template void JlsCodec::InitParams(int32_t t1, int32_t t2, int32_t t3, int32_t nReset) { T1 = t1; T2 = t2; T3 = t3; InitQuantizationLUT(); const int32_t A = std::max(2, (traits.RANGE + 32) / 64); for (unsigned int Q = 0; Q < sizeof(_contexts) / sizeof(_contexts[0]); ++Q) { _contexts[Q] = JlsContext(A); } _contextRunmode[0] = CContextRunMode(std::max(2, (traits.RANGE + 32) / 64), 0, nReset); _contextRunmode[1] = CContextRunMode(std::max(2, (traits.RANGE + 32) / 64), 1, nReset); _RUNindex = 0; } #endif dcm2niix-1.0.20211006/console/charls/util.h000066400000000000000000000115551412761503300200360ustar00rootroot00000000000000// // (C) Jan de Vaan 2007-2010, all rights reserved. See the accompanying "License.txt" for licensed use. // #ifndef CHARLS_UTIL #define CHARLS_UTIL #include "publictypes.h" #include #include #include // ReSharper disable once CppUnusedIncludeDirective #include // Use an uppercase alias for assert to make it clear that it is a pre-processor macro. #define ASSERT(t) assert(t) //https://github.com/team-charls/charls/issues/38 #if __cplusplus == 201103L template std::unique_ptr make_unique(Args&&... args) { return std::unique_ptr(new T(std::forward(args)...)); } #endif // Only use __forceinline for the Microsoft C++ compiler in release mode (verified scenario) // Use the build-in optimizer for all other C++ compilers. // Note: usage of FORCE_INLINE may be reduced in the future as the latest generation of C++ compilers // can handle optimization by themselves. #ifndef FORCE_INLINE # ifdef _MSC_VER # ifdef NDEBUG # define FORCE_INLINE __forceinline # else # define FORCE_INLINE # endif # else # define FORCE_INLINE # endif #endif #ifdef _MSC_VER #define WARNING_SUPPRESS(x) __pragma(warning(push)) __pragma(warning(disable : x)) #define WARNING_UNSUPPRESS() __pragma(warning(pop)) #else #define WARNING_SUPPRESS(x) #define WARNING_UNSUPPRESS() #endif constexpr size_t int32_t_bit_count = sizeof(int32_t) * 8; inline void push_back(std::vector& values, uint16_t value) { values.push_back(uint8_t(value / 0x100)); values.push_back(uint8_t(value % 0x100)); } inline int32_t log_2(int32_t n) noexcept { int32_t x = 0; while (n > (static_cast(1) << x)) { ++x; } return x; } inline int32_t Sign(int32_t n) noexcept { return (n >> (int32_t_bit_count - 1)) | 1; } inline int32_t BitWiseSign(int32_t i) noexcept { return i >> (int32_t_bit_count - 1); } template struct Triplet { Triplet() noexcept : v1(0), v2(0), v3(0) {} Triplet(int32_t x1, int32_t x2, int32_t x3) noexcept : v1(static_cast(x1)), v2(static_cast(x2)), v3(static_cast(x3)) {} union { T v1; T R; }; union { T v2; T G; }; union { T v3; T B; }; }; inline bool operator==(const Triplet& lhs, const Triplet& rhs) noexcept { return lhs.v1 == rhs.v1 && lhs.v2 == rhs.v2 && lhs.v3 == rhs.v3; } inline bool operator!=(const Triplet& lhs, const Triplet& rhs) noexcept { return !(lhs == rhs); } template struct Quad : Triplet { Quad() : v4(0) {} WARNING_SUPPRESS(26495) // false warning that v4 is unintialized Quad(Triplet triplet, int32_t alpha) noexcept : Triplet(triplet), A(static_cast(alpha)) {} WARNING_UNSUPPRESS() union { sample v4; sample A; }; }; template struct FromBigEndian { }; template<> struct FromBigEndian<4> { FORCE_INLINE static unsigned int Read(const uint8_t* pbyte) noexcept { return (pbyte[0] << 24u) + (pbyte[1] << 16u) + (pbyte[2] << 8u) + (pbyte[3] << 0u); } }; template<> struct FromBigEndian<8> { FORCE_INLINE static uint64_t Read(const uint8_t* pbyte) noexcept { return (static_cast(pbyte[0]) << 56u) + (static_cast(pbyte[1]) << 48u) + (static_cast(pbyte[2]) << 40u) + (static_cast(pbyte[3]) << 32u) + (static_cast(pbyte[4]) << 24u) + (static_cast(pbyte[5]) << 16u) + (static_cast(pbyte[6]) << 8u) + (static_cast(pbyte[7]) << 0u); } }; class charls_error : public std::system_error { public: explicit charls_error(charls::ApiResult errorCode) : system_error(static_cast(errorCode), CharLSCategoryInstance()) { } charls_error(charls::ApiResult errorCode, const std::string& message) : system_error(static_cast(errorCode), CharLSCategoryInstance(), message) { } private: static const std::error_category& CharLSCategoryInstance() noexcept; }; inline void SkipBytes(ByteStreamInfo& streamInfo, std::size_t count) noexcept { if (!streamInfo.rawData) return; streamInfo.rawData += count; streamInfo.count -= count; } template std::ostream& operator<<(typename std::enable_if::value, std::ostream>::type& stream, const T& e) { return stream << static_cast::type>(e); } #endif dcm2niix-1.0.20211006/console/jpg_0XC3.cpp000066400000000000000000000544351412761503300174610ustar00rootroot00000000000000#include "jpg_0XC3.h" #include "print.h" #include //requires VS 2015 or later #include #include #include #include unsigned char readByte(unsigned char *lRawRA, long *lRawPos, long lRawSz) { unsigned char ret = 0x00; if (*lRawPos < lRawSz) ret = lRawRA[*lRawPos]; (*lRawPos)++; return ret; } // readByte() uint16_t readWord(unsigned char *lRawRA, long *lRawPos, long lRawSz) { return ((readByte(lRawRA, lRawPos, lRawSz) << 8) + readByte(lRawRA, lRawPos, lRawSz)); } // readWord() int readBit(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos) { //Read the next single bit int result = (lRawRA[*lRawPos] >> (7 - *lCurrentBitPos)) & 1; (*lCurrentBitPos)++; if (*lCurrentBitPos == 8) { (*lRawPos)++; *lCurrentBitPos = 0; } return result; } // readBit() int bitMask(int bits) { return ((2 << (bits - 1)) - 1); } // bitMask() int readBits(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos, int lNum) { //lNum: bits to read, not to exceed 16 int result = lRawRA[*lRawPos]; result = (result << 8) + lRawRA[(*lRawPos) + 1]; result = (result << 8) + lRawRA[(*lRawPos) + 2]; result = (result >> (24 - *lCurrentBitPos - lNum)) & bitMask(lNum); //lCurrentBitPos is incremented from 1, so -1 *lCurrentBitPos = *lCurrentBitPos + lNum; if (*lCurrentBitPos > 7) { *lRawPos = *lRawPos + (*lCurrentBitPos >> 3); // div 8 *lCurrentBitPos = *lCurrentBitPos & 7; //mod 8 } return result; } // readBits() struct HufTables { uint8_t SSSSszRA[18]; uint8_t LookUpRA[256]; int DHTliRA[32]; int DHTstartRA[32]; int HufSz[32]; int HufCode[32]; int HufVal[32]; int MaxHufSi; int MaxHufVal; }; // HufTables() int decodePixelDifference(unsigned char *lRawRA, long *lRawPos, int *lCurrentBitPos, struct HufTables l) { int lByte = (lRawRA[*lRawPos] << *lCurrentBitPos) + (lRawRA[*lRawPos + 1] >> (8 - *lCurrentBitPos)); lByte = lByte & 255; int lHufValSSSS = l.LookUpRA[lByte]; if (lHufValSSSS < 255) { *lCurrentBitPos = l.SSSSszRA[lHufValSSSS] + *lCurrentBitPos; *lRawPos = *lRawPos + (*lCurrentBitPos >> 3); *lCurrentBitPos = *lCurrentBitPos & 7; } else { //full SSSS is not in the first 8-bits int lInput = lByte; int lInputBits = 8; (*lRawPos)++; // forward 8 bits = precisely 1 byte do { lInputBits++; lInput = (lInput << 1) + readBit(lRawRA, lRawPos, lCurrentBitPos); if (l.DHTliRA[lInputBits] != 0) { //if any entries with this length for (int lI = l.DHTstartRA[lInputBits]; lI <= (l.DHTstartRA[lInputBits] + l.DHTliRA[lInputBits] - 1); lI++) { if (lInput == l.HufCode[lI]) lHufValSSSS = l.HufVal[lI]; } //check each code } //if any entries with this length if ((lInputBits >= l.MaxHufSi) && (lHufValSSSS > 254)) { //exhausted options CR: added rev13 lHufValSSSS = l.MaxHufVal; } } while (!(lHufValSSSS < 255)); // found; } //answer in first 8 bits //The HufVal is referred to as the SSSS in the Codec, so it is called 'lHufValSSSS' if (lHufValSSSS == 0) //NO CHANGE return 0; if (lHufValSSSS == 1) { if (readBit(lRawRA, lRawPos, lCurrentBitPos) == 0) return -1; else return 1; } if (lHufValSSSS == 16) { //ALL CHANGE 16 bit difference: Codec H.1.2.2 "No extra bits are appended after SSSS = 16 is encoded." Osiris fails here return 32768; } //to get here - there is a 2..15 bit difference int lDiff = readBits(lRawRA, lRawPos, lCurrentBitPos, lHufValSSSS); if (lDiff <= bitMask(lHufValSSSS - 1)) //add lDiff = lDiff - bitMask(lHufValSSSS); return lDiff; } // decodePixelDifference() unsigned char *decode_JPEG_SOF_0XC3(const char *fn, int skipBytes, bool verbose, int *dimX, int *dimY, int *bits, int *frames, int diskBytes) { //decompress JPEG image named "fn" where image data is located skipBytes into file. diskBytes is compressed size of image (set to 0 if unknown) //next line breaks MSVC #define abortGoto(...) do {printError(__VA_ARGS__); free(lRawRA); return NULL;} while(0) unsigned char *lImgRA8 = NULL; FILE *reader = fopen(fn, "rb"); int lSuccess = fseek(reader, 0, SEEK_END); long lRawSz = ftell(reader) - skipBytes; if ((diskBytes > 0) && (diskBytes < lRawSz)) //only if diskBytes is known and does not exceed length of file lRawSz = diskBytes; if ((lSuccess != 0) || (lRawSz <= 8)) { printError("Unable to load 0XC3 JPEG %s\n", fn); return NULL; //read failure } lSuccess = fseek(reader, skipBytes, SEEK_SET); //If successful, the function returns zero if (lSuccess != 0) { printError("Unable to open 0XC3 JPEG %s\n", fn); return NULL; //read failure } unsigned char *lRawRA = (unsigned char *)malloc(lRawSz); size_t lSz = fread(lRawRA, 1, lRawSz, reader); fclose(reader); if ((lSz < (size_t)lRawSz) || (lRawRA[0] != 0xFF) || (lRawRA[1] != 0xD8) || (lRawRA[2] != 0xFF)) { abortGoto("JPEG signature 0xFFD8FF not found at offset %d of %s\n", skipBytes, fn); //signature failure http://en.wikipedia.org/wiki/List_of_file_signatures } if (verbose) printMessage("JPEG signature 0xFFD8FF found at offset %d of %s\n", skipBytes, fn); //next: read header long lRawPos = 2; //Skip initial 0xFFD8, begin with third byte //long lRawPos = 0; //Skip initial 0xFFD8, begin with third byte unsigned char btS1, btS2, SOSse, SOSahal, btMarkerType, SOSns = 0x00; //tag unsigned char SOSpttrans = 0; unsigned char SOSss = 0; uint8_t SOFnf = 0; uint8_t SOFprecision = 0; uint16_t SOFydim = 0; uint16_t SOFxdim = 0; // long SOSarrayPos; //SOFarrayPos int lnHufTables = 0; int lFrameCount = 1; const int kmaxFrames = 4; struct HufTables l[kmaxFrames + 1]; do { //read each marker in the header do { btS1 = readByte(lRawRA, &lRawPos, lRawSz); if (btS1 != 0xFF) { abortGoto("JPEG header tag must begin with 0xFF\n"); } btMarkerType = readByte(lRawRA, &lRawPos, lRawSz); if ((btMarkerType == 0x01) || (btMarkerType == 0xFF) || ((btMarkerType >= 0xD0) && (btMarkerType <= 0xD7))) btMarkerType = 0; //only process segments with length fields } while ((lRawPos < lRawSz) && (btMarkerType == 0)); uint16_t lSegmentLength = readWord(lRawRA, &lRawPos, lRawSz); //read marker length long lSegmentEnd = lRawPos + (lSegmentLength - 2); if (lSegmentEnd > lRawSz) { abortGoto("Segment larger than image\n"); } if (verbose) printMessage("btMarkerType %#02X length %d@%ld\n", btMarkerType, lSegmentLength, lRawPos); if (((btMarkerType >= 0xC0) && (btMarkerType <= 0xC3)) || ((btMarkerType >= 0xC5) && (btMarkerType <= 0xCB)) || ((btMarkerType >= 0xCD) && (btMarkerType <= 0xCF))) { //if Start-Of-Frame (SOF) marker SOFprecision = readByte(lRawRA, &lRawPos, lRawSz); SOFydim = readWord(lRawRA, &lRawPos, lRawSz); SOFxdim = readWord(lRawRA, &lRawPos, lRawSz); SOFnf = readByte(lRawRA, &lRawPos, lRawSz); //SOFarrayPos = lRawPos; lRawPos = (lSegmentEnd); if (verbose) printMessage(" [Precision %d X*Y %d*%d Frames %d]\n", SOFprecision, SOFxdim, SOFydim, SOFnf); if (btMarkerType != 0xC3) { //lImgTypeC3 = true; abortGoto("This JPEG decoder can only decompress lossless JPEG ITU-T81 images (SoF must be 0XC3, not %#02X)\n", btMarkerType); } if ((SOFprecision < 1) || (SOFprecision > 16) || (SOFnf < 1) || (SOFnf == 2) || (SOFnf > 3) || ((SOFnf == 3) && (SOFprecision > 8))) { abortGoto("Scalar data must be 1..16 bit, RGB data must be 8-bit (%d-bit, %d frames)\n", SOFprecision, SOFnf); } } else if (btMarkerType == 0xC4) { //if SOF marker else if define-Huffman-tables marker (DHT) if (verbose) printMessage(" [Huffman Length %d]\n", lSegmentLength); do { uint8_t DHTnLi = readByte(lRawRA, &lRawPos, lRawSz); //we read but ignore DHTtcth. #pragma unused(DHTnLi) //we need to increment the input file position, but we do not care what the value is DHTnLi = 0; for (int lInc = 1; lInc <= 16; lInc++) { l[lFrameCount].DHTliRA[lInc] = readByte(lRawRA, &lRawPos, lRawSz); DHTnLi = DHTnLi + l[lFrameCount].DHTliRA[lInc]; if (l[lFrameCount].DHTliRA[lInc] != 0) l[lFrameCount].MaxHufSi = lInc; if (verbose) printMessage("DHT has %d combinations with %d bits\n", l[lFrameCount].DHTliRA[lInc], lInc); } if (DHTnLi > 17) { abortGoto("Huffman table corrupted.\n"); } int lIncY = 0; //frequency for (int lInc = 0; lInc <= 31; lInc++) { //lInc := 0 to 31 do begin l[lFrameCount].HufVal[lInc] = -1; l[lFrameCount].HufSz[lInc] = -1; l[lFrameCount].HufCode[lInc] = -1; } for (int lInc = 1; lInc <= 16; lInc++) { //set the huffman size values if (l[lFrameCount].DHTliRA[lInc] > 0) { l[lFrameCount].DHTstartRA[lInc] = lIncY + 1; for (int lIncX = 1; lIncX <= l[lFrameCount].DHTliRA[lInc]; lIncX++) { lIncY++; btS1 = readByte(lRawRA, &lRawPos, lRawSz); l[lFrameCount].HufVal[lIncY] = btS1; l[lFrameCount].MaxHufVal = btS1; if (verbose) printMessage("DHT combination %d has a value of %d\n", lIncY, btS1); if (btS1 <= 16) //unsigned ints ALWAYS >0, so no need for(btS1 >= 0) l[lFrameCount].HufSz[lIncY] = lInc; else { abortGoto("Huffman size array corrupted.\n"); } } } } //set huffman size values int K = 1; int Code = 0; int Si = l[lFrameCount].HufSz[K]; do { while (Si == l[lFrameCount].HufSz[K]) { l[lFrameCount].HufCode[K] = Code; Code = Code + 1; K++; } if (K <= DHTnLi) { while (l[lFrameCount].HufSz[K] > Si) { Code = Code << 1; //Shl!!! Si = Si + 1; } //while Si } //K <= 17 } while (K <= DHTnLi); //if (verbose) // for (int j = 1; j <= DHTnLi; j++) // printMessage(" [%d Sz %d Code %d Value %d]\n", j, l[lFrameCount].HufSz[j], l[lFrameCount].HufCode[j], l[lFrameCount].HufVal[j]); lFrameCount++; } while ((lSegmentEnd - lRawPos) >= 18); lnHufTables = lFrameCount - 1; lRawPos = (lSegmentEnd); if (verbose) printMessage(" [FrameCount %d]\n", lnHufTables); } else if (btMarkerType == 0xDD) { //if DHT marker else if Define restart interval (DRI) marker abortGoto("btMarkerType == 0xDD: unsupported Restart Segments\n"); //lRestartSegmentSz = ReadWord(lRawRA, &lRawPos, lRawSz); //lRawPos = lSegmentEnd; } else if (btMarkerType == 0xDA) { //if DRI marker else if read Start of Scan (SOS) marker SOSns = readByte(lRawRA, &lRawPos, lRawSz); //if Ns = 1 then NOT interleaved, else interleaved: see B.2.3 // SOSarrayPos = lRawPos; //not required... if (SOSns > 0) { for (int lInc = 1; lInc <= SOSns; lInc++) { btS1 = readByte(lRawRA, &lRawPos, lRawSz); //component identifier 1=Y,2=Cb,3=Cr,4=I,5=Q #pragma unused(btS1) //dummy value used to increment file position btS2 = readByte(lRawRA, &lRawPos, lRawSz); //horizontal and vertical sampling factors #pragma unused(btS2) //dummy value used to increment file position } } SOSss = readByte(lRawRA, &lRawPos, lRawSz); //predictor selection B.3 SOSse = readByte(lRawRA, &lRawPos, lRawSz); #pragma unused(SOSse) //dummy value used to increment file position SOSahal = readByte(lRawRA, &lRawPos, lRawSz); //lower 4bits= pointtransform SOSpttrans = SOSahal & 16; if (verbose) printMessage(" [Predictor: %d Transform %d]\n", SOSss, SOSahal); lRawPos = (lSegmentEnd); } else //if SOS marker else skip marker lRawPos = (lSegmentEnd); } while ((lRawPos < lRawSz) && (btMarkerType != 0xDA)); //0xDA=Start of scan: loop for reading header //NEXT: Huffman decoding if (lnHufTables < 1) { abortGoto("Decoding error: no Huffman tables.\n"); } //NEXT: unpad data - delete byte that follows $FF //int lIsRestartSegments = 0; long lIncI = lRawPos; //input position long lIncO = lRawPos; //output position do { lRawRA[lIncO] = lRawRA[lIncI]; if (lRawRA[lIncI] == 255) { if (lRawRA[lIncI + 1] == 0) lIncI = lIncI + 1; else if (lRawRA[lIncI + 1] == 0xD9) lIncO = -666; //end of padding //else // lIsRestartSegments = lRawRA[lIncI+1]; } lIncI++; lIncO++; } while (lIncO > 0); //if (lIsRestartSegments != 0) //detects both restart and corruption https://groups.google.com/forum/#!topic/comp.protocols.dicom/JUuz0B_aE5o // printWarning("Detected restart segments, decompress with dcmdjpeg or gdcmconv 0xFF%02X.\n", lIsRestartSegments); //NEXT: some RGB images use only a single Huffman table for all 3 colour planes. In this case, replicate the correct values //NEXT: prepare lookup table for (int lFrameCount = 1; lFrameCount <= lnHufTables; lFrameCount++) { for (int lInc = 0; lInc <= 17; lInc++) l[lFrameCount].SSSSszRA[lInc] = 123; //Impossible value for SSSS, suggests 8-bits can not describe answer for (int lInc = 0; lInc <= 255; lInc++) l[lFrameCount].LookUpRA[lInc] = 255; //Impossible value for SSSS, suggests 8-bits can not describe answer } //NEXT: fill lookuptable for (int lFrameCount = 1; lFrameCount <= lnHufTables; lFrameCount++) { int lIncY = 0; for (int lSz = 1; lSz <= 8; lSz++) { //set the huffman lookup table for keys with lengths <=8 if (l[lFrameCount].DHTliRA[lSz] > 0) { for (int lIncX = 1; lIncX <= l[lFrameCount].DHTliRA[lSz]; lIncX++) { lIncY++; int lHufVal = l[lFrameCount].HufVal[lIncY]; //SSSS l[lFrameCount].SSSSszRA[lHufVal] = lSz; int k = (l[lFrameCount].HufCode[lIncY] << (8 - lSz)) & 255; //K= most sig bits for hufman table if (lSz < 8) { //fill in all possible bits that exceed the huffman table int lInc = bitMask(8 - lSz); for (int lCurrentBitPos = 0; lCurrentBitPos <= lInc; lCurrentBitPos++) { l[lFrameCount].LookUpRA[k + lCurrentBitPos] = lHufVal; } } else l[lFrameCount].LookUpRA[k] = lHufVal; //SSSS //printMessage("Frame %d SSSS %d Size %d Code %d SHL %d EmptyBits %ld\n", lFrameCount, lHufRA[lFrameCount][lIncY].HufVal, lHufRA[lFrameCount][lIncY].HufSz,lHufRA[lFrameCount][lIncY].HufCode, k, lInc); } //Set SSSS } //Length of size lInc > 0 } //for lInc := 1 to 8 } //For each frame, e.g. once each for Red/Green/Blue //NEXT: some RGB images use only a single Huffman table for all 3 colour planes. In this case, replicate the correct values if (lnHufTables < SOFnf) { //use single Hufman table for each frame for (int lFrameCount = lnHufTables + 1; lFrameCount <= SOFnf; lFrameCount++) { l[lFrameCount] = l[lnHufTables]; } //for each frame } // if lnHufTables < SOFnf //NEXT: uncompress data: different loops for different predictors int lItems = SOFxdim * SOFydim * SOFnf; // lRawPos++;// <- only for Pascal where array is indexed from 1 not 0 first byte of data int lCurrentBitPos = 0; //read in a new byte //depending on SOSss, we see Table H.1 int lPredA = 0; int lPredB = 0; int lPredC = 0; if (SOSss == 2) //predictor selection 2: above lPredA = SOFxdim - 1; else if (SOSss == 3) //predictor selection 3: above+left lPredA = SOFxdim; else if ((SOSss == 4) || (SOSss == 5)) { //these use left, above and above+left WEIGHT LEFT lPredA = 0; //Ra left lPredB = SOFxdim - 1; //Rb directly above lPredC = SOFxdim; //Rc UpperLeft:above and to the left } else if (SOSss == 6) { //also use left, above and above+left, WEIGHT ABOVE lPredB = 0; lPredA = SOFxdim - 1; //Rb directly above lPredC = SOFxdim; //Rc UpperLeft:above and to the left } else lPredA = 0; //Ra: directly to left) if (SOFprecision > 8) { //start - 16 bit data *bits = 16; int lPx = -1; //pixel position int lPredicted = 1 << (SOFprecision - 1 - SOSpttrans); lImgRA8 = (unsigned char *)malloc(lItems * 2); uint16_t *lImgRA16 = (uint16_t *)lImgRA8; for (int i = 0; i < lItems; i++) lImgRA16[i] = 0; //zero array int frame = 1; for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor lPx++; //writenext voxel if (lIncX > 1) lPredicted = lImgRA16[lPx - 1]; lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); } for (int lIncY = 2; lIncY <= SOFydim; lIncY++) { //for all subsequent rows lPx++; //write next voxel lPredicted = lImgRA16[lPx - SOFxdim]; //use ABOVE lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); if (SOSss == 4) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPredicted = lImgRA16[lPx - lPredA] + lImgRA16[lPx - lPredB] - lImgRA16[lPx - lPredC]; lPx++; //writenext voxel lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); } //for lIncX } else if ((SOSss == 5) || (SOSss == 6)) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPredicted = lImgRA16[lPx - lPredA] + ((lImgRA16[lPx - lPredB] - lImgRA16[lPx - lPredC]) >> 1); lPx++; //writenext voxel lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); } //for lIncX } else if (SOSss == 7) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPx++; //writenext voxel lPredicted = (lImgRA16[lPx - 1] + lImgRA16[lPx - SOFxdim]) >> 1; lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); } //for lIncX } else { //SOSss 1,2,3 read single values for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPredicted = lImgRA16[lPx - lPredA]; lPx++; //writenext voxel lImgRA16[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[frame]); } //for lIncX } // if..else possible predictors } //for lIncY } else if (SOFnf == 3) { //if 16-bit data; else 8-bit 3 frames *bits = 8; lImgRA8 = (unsigned char *)malloc(lItems); int lPx[kmaxFrames + 1], lPredicted[kmaxFrames + 1]; //pixel position for (int f = 1; f <= SOFnf; f++) { lPx[f] = ((f - 1) * (SOFxdim * SOFydim)) - 1; lPredicted[f] = 1 << (SOFprecision - 1 - SOSpttrans); } for (int i = 0; i < lItems; i++) lImgRA8[i] = 255; //zero array for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor for (int f = 1; f <= SOFnf; f++) { lPx[f]++; //writenext voxel if (lIncX > 1) lPredicted[f] = lImgRA8[lPx[f] - 1]; lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); } } //first row always predicted by LEFT for (int lIncY = 2; lIncY <= SOFydim; lIncY++) { //for all subsequent rows for (int f = 1; f <= SOFnf; f++) { lPx[f]++; //write next voxel lPredicted[f] = lImgRA8[lPx[f] - SOFxdim]; //use ABOVE lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); } //first column of row always predicted by ABOVE if (SOSss == 4) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { for (int f = 1; f <= SOFnf; f++) { lPredicted[f] = lImgRA8[lPx[f] - lPredA] + lImgRA8[lPx[f] - lPredB] - lImgRA8[lPx[f] - lPredC]; lPx[f]++; //writenext voxel lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); } } //for lIncX } else if ((SOSss == 5) || (SOSss == 6)) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { for (int f = 1; f <= SOFnf; f++) { lPredicted[f] = lImgRA8[lPx[f] - lPredA] + ((lImgRA8[lPx[f] - lPredB] - lImgRA8[lPx[f] - lPredC]) >> 1); lPx[f]++; //writenext voxel lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); } } //for lIncX } else if (SOSss == 7) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { for (int f = 1; f <= SOFnf; f++) { lPx[f]++; //writenext voxel lPredicted[f] = (lImgRA8[lPx[f] - 1] + lImgRA8[lPx[f] - SOFxdim]) >> 1; lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); } } //for lIncX } else { //SOSss 1,2,3 read single values for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { for (int f = 1; f <= SOFnf; f++) { lPredicted[f] = lImgRA8[lPx[f] - lPredA]; lPx[f]++; //writenext voxel lImgRA8[lPx[f]] = lPredicted[f] + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[f]); } } //for lIncX } // if..else possible predictors } //for lIncY } else { //if 8-bit data 3frames; else 8-bit 1 frames *bits = 8; lImgRA8 = (unsigned char *)malloc(lItems); int lPx = -1; //pixel position int lPredicted = 1 << (SOFprecision - 1 - SOSpttrans); for (int i = 0; i < lItems; i++) lImgRA8[i] = 0; //zero array for (int lIncX = 1; lIncX <= SOFxdim; lIncX++) { //for first row - here we ALWAYS use LEFT as predictor lPx++; //writenext voxel if (lIncX > 1) lPredicted = lImgRA8[lPx - 1]; int dx = decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); lImgRA8[lPx] = lPredicted + dx; } for (int lIncY = 2; lIncY <= SOFydim; lIncY++) { //for all subsequent rows lPx++; //write next voxel lPredicted = lImgRA8[lPx - SOFxdim]; //use ABOVE lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); if (SOSss == 4) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPredicted = lImgRA8[lPx - lPredA] + lImgRA8[lPx - lPredB] - lImgRA8[lPx - lPredC]; lPx++; //writenext voxel lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); } //for lIncX } else if ((SOSss == 5) || (SOSss == 6)) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPredicted = lImgRA8[lPx - lPredA] + ((lImgRA8[lPx - lPredB] - lImgRA8[lPx - lPredC]) >> 1); lPx++; //writenext voxel lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); } //for lIncX } else if (SOSss == 7) { for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPx++; //writenext voxel lPredicted = (lImgRA8[lPx - 1] + lImgRA8[lPx - SOFxdim]) >> 1; lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); } //for lIncX } else { //SOSss 1,2,3 read single values for (int lIncX = 2; lIncX <= SOFxdim; lIncX++) { lPredicted = lImgRA8[lPx - lPredA]; lPx++; //writenext voxel lImgRA8[lPx] = lPredicted + decodePixelDifference(lRawRA, &lRawPos, &lCurrentBitPos, l[1]); } //for lIncX } // if..else possible predictors } //for lIncY } //if 16bit else 8bit free(lRawRA); *dimX = SOFxdim; *dimY = SOFydim; *frames = SOFnf; if (verbose) printMessage("JPEG ends %ld@%ld\n", lRawPos, lRawPos + skipBytes); return lImgRA8; } // decode_JPEG_SOF_0XC3() dcm2niix-1.0.20211006/console/jpg_0XC3.h000066400000000000000000000014361412761503300171170ustar00rootroot00000000000000//Decode DICOM Transfer Syntax 1.2.840.10008.1.2.4.70 and 1.2.840.10008.1.2.4.57 // JPEG Lossless, Nonhierarchical // see ISO/IEC 10918-1 / ITU T.81 // specifically, format with 'Start of Frame' (SOF) code 0xC3 // http://www.w3.org/Graphics/JPEG/itu-t81.pdf // This code decodes data with 1..16 bits per pixel // It appears unique to medical imaging, and is not supported by most JPEG libraries // http://www.dicomlibrary.com/dicom/transfer-syntax/ // https://en.wikipedia.org/wiki/Lossless_JPEG#Lossless_mode_of_operation #ifndef _JPEG_SOF_0XC3_ #define _JPEG_SOF_0XC3_ #ifdef __cplusplus extern "C" { #endif unsigned char * decode_JPEG_SOF_0XC3 (const char *fn, int skipBytes, bool verbose, int *dimX, int *dimY, int *bits, int *frames, int diskBytes); #ifdef __cplusplus } #endif #endifdcm2niix-1.0.20211006/console/main_console.cpp000066400000000000000000000607501412761503300206070ustar00rootroot00000000000000//main_console.cpp dcm2niix // by Chris Rorden on 3/22/14, see license.txt // Copyright (c) 2014-2021 Chris Rorden. All rights reserved. //g++ -O3 main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -s -o dcm2niix -lz //if you do not have zlib,you can compile without it // g++ -O3 -DmyDisableZLib main_console.cpp nii_dicom.cpp nifti1_io_core.cpp nii_ortho.cpp nii_dicom_batch.cpp -s -o dcm2niix //or you can build your own copy: // to compile you will first want to build the Z library, then compile the project // cd zlib-1.2.8 // sudo ./configure; // sudo make //to generate combined 32-bit and 64-bit builds for OSX : // g++ -O3 -x c++ main_console.c nii_dicom.c nifti1_io_core.c nii_ortho.c nii_dicom_batch.c -s -arch x86_64 -o dcm2niix64 -lz // g++ -O3 -x c++ main_console.c nii_dicom.c nifti1_io_core.c nii_ortho.c nii_dicom_batch.c -s -arch i386 -o dcm2niix32 -lz // lipo -create dcm2niix32 dcm2niix64 -o dcm2niix //On windows with mingw you may get "fatal error: zlib.h: No such file // to remedy, run "mingw-get install libz-dev" from mingw //Alternatively, windows users with VisualStudio can compile this project // vcvarsall amd64 // cl /EHsc 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 -DmyDisableOpenJPEG /o dcm2niix //#define mydebugtest //automatically process directory specified in main, ignore input arguments #include #include #include //requires VS 2015 or later #include #include #include #include //#include #include "nifti1_io_core.h" #include "nii_dicom.h" #include "nii_dicom_batch.h" #include #include #include // clock_t, clock, CLOCKS_PER_SEC #if !defined(_WIN64) && !defined(_WIN32) #include #include double get_wall_time() { struct timeval time; if (gettimeofday(&time, NULL)) { // Handle error return 0; } return (double)time.tv_sec + (double)time.tv_usec * .000001; } #endif const char *removePath(const char *path) { // "/usr/path/filename.exe" -> "filename.exe" const char *pDelimeter = strrchr(path, '\\'); if (pDelimeter) path = pDelimeter + 1; pDelimeter = strrchr(path, '/'); if (pDelimeter) path = pDelimeter + 1; return path; } //removePath() char bool2Char(bool b) { if (b) return ('y'); return ('n'); } void showHelp(const char *argv[], struct TDCMopts opts) { const char *cstr = removePath(argv[0]); printf("usage: %s [options] \n", cstr); printf(" Options :\n"); printf(" -1..-9 : gz compression level (1=fastest..9=smallest, default %d)\n", opts.gzLevel); printf(" -a : adjacent DICOMs (images from same series always in same folder) for faster conversion (n/y, default n)\n"); printf(" -b : BIDS sidecar (y/n/o [o=only: no NIfTI], default %c)\n", bool2Char(opts.isCreateBIDS)); 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 mySegmentByAcq #define kQstr " %%q=sequence number," #else #define kQstr "" #endif printf(" -f : filename (%%a=antenna (coil) name, %%b=basename, %%c=comments, %%d=description, %%e=echo number, %%f=folder name, %%g=accession number, %%i=ID of patient, %%j=seriesInstanceUID, %%k=studyInstanceUID, %%m=manufacturer, %%n=name of patient, %%o=mediaObjectInstanceUID, %%p=protocol,%s %%r=instance number, %%s=series number, %%t=time, %%u=acquisition number, %%v=vendor, %%x=study ID; %%z=sequence name; default '%s')\n", kQstr, opts.filename); printf(" -g : generate defaults file (y/n/o/i [o=only: reset and write defaults; i=ignore: reset defaults], default n)\n"); printf(" -h : show help\n"); printf(" -i : ignore derived, localizer and 2D images (y/n, default n)\n"); char max16Ch = 'n'; if (opts.isMaximize16BitRange == kMaximize16BitRange_True) max16Ch = 'y'; if (opts.isMaximize16BitRange == kMaximize16BitRange_Raw) max16Ch = 'o'; printf(" -l : losslessly scale 16-bit integers to use dynamic range (y/n/o [yes=scale, no=no, but uint16->int16, o=original], default %c)\n", max16Ch); printf(" -m : merge 2D slices from same series regardless of echo, exposure, etc. (n/y or 0/1/2, default 2) [no, yes, auto]\n"); printf(" -n : only convert this series CRC number - can be used up to %i times (default convert all)\n", MAX_NUM_SERIES); printf(" -o : output directory (omit to save to input folder)\n"); printf(" -p : Philips precise float (not display) scaling (y/n, default y)\n"); printf(" -r : rename instead of convert DICOMs (y/n, default n)\n"); printf(" -s : single file mode, do not convert other images in folder (y/n, default n)\n"); //text notes replaced with BIDS: this function is deprecated //printf(" -t : text notes includes private patient details (y/n, default n)\n"); #if !defined(_WIN64) && !defined(_WIN32) //shell script for Unix only printf(" -u : up-to-date check\n"); #endif printf(" -v : verbose (n/y or 0/1/2, default 0) [no, yes, logorrheic]\n"); //#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 printf(" -w : write behavior for name conflicts (0,1,2, default 2: 0=skip duplicates, 1=overwrite, 2=add suffix)\n"); printf(" -x : crop 3D acquisitions (y/n/i, default n, use 'i'gnore to neither crop nor rotate 3D acquistions)\n"); char gzCh = 'n'; if (opts.isGz) gzCh = 'y'; #if defined(_WIN64) || defined(_WIN32) //n.b. the optimal use of pigz requires pipes that are not provided for Windows #ifdef myDisableZLib if (strlen(opts.pigzname) > 0) printf(" -z : gz compress images (y/n/3, default %c) [y=pigz, n=no, 3=no,3D]\n", gzCh); else printf(" -z : gz compress images (y/n/3, default %c) [y=pigz(MISSING!), n=no, 3=no,3D]\n", gzCh); #else #ifdef myDisableMiniZ printf(" -z : gz compress images (y/i/n/3, default %c) [y=pigz, i=internal:zlib, n=no, 3=no,3D]\n", gzCh); #else printf(" -z : gz compress images (y/i/n/3, default %c) [y=pigz, i=internal:miniz, n=no, 3=no,3D]\n", gzCh); #endif #endif printf(" --big-endian : byte order (y/n/o, default o) [y=big-end, n=little-end, o=optimal/native]\n"); printf(" --progress : report progress (y/n, default n)\n"); printf(" --ignore_trigger_times : disregard values in 0018,1060 and 0020,9153\n"); printf(" --terse : omit filename post-fixes (can cause overwrites)\n"); printf(" --version : report version\n"); printf(" --xml : Slicer format features\n"); printf(" Defaults stored in Windows registry\n"); printf(" Examples :\n"); printf(" %s c:\\DICOM\\dir\n", cstr); printf(" %s -c \"my comment\" c:\\DICOM\\dir\n", cstr); printf(" %s -o c:\\out\\dir c:\\DICOM\\dir\n", cstr); printf(" %s -f mystudy%%s c:\\DICOM\\dir\n", cstr); printf(" %s -o \"c:\\dir with spaces\\dir\" c:\\dicomdir\n", cstr); #else #ifdef myDisableZLib if (strlen(opts.pigzname) > 0) printf(" -z : gz compress images (y/o/n/3, default %c) [y=pigz, o=optimal pigz, n=no, 3=no,3D]\n", gzCh); else printf(" -z : gz compress images (y/o/n/3, default %c) [y=pigz(MISSING!), o=optimal(requires pigz), n=no, 3=no,3D]\n", gzCh); #else #ifdef myDisableMiniZ printf(" -z : gz compress images (y/o/i/n/3, default %c) [y=pigz, o=optimal pigz, i=internal:zlib, n=no, 3=no,3D]\n", gzCh); #else printf(" -z : gz compress images (y/o/i/n/3, default %c) [y=pigz, o=optimal pigz, i=internal:miniz, n=no, 3=no,3D]\n", gzCh); #endif #endif printf(" --big-endian : byte order (y/n/o, default o) [y=big-end, n=little-end, o=optimal/native]\n"); printf(" --progress : Slicer format progress information (y/n, default n)\n"); printf(" --ignore_trigger_times : disregard values in 0018,1060 and 0020,9153\n"); printf(" --terse : omit filename post-fixes (can cause overwrites)\n"); printf(" --version : report version\n"); printf(" --xml : Slicer format features\n"); printf(" Defaults file : %s\n", opts.optsname); printf(" Examples :\n"); printf(" %s /Users/chris/dir\n", cstr); printf(" %s -c \"my comment\" /Users/chris/dir\n", cstr); printf(" %s -o /users/cr/outdir/ -z y ~/dicomdir\n", cstr); printf(" %s -f %%p_%%s -b y -ba n ~/dicomdir\n", cstr); printf(" %s -f mystudy%%s ~/dicomdir\n", cstr); printf(" %s -o \"~/dir with spaces/dir\" ~/dicomdir\n", cstr); #endif } //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')) return 0; //if (argv[i][0] != '-') return 0; printf(" Error: invalid option '%s %s'\n", argv[i - 1], argv[i]); return 1; } #if !defined(_WIN64) && !defined(_WIN32) //shell script for Unix only int checkUpToDate() { #define URL "/rordenlab/dcm2niix/releases/" #define APIURL "\"https://api.github.com/repos" URL "latest\"" #define HTMURL "https://github.com" URL #define SHELLSCRIPT "#!/usr/bin/env bash\n curl --silent " APIURL " | grep '\"tag_name\":' | sed -E 's/.*\"([^\"]+)\".*/\\1/'" //check first 13 characters, e.g. "v1.0.20171204" #define versionChars 13 FILE *pipe = popen(SHELLSCRIPT, "r"); char ch, gitvers[versionChars + 1]; int n = 0; int nMatch = 0; while ((ch = fgetc(pipe)) != EOF) { if (n < versionChars) { gitvers[n] = ch; if (gitvers[n] == kDCMvers[n]) nMatch++; n++; } } pclose(pipe); gitvers[n] = 0; //null terminate if (n < 1) { //script reported nothing printf("Error: unable to check version with script:\n %s\n", SHELLSCRIPT); return 3; //different from EXIT_SUCCESS (0) and EXIT_FAILURE (1) } if (nMatch == versionChars) { //versions match printf("Good news: Your version is up to date: %s\n", gitvers); return EXIT_SUCCESS; } //report error char myvers[versionChars + 1]; for (int i = 0; i < versionChars; i++) myvers[i] = kDCMvers[i]; myvers[versionChars] = 0; //null terminate int myv = atoi(myvers + 5); //skip "v1.0." int gitv = atoi(gitvers + 5); //skip "v1.0." if (myv > gitv) { printf("Warning: your version ('%s') more recent than stable release ('%s')\n %s\n", myvers, gitvers, HTMURL); return 2; //different from EXIT_SUCCESS (0) and EXIT_FAILURE (1) } printf("Error: your version ('%s') is not the latest release ('%s')\n %s\n", myvers, gitvers, HTMURL); return EXIT_FAILURE; } //checkUpToDate() #endif //shell script for UNIX only void showXML() { //https://www.slicer.org/wiki/Documentation/Nightly/Developers/SlicerExecutionModel#XML_Schema printf("\n"); printf("\n"); printf("dcm2niix\n"); printf("DICOM importer\n"); printf(" \n"); printf(" At least one parameter\n"); printf(" \n"); printf("\n"); } //#define mydebugtest int main(int argc, const char *argv[]) { struct TDCMopts opts; bool isSaveIni = false; bool isOutNameSpecified = false; bool isResetDefaults = false; readIniFile(&opts, argv); //set default preferences #ifdef mydebugtest //strcpy(opts.indir, "/Users/rorden/desktop/sliceOrder/dicom2/Philips_PARREC_Rotation/NoRotation/DBIEX_4_1.PAR"); //strcpy(opts.indir, "/Users/rorden/desktop/sliceOrder/dicom2/test"); strcpy(opts.indir, "e:\\t1s"); #else #if defined(__APPLE__) #define kOS "MacOS" #elif (defined(__linux) || defined(__linux__)) #define kOS "Linux" #else #define kOS "Windows" #endif printf("Chris Rorden's dcm2niiX version %s (%llu-bit %s)\n", kDCMvers, (unsigned long long)sizeof(size_t) * 8, kOS); if (argc < 2) { showHelp(argv, opts); return 0; } //for (int i = 1; i < argc; i++) { printf(" argument %d= '%s'\n", i, argv[i]);} int i = 1; int lastCommandArg = 0; while (i < (argc)) { //-1 as final parameter is DICOM directory if ((strlen(argv[i]) > 1) && (argv[i][0] == '-')) { //command if (argv[i][1] == 'h') { showHelp(argv, opts); } else if ((!strcmp(argv[i], "--big-endian")) && ((i + 1) < argc)) { i++; if ((littleEndianPlatform()) && ((argv[i][0] == 'y') || (argv[i][0] == 'Y'))) { opts.isSaveNativeEndian = false; printf("NIfTI data will be big-endian (byte-swapped)\n"); } if ((!littleEndianPlatform()) && ((argv[i][0] == 'n') || (argv[i][0] == 'N'))) { opts.isSaveNativeEndian = false; printf("NIfTI data will be little-endian\n"); } } else if (!strcmp(argv[i], "--ignore_trigger_times")) { opts.isIgnoreTriggerTimes = true; printf("ignore_trigger_times may have unintended consequences (issue 499)\n"); } else if (!strcmp(argv[i], "--terse")) { opts.isAddNamePostFixes = false; } else if (!strcmp(argv[i], "--version")) { printf("%s\n", kDCMdate); return kEXIT_REPORT_VERSION; } else if ((!strcmp(argv[i], "--progress")) && ((i + 1) < argc)) { i++; if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) opts.isProgress = 0; else opts.isProgress = 1; if (argv[i][0] == '2') opts.isProgress = 2; //logorrheic } else if (!strcmp(argv[i], "--xml")) { showXML(); return EXIT_SUCCESS; } else if ((argv[i][1] == 'a') && ((i + 1) < argc)) { //adjacent DICOMs i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) opts.isOneDirAtATime = false; else opts.isOneDirAtATime = true; } else if ((argv[i][1] >= '1') && (argv[i][1] <= '9')) { opts.gzLevel = abs((int)strtol(argv[i], NULL, 10)); if (opts.gzLevel > 11) opts.gzLevel = 11; } else if ((argv[i][1] == 'b') && ((i + 1) < argc)) { if (strlen(argv[i]) < 3) { //"-b y" i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) opts.isCreateBIDS = false; else if ((argv[i][0] == 'i') || (argv[i][0] == 'I')) { //input only mode (for development): does not create NIfTI or BIDS outputs! opts.isCreateBIDS = false; opts.isOnlyBIDS = true; } else { opts.isCreateBIDS = true; if ((argv[i][0] == 'o') || (argv[i][0] == 'O')) opts.isOnlyBIDS = true; } } else if (argv[i][2] == 'a') { //"-ba y" i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) opts.isAnonymizeBIDS = false; else opts.isAnonymizeBIDS = true; } else printf("Error: Unknown command line argument: '%s'\n", argv[i]); } else if ((argv[i][1] == 'c') && ((i + 1) < argc)) { i++; snprintf(opts.imageComments, 24, "%s", argv[i]); } else if ((argv[i][1] == 'd') && ((i + 1) < argc)) { i++; if ((argv[i][0] >= '0') && (argv[i][0] <= '9')) opts.dirSearchDepth = abs((int)strtol(argv[i], NULL, 10)); } else if ((argv[i][1] == 'e') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == '1')) opts.saveFormat = kSaveFormatNRRD; if ((argv[i][0] == 'o') || (argv[i][0] == 'O') || (argv[i][0] == '2')) opts.saveFormat = kSaveFormatMGH; } else if ((argv[i][1] == 'g') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'y') || (argv[i][0] == 'Y')) isSaveIni = true; if (((argv[i][0] == 'i') || (argv[i][0] == 'I')) && (!isResetDefaults)) { isResetDefaults = true; printf("Defaults ignored\n"); setDefaultOpts(&opts, argv); i = 0; //re-read all settings for this pass, e.g. "dcm2niix -f %p_%s -d o" should save filename as "%p_%s" } if (((argv[i][0] == 'o') || (argv[i][0] == 'O')) && (!isResetDefaults)) { //reset defaults - do not read, but do write defaults isSaveIni = true; isResetDefaults = true; printf("Defaults reset\n"); setDefaultOpts(&opts, argv); //this next line is optional, otherwise "dcm2niix -f %p_%s -d o" and "dcm2niix -d o -f %p_%s" will create different results i = 0; //re-read all settings for this pass, e.g. "dcm2niix -f %p_%s -d o" should save filename as "%p_%s" } } else if ((argv[i][1] == 'i') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) opts.isIgnoreDerivedAnd2D = false; else opts.isIgnoreDerivedAnd2D = true; } else if ((argv[i][1] == 'j') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == '1')) { opts.isTestx0021x105E = true; printf("undocumented '-j y' compares GE slice timing from 0021,105E\n"); } } else if ((argv[i][1] == 'l') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'o') || (argv[i][0] == 'O')) opts.isMaximize16BitRange = kMaximize16BitRange_Raw; else if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) opts.isMaximize16BitRange = kMaximize16BitRange_False; else opts.isMaximize16BitRange = kMaximize16BitRange_True; } else if ((argv[i][1] == 'm') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) opts.isForceStackSameSeries = 0; if ((argv[i][0] == 'y') || (argv[i][0] == 'Y') || (argv[i][0] == '1')) opts.isForceStackSameSeries = 1; if ((argv[i][0] == '2')) opts.isForceStackSameSeries = 2; if ((argv[i][0] == 'o') || (argv[i][0] == 'O')) { opts.isForceStackDCE = false; //printf("Advanced feature: '-m o' merges images despite varying series number\n"); } if ((argv[i][0] == '2')) { opts.isIgnoreSeriesInstanceUID = true; printf("Advanced feature: '-m 2' ignores Series Instance UID.\n"); } } else if ((argv[i][1] == 'p') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) opts.isPhilipsFloatNotDisplayScaling = false; else opts.isPhilipsFloatNotDisplayScaling = true; } else if ((argv[i][1] == 'r') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'y') || (argv[i][0] == 'Y')) opts.isRenameNotConvert = true; } else if ((argv[i][1] == 's') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) opts.isOnlySingleFile = false; else opts.isOnlySingleFile = true; } else if ((argv[i][1] == 't') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) opts.isCreateText = false; else opts.isCreateText = true; #if !defined(_WIN64) && !defined(_WIN32) //shell script for Unix only } else if (argv[i][1] == 'u') { return checkUpToDate(); #endif } else if ((argv[i][1] == 'v') && ((i + 1) >= argc)) { printf("%s\n", kDCMdate); return kEXIT_REPORT_VERSION; } else if ((argv[i][1] == 'v') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) //0: verbose OFF opts.isVerbose = 0; else if ((argv[i][0] == 'h') || (argv[i][0] == 'H') || (argv[i][0] == '2')) //2: verbose HYPER opts.isVerbose = 2; else opts.isVerbose = 1; //1: verbose ON } else if ((argv[i][1] == 'w') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if (argv[i][0] == '0') opts.nameConflictBehavior = 0; if (argv[i][0] == '1') opts.nameConflictBehavior = 1; if (argv[i][0] == '2') opts.nameConflictBehavior = 2; } else if ((argv[i][1] == 'x') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) opts.isCrop = false; else if ((argv[i][0] == 'i') || (argv[i][0] == 'I')) { opts.isRotate3DAcq = false; opts.isCrop = false; } else opts.isCrop = true; } else if ((argv[i][1] == 'y') && ((i + 1) < argc)) { i++; bool isFlipY = opts.isFlipY; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == 'y') || (argv[i][0] == 'Y')) { opts.isFlipY = true; //force use of internal compression instead of pigz strcpy(opts.pigzname, ""); } else if ((argv[i][0] == 'n') || (argv[i][0] == 'N')) opts.isFlipY = false; if (isFlipY != opts.isFlipY) printf("Advanced feature: You are flipping the default order of rows in your image.\n"); } else if ((argv[i][1] == 'z') && ((i + 1) < argc)) { i++; if (invalidParam(i, argv)) return 0; if ((argv[i][0] == '3')) { opts.isGz = false; //uncompressed 3D opts.isSave3D = true; } else if ((argv[i][0] == 'i') || (argv[i][0] == 'I')) { opts.isGz = true; #ifndef myDisableZLib strcpy(opts.pigzname, ""); //force use of internal compression instead of pigz #endif } else if ((argv[i][0] == 'n') || (argv[i][0] == 'N') || (argv[i][0] == '0')) opts.isGz = false; else opts.isGz = true; if (argv[i][0] == 'o') opts.isPipedGz = true; //pipe to pigz without saving uncompressed to disk } else if ((argv[i][1] == 'f') && ((i + 1) < argc)) { i++; strcpy(opts.filename, argv[i]); isOutNameSpecified = true; } else if ((argv[i][1] == 'o') && ((i + 1) < argc)) { i++; strcpy(opts.outdir, argv[i]); } else if ((argv[i][1] == 'n') && ((i + 1) < argc)) { i++; double seriesNumber = atof(argv[i]); if (seriesNumber < 0) opts.numSeries = -1.0; //report series: convert none else if ((opts.numSeries >= 0) && (opts.numSeries < MAX_NUM_SERIES)) { opts.seriesNumber[opts.numSeries] = seriesNumber; opts.numSeries += 1; } else { printf("Warning: too many series specified, ignoring -n %s\n", argv[i]); } } else printf(" Error: invalid option '%s %s'\n", argv[i], argv[i + 1]); ; lastCommandArg = i; } //if parameter is a command i++; //read next parameter } //while parameters to read #ifndef myDisableZLib if ((opts.isGz) && (opts.dirSearchDepth < 1) && (strlen(opts.pigzname) > 0)) { strcpy(opts.pigzname, ""); printf("n.b. Setting directory search depth of zero invokes internal gz (network mode)\n"); } #endif if ((opts.isRenameNotConvert) && (!isOutNameSpecified)) { //sensible naming scheme for renaming option //strcpy(opts.filename,argv[i]); //2019 - now include "%o" to append media SOP UID, as instance number is not required to be unique #if defined(_WIN64) || defined(_WIN32) strcpy(opts.filename, "%t\\%s_%p\\%4r_%o.dcm"); //nrrd or nhdr (windows folders) #else strcpy(opts.filename, "%t/%s_%p/%4r_%o.dcm"); //nrrd or nhdr (unix folders) #endif printf("renaming without output filename, assuming '-f %s'\n", opts.filename); } if (isSaveIni) saveIniFile(opts); //printf("%d %d",argc,lastCommandArg); if (argc == (lastCommandArg + 1)) { //+1 as array indexed from 0 //the user did not provide an input filename, report filename structure char niiFilename[1024]; strcpy(opts.outdir, ""); //no input supplied nii_createDummyFilename(niiFilename, opts); printf("%s\n", niiFilename); return EXIT_SUCCESS; } #endif #ifndef myEnableMultipleInputs if ((argc - lastCommandArg - 1) > 1) { printf("Warning: only processing last of %d input files (recompile with 'myEnableMultipleInputs' to recursively process multiple files)\n", argc - lastCommandArg - 1); lastCommandArg = argc - 2; } #endif #if !defined(_WIN64) && !defined(_WIN32) double startWall = get_wall_time(); #endif clock_t start = clock(); for (i = (lastCommandArg + 1); i < argc; i++) { strcpy(opts.indir, argv[i]); // [argc-1] int ret = nii_loadDir(&opts); if (ret != EXIT_SUCCESS) return ret; } #if !defined(_WIN64) && !defined(_WIN32) printf("Conversion required %f seconds (%f for core code).\n", get_wall_time() - startWall, ((float)(clock() - start)) / CLOCKS_PER_SEC); #else printf("Conversion required %f seconds.\n", ((float)(clock() - start)) / CLOCKS_PER_SEC); #endif //if (isSaveIni) //we now save defaults earlier, in case of early termination. // saveIniFile(opts); return EXIT_SUCCESS; } dcm2niix-1.0.20211006/console/main_console_batch.cpp000066400000000000000000000107671412761503300217530ustar00rootroot00000000000000// main.m dcm2niix // by Chris Rorden on 3/22/14, see license.txt // Copyright (c) 2014 Chris Rorden. All rights reserved. // yaml batch support by Benjamin Irving, 2016 - maintains copyright #include //requires VS 2015 or later #ifdef _MSC_VER #include //access() #ifndef F_OK #define F_OK 0 /* existence check */ #endif #else #include //access() #endif #include #include #include #include #include #include #include // clock_t, clock, CLOCKS_PER_SEC #include #include "nii_dicom.h" #include "nii_dicom_batch.h" const char* removePath(const char* path) { // "/usr/path/filename.exe" -> "filename.exe" const char* pDelimeter = strrchr (path, '\\'); if (pDelimeter) path = pDelimeter+1; pDelimeter = strrchr (path, '/'); if (pDelimeter) path = pDelimeter+1; return path; } //removePath() int rmainbatch(TDCMopts opts) { clock_t start = clock(); nii_loadDir(&opts); printf ("Conversion required %f seconds.\n",((float)(clock()-start))/CLOCKS_PER_SEC); return EXIT_SUCCESS; } //rmainbatch() void showHelp(const char * argv[]) { const char *cstr = removePath(argv[0]); printf("Usage: %s \n", cstr); printf("\n"); printf("The configuration file must be in yaml format as shown below\n"); printf("\n"); printf("### START YAML FILE ###\n"); printf("Options:\n"); printf(" isGz: false\n"); printf(" isFlipY: false\n"); printf(" isVerbose: false\n"); printf(" isCreateBIDS: false\n"); printf(" isOnlySingleFile: false\n"); printf("Files:\n"); printf(" -\n"); printf(" in_dir: /path/to/first/folder\n"); printf(" out_dir: /path/to/output/folder\n"); printf(" filename: dcemri\n"); printf(" -\n"); printf(" in_dir: /path/to/second/folder\n"); printf(" out_dir: /path/to/output/folder\n"); printf(" filename: fa3\n"); printf("### END YAML FILE ###\n"); printf("\n"); #if defined(_WIN64) || defined(_WIN32) printf(" Example :\n"); printf(" %s c:\\dir\\yaml.yml\n", cstr); printf(" %s \"c:\\dir with spaces\\yaml.yml\"\n", cstr); #else printf(" Examples :\n"); printf(" %s /Users/chris/yaml.yml\n", cstr); printf(" %s \"/Users/dir with spaces/yaml.yml\"\n", cstr); #endif } //showHelp() int main(int argc, const char * argv[]) { #if defined(__APPLE__) #define kOS "MacOS" #elif (defined(__linux) || defined(__linux__)) #define kOS "Linux" #else #define kOS "Windows" #endif printf("dcm2niibatch using Chris Rorden's dcm2niiX version %s (%llu-bit %s)\n",kDCMvers, (unsigned long long) sizeof(size_t)*8, kOS); if (argc != 2) { if (argc < 2) printf(" Please provide location of config file\n"); else printf(" Do not include additional inputs with a config file\n"); printf("\n"); showHelp(argv); return EXIT_FAILURE; } if( access( argv[1], F_OK ) == -1 ) { printf(" Please provide location of config file\n"); printf("\n"); showHelp(argv); return EXIT_FAILURE; } // Process it all via a yaml file std::string yaml_file = argv[1]; std::cout << "yaml_path: " << yaml_file << std::endl; YAML::Node config = YAML::LoadFile(yaml_file); struct TDCMopts opts; readIniFile(&opts, argv); //setup defaults, e.g. path to pigz opts.isCreateBIDS = config["Options"]["isCreateBIDS"].as(); opts.isOnlySingleFile = config["Options"]["isOnlySingleFile"].as(); opts.isFlipY = config["Options"]["isFlipY"].as(); opts.isCreateText = false; opts.isVerbose = 0; opts.isGz = config["Options"]["isGz"].as(); //save data as compressed (.nii.gz) or raw (.nii) /*bool isInternalGz = config["Options"]["isInternalGz"].as(); if (isInternalGz) { strcpy(opts.pigzname, "”); //do NOT use pigz: force internal compressor //in general, pigz is faster unless you have a very slow network, in which case the internal compressor is better }*/ for (auto i: config["Files"]) { std::string indir = i["in_dir"].as(); strcpy(opts.indir, indir.c_str()); std::string outdir = i["out_dir"].as(); strcpy(opts.outdir, outdir.c_str()); std::string filename = i["filename"].as(); strcpy(opts.filename, filename.c_str()); rmainbatch(opts); } return EXIT_SUCCESS; } // main() dcm2niix-1.0.20211006/console/makefile000066400000000000000000000025701412761503300171310ustar00rootroot00000000000000# Regular use CFLAGS=-s -O3 # Debugging #CFLAGS=-g #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 #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 endif ifneq ($(OS),Windows_NT) OS = $(shell uname) ifeq "$(OS)" "Darwin" #CFLAGS=-dead_strip -O3 #CFLAGS= -O3 CFLAGS=-O3 -sectcreate __TEXT __info_plist Info.plist #Apple notarization requires a Info.plist # For .app bundles, the Info.plist is a separate file, for executables it is appended as a section #you can check that the Info.plist section has been inserted with either of these commands # otool -l ./dcm2niix | grep info_plist -B1 -A10 # launchctl plist ./dcm2niix #MacOS links g++ to clang++, for gcc install via homebrew and replace g++ with /usr/local/bin/gcc-9 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 dcm2niix-1.0.20211006/console/miniz.c000066400000000000000000007035571412761503300167400ustar00rootroot00000000000000/*miniz.c 2.08 is available. In April 2018 Martin Rainer (who created the 2.0 release) notes: The main change is that it supports ZIP64. If you do not need that because your use case does not run into the ZIP limitations, you should stick with the old version. Since DICOM images are inherently limited to 2gb, dcm2niix will keep using v1.15 */ /* miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing See "unlicense" statement at the end of this file. Rich Geldreich , last updated Oct. 13, 2013 Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). * Change History 10/13/13 v1.15 r4 - Interim bugfix release while I work on the next major release with Zip64 support (almost there!): - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug would only have occurred in earlier versions if you explicitly used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or mz_zip_add_mem_to_archive_file_in_place() (which used this flag). If you can't switch to v1.15 but want to fix this bug, just remove the uses of this flag from both helper funcs (and of course don't use the flag). - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when pUser_read_buf is not NULL and compressed size is > uncompressed size - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract compressed data from directory entries, to account for weird zipfiles which contain zero-size compressed data on dir entries. Hopefully this fix won't cause any issues on weird zip archives, because it assumes the low 16-bits of zip external attributes are DOS attributes (which I believe they always are in practice). - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the internal attributes, just the filename and external attributes - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed - Added cmake support for Linux builds which builds all the examples, tested with clang v3.3 and gcc v4.6. - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti - Merged MZ_FORCEINLINE fix from hdeanclark - Fix include before config #ifdef, thanks emil.brink - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping (super useful for OpenGL apps), and explicit control over the compression level (so you can set it to 1 for real-time compression). - Merged in some compiler fixes from paulharris's github repro. - Retested this build under Windows (VS 2010, including static analysis), tcc 0.9.26, gcc v4.6 and clang v3.3. - Added example6.c, which dumps an image of the mandelbrot set to a PNG file. - Modified example2 to help test the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more. - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix possible src file fclose() leak if alignment bytes+local header file write faiiled - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the wrong central dir header offset, appears harmless in this release, but it became a problem in the zip64 branch 5/20/12 v1.14 - MinGW32/64 GCC 4.6.1 compiler fixes: added MZ_FORCEINLINE, #include (thanks fermtect). 5/19/12 v1.13 - From jason@cornsyrup.org and kelwert@mtu.edu - Fix mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit. - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files. - Eliminated a bunch of warnings when compiling with GCC 32-bit/64. - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning). - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64. - Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test. - Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives. - Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.) - Fix ftell() usage in examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself). 4/12/12 v1.12 - More comments, added low-level example5.c, fixed a couple minor level_and_flags issues in the archive API's. level_and_flags can now be set to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson for the feedback/bug report. 5/28/11 v1.11 - Added statement from unlicense.org 5/27/11 v1.10 - Substantial compressor optimizations: - Level 1 is now ~4x faster than before. The L1 compressor's throughput now varies between 70-110MB/sec. on a - Core i7 (actual throughput varies depending on the type of data, and x64 vs. x86). - Improved baseline L2-L9 compression perf. Also, greatly improved compression perf. issues on some file types. - Refactored the compression code for better readability and maintainability. - Added level 10 compression level (L10 has slightly better ratio than level 9, but could have a potentially large drop in throughput on some files). 5/15/11 v1.09 - Initial stable release. * Low-level Deflate/Inflate implementation notes: Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses approximately as well as zlib. Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory block large enough to hold the entire file. The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. * zlib-style API notes: miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in zlib replacement in many apps: The z_stream struct, optional memory allocation callbacks deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound inflateInit/inflateInit2/inflate/inflateEnd compress, compress2, compressBound, uncompress CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. Supports raw deflate streams or standard zlib streams with adler-32 checking. Limitations: The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but there are no guarantees that miniz.c pulls this off perfectly. * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by Alex Evans. Supports 1-4 bytes/pixel images. * ZIP archive API notes: The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to get the job done with minimal fuss. There are simple API's to retrieve file information, read files from existing archives, create new archives, append new files to existing archives, or clone archive data from one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), or you can specify custom file read/write callbacks. - Archive reading: Just call this function to read a single file from a disk archive: void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); The locate operation can optionally check file comments too, which (as one example) can be used to identify multiple versions of the same file in an archive. This function uses a simple linear search through the central directory, so it's not very fast. Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and retrieve detailed info on each file by calling mz_zip_reader_file_stat(). - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data to disk and builds an exact image of the central directory in memory. The central directory image is written all at once at the end of the archive file when the archive is finalized. The archive writer can optionally align each file's local header and file data to any power of 2 alignment, which can be useful when the archive will be read from optical media. Also, the writer supports placing arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still readable by any ZIP tool. - Archive appending: The simple way to add a single file to an archive is to call this function: mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); The archive will be created if it doesn't already exist, otherwise it'll be appended to. Note the appending is done in-place and is not an atomic operation, so if something goes wrong during the operation it's possible the archive could be left without a central directory (although the local file headers and file data will be fine, so the archive will be recoverable). For more complex archive modification scenarios: 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and you're done. This is safe but requires a bunch of temporary disk space or heap memory. 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), append new files as needed, then finalize the archive which will write an updated central directory to the original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a possibility that the archive's central directory could be lost with this method if anything goes wrong, though. - ZIP archive support limitations: No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. Requires streams capable of seeking. * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. * Important: For best perf. be sure to customize the below macros for your target platform: #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #define MINIZ_LITTLE_ENDIAN 1 #define MINIZ_HAS_64BIT_REGISTERS 1 * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). */ #ifndef MINIZ_HEADER_INCLUDED #define MINIZ_HEADER_INCLUDED #include // Defines to completely disable specific portions of miniz.c: // If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. // Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. //#define MINIZ_NO_STDIO // If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or // get/set file times, and the C run-time funcs that get/set times won't be called. // The current downside is the times written to your archives will be from 1979. //#define MINIZ_NO_TIME // Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. //#define MINIZ_NO_ARCHIVE_APIS // Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive API's. //#define MINIZ_NO_ARCHIVE_WRITING_APIS // Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. //#define MINIZ_NO_ZLIB_APIS // Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. //#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES // Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. // Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc // callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user // functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. //#define MINIZ_NO_MALLOC #if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) // TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux #define MINIZ_NO_TIME #endif #if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) #include #endif #if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) // MINIZ_X86_OR_X64_CPU is only used to help set the below macros. #define MINIZ_X86_OR_X64_CPU 1 #endif #if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU // Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. #define MINIZ_LITTLE_ENDIAN 1 #endif #if MINIZ_X86_OR_X64_CPU // Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #endif #if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) // Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). #define MINIZ_HAS_64BIT_REGISTERS 1 #endif #ifdef __cplusplus extern "C" { #endif // ------------------- zlib-style API Definitions. // For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! typedef unsigned long mz_ulong; // mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. void mz_free(void *p); #define MZ_ADLER32_INIT (1) // mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); #define MZ_CRC32_INIT (0) // mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); // Compression strategies. enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 }; // Method #define MZ_DEFLATED 8 #ifndef MINIZ_NO_ZLIB_APIS // Heap allocation callbacks. // Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); typedef void (*mz_free_func)(void *opaque, void *address); typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); #define MZ_VERSION "9.1.15" #define MZ_VERNUM 0x91F0 #define MZ_VER_MAJOR 9 #define MZ_VER_MINOR 1 #define MZ_VER_REVISION 15 #define MZ_VER_SUBREVISION 0 // Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 }; // Return status codes. MZ_PARAM_ERROR is non-standard. enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 }; // Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 }; // Window bits #define MZ_DEFAULT_WINDOW_BITS 15 struct mz_internal_state; // Compression/decompression stream struct. typedef struct mz_stream_s { const unsigned char *next_in; // pointer to next byte to read unsigned int avail_in; // number of bytes available at next_in mz_ulong total_in; // total number of bytes consumed so far unsigned char *next_out; // pointer to next byte to write unsigned int avail_out; // number of bytes that can be written to next_out mz_ulong total_out; // total number of bytes produced so far char *msg; // error msg (unused) struct mz_internal_state *state; // internal state, allocated by zalloc/zfree mz_alloc_func zalloc; // optional heap allocation function (defaults to malloc) mz_free_func zfree; // optional heap free function (defaults to free) void *opaque; // heap alloc function user pointer int data_type; // data_type (unused) mz_ulong adler; // adler32 of the source or uncompressed data mz_ulong reserved; // not used } mz_stream; typedef mz_stream *mz_streamp; // Returns the version string of miniz.c. const char *mz_version(void); // mz_deflateInit() initializes a compressor with default options: // Parameters: // pStream must point to an initialized mz_stream struct. // level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. // level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. // (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) // Return values: // MZ_OK on success. // MZ_STREAM_ERROR if the stream is bogus. // MZ_PARAM_ERROR if the input parameters are bogus. // MZ_MEM_ERROR on out of memory. int mz_deflateInit(mz_streamp pStream, int level); // mz_deflateInit2() is like mz_deflate(), except with more control: // Additional parameters: // method must be MZ_DEFLATED // window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) // mem_level must be between [1, 9] (it's checked but ignored by miniz.c) int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); // Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). int mz_deflateReset(mz_streamp pStream); // mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. // Parameters: // pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. // flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. // Return values: // MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). // MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. // MZ_STREAM_ERROR if the stream is bogus. // MZ_PARAM_ERROR if one of the parameters is invalid. // MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) int mz_deflate(mz_streamp pStream, int flush); // mz_deflateEnd() deinitializes a compressor: // Return values: // MZ_OK on success. // MZ_STREAM_ERROR if the stream is bogus. int mz_deflateEnd(mz_streamp pStream); // mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); // Single-call compression functions mz_compress() and mz_compress2(): // Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); // mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). mz_ulong mz_compressBound(mz_ulong source_len); // Initializes a decompressor. int mz_inflateInit(mz_streamp pStream); // mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: // window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). int mz_inflateInit2(mz_streamp pStream, int window_bits); // Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. // Parameters: // pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. // flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. // On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). // MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. // Return values: // MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. // MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. // MZ_STREAM_ERROR if the stream is bogus. // MZ_DATA_ERROR if the deflate stream is invalid. // MZ_PARAM_ERROR if one of the parameters is invalid. // MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again // with more input data, or with more room in the output buffer (except when using single call decompression, described above). int mz_inflate(mz_streamp pStream, int flush); // Deinitializes a decompressor. int mz_inflateEnd(mz_streamp pStream); // Single-call decompression. // Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); // Returns a string description of the specified error code, or NULL if the error code is invalid. const char *mz_error(int err); // Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. // Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES typedef unsigned char Byte; typedef unsigned int uInt; typedef mz_ulong uLong; typedef Byte Bytef; typedef uInt uIntf; typedef char charf; typedef int intf; typedef void *voidpf; typedef uLong uLongf; typedef void *voidp; typedef void *const voidpc; #define Z_NULL 0 #define Z_NO_FLUSH MZ_NO_FLUSH #define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH #define Z_SYNC_FLUSH MZ_SYNC_FLUSH #define Z_FULL_FLUSH MZ_FULL_FLUSH #define Z_FINISH MZ_FINISH #define Z_BLOCK MZ_BLOCK #define Z_OK MZ_OK #define Z_STREAM_END MZ_STREAM_END #define Z_NEED_DICT MZ_NEED_DICT #define Z_ERRNO MZ_ERRNO #define Z_STREAM_ERROR MZ_STREAM_ERROR #define Z_DATA_ERROR MZ_DATA_ERROR #define Z_MEM_ERROR MZ_MEM_ERROR #define Z_BUF_ERROR MZ_BUF_ERROR #define Z_VERSION_ERROR MZ_VERSION_ERROR #define Z_PARAM_ERROR MZ_PARAM_ERROR #define Z_NO_COMPRESSION MZ_NO_COMPRESSION #define Z_BEST_SPEED MZ_BEST_SPEED #define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION #define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY #define Z_FILTERED MZ_FILTERED #define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY #define Z_RLE MZ_RLE #define Z_FIXED MZ_FIXED #define Z_DEFLATED MZ_DEFLATED #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS #define alloc_func mz_alloc_func #define free_func mz_free_func #define internal_state mz_internal_state #define z_stream mz_stream #define deflateInit mz_deflateInit #define deflateInit2 mz_deflateInit2 #define deflateReset mz_deflateReset #define deflate mz_deflate #define deflateEnd mz_deflateEnd #define deflateBound mz_deflateBound #define compress mz_compress #define compress2 mz_compress2 #define compressBound mz_compressBound #define inflateInit mz_inflateInit #define inflateInit2 mz_inflateInit2 #define inflate mz_inflate #define inflateEnd mz_inflateEnd #define uncompress mz_uncompress #define crc32 mz_crc32 #define adler32 mz_adler32 #define MAX_WBITS 15 #define MAX_MEM_LEVEL 9 #define zError mz_error #define ZLIB_VERSION MZ_VERSION #define ZLIB_VERNUM MZ_VERNUM #define ZLIB_VER_MAJOR MZ_VER_MAJOR #define ZLIB_VER_MINOR MZ_VER_MINOR #define ZLIB_VER_REVISION MZ_VER_REVISION #define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION #define zlibVersion mz_version #define zlib_version mz_version() #endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES #endif // MINIZ_NO_ZLIB_APIS // ------------------- Types and macros typedef unsigned char mz_uint8; typedef signed short mz_int16; typedef unsigned short mz_uint16; typedef unsigned int mz_uint32; typedef unsigned int mz_uint; typedef long long mz_int64; typedef unsigned long long mz_uint64; typedef int mz_bool; #define MZ_FALSE (0) #define MZ_TRUE (1) // An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message. #ifdef _MSC_VER #define MZ_MACRO_END while (0, 0) #else #define MZ_MACRO_END while (0) #endif // ------------------- ZIP archive reading/writing #ifndef MINIZ_NO_ARCHIVE_APIS enum { MZ_ZIP_MAX_IO_BUF_SIZE = 64*1024, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 }; typedef struct { mz_uint32 m_file_index; mz_uint32 m_central_dir_ofs; mz_uint16 m_version_made_by; mz_uint16 m_version_needed; mz_uint16 m_bit_flag; mz_uint16 m_method; #ifndef MINIZ_NO_TIME time_t m_time; #endif mz_uint32 m_crc32; mz_uint64 m_comp_size; mz_uint64 m_uncomp_size; mz_uint16 m_internal_attr; mz_uint32 m_external_attr; mz_uint64 m_local_header_ofs; mz_uint32 m_comment_size; char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; } mz_zip_archive_file_stat; typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); struct mz_zip_internal_state_tag; typedef struct mz_zip_internal_state_tag mz_zip_internal_state; typedef enum { MZ_ZIP_MODE_INVALID = 0, MZ_ZIP_MODE_READING = 1, MZ_ZIP_MODE_WRITING = 2, MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 } mz_zip_mode; typedef struct mz_zip_archive_tag { mz_uint64 m_archive_size; mz_uint64 m_central_directory_file_ofs; mz_uint m_total_files; mz_zip_mode m_zip_mode; mz_uint m_file_offset_alignment; mz_alloc_func m_pAlloc; mz_free_func m_pFree; mz_realloc_func m_pRealloc; void *m_pAlloc_opaque; mz_file_read_func m_pRead; mz_file_write_func m_pWrite; void *m_pIO_opaque; mz_zip_internal_state *m_pState; } mz_zip_archive; typedef enum { MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 } mz_zip_flags; // ZIP archive reading // Inits a ZIP archive reader. // These functions read and validate the archive's central directory. mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags); mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags); #ifndef MINIZ_NO_STDIO mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); #endif // Returns the total number of files in the archive. mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); // Returns detailed information about an archive file entry. mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); // Determines if an archive file entry is a directory entry. mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); // Retrieves the filename of an archive file entry. // Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); // Attempts to locates a file in the archive's central directory. // Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH // Returns -1 if the file cannot be found. int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); // Extracts a archive file to a memory buffer using no memory allocation. mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); // Extracts a archive file to a memory buffer. mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); // Extracts a archive file to a dynamically allocated heap buffer. void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); // Extracts a archive file using a callback function to output the file's data. mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); #ifndef MINIZ_NO_STDIO // Extracts a archive file to a disk file and sets its last accessed and modified times. // This function only extracts files, not archive directory records. mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); #endif // Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. mz_bool mz_zip_reader_end(mz_zip_archive *pZip); // ZIP archive writing #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS // Inits a ZIP archive writer. mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); #ifndef MINIZ_NO_STDIO mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); #endif // Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. // For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. // For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). // Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. // Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before // the archive is finalized the file's central directory will be hosed. mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); // Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. // To add a directory entry, call this method with an archive name ending in a forwardslash with empty buffer. // level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); #ifndef MINIZ_NO_STDIO // Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. // level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); #endif // Adds a file to an archive by fully cloning the data from another archive. // This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data, and comment fields. mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index); // Finalizes the archive by writing the central directory records followed by the end of central directory record. // After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). // An archive must be manually finalized by calling this function for it to be valid. mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize); // Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. // Note for the archive to be valid, it must have been finalized before ending. mz_bool mz_zip_writer_end(mz_zip_archive *pZip); // Misc. high-level helper functions: // mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. // level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); // Reads a single file from an archive into a heap block. // Returns NULL on failure. void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); #endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS #endif // #ifndef MINIZ_NO_ARCHIVE_APIS // ------------------- Low-level Decompression API Definitions // Decompression flags used by tinfl_decompress(). // TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. // TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. // TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). // TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. enum { TINFL_FLAG_PARSE_ZLIB_HEADER = 1, TINFL_FLAG_HAS_MORE_INPUT = 2, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, TINFL_FLAG_COMPUTE_ADLER32 = 8 }; // High level decompression functions: // tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). // On entry: // pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. // On return: // Function returns a pointer to the decompressed data, or NULL on failure. // *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. // The caller must call mz_free() on the returned block when it's no longer needed. void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); // tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. // Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. #define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); // tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. // Returns 1 on success or 0 on failure. typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; // Max size of LZ dictionary. #define TINFL_LZ_DICT_SIZE 32768 // Return status. typedef enum { TINFL_STATUS_BAD_PARAM = -3, TINFL_STATUS_ADLER32_MISMATCH = -2, TINFL_STATUS_FAILED = -1, TINFL_STATUS_DONE = 0, TINFL_STATUS_NEEDS_MORE_INPUT = 1, TINFL_STATUS_HAS_MORE_OUTPUT = 2 } tinfl_status; // Initializes the decompressor to its initial state. #define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END #define tinfl_get_adler32(r) (r)->m_check_adler32 // Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. // This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); // Internal/private bits follow. enum { TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS }; typedef struct { mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; } tinfl_huff_table; #if MINIZ_HAS_64BIT_REGISTERS #define TINFL_USE_64BIT_BITBUF 1 #endif #if TINFL_USE_64BIT_BITBUF typedef mz_uint64 tinfl_bit_buf_t; #define TINFL_BITBUF_SIZE (64) #else typedef mz_uint32 tinfl_bit_buf_t; #define TINFL_BITBUF_SIZE (32) #endif struct tinfl_decompressor_tag { mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; tinfl_bit_buf_t m_bit_buf; size_t m_dist_from_out_buf_start; tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; }; // ------------------- Low-level Compression API Definitions // Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). #define TDEFL_LESS_MEMORY 0 // tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): // TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). enum { TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF }; // TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. // TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). // TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. // TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). // TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) // TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. // TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. // TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. // The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). enum { TDEFL_WRITE_ZLIB_HEADER = 0x01000, TDEFL_COMPUTE_ADLER32 = 0x02000, TDEFL_GREEDY_PARSING_FLAG = 0x04000, TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, TDEFL_RLE_MATCHES = 0x10000, TDEFL_FILTER_MATCHES = 0x20000, TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 }; // High level compression functions: // tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). // On entry: // pSrc_buf, src_buf_len: Pointer and size of source block to compress. // flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. // On return: // Function returns a pointer to the compressed data, or NULL on failure. // *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. // The caller must free() the returned block when it's no longer needed. void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); // tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. // Returns 0 on failure. size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); // Compresses an image to a compressed PNG file in memory. // On entry: // pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. // The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. // level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL // If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). // On return: // Function returns a pointer to the compressed data, or NULL on failure. // *pLen_out will be set to the size of the PNG image file. // The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); // Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. typedef mz_bool (*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); // tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 }; // TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). #if TDEFL_LESS_MEMORY enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; #else enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; #endif // The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. typedef enum { TDEFL_STATUS_BAD_PARAM = -2, TDEFL_STATUS_PUT_BUF_FAILED = -1, TDEFL_STATUS_OKAY = 0, TDEFL_STATUS_DONE = 1, } tdefl_status; // Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums typedef enum { TDEFL_NO_FLUSH = 0, TDEFL_SYNC_FLUSH = 2, TDEFL_FULL_FLUSH = 3, TDEFL_FINISH = 4 } tdefl_flush; // tdefl's compression state structure. typedef struct { tdefl_put_buf_func_ptr m_pPut_buf_func; void *m_pPut_buf_user; mz_uint m_flags, m_max_probes[2]; int m_greedy_parsing; mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; tdefl_status m_prev_return_status; const void *m_pIn_buf; void *m_pOut_buf; size_t *m_pIn_buf_size, *m_pOut_buf_size; tdefl_flush m_flush; const mz_uint8 *m_pSrc; size_t m_src_buf_left, m_out_buf_ofs; mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; } tdefl_compressor; // Initializes the compressor. // There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. // pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. // If pBut_buf_func is NULL the user should always call the tdefl_compress() API. // flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); // Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); // tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. // tdefl_compress_buffer() always consumes the entire input buffer. tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); mz_uint32 tdefl_get_adler32(tdefl_compressor *d); // Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't defined, because it uses some of its macros. #ifndef MINIZ_NO_ZLIB_APIS // Create tdefl_compress() flags given zlib-style compression parameters. // level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) // window_bits may be -15 (raw deflate) or 15 (zlib) // strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); #endif // #ifndef MINIZ_NO_ZLIB_APIS #ifdef __cplusplus } #endif #endif // MINIZ_HEADER_INCLUDED // ------------------- End of Header: Implementation follows. (If you only want the header, define MINIZ_HEADER_FILE_ONLY.) #ifndef MINIZ_HEADER_FILE_ONLY typedef unsigned char mz_validate_uint16[sizeof(mz_uint16)==2 ? 1 : -1]; typedef unsigned char mz_validate_uint32[sizeof(mz_uint32)==4 ? 1 : -1]; typedef unsigned char mz_validate_uint64[sizeof(mz_uint64)==8 ? 1 : -1]; #include #include #define MZ_ASSERT(x) assert(x) #ifdef MINIZ_NO_MALLOC #define MZ_MALLOC(x) NULL #define MZ_FREE(x) (void)x, ((void)0) #define MZ_REALLOC(p, x) NULL #else #define MZ_MALLOC(x) malloc(x) #define MZ_FREE(x) free(x) #define MZ_REALLOC(p, x) realloc(p, x) #endif #define MZ_MAX(a,b) (((a)>(b))?(a):(b)) #define MZ_MIN(a,b) (((a)<(b))?(a):(b)) #define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) #define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) #else #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) #endif #ifdef _MSC_VER #define MZ_FORCEINLINE __forceinline #elif defined(__GNUC__) #define MZ_FORCEINLINE inline __attribute__((__always_inline__)) #else #define MZ_FORCEINLINE inline #endif #ifdef __cplusplus extern "C" { #endif // ------------------- zlib-style API's mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) { mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552; if (!ptr) return MZ_ADLER32_INIT; while (buf_len) { for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; } for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; } return (s2 << 16) + s1; } // Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) { static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; mz_uint32 crcu32 = (mz_uint32)crc; if (!ptr) return MZ_CRC32_INIT; crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } return ~crcu32; } void mz_free(void *p) { MZ_FREE(p); } #ifndef MINIZ_NO_ZLIB_APIS static void *def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } static void def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } static void *def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); } const char *mz_version(void) { return MZ_VERSION; } int mz_deflateInit(mz_streamp pStream, int level) { return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); } int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) { tdefl_compressor *pComp; mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); if (!pStream) return MZ_STREAM_ERROR; if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR; pStream->data_type = 0; pStream->adler = MZ_ADLER32_INIT; pStream->msg = NULL; pStream->reserved = 0; pStream->total_in = 0; pStream->total_out = 0; if (!pStream->zalloc) pStream->zalloc = def_alloc_func; if (!pStream->zfree) pStream->zfree = def_free_func; pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); if (!pComp) return MZ_MEM_ERROR; pStream->state = (struct mz_internal_state *)pComp; if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) { mz_deflateEnd(pStream); return MZ_PARAM_ERROR; } return MZ_OK; } int mz_deflateReset(mz_streamp pStream) { if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR; pStream->total_in = pStream->total_out = 0; tdefl_init((tdefl_compressor*)pStream->state, NULL, NULL, ((tdefl_compressor*)pStream->state)->m_flags); return MZ_OK; } int mz_deflate(mz_streamp pStream, int flush) { size_t in_bytes, out_bytes; mz_ulong orig_total_in, orig_total_out; int mz_status = MZ_OK; if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR; if (!pStream->avail_out) return MZ_BUF_ERROR; if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; if (((tdefl_compressor*)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; orig_total_in = pStream->total_in; orig_total_out = pStream->total_out; for ( ; ; ) { tdefl_status defl_status; in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; defl_status = tdefl_compress((tdefl_compressor*)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor*)pStream->state); pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; if (defl_status < 0) { mz_status = MZ_STREAM_ERROR; break; } else if (defl_status == TDEFL_STATUS_DONE) { mz_status = MZ_STREAM_END; break; } else if (!pStream->avail_out) break; else if ((!pStream->avail_in) && (flush != MZ_FINISH)) { if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) break; return MZ_BUF_ERROR; // Can't make forward progress without some input. } } return mz_status; } int mz_deflateEnd(mz_streamp pStream) { if (!pStream) return MZ_STREAM_ERROR; if (pStream->state) { pStream->zfree(pStream->opaque, pStream->state); pStream->state = NULL; } return MZ_OK; } mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) { (void)pStream; // This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); } int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) { int status; mz_stream stream; memset(&stream, 0, sizeof(stream)); // In case mz_ulong is 64-bits (argh I hate longs). if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; stream.avail_in = (mz_uint32)source_len; stream.next_out = pDest; stream.avail_out = (mz_uint32)*pDest_len; status = mz_deflateInit(&stream, level); if (status != MZ_OK) return status; status = mz_deflate(&stream, MZ_FINISH); if (status != MZ_STREAM_END) { mz_deflateEnd(&stream); return (status == MZ_OK) ? MZ_BUF_ERROR : status; } *pDest_len = stream.total_out; return mz_deflateEnd(&stream); } int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) { return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); } mz_ulong mz_compressBound(mz_ulong source_len) { return mz_deflateBound(NULL, source_len); } typedef struct { tinfl_decompressor m_decomp; mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits; mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; tinfl_status m_last_status; } inflate_state; int mz_inflateInit2(mz_streamp pStream, int window_bits) { inflate_state *pDecomp; if (!pStream) return MZ_STREAM_ERROR; if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR; pStream->data_type = 0; pStream->adler = 0; pStream->msg = NULL; pStream->total_in = 0; pStream->total_out = 0; pStream->reserved = 0; if (!pStream->zalloc) pStream->zalloc = def_alloc_func; if (!pStream->zfree) pStream->zfree = def_free_func; pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); if (!pDecomp) return MZ_MEM_ERROR; pStream->state = (struct mz_internal_state *)pDecomp; tinfl_init(&pDecomp->m_decomp); pDecomp->m_dict_ofs = 0; pDecomp->m_dict_avail = 0; pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; pDecomp->m_first_call = 1; pDecomp->m_has_flushed = 0; pDecomp->m_window_bits = window_bits; return MZ_OK; } int mz_inflateInit(mz_streamp pStream) { return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); } int mz_inflate(mz_streamp pStream, int flush) { inflate_state* pState; mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; size_t in_bytes, out_bytes, orig_avail_in; tinfl_status status; if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR; if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; pState = (inflate_state*)pStream->state; if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; orig_avail_in = pStream->avail_in; first_call = pState->m_first_call; pState->m_first_call = 0; if (pState->m_last_status < 0) return MZ_DATA_ERROR; if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; pState->m_has_flushed |= (flush == MZ_FINISH); if ((flush == MZ_FINISH) && (first_call)) { // MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); pState->m_last_status = status; pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; if (status < 0) return MZ_DATA_ERROR; else if (status != TINFL_STATUS_DONE) { pState->m_last_status = TINFL_STATUS_FAILED; return MZ_BUF_ERROR; } return MZ_STREAM_END; } // flush != MZ_FINISH then we must assume there's more input. if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; if (pState->m_dict_avail) { n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; } for ( ; ; ) { in_bytes = pStream->avail_in; out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); pState->m_last_status = status; pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); pState->m_dict_avail = (mz_uint)out_bytes; n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); if (status < 0) return MZ_DATA_ERROR; // Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) return MZ_BUF_ERROR; // Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. else if (flush == MZ_FINISH) { // The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. if (status == TINFL_STATUS_DONE) return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. else if (!pStream->avail_out) return MZ_BUF_ERROR; } else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) break; } return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; } int mz_inflateEnd(mz_streamp pStream) { if (!pStream) return MZ_STREAM_ERROR; if (pStream->state) { pStream->zfree(pStream->opaque, pStream->state); pStream->state = NULL; } return MZ_OK; } int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) { mz_stream stream; int status; memset(&stream, 0, sizeof(stream)); // In case mz_ulong is 64-bits (argh I hate longs). if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; stream.avail_in = (mz_uint32)source_len; stream.next_out = pDest; stream.avail_out = (mz_uint32)*pDest_len; status = mz_inflateInit(&stream); if (status != MZ_OK) return status; status = mz_inflate(&stream, MZ_FINISH); if (status != MZ_STREAM_END) { mz_inflateEnd(&stream); return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; } *pDest_len = stream.total_out; return mz_inflateEnd(&stream); } const char *mz_error(int err) { static struct { int m_err; const char *m_pDesc; } s_error_descs[] = { { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } }; mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc; return NULL; } #endif //MINIZ_NO_ZLIB_APIS // ------------------- Low-level Decompression (completely independent from all compression API's) #define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) #define TINFL_MEMSET(p, c, l) memset(p, c, l) #define TINFL_CR_BEGIN switch(r->m_state) { case 0: #define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END #define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END #define TINFL_CR_FINISH } // TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never // reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. #define TINFL_GET_BYTE(state_index, c) do { \ if (pIn_buf_cur >= pIn_buf_end) { \ for ( ; ; ) { \ if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ if (pIn_buf_cur < pIn_buf_end) { \ c = *pIn_buf_cur++; \ break; \ } \ } else { \ c = 0; \ break; \ } \ } \ } else c = *pIn_buf_cur++; } MZ_MACRO_END #define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n)) #define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END #define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END // TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. // It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a // Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the // bit buffer contains >=15 bits (deflate's max. Huffman code size). #define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ do { \ temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ if (temp >= 0) { \ code_len = temp >> 9; \ if ((code_len) && (num_bits >= code_len)) \ break; \ } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ code_len = TINFL_FAST_LOOKUP_BITS; \ do { \ temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \ } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \ } while (num_bits < 15); // TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read // beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully // decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. // The slow path is only executed at the very end of the input buffer. #define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \ int temp; mz_uint code_len, c; \ if (num_bits < 15) { \ if ((pIn_buf_end - pIn_buf_cur) < 2) { \ TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ } else { \ bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \ } \ } \ if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ code_len = temp >> 9, temp &= 511; \ else { \ code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \ } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) { static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; static const int s_min_table_sizes[3] = { 257, 1, 4 }; tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; TINFL_CR_BEGIN bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } } do { TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; if (r->m_type == 0) { TINFL_SKIP_BITS(5, num_bits & 7); for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } while ((counter) && (num_bits)) { TINFL_GET_BITS(51, dist, 8); while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } *pOut_buf_cur++ = (mz_uint8)dist; counter--; } while (counter) { size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } while (pIn_buf_cur >= pIn_buf_end) { if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); } else { TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); } } n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; } } else if (r->m_type == 3) { TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); } else { if (r->m_type == 1) { mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i; r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8; } else { for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; } r->m_table_sizes[2] = 19; } for ( ; (int)r->m_type >= 0; r->m_type--) { int tree_next, tree_cur; tinfl_huff_table *pTable; mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree); for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } if ((65536 != total) && (used_syms > 1)) { TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); } for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) { mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) { tree_cur -= ((rev_code >>= 1) & 1); if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1]; } tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; } if (r->m_type == 2) { for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); ) { mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } if ((dist == 16) && (!counter)) { TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); } num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; } if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) { TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); } TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); } } for ( ; ; ) { mz_uint8 *pSrc; for ( ; ; ) { if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) { TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); if (counter >= 256) break; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } *pOut_buf_cur++ = (mz_uint8)counter; } else { int sym2; mz_uint code_len; #if TINFL_USE_64BIT_BITBUF if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } #else if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } #endif if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } counter = sym2; bit_buf >>= code_len; num_bits -= code_len; if (counter & 256) break; #if !TINFL_USE_64BIT_BITBUF if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } #endif if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } bit_buf >>= code_len; num_bits -= code_len; pOut_buf_cur[0] = (mz_uint8)counter; if (sym2 & 256) { pOut_buf_cur++; counter = sym2; break; } pOut_buf_cur[1] = (mz_uint8)sym2; pOut_buf_cur += 2; } } if ((counter &= 511) == 256) break; num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) { TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); } pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) { while (counter--) { while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; } continue; } #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES else if ((counter >= 9) && (counter <= dist)) { const mz_uint8 *pSrc_end = pSrc + (counter & ~7); do { ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; pOut_buf_cur += 8; } while ((pSrc += 8) < pSrc_end); if ((counter &= 7) < 3) { if (counter) { pOut_buf_cur[0] = pSrc[0]; if (counter > 1) pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur += counter; } continue; } } #endif do { pOut_buf_cur[0] = pSrc[0]; pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur[2] = pSrc[2]; pOut_buf_cur += 3; pSrc += 3; } while ((int)(counter -= 3) > 2); if ((int)counter > 0) { pOut_buf_cur[0] = pSrc[0]; if ((int)counter > 1) pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur += counter; } } } } while (!(r->m_final & 1)); if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } } TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); TINFL_CR_FINISH common_exit: r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) { const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; while (buf_len) { for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; } for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; } r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; } return status; } // Higher level helper functions. void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) { tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; *pOut_len = 0; tinfl_init(&decomp); for ( ; ; ) { size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) { MZ_FREE(pBuf); *pOut_len = 0; return NULL; } src_buf_ofs += src_buf_size; *pOut_len += dst_buf_size; if (status == TINFL_STATUS_DONE) break; new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); if (!pNew_buf) { MZ_FREE(pBuf); *pOut_len = 0; return NULL; } pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; } return pBuf; } size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) { tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, (mz_uint8*)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; } int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { int result = 0; tinfl_decompressor decomp; mz_uint8 *pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; if (!pDict) return TINFL_STATUS_FAILED; tinfl_init(&decomp); for ( ; ; ) { size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); in_buf_ofs += in_buf_size; if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) break; if (status != TINFL_STATUS_HAS_MORE_OUTPUT) { result = (status == TINFL_STATUS_DONE); break; } dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); } MZ_FREE(pDict); *pIn_buf_size = in_buf_ofs; return result; } // ------------------- Low-level Compression (independent from all decompression API's) // Purposely making these tables static for faster init and thread safety. static const mz_uint16 s_tdefl_len_sym[256] = { 257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272, 273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276, 277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, 279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280, 281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281, 282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282, 283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283, 284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 }; static const mz_uint8 s_tdefl_len_extra[256] = { 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0 }; static const mz_uint8 s_tdefl_small_dist_sym[512] = { 0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, 11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13, 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14, 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, 14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16, 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 }; static const mz_uint8 s_tdefl_small_dist_extra[512] = { 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7 }; static const mz_uint8 s_tdefl_large_dist_sym[128] = { 0,0,18,19,20,20,21,21,22,22,22,22,23,23,23,23,24,24,24,24,24,24,24,24,25,25,25,25,25,25,25,25,26,26,26,26,26,26,26,26,26,26,26,26, 26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28, 28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29 }; static const mz_uint8 s_tdefl_large_dist_extra[128] = { 0,0,8,8,9,9,9,9,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13 }; // Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq; static tdefl_sym_freq* tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq* pSyms0, tdefl_sym_freq* pSyms1) { mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq* pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_OBJ(hist); for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; } while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) { const mz_uint32* pHist = &hist[pass << 8]; mz_uint offsets[256], cur_ofs = 0; for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; { tdefl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; } } return pCur_syms; } // tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) { int root, leaf, next, avbl, used, dpth; if (n==0) return; else if (n==1) { A[0].m_key = 1; return; } A[0].m_key += A[1].m_key; root = 0; leaf = 2; for (next=1; next < n-1; next++) { if (leaf>=n || A[root].m_key=n || (root=0; next--) A[next].m_key = A[A[next].m_key].m_key+1; avbl = 1; used = dpth = 0; root = n-2; next = n-1; while (avbl>0) { while (root>=0 && (int)A[root].m_key==dpth) { used++; root--; } while (avbl>used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; } avbl = 2*used; dpth++; used = 0; } } // Limits canonical Huffman code table's max code size. enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) { int i; mz_uint32 total = 0; if (code_list_len <= 1) return; for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); while (total != (1UL << max_code_size)) { pNum_codes[max_code_size]--; for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } total--; } } static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) { int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_OBJ(num_codes); if (static_table) { for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++; } else { tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; int num_used_syms = 0; const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; } pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++; tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); for (i = 1, j = num_used_syms; i <= code_size_limit; i++) for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); } next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1); for (i = 0; i < table_len; i++) { mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue; code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1); d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; } } #define TDEFL_PUT_BITS(b, l) do { \ mz_uint bits = b; mz_uint len = l; MZ_ASSERT(bits <= ((1U << len) - 1U)); \ d->m_bit_buffer |= (bits << d->m_bits_in); d->m_bits_in += len; \ while (d->m_bits_in >= 8) { \ if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ d->m_bit_buffer >>= 8; \ d->m_bits_in -= 8; \ } \ } MZ_MACRO_END #define TDEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \ if (rle_repeat_count < 3) { \ d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ } else { \ d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ } rle_repeat_count = 0; } } #define TDEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \ if (rle_z_count < 3) { \ d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \ } else if (rle_z_count <= 10) { \ d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ } else { \ d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ } rle_z_count = 0; } } static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; static void tdefl_start_dynamic_block(tdefl_compressor *d) { int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; d->m_huff_count[0][256] = 1; tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break; for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break; memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0; memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); for (i = 0; i < total_code_sizes_to_pack; i++) { mz_uint8 code_size = code_sizes_to_pack[i]; if (!code_size) { TDEFL_RLE_PREV_CODE_SIZE(); if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); } } else { TDEFL_RLE_ZERO_CODE_SIZE(); if (code_size != prev_code_size) { TDEFL_RLE_PREV_CODE_SIZE(); d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size; } else if (++rle_repeat_count == 6) { TDEFL_RLE_PREV_CODE_SIZE(); } } prev_code_size = code_size; } if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } else { TDEFL_RLE_ZERO_CODE_SIZE(); } tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); TDEFL_PUT_BITS(2, 2); TDEFL_PUT_BITS(num_lit_codes - 257, 5); TDEFL_PUT_BITS(num_dist_codes - 1, 5); for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break; num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4); for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; ) { mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); } } static void tdefl_start_static_block(tdefl_compressor *d) { mz_uint i; mz_uint8 *p = &d->m_huff_code_sizes[0][0]; for (i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8; memset(d->m_huff_code_sizes[1], 5, 32); tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); TDEFL_PUT_BITS(1, 2); } static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { mz_uint flags; mz_uint8 *pLZ_codes; mz_uint8 *pOutput_buf = d->m_pOutput_buf; mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; mz_uint64 bit_buffer = d->m_bit_buffer; mz_uint bits_in = d->m_bits_in; #define TDEFL_PUT_BITS_FAST(b, l) { bit_buffer |= (((mz_uint64)(b)) << bits_in); bits_in += (l); } flags = 1; for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) { if (flags == 1) flags = *pLZ_codes++ | 0x100; if (flags & 1) { mz_uint s0, s1, n0, n1, sym, num_extra_bits; mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); pLZ_codes += 3; MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); // This sequence coaxes MSVC into using cmov's vs. jmp's. s0 = s_tdefl_small_dist_sym[match_dist & 511]; n0 = s_tdefl_small_dist_extra[match_dist & 511]; s1 = s_tdefl_large_dist_sym[match_dist >> 8]; n1 = s_tdefl_large_dist_extra[match_dist >> 8]; sym = (match_dist < 512) ? s0 : s1; num_extra_bits = (match_dist < 512) ? n0 : n1; MZ_ASSERT(d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); } else { mz_uint lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { flags >>= 1; lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { flags >>= 1; lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); } } } if (pOutput_buf >= d->m_pOutput_buf_end) return MZ_FALSE; *(mz_uint64*)pOutput_buf = bit_buffer; pOutput_buf += (bits_in >> 3); bit_buffer >>= (bits_in & ~7); bits_in &= 7; } #undef TDEFL_PUT_BITS_FAST d->m_pOutput_buf = pOutput_buf; d->m_bits_in = 0; d->m_bit_buffer = 0; while (bits_in) { mz_uint32 n = MZ_MIN(bits_in, 16); TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); bit_buffer >>= n; bits_in -= n; } TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); return (d->m_pOutput_buf < d->m_pOutput_buf_end); } #else static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { mz_uint flags; mz_uint8 *pLZ_codes; flags = 1; for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) { if (flags == 1) flags = *pLZ_codes++ | 0x100; if (flags & 1) { mz_uint sym, num_extra_bits; mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); if (match_dist < 512) { sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist]; } else { sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; } MZ_ASSERT(d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); } else { mz_uint lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); } } TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); return (d->m_pOutput_buf < d->m_pOutput_buf_end); } #endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) { if (static_block) tdefl_start_static_block(d); else tdefl_start_dynamic_block(d); return tdefl_compress_lz_codes(d); } static int tdefl_flush_block(tdefl_compressor *d, int flush) { mz_uint saved_bit_buf, saved_bits_in; mz_uint8 *pSaved_output_buf; mz_bool comp_block_succeeded = MZ_FALSE; int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; d->m_pOutput_buf = pOutput_buf_start; d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; MZ_ASSERT(!d->m_output_flush_remaining); d->m_output_flush_ofs = 0; d->m_output_flush_remaining = 0; *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) { TDEFL_PUT_BITS(0x78, 8); TDEFL_PUT_BITS(0x01, 8); } TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in; if (!use_raw_block) comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); // If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. if ( ((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size) ) { mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; TDEFL_PUT_BITS(0, 2); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) { TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); } for (i = 0; i < d->m_total_lz_bytes; ++i) { TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); } } // Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. else if (!comp_block_succeeded) { d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; tdefl_compress_block(d, MZ_TRUE); } if (flush) { if (flush == TDEFL_FINISH) { if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } } } else { mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); } } } MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++; if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) { if (d->m_pPut_buf_func) { *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); } else if (pOutput_buf_start == d->m_output_buf) { int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); d->m_out_buf_ofs += bytes_to_copy; if ((n -= bytes_to_copy) != 0) { d->m_output_flush_ofs = bytes_to_copy; d->m_output_flush_remaining = n; } } else { d->m_out_buf_ofs += n; } } return d->m_output_flush_remaining; } #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES #define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16*)(p) static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) { mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; const mz_uint16 *s = (const mz_uint16*)(d->m_dict + pos), *p, *q; mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD(s); MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; for ( ; ; ) { for ( ; ; ) { if (--num_probes_left == 0) return; #define TDEFL_PROBE \ next_probe_pos = d->m_next[probe_pos]; \ if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) break; TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; } if (!dist) break; q = (const mz_uint16*)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD(q) != s01) continue; p = s; probe_len = 32; do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); if (!probe_len) { *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); break; } else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q)) > match_len) { *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break; c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); } } } #else static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) { mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; const mz_uint8 *s = d->m_dict + pos, *p, *q; mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; for ( ; ; ) { for ( ; ; ) { if (--num_probes_left == 0) return; #define TDEFL_PROBE \ next_probe_pos = d->m_next[probe_pos]; \ if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) break; TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; } if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break; if (probe_len > match_len) { *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return; c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1]; } } } #endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN static mz_bool tdefl_compress_fast(tdefl_compressor *d) { // Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) { const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); d->m_src_buf_left -= num_bytes_to_process; lookahead_size += num_bytes_to_process; while (num_bytes_to_process) { mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); memcpy(d->m_dict + dst_pos, d->m_pSrc, n); if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); d->m_pSrc += n; dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; num_bytes_to_process -= n; } dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break; while (lookahead_size >= 4) { mz_uint cur_match_dist, cur_match_len = 1; mz_uint8 *pCur_dict = d->m_dict + cur_pos; mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; mz_uint probe_pos = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)lookahead_pos; if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) { const mz_uint16 *p = (const mz_uint16 *)pCur_dict; const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); mz_uint32 probe_len = 32; do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); if (!probe_len) cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U))) { cur_match_len = 1; *pLZ_code_buf++ = (mz_uint8)first_trigram; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); d->m_huff_count[0][(mz_uint8)first_trigram]++; } else { mz_uint32 s0, s1; cur_match_len = MZ_MIN(cur_match_len, lookahead_size); MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); cur_match_dist--; pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; pLZ_code_buf += 3; *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; } } else { *pLZ_code_buf++ = (mz_uint8)first_trigram; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); d->m_huff_count[0][(mz_uint8)first_trigram]++; } if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } total_lz_bytes += cur_match_len; lookahead_pos += cur_match_len; dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE); cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; MZ_ASSERT(lookahead_size >= cur_match_len); lookahead_size -= cur_match_len; if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { int n; d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; } } while (lookahead_size) { mz_uint8 lit = d->m_dict[cur_pos]; total_lz_bytes++; *pLZ_code_buf++ = lit; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } d->m_huff_count[0][lit]++; lookahead_pos++; dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE); cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; lookahead_size--; if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { int n; d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; } } } d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; return MZ_TRUE; } #endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) { d->m_total_lz_bytes++; *d->m_pLZ_code_buf++ = lit; *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } d->m_huff_count[0][lit]++; } static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) { mz_uint32 s0, s1; MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); d->m_total_lz_bytes += match_len; d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); match_dist -= 1; d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3; *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; if (match_len >= TDEFL_MIN_MATCH_LEN) d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; } static mz_bool tdefl_compress_normal(tdefl_compressor *d) { const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left; tdefl_flush flush = d->m_flush; while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) { mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; // Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) { mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; src_buf_left -= num_bytes_to_process; d->m_lookahead_size += num_bytes_to_process; while (pSrc != pSrc_end) { mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++; } } else { while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) { mz_uint8 c = *pSrc++; mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; src_buf_left--; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) { mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); } } } d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) break; // Simple lazy/greedy parsing state machine. len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) { if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) { mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; } if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1; } } else { tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); } if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) { cur_match_dist = cur_match_len = 0; } if (d->m_saved_match_len) { if (cur_match_len > d->m_saved_match_len) { tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); if (cur_match_len >= 128) { tdefl_record_match(d, cur_match_len, cur_match_dist); d->m_saved_match_len = 0; len_to_move = cur_match_len; } else { d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; } } else { tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0; } } else if (!cur_match_dist) tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) { tdefl_record_match(d, cur_match_len, cur_match_dist); len_to_move = cur_match_len; } else { d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; } // Move the lookahead forward by len_to_move bytes. d->m_lookahead_pos += len_to_move; MZ_ASSERT(d->m_lookahead_size >= len_to_move); d->m_lookahead_size -= len_to_move; d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE); // Check if it's time to flush the current LZ codes to the internal output buffer. if ( (d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || ( (d->m_total_lz_bytes > 31*1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) ) { int n; d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; } } d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; return MZ_TRUE; } static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) { if (d->m_pIn_buf_size) { *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; } if (d->m_pOut_buf_size) { size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); d->m_output_flush_ofs += (mz_uint)n; d->m_output_flush_remaining -= (mz_uint)n; d->m_out_buf_ofs += n; *d->m_pOut_buf_size = d->m_out_buf_ofs; } return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; } tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) { if (!d) { if (pIn_buf_size) *pIn_buf_size = 0; if (pOut_buf_size) *pOut_buf_size = 0; return TDEFL_STATUS_BAD_PARAM; } d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size; d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size; d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; d->m_out_buf_ofs = 0; d->m_flush = flush; if ( ((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf) ) { if (pIn_buf_size) *pIn_buf_size = 0; if (pOut_buf_size) *pOut_buf_size = 0; return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); } d->m_wants_to_finish |= (flush == TDEFL_FINISH); if ((d->m_output_flush_remaining) || (d->m_finished)) return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) { if (!tdefl_compress_fast(d)) return d->m_prev_return_status; } else #endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN { if (!tdefl_compress_normal(d)) return d->m_prev_return_status; } if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) { if (tdefl_flush_block(d, flush) < 0) return d->m_prev_return_status; d->m_finished = (flush == TDEFL_FINISH); if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_OBJ(d->m_hash); MZ_CLEAR_OBJ(d->m_next); d->m_dict_size = 0; } } return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); } tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) { MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); } tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user; d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_OBJ(d->m_hash); d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY; d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1; d->m_pIn_buf = NULL; d->m_pOut_buf = NULL; d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL; d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); return TDEFL_STATUS_OKAY; } tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) { return d->m_prev_return_status; } mz_uint32 tdefl_get_adler32(tdefl_compressor *d) { return d->m_adler32; } mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE; pComp = (tdefl_compressor*)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE; succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); MZ_FREE(pComp); return succeeded; } typedef struct { size_t m_size, m_capacity; mz_uint8 *m_pBuf; mz_bool m_expandable; } tdefl_output_buffer; static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) { tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; size_t new_size = p->m_size + len; if (new_size > p->m_capacity) { size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE; do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity); pNew_buf = (mz_uint8*)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE; p->m_pBuf = pNew_buf; p->m_capacity = new_capacity; } memcpy((mz_uint8*)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size; return MZ_TRUE; } void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) { tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); if (!pOut_len) return MZ_FALSE; else *pOut_len = 0; out_buf.m_expandable = MZ_TRUE; if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL; *pOut_len = out_buf.m_size; return out_buf.m_pBuf; } size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) { tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); if (!pOut_buf) return 0; out_buf.m_pBuf = (mz_uint8*)pOut_buf; out_buf.m_capacity = out_buf_len; if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0; return out_buf.m_size; } #ifndef MINIZ_NO_ZLIB_APIS static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; // level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) { mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER; if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES; else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK; else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES; return comp_flags; } #endif //MINIZ_NO_ZLIB_APIS #ifdef _MSC_VER #pragma warning (push) #pragma warning (disable:4204) // nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) #endif // Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at // http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. // This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) { // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0; if (!pComp) return NULL; MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57+MZ_MAX(64, (1+bpl)*h); if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } // write dummy header for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf); // compress image data tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } // write real header *pLen_out = out_buf.m_size-41; { static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06}; mz_uint8 pnghdr[41]={0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52, 0,0,(mz_uint8)(w>>8),(mz_uint8)w,0,0,(mz_uint8)(h>>8),(mz_uint8)h,8,chans[num_chans],0,0,0,0,0,0,0, (mz_uint8)(*pLen_out>>24),(mz_uint8)(*pLen_out>>16),(mz_uint8)(*pLen_out>>8),(mz_uint8)*pLen_out,0x49,0x44,0x41,0x54}; c=(mz_uint32)mz_crc32(MZ_CRC32_INIT,pnghdr+12,17); for (i=0; i<4; ++i, c<<=8) ((mz_uint8*)(pnghdr+29))[i]=(mz_uint8)(c>>24); memcpy(out_buf.m_pBuf, pnghdr, 41); } // write footer (IDAT CRC-32, followed by IEND chunk) if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+41-4, *pLen_out+4); for (i=0; i<4; ++i, c<<=8) (out_buf.m_pBuf+out_buf.m_size-16)[i] = (mz_uint8)(c >> 24); // compute final size of file, grab compressed data buffer and return *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf; } void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) { // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); } #ifdef _MSC_VER #pragma warning (pop) #endif // ------------------- .ZIP archive reading #ifndef MINIZ_NO_ARCHIVE_APIS #ifdef MINIZ_NO_STDIO #define MZ_FILE void * #else #include #include #if defined(_MSC_VER) || defined(__MINGW64__) static FILE *mz_fopen(const char *pFilename, const char *pMode) { FILE* pFile = NULL; fopen_s(&pFile, pFilename, pMode); return pFile; } static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) { FILE* pFile = NULL; if (freopen_s(&pFile, pPath, pMode, pStream)) return NULL; return pFile; } #ifndef MINIZ_NO_TIME #include #endif #define MZ_FILE FILE #define MZ_FOPEN mz_fopen #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 _ftelli64 #define MZ_FSEEK64 _fseeki64 #define MZ_FILE_STAT_STRUCT _stat #define MZ_FILE_STAT _stat #define MZ_FFLUSH fflush #define MZ_FREOPEN mz_freopen #define MZ_DELETE_FILE remove #elif defined(__MINGW32__) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FILE FILE #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftello64 #define MZ_FSEEK64 fseeko64 #define MZ_FILE_STAT_STRUCT _stat #define MZ_FILE_STAT _stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #elif defined(__TINYC__) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FILE FILE #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftell #define MZ_FSEEK64 fseek #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #elif defined(__GNUC__) && _LARGEFILE64_SOURCE #ifndef MINIZ_NO_TIME #include #endif #define MZ_FILE FILE #define MZ_FOPEN(f, m) fopen64(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftello64 #define MZ_FSEEK64 fseeko64 #define MZ_FILE_STAT_STRUCT stat64 #define MZ_FILE_STAT stat64 #define MZ_FFLUSH fflush #define MZ_FREOPEN(p, m, s) freopen64(p, m, s) #define MZ_DELETE_FILE remove #else #ifndef MINIZ_NO_TIME #include #endif #define MZ_FILE FILE #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftello #define MZ_FSEEK64 fseeko #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #endif // #ifdef _MSC_VER #endif // #ifdef MINIZ_NO_STDIO #define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) // Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. enum { // ZIP archive identifiers and record sizes MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, // Central directory header record offsets MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8, MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16, MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, // Local directory header offsets MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10, MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, // End of central directory offsets MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, }; typedef struct { void *m_p; size_t m_size, m_capacity; mz_uint m_element_size; } mz_zip_array; struct mz_zip_internal_state_tag { mz_zip_array m_central_dir; mz_zip_array m_central_dir_offsets; mz_zip_array m_sorted_central_dir_offsets; MZ_FILE *m_pFile; void *m_pMem; size_t m_mem_size; size_t m_mem_capacity; }; #define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size #define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) { pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); memset(pArray, 0, sizeof(mz_zip_array)); } static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) { void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE; if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; } if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE; pArray->m_p = pNew_p; pArray->m_capacity = new_capacity; return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) { if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; } return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) { if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; } pArray->m_size = new_size; return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) { return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); } static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) { size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE; memcpy((mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); return MZ_TRUE; } #ifndef MINIZ_NO_TIME static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date) { struct tm tm; memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31; tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62; return mktime(&tm); } static void mz_zip_time_to_dos_time(time_t time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) { #ifdef _MSC_VER struct tm tm_struct; struct tm *tm = &tm_struct; errno_t err = localtime_s(tm, &time); if (err) { *pDOS_date = 0; *pDOS_time = 0; return; } #else struct tm *tm = localtime(&time); #endif *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); } #endif #ifndef MINIZ_NO_STDIO static mz_bool mz_zip_get_file_modified_time(const char *pFilename, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) { #ifdef MINIZ_NO_TIME (void)pFilename; *pDOS_date = *pDOS_time = 0; #else struct MZ_FILE_STAT_STRUCT file_stat; // On Linux with x86 glibc, this call will fail on large files (>= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. if (MZ_FILE_STAT(pFilename, &file_stat) != 0) return MZ_FALSE; mz_zip_time_to_dos_time(file_stat.st_mtime, pDOS_time, pDOS_date); #endif // #ifdef MINIZ_NO_TIME return MZ_TRUE; } #ifndef MINIZ_NO_TIME static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, time_t modified_time) { struct utimbuf t; t.actime = access_time; t.modtime = modified_time; return !utime(pFilename, &t); } #endif // #ifndef MINIZ_NO_TIME #endif // #ifndef MINIZ_NO_STDIO static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint32 flags) { (void)flags; if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) return MZ_FALSE; if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; if (!pZip->m_pFree) pZip->m_pFree = def_free_func; if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; pZip->m_zip_mode = MZ_ZIP_MODE_READING; pZip->m_archive_size = 0; pZip->m_central_directory_file_ofs = 0; pZip->m_total_files = 0; if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) return MZ_FALSE; memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) { const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); mz_uint8 l = 0, r = 0; pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pE = pL + MZ_MIN(l_len, r_len); while (pL < pE) { if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) break; pL++; pR++; } return (pL == pE) ? (l_len < r_len) : (l < r); } #define MZ_SWAP_UINT32(a, b) do { mz_uint32 t = a; a = b; b = t; } MZ_MACRO_END // Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) { mz_zip_internal_state *pState = pZip->m_pState; const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; const mz_zip_array *pCentral_dir = &pState->m_central_dir; mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); const int size = pZip->m_total_files; int start = (size - 2) >> 1, end; while (start >= 0) { int child, root = start; for ( ; ; ) { if ((child = (root << 1) + 1) >= size) break; child += (((child + 1) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1]))); if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) break; MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; } start--; } end = size - 1; while (end > 0) { int child, root = 0; MZ_SWAP_UINT32(pIndices[end], pIndices[0]); for ( ; ; ) { if ((child = (root << 1) + 1) >= end) break; child += (((child + 1) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1])); if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) break; MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; } end--; } } static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint32 flags) { mz_uint cdir_size, num_this_disk, cdir_disk_index; mz_uint64 cdir_ofs; mz_int64 cur_file_ofs; const mz_uint8 *p; mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return MZ_FALSE; // Find the end of central directory record by scanning the file from the end towards the beginning. cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); for ( ; ; ) { int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) return MZ_FALSE; for (i = n - 4; i >= 0; --i) if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) break; if (i >= 0) { cur_file_ofs += i; break; } if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) return MZ_FALSE; cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); } // Read and verify the end of central directory record. if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return MZ_FALSE; if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) || ((pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS))) return MZ_FALSE; num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) return MZ_FALSE; if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) return MZ_FALSE; cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) return MZ_FALSE; pZip->m_central_directory_file_ofs = cdir_ofs; if (pZip->m_total_files) { mz_uint i, n; // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices. if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) return MZ_FALSE; if (sort_central_dir) { if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) return MZ_FALSE; } if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) return MZ_FALSE; // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported). p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) { mz_uint total_header_size, comp_size, decomp_size, disk_index; if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) return MZ_FALSE; MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); if (sort_central_dir) MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || (comp_size == 0xFFFFFFFF)) return MZ_FALSE; disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); if ((disk_index != num_this_disk) && (disk_index != 1)) return MZ_FALSE; if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) return MZ_FALSE; if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) return MZ_FALSE; n -= total_header_size; p += total_header_size; } } if (sort_central_dir) mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); return MZ_TRUE; } mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags) { if ((!pZip) || (!pZip->m_pRead)) return MZ_FALSE; if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; pZip->m_archive_size = size; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end(pZip); return MZ_FALSE; } return MZ_TRUE; } static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); return s; } mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags) { if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; pZip->m_archive_size = size; pZip->m_pRead = mz_zip_mem_read_func; pZip->m_pIO_opaque = pZip; #ifdef __cplusplus pZip->m_pState->m_pMem = const_cast(pMem); #else pZip->m_pState->m_pMem = (void *)pMem; #endif pZip->m_pState->m_mem_size = size; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end(pZip); return MZ_FALSE; } return MZ_TRUE; } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) return 0; return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); } mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) { mz_uint64 file_size; MZ_FILE *pFile = MZ_FOPEN(pFilename, "rb"); if (!pFile) return MZ_FALSE; if (MZ_FSEEK64(pFile, 0, SEEK_END)) { MZ_FCLOSE(pFile); return MZ_FALSE; } file_size = MZ_FTELL64(pFile); if (!mz_zip_reader_init_internal(pZip, flags)) { MZ_FCLOSE(pFile); return MZ_FALSE; } pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; pZip->m_pState->m_pFile = pFile; pZip->m_archive_size = file_size; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end(pZip); return MZ_FALSE; } return MZ_TRUE; } #endif // #ifndef MINIZ_NO_STDIO mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) { return pZip ? pZip->m_total_files : 0; } static MZ_FORCEINLINE const mz_uint8 *mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index) { if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) return NULL; return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); } mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) { mz_uint m_bit_flag; const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); if (!p) return MZ_FALSE; m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); return (m_bit_flag & 1); } mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) { mz_uint filename_len, external_attr; const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); if (!p) return MZ_FALSE; // First see if the filename ends with a '/' character. filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); if (filename_len) { if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') return MZ_TRUE; } // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. // FIXME: Remove this check? Is it necessary - we already check the filename. external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); if ((external_attr & 0x10) != 0) return MZ_TRUE; return MZ_FALSE; } mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) { mz_uint n; const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); if ((!p) || (!pStat)) return MZ_FALSE; // Unpack the central directory record. pStat->m_file_index = file_index; pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); #ifndef MINIZ_NO_TIME pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); #endif pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); // Copy as much of the filename and comment as possible. n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0'; n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); pStat->m_comment_size = n; memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; return MZ_TRUE; } mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) { mz_uint n; const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); if (!p) { if (filename_buf_size) pFilename[0] = '\0'; return 0; } n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); if (filename_buf_size) { n = MZ_MIN(n, filename_buf_size - 1); memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pFilename[n] = '\0'; } return n + 1; } static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) { mz_uint i; if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) return 0 == memcmp(pA, pB, len); for (i = 0; i < len; ++i) if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) return MZ_FALSE; return MZ_TRUE; } static MZ_FORCEINLINE int mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) { const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); mz_uint8 l = 0, r = 0; pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pE = pL + MZ_MIN(l_len, r_len); while (pL < pE) { if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) break; pL++; pR++; } return (pL == pE) ? (int)(l_len - r_len) : (l - r); } static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename) { mz_zip_internal_state *pState = pZip->m_pState; const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; const mz_zip_array *pCentral_dir = &pState->m_central_dir; mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); const int size = pZip->m_total_files; const mz_uint filename_len = (mz_uint)strlen(pFilename); int l = 0, h = size - 1; while (l <= h) { int m = (l + h) >> 1, file_index = pIndices[m], comp = mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); if (!comp) return file_index; else if (comp < 0) l = m + 1; else h = m - 1; } return -1; } int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) { mz_uint file_index; size_t name_len, comment_len; if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) return -1; if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) return mz_zip_reader_locate_file_binary_search(pZip, pName); name_len = strlen(pName); if (name_len > 0xFFFF) return -1; comment_len = pComment ? strlen(pComment) : 0; if (comment_len > 0xFFFF) return -1; for (file_index = 0; file_index < pZip->m_total_files; file_index++) { const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; if (filename_len < name_len) continue; if (comment_len) { mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); const char *pFile_comment = pFilename + filename_len + file_extra_len; if ((file_comment_len != comment_len) || (!mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags))) continue; } if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) { int ofs = filename_len - 1; do { if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) break; } while (--ofs >= 0); ofs++; pFilename += ofs; filename_len -= ofs; } if ((filename_len == name_len) && (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) return file_index; } return -1; } mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { int status = TINFL_STATUS_DONE; mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; mz_zip_archive_file_stat file_stat; void *pRead_buf; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; tinfl_decompressor inflator; if ((buf_size) && (!pBuf)) return MZ_FALSE; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) if (!file_stat.m_comp_size) return MZ_TRUE; // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). // I'm torn how to handle this case - should it fail instead? if (mz_zip_reader_is_file_a_directory(pZip, file_index)) return MZ_TRUE; // Encryption and patch files are not supported. if (file_stat.m_bit_flag & (1 | 32)) return MZ_FALSE; // This function only supports stored and deflate. if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) return MZ_FALSE; // Ensure supplied output buffer is large enough. needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; if (buf_size < needed_size) return MZ_FALSE; // Read and parse the local directory entry. cur_file_ofs = file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return MZ_FALSE; if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return MZ_FALSE; cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) return MZ_FALSE; if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { // The file is stored or the caller has requested the compressed data. if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) return MZ_FALSE; return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32); } // Decompress the file either directly from memory or from a file input buffer. tinfl_init(&inflator); if (pZip->m_pState->m_pMem) { // Read directly from the archive in memory. pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; read_buf_size = read_buf_avail = file_stat.m_comp_size; comp_remaining = 0; } else if (pUser_read_buf) { // Use a user provided read buffer. if (!user_read_buf_size) return MZ_FALSE; pRead_buf = (mz_uint8 *)pUser_read_buf; read_buf_size = user_read_buf_size; read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } else { // Temporarily allocate a read buffer. read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); #ifdef _MSC_VER if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) #else if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) #endif return MZ_FALSE; if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) return MZ_FALSE; read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } do { size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { status = TINFL_STATUS_FAILED; break; } cur_file_ofs += read_buf_avail; comp_remaining -= read_buf_avail; read_buf_ofs = 0; } in_buf_size = (size_t)read_buf_avail; status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); read_buf_avail -= in_buf_size; read_buf_ofs += in_buf_size; out_buf_ofs += out_buf_size; } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); if (status == TINFL_STATUS_DONE) { // Make sure the entire file was decompressed, and check its CRC. if ((out_buf_ofs != file_stat.m_uncomp_size) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)) status = TINFL_STATUS_FAILED; } if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return status == TINFL_STATUS_DONE; } mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); if (file_index < 0) return MZ_FALSE; return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); } mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) { return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); } mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) { return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); } void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) { mz_uint64 comp_size, uncomp_size, alloc_size; const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); void *pBuf; if (pSize) *pSize = 0; if (!p) return NULL; comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; #ifdef _MSC_VER if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) #else if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) #endif return NULL; if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) return NULL; if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return NULL; } if (pSize) *pSize = (size_t)alloc_size; return pBuf; } void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) { int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); if (file_index < 0) { if (pSize) *pSize = 0; return MZ_FALSE; } return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); } mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) { int status = TINFL_STATUS_DONE; mz_uint file_crc32 = MZ_CRC32_INIT; mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; mz_zip_archive_file_stat file_stat; void *pRead_buf = NULL; void *pWrite_buf = NULL; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) if (!file_stat.m_comp_size) return MZ_TRUE; // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). // I'm torn how to handle this case - should it fail instead? if (mz_zip_reader_is_file_a_directory(pZip, file_index)) return MZ_TRUE; // Encryption and patch files are not supported. if (file_stat.m_bit_flag & (1 | 32)) return MZ_FALSE; // This function only supports stored and deflate. if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) return MZ_FALSE; // Read and parse the local directory entry. cur_file_ofs = file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return MZ_FALSE; if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return MZ_FALSE; cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) return MZ_FALSE; // Decompress the file either directly from memory or from a file input buffer. if (pZip->m_pState->m_pMem) { pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; read_buf_size = read_buf_avail = file_stat.m_comp_size; comp_remaining = 0; } else { read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) return MZ_FALSE; read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { // The file is stored or the caller has requested the compressed data. if (pZip->m_pState->m_pMem) { #ifdef _MSC_VER if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) #else if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) #endif return MZ_FALSE; if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) status = TINFL_STATUS_FAILED; else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); cur_file_ofs += file_stat.m_comp_size; #pragma unused(cur_file_ofs) out_buf_ofs += file_stat.m_comp_size; comp_remaining = 0; #pragma unused(comp_remaining) } else { while (comp_remaining) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { status = TINFL_STATUS_FAILED; break; } if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { status = TINFL_STATUS_FAILED; break; } cur_file_ofs += read_buf_avail; out_buf_ofs += read_buf_avail; comp_remaining -= read_buf_avail; } } } else { tinfl_decompressor inflator; tinfl_init(&inflator); if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) status = TINFL_STATUS_FAILED; else { do { mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { status = TINFL_STATUS_FAILED; break; } cur_file_ofs += read_buf_avail; comp_remaining -= read_buf_avail; read_buf_ofs = 0; } in_buf_size = (size_t)read_buf_avail; status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); read_buf_avail -= in_buf_size; read_buf_ofs += in_buf_size; if (out_buf_size) { if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) { status = TINFL_STATUS_FAILED; break; } file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) { status = TINFL_STATUS_FAILED; break; } } } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); } } if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) { // Make sure the entire file was decompressed, and check its CRC. if ((out_buf_ofs != file_stat.m_uncomp_size) || (file_crc32 != file_stat.m_crc32)) status = TINFL_STATUS_FAILED; } if (!pZip->m_pState->m_pMem) pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); if (pWrite_buf) pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); return status == TINFL_STATUS_DONE; } mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) { int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); if (file_index < 0) return MZ_FALSE; return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) { (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE*)pOpaque); } mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) { mz_bool status; mz_zip_archive_file_stat file_stat; MZ_FILE *pFile; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; pFile = MZ_FOPEN(pDst_filename, "wb"); if (!pFile) return MZ_FALSE; status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); if (MZ_FCLOSE(pFile) == EOF) return MZ_FALSE; #ifndef MINIZ_NO_TIME if (status) mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); #endif return status; } #endif // #ifndef MINIZ_NO_STDIO mz_bool mz_zip_reader_end(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) return MZ_FALSE; if (pZip->m_pState) { mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL; mz_zip_array_clear(pZip, &pState->m_central_dir); mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); #ifndef MINIZ_NO_STDIO if (pState->m_pFile) { MZ_FCLOSE(pState->m_pFile); pState->m_pFile = NULL; } #endif // #ifndef MINIZ_NO_STDIO pZip->m_pFree(pZip->m_pAlloc_opaque, pState); } pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; return MZ_TRUE; } #ifndef MINIZ_NO_STDIO mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) { int file_index = mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags); if (file_index < 0) return MZ_FALSE; return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); } #endif // ------------------- .ZIP archive writing #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); } static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); } #define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) #define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) { if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) return MZ_FALSE; if (pZip->m_file_offset_alignment) { // Ensure user specified file offset alignment is a power of 2. if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) return MZ_FALSE; } if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; if (!pZip->m_pFree) pZip->m_pFree = def_free_func; if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; pZip->m_archive_size = existing_size; pZip->m_central_directory_file_ofs = 0; pZip->m_total_files = 0; if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) return MZ_FALSE; memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); return MZ_TRUE; } static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_zip_internal_state *pState = pZip->m_pState; mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); #ifdef _MSC_VER if ((!n) || ((0, sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) #else if ((!n) || ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) #endif return 0; if (new_size > pState->m_mem_capacity) { void *pNew_block; size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2; if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) return 0; pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity; } memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); pState->m_mem_size = (size_t)new_size; return n; } mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) { pZip->m_pWrite = mz_zip_heap_write_func; pZip->m_pIO_opaque = pZip; if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) return MZ_FALSE; if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) { if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) { mz_zip_writer_end(pZip); return MZ_FALSE; } pZip->m_pState->m_mem_capacity = initial_allocation_size; } return MZ_TRUE; } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) return 0; return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); } mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) { MZ_FILE *pFile; pZip->m_pWrite = mz_zip_file_write_func; pZip->m_pIO_opaque = pZip; if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) return MZ_FALSE; if (NULL == (pFile = MZ_FOPEN(pFilename, "wb"))) { mz_zip_writer_end(pZip); return MZ_FALSE; } pZip->m_pState->m_pFile = pFile; if (size_to_reserve_at_beginning) { mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_OBJ(buf); do { size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) { mz_zip_writer_end(pZip); return MZ_FALSE; } cur_ofs += n; size_to_reserve_at_beginning -= n; } while (size_to_reserve_at_beginning); } return MZ_TRUE; } #endif // #ifndef MINIZ_NO_STDIO mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) { mz_zip_internal_state *pState; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) return MZ_FALSE; // No sense in trying to write to an archive that's already at the support max size if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) return MZ_FALSE; pState = pZip->m_pState; if (pState->m_pFile) { #ifdef MINIZ_NO_STDIO pFilename; return MZ_FALSE; #else // Archive is being read from stdio - try to reopen as writable. if (pZip->m_pIO_opaque != pZip) return MZ_FALSE; if (!pFilename) return MZ_FALSE; pZip->m_pWrite = mz_zip_file_write_func; if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) { // The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. mz_zip_reader_end(pZip); return MZ_FALSE; } #endif // #ifdef MINIZ_NO_STDIO } else if (pState->m_pMem) { // Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. if (pZip->m_pIO_opaque != pZip) return MZ_FALSE; pState->m_mem_capacity = pState->m_mem_size; pZip->m_pWrite = mz_zip_heap_write_func; } // Archive is being read via a user provided read function - make sure the user has specified a write function too. else if (!pZip->m_pWrite) return MZ_FALSE; // Start writing new files at the archive's current central directory location. pZip->m_archive_size = pZip->m_central_directory_file_ofs; pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; pZip->m_central_directory_file_ofs = 0; return MZ_TRUE; } mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) { return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); } typedef struct { mz_zip_archive *m_pZip; mz_uint64 m_cur_archive_file_ofs; mz_uint64 m_comp_size; } mz_zip_writer_add_state; static mz_bool mz_zip_writer_add_put_buf_callback(const void* pBuf, int len, void *pUser) { mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) return MZ_FALSE; pState->m_cur_archive_file_ofs += len; pState->m_comp_size += len; return MZ_TRUE; } static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) { (void)pZip; memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); return MZ_TRUE; } static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) { (void)pZip; memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); return MZ_TRUE; } static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) { mz_zip_internal_state *pState = pZip->m_pState; mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; size_t orig_central_dir_size = pState->m_central_dir.m_size; mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; // No zip64 support yet if ((local_header_ofs > 0xFFFFFFFF) || (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + comment_size) > 0xFFFFFFFF)) return MZ_FALSE; if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) return MZ_FALSE; if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) { // Try to push the central directory array back into its original state. mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) { // Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. if (*pArchive_name == '/') return MZ_FALSE; while (*pArchive_name) { if ((*pArchive_name == '\\') || (*pArchive_name == ':')) return MZ_FALSE; pArchive_name++; } return MZ_TRUE; } static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) { mz_uint32 n; if (!pZip->m_file_offset_alignment) return 0; n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); return (pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1); } static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) { char buf[4096]; memset(buf, 0, MZ_MIN(sizeof(buf), n)); while (n) { mz_uint32 s = MZ_MIN(sizeof(buf), n); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) return MZ_FALSE; cur_file_ofs += s; n -= s; } return MZ_TRUE; } mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) { mz_uint16 method = 0, dos_time = 0, dos_date = 0; mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; size_t archive_name_size; mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; tdefl_compressor *pComp = NULL; mz_bool store_data_uncompressed; mz_zip_internal_state *pState; if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; level = level_and_flags & 0xF; store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION)) return MZ_FALSE; pState = pZip->m_pState; if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) return MZ_FALSE; // No zip64 support yet if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) return MZ_FALSE; if (!mz_zip_writer_validate_archive_name(pArchive_name)) return MZ_FALSE; #ifndef MINIZ_NO_TIME { time_t cur_time; time(&cur_time); mz_zip_time_to_dos_time(cur_time, &dos_time, &dos_date); } #endif // #ifndef MINIZ_NO_TIME archive_name_size = strlen(pArchive_name); if (archive_name_size > 0xFFFF) return MZ_FALSE; num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); // no zip64 support yet if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) return MZ_FALSE; if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) { // Set DOS Subdirectory attribute bit. ext_attributes |= 0x10; // Subdirectories cannot contain data. if ((buf_size) || (uncomp_size)) return MZ_FALSE; } // Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size)) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) return MZ_FALSE; if ((!store_data_uncompressed) && (buf_size)) { if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) return MZ_FALSE; } if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return MZ_FALSE; } local_dir_header_ofs += num_alignment_padding_bytes; if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); MZ_CLEAR_OBJ(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return MZ_FALSE; } cur_archive_file_ofs += archive_name_size; if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, buf_size); uncomp_size = buf_size; if (uncomp_size <= 3) { level = 0; store_data_uncompressed = MZ_TRUE; } } if (store_data_uncompressed) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return MZ_FALSE; } cur_archive_file_ofs += buf_size; comp_size = buf_size; if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) method = MZ_DEFLATED; } else if (buf_size) { mz_zip_writer_add_state state; state.m_pZip = pZip; state.m_cur_archive_file_ofs = cur_archive_file_ofs; state.m_comp_size = 0; if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return MZ_FALSE; } comp_size = state.m_comp_size; cur_archive_file_ofs = state.m_cur_archive_file_ofs; method = MZ_DEFLATED; } pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); pComp = NULL; // no zip64 support yet if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) return MZ_FALSE; if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) return MZ_FALSE; if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return MZ_FALSE; if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) return MZ_FALSE; pZip->m_total_files++; pZip->m_archive_size = cur_archive_file_ofs; return MZ_TRUE; } #ifndef MINIZ_NO_STDIO mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) { mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; size_t archive_name_size; mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; MZ_FILE *pSrc_file = NULL; if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; level = level_and_flags & 0xF; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) return MZ_FALSE; if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) return MZ_FALSE; if (!mz_zip_writer_validate_archive_name(pArchive_name)) return MZ_FALSE; archive_name_size = strlen(pArchive_name); if (archive_name_size > 0xFFFF) return MZ_FALSE; num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); // no zip64 support yet if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) return MZ_FALSE; if (!mz_zip_get_file_modified_time(pSrc_filename, &dos_time, &dos_date)) return MZ_FALSE; pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); if (!pSrc_file) return MZ_FALSE; MZ_FSEEK64(pSrc_file, 0, SEEK_END); uncomp_size = MZ_FTELL64(pSrc_file); MZ_FSEEK64(pSrc_file, 0, SEEK_SET); if (uncomp_size > 0xFFFFFFFF) { // No zip64 support yet MZ_FCLOSE(pSrc_file); return MZ_FALSE; } if (uncomp_size <= 3) level = 0; if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) { MZ_FCLOSE(pSrc_file); return MZ_FALSE; } local_dir_header_ofs += num_alignment_padding_bytes; if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); MZ_CLEAR_OBJ(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { MZ_FCLOSE(pSrc_file); return MZ_FALSE; } cur_archive_file_ofs += archive_name_size; if (uncomp_size) { mz_uint64 uncomp_remaining = uncomp_size; void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); if (!pRead_buf) { MZ_FCLOSE(pSrc_file); return MZ_FALSE; } if (!level) { while (uncomp_remaining) { mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); MZ_FCLOSE(pSrc_file); return MZ_FALSE; } uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); uncomp_remaining -= n; cur_archive_file_ofs += n; } comp_size = uncomp_size; } else { mz_bool result = MZ_FALSE; mz_zip_writer_add_state state; tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); if (!pComp) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); MZ_FCLOSE(pSrc_file); return MZ_FALSE; } state.m_pZip = pZip; state.m_cur_archive_file_ofs = cur_archive_file_ofs; state.m_comp_size = 0; if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); MZ_FCLOSE(pSrc_file); return MZ_FALSE; } for ( ; ; ) { size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE); tdefl_status status; if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) break; uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); uncomp_remaining -= in_buf_size; status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH); if (status == TDEFL_STATUS_DONE) { result = MZ_TRUE; break; } else if (status != TDEFL_STATUS_OKAY) break; } pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); if (!result) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); MZ_FCLOSE(pSrc_file); return MZ_FALSE; } comp_size = state.m_comp_size; cur_archive_file_ofs = state.m_cur_archive_file_ofs; method = MZ_DEFLATED; } pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); } MZ_FCLOSE(pSrc_file); pSrc_file = NULL; // no zip64 support yet if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) return MZ_FALSE; if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) return MZ_FALSE; if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return MZ_FALSE; if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) return MZ_FALSE; pZip->m_total_files++; pZip->m_archive_size = cur_archive_file_ofs; return MZ_TRUE; } #endif // #ifndef MINIZ_NO_STDIO mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index) { mz_uint n, bit_flags, num_alignment_padding_bytes; mz_uint64 comp_bytes_remaining, local_dir_header_ofs; mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; size_t orig_central_dir_size; mz_zip_internal_state *pState; void *pBuf; const mz_uint8 *pSrc_central_header; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) return MZ_FALSE; if (NULL == (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index))) return MZ_FALSE; pState = pZip->m_pState; num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); // no zip64 support yet if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) return MZ_FALSE; cur_src_file_ofs = MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS); cur_dst_file_ofs = pZip->m_archive_size; if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return MZ_FALSE; if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return MZ_FALSE; cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) return MZ_FALSE; cur_dst_file_ofs += num_alignment_padding_bytes; local_dir_header_ofs = cur_dst_file_ofs; if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return MZ_FALSE; cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); comp_bytes_remaining = n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(sizeof(mz_uint32) * 4, MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining))))) return MZ_FALSE; while (comp_bytes_remaining) { n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining); if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return MZ_FALSE; } cur_src_file_ofs += n; if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return MZ_FALSE; } cur_dst_file_ofs += n; comp_bytes_remaining -= n; } bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); if (bit_flags & 8) { // Copy data descriptor if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return MZ_FALSE; } n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return MZ_FALSE; } cur_src_file_ofs += n; #pragma unused(cur_src_file_ofs) cur_dst_file_ofs += n; } pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); // no zip64 support yet if (cur_dst_file_ofs > 0xFFFFFFFF) return MZ_FALSE; orig_central_dir_size = pState->m_central_dir.m_size; memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) return MZ_FALSE; n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n)) { mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return MZ_FALSE; } if (pState->m_central_dir.m_size > 0xFFFFFFFF) return MZ_FALSE; n = (mz_uint32)orig_central_dir_size; if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) { mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return MZ_FALSE; } pZip->m_total_files++; pZip->m_archive_size = cur_dst_file_ofs; return MZ_TRUE; } mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) { mz_zip_internal_state *pState; mz_uint64 central_dir_ofs, central_dir_size; mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE]; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) return MZ_FALSE; pState = pZip->m_pState; // no zip64 support yet if ((pZip->m_total_files > 0xFFFF) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) return MZ_FALSE; central_dir_ofs = 0; central_dir_size = 0; if (pZip->m_total_files) { // Write central directory central_dir_ofs = pZip->m_archive_size; central_dir_size = pState->m_central_dir.m_size; pZip->m_central_directory_file_ofs = central_dir_ofs; if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) return MZ_FALSE; pZip->m_archive_size += central_dir_size; } // Write end of central directory record MZ_CLEAR_OBJ(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs); if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, sizeof(hdr)) != sizeof(hdr)) return MZ_FALSE; #ifndef MINIZ_NO_STDIO if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) return MZ_FALSE; #endif // #ifndef MINIZ_NO_STDIO pZip->m_archive_size += sizeof(hdr); pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; return MZ_TRUE; } mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize) { if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize)) return MZ_FALSE; if (pZip->m_pWrite != mz_zip_heap_write_func) return MZ_FALSE; if (!mz_zip_writer_finalize_archive(pZip)) return MZ_FALSE; *pBuf = pZip->m_pState->m_pMem; *pSize = pZip->m_pState->m_mem_size; pZip->m_pState->m_pMem = NULL; pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; return MZ_TRUE; } mz_bool mz_zip_writer_end(mz_zip_archive *pZip) { mz_zip_internal_state *pState; mz_bool status = MZ_TRUE; if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) return MZ_FALSE; pState = pZip->m_pState; pZip->m_pState = NULL; mz_zip_array_clear(pZip, &pState->m_central_dir); mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); #ifndef MINIZ_NO_STDIO if (pState->m_pFile) { MZ_FCLOSE(pState->m_pFile); pState->m_pFile = NULL; } #endif // #ifndef MINIZ_NO_STDIO if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); pState->m_pMem = NULL; } pZip->m_pFree(pZip->m_pAlloc_opaque, pState); pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; return status; } #ifndef MINIZ_NO_STDIO mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) { mz_bool status, created_new_archive = MZ_FALSE; mz_zip_archive zip_archive; struct MZ_FILE_STAT_STRUCT file_stat; MZ_CLEAR_OBJ(zip_archive); if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) return MZ_FALSE; if (!mz_zip_writer_validate_archive_name(pArchive_name)) return MZ_FALSE; if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) { // Create a new archive. if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0)) return MZ_FALSE; created_new_archive = MZ_TRUE; } else { // Append to an existing archive. if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) return MZ_FALSE; if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename)) { mz_zip_reader_end(&zip_archive); return MZ_FALSE; } } status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); // Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) if (!mz_zip_writer_finalize_archive(&zip_archive)) status = MZ_FALSE; if (!mz_zip_writer_end(&zip_archive)) status = MZ_FALSE; if ((!status) && (created_new_archive)) { // It's a new archive and something went wrong, so just delete it. int ignoredStatus = MZ_DELETE_FILE(pZip_filename); (void)ignoredStatus; } return status; } void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) { int file_index; mz_zip_archive zip_archive; void *p = NULL; if (pSize) *pSize = 0; if ((!pZip_filename) || (!pArchive_name)) return NULL; MZ_CLEAR_OBJ(zip_archive); if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) return NULL; if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, flags)) >= 0) p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); mz_zip_reader_end(&zip_archive); return p; } #endif // #ifndef MINIZ_NO_STDIO #endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS #endif // #ifndef MINIZ_NO_ARCHIVE_APIS #ifdef __cplusplus } #endif #endif // MINIZ_HEADER_FILE_ONLY /* This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. 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 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. For more information, please refer to */ dcm2niix-1.0.20211006/console/nifti1.h000066400000000000000000002077001412761503300167760ustar00rootroot00000000000000/** \file nifti1.h \brief Official definition of the nifti1 header. Written by Bob Cox, SSCC, NIMH. HISTORY: 29 Nov 2007 [rickr] - added DT_RGBA32 and NIFTI_TYPE_RGBA32 - added NIFTI_INTENT codes: TIME_SERIES, NODE_INDEX, RGB_VECTOR, RGBA_VECTOR, SHAPE 08 Mar 2019 [PT,DRG] - Updated to include [qs]form_code = 5 */ #ifndef _NIFTI_HEADER_ #define _NIFTI_HEADER_ /***************************************************************************** ** This file defines the "NIFTI-1" header format. ** ** It is derived from 2 meetings at the NIH (31 Mar 2003 and ** ** 02 Sep 2003) of the Data Format Working Group (DFWG), ** ** chartered by the NIfTI (Neuroimaging Informatics Technology ** ** Initiative) at the National Institutes of Health (NIH). ** **--------------------------------------------------------------** ** Neither the National Institutes of Health (NIH), the DFWG, ** ** nor any of the members or employees of these institutions ** ** imply any warranty of usefulness of this material for any ** ** purpose, and do not assume any liability for damages, ** ** incidental or otherwise, caused by any use of this document. ** ** If these conditions are not acceptable, do not use this! ** **--------------------------------------------------------------** ** Author: Robert W Cox (NIMH, Bethesda) ** ** Advisors: John Ashburner (FIL, London), ** ** Stephen Smith (FMRIB, Oxford), ** ** Mark Jenkinson (FMRIB, Oxford) ** ******************************************************************************/ /*---------------------------------------------------------------------------*/ /* Note that the ANALYZE 7.5 file header (dbh.h) is (c) Copyright 1986-1995 Biomedical Imaging Resource Mayo Foundation Incorporation of components of dbh.h are by permission of the Mayo Foundation. Changes from the ANALYZE 7.5 file header in this file are released to the public domain, including the functional comments and any amusing asides. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /*! INTRODUCTION TO NIFTI-1: ------------------------ The twin (and somewhat conflicting) goals of this modified ANALYZE 7.5 format are: (a) To add information to the header that will be useful for functional neuroimaging data analysis and display. These additions include: - More basic data types. - Two affine transformations to specify voxel coordinates. - "Intent" codes and parameters to describe the meaning of the data. - Affine scaling of the stored data values to their "true" values. - Optional storage of the header and image data in one file (.nii). (b) To maintain compatibility with non-NIFTI-aware ANALYZE 7.5 compatible software (i.e., such a program should be able to do something useful with a NIFTI-1 dataset -- at least, with one stored in a traditional .img/.hdr file pair). Most of the unused fields in the ANALYZE 7.5 header have been taken, and some of the lesser-used fields have been co-opted for other purposes. Notably, most of the data_history substructure has been co-opted for other purposes, since the ANALYZE 7.5 format describes this substructure as "not required". NIFTI-1 FLAG (MAGIC STRINGS): ---------------------------- To flag such a struct as being conformant to the NIFTI-1 spec, the last 4 bytes of the header must be either the C String "ni1" or "n+1"; in hexadecimal, the 4 bytes 6E 69 31 00 or 6E 2B 31 00 (in any future version of this format, the '1' will be upgraded to '2', etc.). Normally, such a "magic number" or flag goes at the start of the file, but trying to avoid clobbering widely-used ANALYZE 7.5 fields led to putting this marker last. However, recall that "the last shall be first" (Matthew 20:16). If a NIFTI-aware program reads a header file that is NOT marked with a NIFTI magic string, then it should treat the header as an ANALYZE 7.5 structure. NIFTI-1 FILE STORAGE: -------------------- "ni1" means that the image data is stored in the ".img" file corresponding to the header file (starting at file offset 0). "n+1" means that the image data is stored in the same file as the header information. We recommend that the combined header+data filename suffix be ".nii". When the dataset is stored in one file, the first byte of image data is stored at byte location (int)vox_offset in this combined file. The minimum allowed value of vox_offset is 352; for compatibility with some software, vox_offset should be an integral multiple of 16. GRACE UNDER FIRE: ---------------- Most NIFTI-aware programs will only be able to handle a subset of the full range of datasets possible with this format. All NIFTI-aware programs should take care to check if an input dataset conforms to the program's needs and expectations (e.g., check datatype, intent_code, etc.). If the input dataset can't be handled by the program, the program should fail gracefully (e.g., print a useful warning; not crash). SAMPLE CODES: ------------ The associated files nifti1_io.h and nifti1_io.c provide a sample implementation in C of a set of functions to read, write, and manipulate NIFTI-1 files. The file nifti1_test.c is a sample program that uses the nifti1_io.c functions. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* HEADER STRUCT DECLARATION: ------------------------- In the comments below for each field, only NIFTI-1 specific requirements or changes from the ANALYZE 7.5 format are described. For convenience, the 348 byte header is described as a single struct, rather than as the ANALYZE 7.5 group of 3 substructs. Further comments about the interpretation of various elements of this header are after the data type definition itself. Fields that are marked as ++UNUSED++ have no particular interpretation in this standard. (Also see the UNUSED FIELDS comment section, far below.) The presumption below is that the various C types have particular sizes: sizeof(int) = sizeof(float) = 4 ; sizeof(short) = 2 -----------------------------------------------------------------------------*/ /*=================*/ #ifdef __cplusplus extern "C" { #endif /*=================*/ /*! \struct nifti_1_header \brief Data structure defining the fields in the nifti1 header. This binary header should be found at the beginning of a valid NIFTI-1 header file. */ /*************************/ /************************/ struct nifti_1_header { /* NIFTI-1 usage */ /* ANALYZE 7.5 field(s) */ /*************************/ /************************/ /*--- was header_key substruct ---*/ int sizeof_hdr; /*!< MUST be 348 */ /* int sizeof_hdr; */ char data_type[10]; /*!< ++UNUSED++ */ /* char data_type[10]; */ char db_name[18]; /*!< ++UNUSED++ */ /* char db_name[18]; */ int extents; /*!< ++UNUSED++ */ /* int extents; */ short session_error; /*!< ++UNUSED++ */ /* short session_error; */ char regular; /*!< ++UNUSED++ */ /* char regular; */ char dim_info; /*!< MRI slice ordering. */ /* char hkey_un0; */ /*--- was image_dimension substruct ---*/ short dim[8]; /*!< Data array dimensions.*/ /* short dim[8]; */ float intent_p1 ; /*!< 1st intent parameter. */ /* short unused8; */ /* short unused9; */ float intent_p2 ; /*!< 2nd intent parameter. */ /* short unused10; */ /* short unused11; */ float intent_p3 ; /*!< 3rd intent parameter. */ /* short unused12; */ /* short unused13; */ short intent_code ; /*!< NIFTI_INTENT_* code. */ /* short unused14; */ short datatype; /*!< Defines data type! */ /* short datatype; */ short bitpix; /*!< Number bits/voxel. */ /* short bitpix; */ short slice_start; /*!< First slice index. */ /* short dim_un0; */ float pixdim[8]; /*!< Grid spacings. */ /* float pixdim[8]; */ float vox_offset; /*!< Offset into .nii file */ /* float vox_offset; */ float scl_slope ; /*!< Data scaling: slope. */ /* float funused1; */ float scl_inter ; /*!< Data scaling: offset. */ /* float funused2; */ short slice_end; /*!< Last slice index. */ /* float funused3; */ char slice_code ; /*!< Slice timing order. */ char xyzt_units ; /*!< Units of pixdim[1..4] */ float cal_max; /*!< Max display intensity */ /* float cal_max; */ float cal_min; /*!< Min display intensity */ /* float cal_min; */ float slice_duration;/*!< Time for 1 slice. */ /* float compressed; */ float toffset; /*!< Time axis shift. */ /* float verified; */ int glmax; /*!< ++UNUSED++ */ /* int glmax; */ int glmin; /*!< ++UNUSED++ */ /* int glmin; */ /*--- was data_history substruct ---*/ char descrip[80]; /*!< any text you like. */ /* char descrip[80]; */ char aux_file[24]; /*!< auxiliary filename. */ /* char aux_file[24]; */ short qform_code ; /*!< NIFTI_XFORM_* code. */ /*-- all ANALYZE 7.5 ---*/ short sform_code ; /*!< NIFTI_XFORM_* code. */ /* fields below here */ /* are replaced */ float quatern_b ; /*!< Quaternion b param. */ float quatern_c ; /*!< Quaternion c param. */ float quatern_d ; /*!< Quaternion d param. */ float qoffset_x ; /*!< Quaternion x shift. */ float qoffset_y ; /*!< Quaternion y shift. */ float qoffset_z ; /*!< Quaternion z shift. */ float srow_x[4] ; /*!< 1st row affine transform. */ float srow_y[4] ; /*!< 2nd row affine transform. */ float srow_z[4] ; /*!< 3rd row affine transform. */ char intent_name[16];/*!< 'name' or meaning of data. */ char magic[4] ; /*!< MUST be "ni1\0" or "n+1\0". */ } ; /**** 348 bytes total ****/ typedef struct nifti_1_header nifti_1_header ; /*---------------------------------------------------------------------------*/ /* HEADER EXTENSIONS: ----------------- After the end of the 348 byte header (e.g., after the magic field), the next 4 bytes are a char array field named "extension". By default, all 4 bytes of this array should be set to zero. In a .nii file, these 4 bytes will always be present, since the earliest start point for the image data is byte #352. In a separate .hdr file, these bytes may or may not be present. If not present (i.e., if the length of the .hdr file is 348 bytes), then a NIfTI-1 compliant program should use the default value of extension={0,0,0,0}. The first byte (extension[0]) is the only value of this array that is specified at present. The other 3 bytes are reserved for future use. If extension[0] is nonzero, it indicates that extended header information is present in the bytes following the extension array. In a .nii file, this extended header data is before the image data (and vox_offset must be set correctly to allow for this). In a .hdr file, this extended data follows extension and proceeds (potentially) to the end of the file. The format of extended header data is weakly specified. Each extension must be an integer multiple of 16 bytes long. The first 8 bytes of each extension comprise 2 integers: int esize , ecode ; These values may need to be byte-swapped, as indicated by dim[0] for the rest of the header. * esize is the number of bytes that form the extended header data + esize must be a positive integral multiple of 16 + this length includes the 8 bytes of esize and ecode themselves * ecode is a non-negative integer that indicates the format of the extended header data that follows + different ecode values are assigned to different developer groups + at present, the "registered" values for code are = 0 = unknown private format (not recommended!) = 2 = DICOM format (i.e., attribute tags and values) = 4 = AFNI group (i.e., ASCII XML-ish elements) In the interests of interoperability (a primary rationale for NIfTI), groups developing software that uses this extension mechanism are encouraged to document and publicize the format of their extensions. To this end, the NIfTI DFWG will assign even numbered codes upon request to groups submitting at least rudimentary documentation for the format of their extension; at present, the contact is mailto:rwcox@nih.gov. The assigned codes and documentation will be posted on the NIfTI website. All odd values of ecode (and 0) will remain unassigned; at least, until the even ones are used up, when we get to 2,147,483,646. Note that the other contents of the extended header data section are totally unspecified by the NIfTI-1 standard. In particular, if binary data is stored in such a section, its byte order is not necessarily the same as that given by examining dim[0]; it is incumbent on the programs dealing with such data to determine the byte order of binary extended header data. Multiple extended header sections are allowed, each starting with an esize,ecode value pair. The first esize value, as described above, is at bytes #352-355 in the .hdr or .nii file (files start at byte #0). If this value is positive, then the second (esize2) will be found starting at byte #352+esize1 , the third (esize3) at byte #352+esize1+esize2, et cetera. Of course, in a .nii file, the value of vox_offset must be compatible with these extensions. If a malformed file indicates that an extended header data section would run past vox_offset, then the entire extended header section should be ignored. In a .hdr file, if an extended header data section would run past the end-of-file, that extended header data should also be ignored. With the above scheme, a program can successively examine the esize and ecode values, and skip over each extended header section if the program doesn't know how to interpret the data within. Of course, any program can simply ignore all extended header sections simply by jumping straight to the image data using vox_offset. -----------------------------------------------------------------------------*/ /*! \struct nifti1_extender \brief This structure represents a 4-byte string that should follow the binary nifti_1_header data in a NIFTI-1 header file. If the char values are {1,0,0,0}, the file is expected to contain extensions, values of {0,0,0,0} imply the file does not contain extensions. Other sequences of values are not currently defined. */ struct nifti1_extender { char extension[4] ; } ; typedef struct nifti1_extender nifti1_extender ; /*! \struct nifti1_extension \brief Data structure defining the fields of a header extension. */ struct nifti1_extension { int esize ; /*!< size of extension, in bytes (must be multiple of 16) */ int ecode ; /*!< extension code, one of the NIFTI_ECODE_ values */ char * edata ; /*!< raw data, with no byte swapping (length is esize-8) */ } ; typedef struct nifti1_extension nifti1_extension ; /*---------------------------------------------------------------------------*/ /* DATA DIMENSIONALITY (as in ANALYZE 7.5): --------------------------------------- dim[0] = number of dimensions; - if dim[0] is outside range 1..7, then the header information needs to be byte swapped appropriately - ANALYZE supports dim[0] up to 7, but NIFTI-1 reserves dimensions 1,2,3 for space (x,y,z), 4 for time (t), and 5,6,7 for anything else needed. dim[i] = length of dimension #i, for i=1..dim[0] (must be positive) - also see the discussion of intent_code, far below pixdim[i] = voxel width along dimension #i, i=1..dim[0] (positive) - cf. ORIENTATION section below for use of pixdim[0] - the units of pixdim can be specified with the xyzt_units field (also described far below). Number of bits per voxel value is in bitpix, which MUST correspond with the datatype field. The total number of bytes in the image data is dim[1] * ... * dim[dim[0]] * bitpix / 8 In NIFTI-1 files, dimensions 1,2,3 are for space, dimension 4 is for time, and dimension 5 is for storing multiple values at each spatiotemporal voxel. Some examples: - A typical whole-brain FMRI experiment's time series: - dim[0] = 4 - dim[1] = 64 pixdim[1] = 3.75 xyzt_units = NIFTI_UNITS_MM - dim[2] = 64 pixdim[2] = 3.75 | NIFTI_UNITS_SEC - dim[3] = 20 pixdim[3] = 5.0 - dim[4] = 120 pixdim[4] = 2.0 - A typical T1-weighted anatomical volume: - dim[0] = 3 - dim[1] = 256 pixdim[1] = 1.0 xyzt_units = NIFTI_UNITS_MM - dim[2] = 256 pixdim[2] = 1.0 - dim[3] = 128 pixdim[3] = 1.1 - A single slice EPI time series: - dim[0] = 4 - dim[1] = 64 pixdim[1] = 3.75 xyzt_units = NIFTI_UNITS_MM - dim[2] = 64 pixdim[2] = 3.75 | NIFTI_UNITS_SEC - dim[3] = 1 pixdim[3] = 5.0 - dim[4] = 1200 pixdim[4] = 0.2 - A 3-vector stored at each point in a 3D volume: - dim[0] = 5 - dim[1] = 256 pixdim[1] = 1.0 xyzt_units = NIFTI_UNITS_MM - dim[2] = 256 pixdim[2] = 1.0 - dim[3] = 128 pixdim[3] = 1.1 - dim[4] = 1 pixdim[4] = 0.0 - dim[5] = 3 intent_code = NIFTI_INTENT_VECTOR - A single time series with a 3x3 matrix at each point: - dim[0] = 5 - dim[1] = 1 xyzt_units = NIFTI_UNITS_SEC - dim[2] = 1 - dim[3] = 1 - dim[4] = 1200 pixdim[4] = 0.2 - dim[5] = 9 intent_code = NIFTI_INTENT_GENMATRIX - intent_p1 = intent_p2 = 3.0 (indicates matrix dimensions) -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* DATA STORAGE: ------------ If the magic field is "n+1", then the voxel data is stored in the same file as the header. In this case, the voxel data starts at offset (int)vox_offset into the header file. Thus, vox_offset=352.0 means that the data starts immediately after the NIFTI-1 header. If vox_offset is greater than 352, the NIFTI-1 format does not say much about the contents of the dataset file between the end of the header and the start of the data. FILES: ----- If the magic field is "ni1", then the voxel data is stored in the associated ".img" file, starting at offset 0 (i.e., vox_offset is not used in this case, and should be set to 0.0). When storing NIFTI-1 datasets in pairs of files, it is customary to name the files in the pattern "name.hdr" and "name.img", as in ANALYZE 7.5. When storing in a single file ("n+1"), the file name should be in the form "name.nii" (the ".nft" and ".nif" suffixes are already taken; cf. http://www.icdatamaster.com/n.html ). BYTE ORDERING: ------------- The byte order of the data arrays is presumed to be the same as the byte order of the header (which is determined by examining dim[0]). Floating point types are presumed to be stored in IEEE-754 format. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* DETAILS ABOUT vox_offset: ------------------------ In a .nii file, the vox_offset field value is interpreted as the start location of the image data bytes in that file. In a .hdr/.img file pair, the vox_offset field value is the start location of the image data bytes in the .img file. * If vox_offset is less than 352 in a .nii file, it is equivalent to 352 (i.e., image data never starts before byte #352 in a .nii file). * The default value for vox_offset in a .nii file is 352. * In a .hdr file, the default value for vox_offset is 0. * vox_offset should be an integer multiple of 16; otherwise, some programs may not work properly (e.g., SPM). This is to allow memory-mapped input to be properly byte-aligned. Note that since vox_offset is an IEEE-754 32 bit float (for compatibility with the ANALYZE-7.5 format), it effectively has a 24 bit mantissa. All integers from 0 to 2^24 can be represented exactly in this format, but not all larger integers are exactly storable as IEEE-754 32 bit floats. However, unless you plan to have vox_offset be potentially larger than 16 MB, this should not be an issue. (Actually, any integral multiple of 16 up to 2^27 can be represented exactly in this format, which allows for up to 128 MB of random information before the image data. If that isn't enough, then perhaps this format isn't right for you.) In a .img file (i.e., image data stored separately from the NIfTI-1 header), data bytes between #0 and #vox_offset-1 (inclusive) are completely undefined and unregulated by the NIfTI-1 standard. One potential use of having vox_offset > 0 in the .hdr/.img file pair storage method is to make the .img file be a copy of (or link to) a pre-existing image file in some other format, such as DICOM; then vox_offset would be set to the offset of the image data in this file. (It may not be possible to follow the "multiple-of-16 rule" with an arbitrary external file; using the NIfTI-1 format in such a case may lead to a file that is incompatible with software that relies on vox_offset being a multiple of 16.) In a .nii file, data bytes between #348 and #vox_offset-1 (inclusive) may be used to store user-defined extra information; similarly, in a .hdr file, any data bytes after byte #347 are available for user-defined extra information. The (very weak) regulation of this extra header data is described elsewhere. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* DATA SCALING: ------------ If the scl_slope field is nonzero, then each voxel value in the dataset should be scaled as y = scl_slope * x + scl_inter where x = voxel value stored y = "true" voxel value Normally, we would expect this scaling to be used to store "true" floating values in a smaller integer datatype, but that is not required. That is, it is legal to use scaling even if the datatype is a float type (crazy, perhaps, but legal). - However, the scaling is to be ignored if datatype is DT_RGB24. - If datatype is a complex type, then the scaling is to be applied to both the real and imaginary parts. The cal_min and cal_max fields (if nonzero) are used for mapping (possibly scaled) dataset values to display colors: - Minimum display intensity (black) corresponds to dataset value cal_min. - Maximum display intensity (white) corresponds to dataset value cal_max. - Dataset values below cal_min should display as black also, and values above cal_max as white. - Colors "black" and "white", of course, may refer to any scalar display scheme (e.g., a color lookup table specified via aux_file). - cal_min and cal_max only make sense when applied to scalar-valued datasets (i.e., dim[0] < 5 or dim[5] = 1). -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* TYPE OF DATA (acceptable values for datatype field): --------------------------------------------------- Values of datatype smaller than 256 are ANALYZE 7.5 compatible. Larger values are NIFTI-1 additions. These are all multiples of 256, so that no bits below position 8 are set in datatype. But there is no need to use only powers-of-2, as the original ANALYZE 7.5 datatype codes do. The additional codes are intended to include a complete list of basic scalar types, including signed and unsigned integers from 8 to 64 bits, floats from 32 to 128 bits, and complex (float pairs) from 64 to 256 bits. Note that most programs will support only a few of these datatypes! A NIFTI-1 program should fail gracefully (e.g., print a warning message) when it encounters a dataset with a type it doesn't like. -----------------------------------------------------------------------------*/ #undef DT_UNKNOWN /* defined in dirent.h on some Unix systems */ /*! \defgroup NIFTI1_DATATYPES \brief nifti1 datatype codes @{ */ /*--- the original ANALYZE 7.5 type codes ---*/ #define DT_NONE 0 #define DT_UNKNOWN 0 /* what it says, dude */ #define DT_BINARY 1 /* binary (1 bit/voxel) */ #define DT_UNSIGNED_CHAR 2 /* unsigned char (8 bits/voxel) */ #define DT_SIGNED_SHORT 4 /* signed short (16 bits/voxel) */ #define DT_SIGNED_INT 8 /* signed int (32 bits/voxel) */ #define DT_FLOAT 16 /* float (32 bits/voxel) */ #define DT_COMPLEX 32 /* complex (64 bits/voxel) */ #define DT_DOUBLE 64 /* double (64 bits/voxel) */ #define DT_RGB 128 /* RGB triple (24 bits/voxel) */ #define DT_ALL 255 /* not very useful (?) */ /*----- another set of names for the same ---*/ #define DT_UINT8 2 #define DT_INT16 4 #define DT_INT32 8 #define DT_FLOAT32 16 #define DT_COMPLEX64 32 #define DT_FLOAT64 64 #define DT_RGB24 128 /*------------------- new codes for NIFTI ---*/ #define DT_INT8 256 /* signed char (8 bits) */ #define DT_UINT16 512 /* unsigned short (16 bits) */ #define DT_UINT32 768 /* unsigned int (32 bits) */ #define DT_INT64 1024 /* long long (64 bits) */ #define DT_UINT64 1280 /* unsigned long long (64 bits) */ #define DT_FLOAT128 1536 /* long double (128 bits) */ #define DT_COMPLEX128 1792 /* double pair (128 bits) */ #define DT_COMPLEX256 2048 /* long double pair (256 bits) */ #define DT_RGBA32 2304 /* 4 byte RGBA (32 bits/voxel) */ /* @} */ /*------- aliases for all the above codes ---*/ /*! \defgroup NIFTI1_DATATYPE_ALIASES \brief aliases for the nifti1 datatype codes @{ */ /*! unsigned char. */ #define NIFTI_TYPE_UINT8 2 /*! signed short. */ #define NIFTI_TYPE_INT16 4 /*! signed int. */ #define NIFTI_TYPE_INT32 8 /*! 32 bit float. */ #define NIFTI_TYPE_FLOAT32 16 /*! 64 bit complex = 2 32 bit floats. */ #define NIFTI_TYPE_COMPLEX64 32 /*! 64 bit float = double. */ #define NIFTI_TYPE_FLOAT64 64 /*! 3 8 bit bytes. */ #define NIFTI_TYPE_RGB24 128 /*! signed char. */ #define NIFTI_TYPE_INT8 256 /*! unsigned short. */ #define NIFTI_TYPE_UINT16 512 /*! unsigned int. */ #define NIFTI_TYPE_UINT32 768 /*! signed long long. */ #define NIFTI_TYPE_INT64 1024 /*! unsigned long long. */ #define NIFTI_TYPE_UINT64 1280 /*! 128 bit float = long double. */ #define NIFTI_TYPE_FLOAT128 1536 /*! 128 bit complex = 2 64 bit floats. */ #define NIFTI_TYPE_COMPLEX128 1792 /*! 256 bit complex = 2 128 bit floats */ #define NIFTI_TYPE_COMPLEX256 2048 /*! 4 8 bit bytes. */ #define NIFTI_TYPE_RGBA32 2304 /* @} */ /*-------- sample typedefs for complicated types ---*/ #if 0 typedef struct { float r,i; } complex_float ; typedef struct { double r,i; } complex_double ; typedef struct { long double r,i; } complex_longdouble ; typedef struct { unsigned char r,g,b; } rgb_byte ; #endif /*---------------------------------------------------------------------------*/ /* INTERPRETATION OF VOXEL DATA: ---------------------------- The intent_code field can be used to indicate that the voxel data has some particular meaning. In particular, a large number of codes is given to indicate that the the voxel data should be interpreted as being drawn from a given probability distribution. VECTOR-VALUED DATASETS: ---------------------- The 5th dimension of the dataset, if present (i.e., dim[0]=5 and dim[5] > 1), contains multiple values (e.g., a vector) to be stored at each spatiotemporal location. For example, the header values - dim[0] = 5 - dim[1] = 64 - dim[2] = 64 - dim[3] = 20 - dim[4] = 1 (indicates no time axis) - dim[5] = 3 - datatype = DT_FLOAT - intent_code = NIFTI_INTENT_VECTOR mean that this dataset should be interpreted as a 3D volume (64x64x20), with a 3-vector of floats defined at each point in the 3D grid. A program reading a dataset with a 5th dimension may want to reformat the image data to store each voxels' set of values together in a struct or array. This programming detail, however, is beyond the scope of the NIFTI-1 file specification! Uses of dimensions 6 and 7 are also not specified here. STATISTICAL PARAMETRIC DATASETS (i.e., SPMs): -------------------------------------------- Values of intent_code from NIFTI_FIRST_STATCODE to NIFTI_LAST_STATCODE (inclusive) indicate that the numbers in the dataset should be interpreted as being drawn from a given distribution. Most such distributions have auxiliary parameters (e.g., NIFTI_INTENT_TTEST has 1 DOF parameter). If the dataset DOES NOT have a 5th dimension, then the auxiliary parameters are the same for each voxel, and are given in header fields intent_p1, intent_p2, and intent_p3. If the dataset DOES have a 5th dimension, then the auxiliary parameters are different for each voxel. For example, the header values - dim[0] = 5 - dim[1] = 128 - dim[2] = 128 - dim[3] = 1 (indicates a single slice) - dim[4] = 1 (indicates no time axis) - dim[5] = 2 - datatype = DT_FLOAT - intent_code = NIFTI_INTENT_TTEST mean that this is a 2D dataset (128x128) of t-statistics, with the t-statistic being in the first "plane" of data and the degrees-of-freedom parameter being in the second "plane" of data. If the dataset 5th dimension is used to store the voxel-wise statistical parameters, then dim[5] must be 1 plus the number of parameters required by that distribution (e.g., intent_code=NIFTI_INTENT_TTEST implies dim[5] must be 2, as in the example just above). Note: intent_code values 2..10 are compatible with AFNI 1.5x (which is why there is no code with value=1, which is obsolescent in AFNI). OTHER INTENTIONS: ---------------- The purpose of the intent_* fields is to help interpret the values stored in the dataset. Some non-statistical values for intent_code and conventions are provided for storing other complex data types. The intent_name field provides space for a 15 character (plus 0 byte) 'name' string for the type of data stored. Examples: - intent_code = NIFTI_INTENT_ESTIMATE; intent_name = "T1"; could be used to signify that the voxel values are estimates of the NMR parameter T1. - intent_code = NIFTI_INTENT_TTEST; intent_name = "House"; could be used to signify that the voxel values are t-statistics for the significance of 'activation' response to a House stimulus. - intent_code = NIFTI_INTENT_DISPVECT; intent_name = "ToMNI152"; could be used to signify that the voxel values are a displacement vector that transforms each voxel (x,y,z) location to the corresponding location in the MNI152 standard brain. - intent_code = NIFTI_INTENT_SYMMATRIX; intent_name = "DTI"; could be used to signify that the voxel values comprise a diffusion tensor image. If no data name is implied or needed, intent_name[0] should be set to 0. -----------------------------------------------------------------------------*/ /*! default: no intention is indicated in the header. */ #define NIFTI_INTENT_NONE 0 /*-------- These codes are for probability distributions ---------------*/ /* Most distributions have a number of parameters, below denoted by p1, p2, and p3, and stored in - intent_p1, intent_p2, intent_p3 if dataset doesn't have 5th dimension - image data array if dataset does have 5th dimension Functions to compute with many of the distributions below can be found in the CDF library from U Texas. Formulas for and discussions of these distributions can be found in the following books: [U] Univariate Discrete Distributions, NL Johnson, S Kotz, AW Kemp. [C1] Continuous Univariate Distributions, vol. 1, NL Johnson, S Kotz, N Balakrishnan. [C2] Continuous Univariate Distributions, vol. 2, NL Johnson, S Kotz, N Balakrishnan. */ /*----------------------------------------------------------------------*/ /*! [C2, chap 32] Correlation coefficient R (1 param): p1 = degrees of freedom R/sqrt(1-R*R) is t-distributed with p1 DOF. */ /*! \defgroup NIFTI1_INTENT_CODES \brief nifti1 intent codes, to describe intended meaning of dataset contents @{ */ #define NIFTI_INTENT_CORREL 2 /*! [C2, chap 28] Student t statistic (1 param): p1 = DOF. */ #define NIFTI_INTENT_TTEST 3 /*! [C2, chap 27] Fisher F statistic (2 params): p1 = numerator DOF, p2 = denominator DOF. */ #define NIFTI_INTENT_FTEST 4 /*! [C1, chap 13] Standard normal (0 params): Density = N(0,1). */ #define NIFTI_INTENT_ZSCORE 5 /*! [C1, chap 18] Chi-squared (1 param): p1 = DOF. Density(x) proportional to exp(-x/2) * x^(p1/2-1). */ #define NIFTI_INTENT_CHISQ 6 /*! [C2, chap 25] Beta distribution (2 params): p1=a, p2=b. Density(x) proportional to x^(a-1) * (1-x)^(b-1). */ #define NIFTI_INTENT_BETA 7 /*! [U, chap 3] Binomial distribution (2 params): p1 = number of trials, p2 = probability per trial. Prob(x) = (p1 choose x) * p2^x * (1-p2)^(p1-x), for x=0,1,...,p1. */ #define NIFTI_INTENT_BINOM 8 /*! [C1, chap 17] Gamma distribution (2 params): p1 = shape, p2 = scale. Density(x) proportional to x^(p1-1) * exp(-p2*x). */ #define NIFTI_INTENT_GAMMA 9 /*! [U, chap 4] Poisson distribution (1 param): p1 = mean. Prob(x) = exp(-p1) * p1^x / x! , for x=0,1,2,.... */ #define NIFTI_INTENT_POISSON 10 /*! [C1, chap 13] Normal distribution (2 params): p1 = mean, p2 = standard deviation. */ #define NIFTI_INTENT_NORMAL 11 /*! [C2, chap 30] Noncentral F statistic (3 params): p1 = numerator DOF, p2 = denominator DOF, p3 = numerator noncentrality parameter. */ #define NIFTI_INTENT_FTEST_NONC 12 /*! [C2, chap 29] Noncentral chi-squared statistic (2 params): p1 = DOF, p2 = noncentrality parameter. */ #define NIFTI_INTENT_CHISQ_NONC 13 /*! [C2, chap 23] Logistic distribution (2 params): p1 = location, p2 = scale. Density(x) proportional to sech^2((x-p1)/(2*p2)). */ #define NIFTI_INTENT_LOGISTIC 14 /*! [C2, chap 24] Laplace distribution (2 params): p1 = location, p2 = scale. Density(x) proportional to exp(-abs(x-p1)/p2). */ #define NIFTI_INTENT_LAPLACE 15 /*! [C2, chap 26] Uniform distribution: p1 = lower end, p2 = upper end. */ #define NIFTI_INTENT_UNIFORM 16 /*! [C2, chap 31] Noncentral t statistic (2 params): p1 = DOF, p2 = noncentrality parameter. */ #define NIFTI_INTENT_TTEST_NONC 17 /*! [C1, chap 21] Weibull distribution (3 params): p1 = location, p2 = scale, p3 = power. Density(x) proportional to ((x-p1)/p2)^(p3-1) * exp(-((x-p1)/p2)^p3) for x > p1. */ #define NIFTI_INTENT_WEIBULL 18 /*! [C1, chap 18] Chi distribution (1 param): p1 = DOF. Density(x) proportional to x^(p1-1) * exp(-x^2/2) for x > 0. p1 = 1 = 'half normal' distribution p1 = 2 = Rayleigh distribution p1 = 3 = Maxwell-Boltzmann distribution. */ #define NIFTI_INTENT_CHI 19 /*! [C1, chap 15] Inverse Gaussian (2 params): p1 = mu, p2 = lambda Density(x) proportional to exp(-p2*(x-p1)^2/(2*p1^2*x)) / x^3 for x > 0. */ #define NIFTI_INTENT_INVGAUSS 20 /*! [C2, chap 22] Extreme value type I (2 params): p1 = location, p2 = scale cdf(x) = exp(-exp(-(x-p1)/p2)). */ #define NIFTI_INTENT_EXTVAL 21 /*! Data is a 'p-value' (no params). */ #define NIFTI_INTENT_PVAL 22 /*! Data is ln(p-value) (no params). To be safe, a program should compute p = exp(-abs(this_value)). The nifti_stats.c library returns this_value as positive, so that this_value = -log(p). */ #define NIFTI_INTENT_LOGPVAL 23 /*! Data is log10(p-value) (no params). To be safe, a program should compute p = pow(10.,-abs(this_value)). The nifti_stats.c library returns this_value as positive, so that this_value = -log10(p). */ #define NIFTI_INTENT_LOG10PVAL 24 /*! Smallest intent_code that indicates a statistic. */ #define NIFTI_FIRST_STATCODE 2 /*! Largest intent_code that indicates a statistic. */ #define NIFTI_LAST_STATCODE 24 /*---------- these values for intent_code aren't for statistics ----------*/ /*! To signify that the value at each voxel is an estimate of some parameter, set intent_code = NIFTI_INTENT_ESTIMATE. The name of the parameter may be stored in intent_name. */ #define NIFTI_INTENT_ESTIMATE 1001 /*! To signify that the value at each voxel is an index into some set of labels, set intent_code = NIFTI_INTENT_LABEL. The filename with the labels may stored in aux_file. */ #define NIFTI_INTENT_LABEL 1002 /*! To signify that the value at each voxel is an index into the NeuroNames labels set, set intent_code = NIFTI_INTENT_NEURONAME. */ #define NIFTI_INTENT_NEURONAME 1003 /*! To store an M x N matrix at each voxel: - dataset must have a 5th dimension (dim[0]=5 and dim[5]>1) - intent_code must be NIFTI_INTENT_GENMATRIX - dim[5] must be M*N - intent_p1 must be M (in float format) - intent_p2 must be N (ditto) - the matrix values A[i][[j] are stored in row-order: - A[0][0] A[0][1] ... A[0][N-1] - A[1][0] A[1][1] ... A[1][N-1] - etc., until - A[M-1][0] A[M-1][1] ... A[M-1][N-1] */ #define NIFTI_INTENT_GENMATRIX 1004 /*! To store an NxN symmetric matrix at each voxel: - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_SYMMATRIX - dim[5] must be N*(N+1)/2 - intent_p1 must be N (in float format) - the matrix values A[i][[j] are stored in row-order: - A[0][0] - A[1][0] A[1][1] - A[2][0] A[2][1] A[2][2] - etc.: row-by-row */ #define NIFTI_INTENT_SYMMATRIX 1005 /*! To signify that the vector value at each voxel is to be taken as a displacement field or vector: - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_DISPVECT - dim[5] must be the dimensionality of the displacement vector (e.g., 3 for spatial displacement, 2 for in-plane) */ #define NIFTI_INTENT_DISPVECT 1006 /* specifically for displacements */ #define NIFTI_INTENT_VECTOR 1007 /* for any other type of vector */ /*! To signify that the vector value at each voxel is really a spatial coordinate (e.g., the vertices or nodes of a surface mesh): - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_POINTSET - dim[0] = 5 - dim[1] = number of points - dim[2] = dim[3] = dim[4] = 1 - dim[5] must be the dimensionality of space (e.g., 3 => 3D space). - intent_name may describe the object these points come from (e.g., "pial", "gray/white" , "EEG", "MEG"). */ #define NIFTI_INTENT_POINTSET 1008 /*! To signify that the vector value at each voxel is really a triple of indexes (e.g., forming a triangle) from a pointset dataset: - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_TRIANGLE - dim[0] = 5 - dim[1] = number of triangles - dim[2] = dim[3] = dim[4] = 1 - dim[5] = 3 - datatype should be an integer type (preferably DT_INT32) - the data values are indexes (0,1,...) into a pointset dataset. */ #define NIFTI_INTENT_TRIANGLE 1009 /*! To signify that the vector value at each voxel is a quaternion: - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_QUATERNION - dim[0] = 5 - dim[5] = 4 - datatype should be a floating point type */ #define NIFTI_INTENT_QUATERNION 1010 /*! Dimensionless value - no params - although, as in _ESTIMATE the name of the parameter may be stored in intent_name. */ #define NIFTI_INTENT_DIMLESS 1011 /*---------- these values apply to GIFTI datasets ----------*/ /*! To signify that the value at each location is from a time series. */ #define NIFTI_INTENT_TIME_SERIES 2001 /*! To signify that the value at each location is a node index, from a complete surface dataset. */ #define NIFTI_INTENT_NODE_INDEX 2002 /*! To signify that the vector value at each location is an RGB triplet, of whatever type. - dataset must have a 5th dimension - dim[0] = 5 - dim[1] = number of nodes - dim[2] = dim[3] = dim[4] = 1 - dim[5] = 3 */ #define NIFTI_INTENT_RGB_VECTOR 2003 /*! To signify that the vector value at each location is a 4 valued RGBA vector, of whatever type. - dataset must have a 5th dimension - dim[0] = 5 - dim[1] = number of nodes - dim[2] = dim[3] = dim[4] = 1 - dim[5] = 4 */ #define NIFTI_INTENT_RGBA_VECTOR 2004 /*! To signify that the value at each location is a shape value, such as the curvature. */ #define NIFTI_INTENT_SHAPE 2005 /* @} */ /*---------------------------------------------------------------------------*/ /* 3D IMAGE (VOLUME) ORIENTATION AND LOCATION IN SPACE: --------------------------------------------------- There are 3 different methods by which continuous coordinates can attached to voxels. The discussion below emphasizes 3D volumes, and the continuous coordinates are referred to as (x,y,z). The voxel index coordinates (i.e., the array indexes) are referred to as (i,j,k), with valid ranges: i = 0 .. dim[1]-1 j = 0 .. dim[2]-1 (if dim[0] >= 2) k = 0 .. dim[3]-1 (if dim[0] >= 3) The (x,y,z) coordinates refer to the CENTER of a voxel. In methods 2 and 3, the (x,y,z) axes refer to a subject-based coordinate system, with +x = Right +y = Anterior +z = Superior. This is a right-handed coordinate system. However, the exact direction these axes point with respect to the subject depends on qform_code (Method 2) and sform_code (Method 3). N.B.: The i index varies most rapidly, j index next, k index slowest. Thus, voxel (i,j,k) is stored starting at location (i + j*dim[1] + k*dim[1]*dim[2]) * (bitpix/8) into the dataset array. N.B.: The ANALYZE 7.5 coordinate system is +x = Left +y = Anterior +z = Superior which is a left-handed coordinate system. This backwardness is too difficult to tolerate, so this NIFTI-1 standard specifies the coordinate order which is most common in functional neuroimaging. N.B.: The 3 methods below all give the locations of the voxel centers in the (x,y,z) coordinate system. In many cases, programs will wish to display image data on some other grid. In such a case, the program will need to convert its desired (x,y,z) values into (i,j,k) values in order to extract (or interpolate) the image data. This operation would be done with the inverse transformation to those described below. N.B.: Method 2 uses a factor 'qfac' which is either -1 or 1; qfac is stored in the otherwise unused pixdim[0]. If pixdim[0]=0.0 (which should not occur), we take qfac=1. Of course, pixdim[0] is only used when reading a NIFTI-1 header, not when reading an ANALYZE 7.5 header. N.B.: The units of (x,y,z) can be specified using the xyzt_units field. METHOD 1 (the "old" way, used only when qform_code = 0): ------------------------------------------------------- The coordinate mapping from (i,j,k) to (x,y,z) is the ANALYZE 7.5 way. This is a simple scaling relationship: x = pixdim[1] * i y = pixdim[2] * j z = pixdim[3] * k No particular spatial orientation is attached to these (x,y,z) coordinates. (NIFTI-1 does not have the ANALYZE 7.5 orient field, which is not general and is often not set properly.) This method is not recommended, and is present mainly for compatibility with ANALYZE 7.5 files. METHOD 2 (used when qform_code > 0, which should be the "normal" case): --------------------------------------------------------------------- The (x,y,z) coordinates are given by the pixdim[] scales, a rotation matrix, and a shift. This method is intended to represent "scanner-anatomical" coordinates, which are often embedded in the image header (e.g., DICOM fields (0020,0032), (0020,0037), (0028,0030), and (0018,0050)), and represent the nominal orientation and location of the data. This method can also be used to represent "aligned" coordinates, which would typically result from some post-acquisition alignment of the volume to a standard orientation (e.g., the same subject on another day, or a rigid rotation to true anatomical orientation from the tilted position of the subject in the scanner). The formula for (x,y,z) in terms of header parameters and (i,j,k) is: [ x ] [ R11 R12 R13 ] [ pixdim[1] * i ] [ qoffset_x ] [ y ] = [ R21 R22 R23 ] [ pixdim[2] * j ] + [ qoffset_y ] [ z ] [ R31 R32 R33 ] [ qfac * pixdim[3] * k ] [ qoffset_z ] The qoffset_* shifts are in the NIFTI-1 header. Note that the center of the (i,j,k)=(0,0,0) voxel (first value in the dataset array) is just (x,y,z)=(qoffset_x,qoffset_y,qoffset_z). The rotation matrix R is calculated from the quatern_* parameters. This calculation is described below. The scaling factor qfac is either 1 or -1. The rotation matrix R defined by the quaternion parameters is "proper" (has determinant 1). This may not fit the needs of the data; for example, if the image grid is i increases from Left-to-Right j increases from Anterior-to-Posterior k increases from Inferior-to-Superior Then (i,j,k) is a left-handed triple. In this example, if qfac=1, the R matrix would have to be [ 1 0 0 ] [ 0 -1 0 ] which is "improper" (determinant = -1). [ 0 0 1 ] If we set qfac=-1, then the R matrix would be [ 1 0 0 ] [ 0 -1 0 ] which is proper. [ 0 0 -1 ] This R matrix is represented by quaternion [a,b,c,d] = [0,1,0,0] (which encodes a 180 degree rotation about the x-axis). METHOD 3 (used when sform_code > 0): ----------------------------------- The (x,y,z) coordinates are given by a general affine transformation of the (i,j,k) indexes: x = srow_x[0] * i + srow_x[1] * j + srow_x[2] * k + srow_x[3] y = srow_y[0] * i + srow_y[1] * j + srow_y[2] * k + srow_y[3] z = srow_z[0] * i + srow_z[1] * j + srow_z[2] * k + srow_z[3] The srow_* vectors are in the NIFTI_1 header. Note that no use is made of pixdim[] in this method. WHY 3 METHODS? -------------- Method 1 is provided only for backwards compatibility. The intention is that Method 2 (qform_code > 0) represents the nominal voxel locations as reported by the scanner, or as rotated to some fiducial orientation and location. Method 3, if present (sform_code > 0), is to be used to give the location of the voxels in some standard space. The sform_code indicates which standard space is present. Both methods 2 and 3 can be present, and be useful in different contexts (method 2 for displaying the data on its original grid; method 3 for displaying it on a standard grid). In this scheme, a dataset would originally be set up so that the Method 2 coordinates represent what the scanner reported. Later, a registration to some standard space can be computed and inserted in the header. Image display software can use either transform, depending on its purposes and needs. In Method 2, the origin of coordinates would generally be whatever the scanner origin is; for example, in MRI, (0,0,0) is the center of the gradient coil. In Method 3, the origin of coordinates would depend on the value of sform_code; for example, for the Talairach coordinate system, (0,0,0) corresponds to the Anterior Commissure. QUATERNION REPRESENTATION OF ROTATION MATRIX (METHOD 2) ------------------------------------------------------- The orientation of the (x,y,z) axes relative to the (i,j,k) axes in 3D space is specified using a unit quaternion [a,b,c,d], where a*a+b*b+c*c+d*d=1. The (b,c,d) values are all that is needed, since we require that a = sqrt(1.0-(b*b+c*c+d*d)) be nonnegative. The (b,c,d) values are stored in the (quatern_b,quatern_c,quatern_d) fields. The quaternion representation is chosen for its compactness in representing rotations. The (proper) 3x3 rotation matrix that corresponds to [a,b,c,d] is [ a*a+b*b-c*c-d*d 2*b*c-2*a*d 2*b*d+2*a*c ] R = [ 2*b*c+2*a*d a*a+c*c-b*b-d*d 2*c*d-2*a*b ] [ 2*b*d-2*a*c 2*c*d+2*a*b a*a+d*d-c*c-b*b ] [ R11 R12 R13 ] = [ R21 R22 R23 ] [ R31 R32 R33 ] If (p,q,r) is a unit 3-vector, then rotation of angle h about that direction is represented by the quaternion [a,b,c,d] = [cos(h/2), p*sin(h/2), q*sin(h/2), r*sin(h/2)]. Requiring a >= 0 is equivalent to requiring -Pi <= h <= Pi. (Note that [-a,-b,-c,-d] represents the same rotation as [a,b,c,d]; there are 2 quaternions that can be used to represent a given rotation matrix R.) To rotate a 3-vector (x,y,z) using quaternions, we compute the quaternion product [0,x',y',z'] = [a,b,c,d] * [0,x,y,z] * [a,-b,-c,-d] which is equivalent to the matrix-vector multiply [ x' ] [ x ] [ y' ] = R [ y ] (equivalence depends on a*a+b*b+c*c+d*d=1) [ z' ] [ z ] Multiplication of 2 quaternions is defined by the following: [a,b,c,d] = a*1 + b*I + c*J + d*K where I*I = J*J = K*K = -1 (I,J,K are square roots of -1) I*J = K J*K = I K*I = J J*I = -K K*J = -I I*K = -J (not commutative!) For example [a,b,0,0] * [0,0,0,1] = [0,0,-b,a] since this expands to (a+b*I)*(K) = (a*K+b*I*K) = (a*K-b*J). The above formula shows how to go from quaternion (b,c,d) to rotation matrix and direction cosines. Conversely, given R, we can compute the fields for the NIFTI-1 header by a = 0.5 * sqrt(1+R11+R22+R33) (not stored) b = 0.25 * (R32-R23) / a => quatern_b c = 0.25 * (R13-R31) / a => quatern_c d = 0.25 * (R21-R12) / a => quatern_d If a=0 (a 180 degree rotation), alternative formulas are needed. See the nifti1_io.c function mat44_to_quatern() for an implementation of the various cases in converting R to [a,b,c,d]. Note that R-transpose (= R-inverse) would lead to the quaternion [a,-b,-c,-d]. The choice to specify the qoffset_x (etc.) values in the final coordinate system is partly to make it easy to convert DICOM images to this format. The DICOM attribute "Image Position (Patient)" (0020,0032) stores the (Xd,Yd,Zd) coordinates of the center of the first voxel. Here, (Xd,Yd,Zd) refer to DICOM coordinates, and Xd=-x, Yd=-y, Zd=z, where (x,y,z) refers to the NIFTI coordinate system discussed above. (i.e., DICOM +Xd is Left, +Yd is Posterior, +Zd is Superior, whereas +x is Right, +y is Anterior , +z is Superior. ) Thus, if the (0020,0032) DICOM attribute is extracted into (px,py,pz), then qoffset_x = -px qoffset_y = -py qoffset_z = pz is a reasonable setting when qform_code=NIFTI_XFORM_SCANNER_ANAT. That is, DICOM's coordinate system is 180 degrees rotated about the z-axis from the neuroscience/NIFTI coordinate system. To transform between DICOM and NIFTI, you just have to negate the x- and y-coordinates. The DICOM attribute (0020,0037) "Image Orientation (Patient)" gives the orientation of the x- and y-axes of the image data in terms of 2 3-vectors. The first vector is a unit vector along the x-axis, and the second is along the y-axis. If the (0020,0037) attribute is extracted into the value (xa,xb,xc,ya,yb,yc), then the first two columns of the R matrix would be [ -xa -ya ] [ -xb -yb ] [ xc yc ] The negations are because DICOM's x- and y-axes are reversed relative to NIFTI's. The third column of the R matrix gives the direction of displacement (relative to the subject) along the slice-wise direction. This orientation is not encoded in the DICOM standard in a simple way; DICOM is mostly concerned with 2D images. The third column of R will be either the cross-product of the first 2 columns or its negative. It is possible to infer the sign of the 3rd column by examining the coordinates in DICOM attribute (0020,0032) "Image Position (Patient)" for successive slices. However, this method occasionally fails for reasons that I (RW Cox) do not understand. -----------------------------------------------------------------------------*/ /* [qs]form_code value: */ /* x,y,z coordinate system refers to: */ /*-----------------------*/ /*---------------------------------------*/ /*! \defgroup NIFTI1_XFORM_CODES \brief nifti1 xform codes to describe the "standard" coordinate system @{ */ /*! Arbitrary coordinates (Method 1). */ #define NIFTI_XFORM_UNKNOWN 0 /*! Scanner-based anatomical coordinates */ #define NIFTI_XFORM_SCANNER_ANAT 1 /*! Coordinates aligned to another file's, or to anatomical "truth". */ #define NIFTI_XFORM_ALIGNED_ANAT 2 /*! Coordinates aligned to Talairach- Tournoux Atlas; (0,0,0)=AC, etc. */ #define NIFTI_XFORM_TALAIRACH 3 /*! MNI 152 normalized coordinates. */ #define NIFTI_XFORM_MNI_152 4 /*! Normalized coordinates (for any general standard template space). Added March 8, 2019. */ #define NIFTI_XFORM_TEMPLATE_OTHER 5 /* @} */ /*---------------------------------------------------------------------------*/ /* UNITS OF SPATIAL AND TEMPORAL DIMENSIONS: ---------------------------------------- The codes below can be used in xyzt_units to indicate the units of pixdim. As noted earlier, dimensions 1,2,3 are for x,y,z; dimension 4 is for time (t). - If dim[4]=1 or dim[0] < 4, there is no time axis. - A single time series (no space) would be specified with - dim[0] = 4 (for scalar data) or dim[0] = 5 (for vector data) - dim[1] = dim[2] = dim[3] = 1 - dim[4] = number of time points - pixdim[4] = time step - xyzt_units indicates units of pixdim[4] - dim[5] = number of values stored at each time point Bits 0..2 of xyzt_units specify the units of pixdim[1..3] (e.g., spatial units are values 1..7). Bits 3..5 of xyzt_units specify the units of pixdim[4] (e.g., temporal units are multiples of 8). This compression of 2 distinct concepts into 1 byte is due to the limited space available in the 348 byte ANALYZE 7.5 header. The macros XYZT_TO_SPACE and XYZT_TO_TIME can be used to mask off the undesired bits from the xyzt_units fields, leaving "pure" space and time codes. Inversely, the macro SPACE_TIME_TO_XYZT can be used to assemble a space code (0,1,2,...,7) with a time code (0,8,16,32,...,56) into the combined value for xyzt_units. Note that codes are provided to indicate the "time" axis units are actually frequency in Hertz (_HZ), in part-per-million (_PPM) or in radians-per-second (_RADS). The toffset field can be used to indicate a nonzero start point for the time axis. That is, time point #m is at t=toffset+m*pixdim[4] for m=0..dim[4]-1. -----------------------------------------------------------------------------*/ /*! \defgroup NIFTI1_UNITS \brief nifti1 units codes to describe the unit of measurement for each dimension of the dataset @{ */ /*! NIFTI code for unspecified units. */ #define NIFTI_UNITS_UNKNOWN 0 /** Space codes are multiples of 1. **/ /*! NIFTI code for meters. */ #define NIFTI_UNITS_METER 1 /*! NIFTI code for millimeters. */ #define NIFTI_UNITS_MM 2 /*! NIFTI code for micrometers. */ #define NIFTI_UNITS_MICRON 3 /** Time codes are multiples of 8. **/ /*! NIFTI code for seconds. */ #define NIFTI_UNITS_SEC 8 /*! NIFTI code for milliseconds. */ #define NIFTI_UNITS_MSEC 16 /*! NIFTI code for microseconds. */ #define NIFTI_UNITS_USEC 24 /*** These units are for spectral data: ***/ /*! NIFTI code for Hertz. */ #define NIFTI_UNITS_HZ 32 /*! NIFTI code for ppm. */ #define NIFTI_UNITS_PPM 40 /*! NIFTI code for radians per second. */ #define NIFTI_UNITS_RADS 48 /* @} */ #undef XYZT_TO_SPACE #undef XYZT_TO_TIME #define XYZT_TO_SPACE(xyzt) ( (xyzt) & 0x07 ) #define XYZT_TO_TIME(xyzt) ( (xyzt) & 0x38 ) #undef SPACE_TIME_TO_XYZT #define SPACE_TIME_TO_XYZT(ss,tt) ( (((char)(ss)) & 0x07) \ | (((char)(tt)) & 0x38) ) /*---------------------------------------------------------------------------*/ /* MRI-SPECIFIC SPATIAL AND TEMPORAL INFORMATION: --------------------------------------------- A few fields are provided to store some extra information that is sometimes important when storing the image data from an FMRI time series experiment. (After processing such data into statistical images, these fields are not likely to be useful.) { freq_dim } = These fields encode which spatial dimension (1,2, or 3) { phase_dim } = corresponds to which acquisition dimension for MRI data. { slice_dim } = Examples: Rectangular scan multi-slice EPI: freq_dim = 1 phase_dim = 2 slice_dim = 3 (or some permutation) Spiral scan multi-slice EPI: freq_dim = phase_dim = 0 slice_dim = 3 since the concepts of frequency- and phase-encoding directions don't apply to spiral scan slice_duration = If this is positive, AND if slice_dim is nonzero, indicates the amount of time used to acquire 1 slice. slice_duration*dim[slice_dim] can be less than pixdim[4] with a clustered acquisition method, for example. slice_code = If this is nonzero, AND if slice_dim is nonzero, AND if slice_duration is positive, indicates the timing pattern of the slice acquisition. The following codes are defined: NIFTI_SLICE_SEQ_INC == sequential increasing NIFTI_SLICE_SEQ_DEC == sequential decreasing NIFTI_SLICE_ALT_INC == alternating increasing NIFTI_SLICE_ALT_DEC == alternating decreasing NIFTI_SLICE_ALT_INC2 == alternating increasing #2 NIFTI_SLICE_ALT_DEC2 == alternating decreasing #2 { slice_start } = Indicates the start and end of the slice acquisition { slice_end } = pattern, when slice_code is nonzero. These values are present to allow for the possible addition of "padded" slices at either end of the volume, which don't fit into the slice timing pattern. If there are no padding slices, then slice_start=0 and slice_end=dim[slice_dim]-1 are the correct values. For these values to be meaningful, slice_start must be non-negative and slice_end must be greater than slice_start. Otherwise, they should be ignored. The following table indicates the slice timing pattern, relative to time=0 for the first slice acquired, for some sample cases. Here, dim[slice_dim]=7 (there are 7 slices, labeled 0..6), slice_duration=0.1, and slice_start=1, slice_end=5 (1 padded slice on each end). slice index SEQ_INC SEQ_DEC ALT_INC ALT_DEC ALT_INC2 ALT_DEC2 6 : n/a n/a n/a n/a n/a n/a n/a = not applicable 5 : 0.4 0.0 0.2 0.0 0.4 0.2 (slice time offset 4 : 0.3 0.1 0.4 0.3 0.1 0.0 doesn't apply to 3 : 0.2 0.2 0.1 0.1 0.3 0.3 slices outside 2 : 0.1 0.3 0.3 0.4 0.0 0.1 the range 1 : 0.0 0.4 0.0 0.2 0.2 0.4 slice_start .. 0 : n/a n/a n/a n/a n/a n/a slice_end) The SEQ slice_codes are sequential ordering (uncommon but not unknown), either increasing in slice number or decreasing (INC or DEC), as illustrated above. The ALT slice codes are alternating ordering. The 'standard' way for these to operate (without the '2' on the end) is for the slice timing to start at the edge of the slice_start .. slice_end group (at slice_start for INC and at slice_end for DEC). For the 'ALT_*2' slice_codes, the slice timing instead starts at the first slice in from the edge (at slice_start+1 for INC2 and at slice_end-1 for DEC2). This latter acquisition scheme is found on some Siemens scanners. The fields freq_dim, phase_dim, slice_dim are all squished into the single byte field dim_info (2 bits each, since the values for each field are limited to the range 0..3). This unpleasantness is due to lack of space in the 348 byte allowance. The macros DIM_INFO_TO_FREQ_DIM, DIM_INFO_TO_PHASE_DIM, and DIM_INFO_TO_SLICE_DIM can be used to extract these values from the dim_info byte. The macro FPS_INTO_DIM_INFO can be used to put these 3 values into the dim_info byte. -----------------------------------------------------------------------------*/ #undef DIM_INFO_TO_FREQ_DIM #undef DIM_INFO_TO_PHASE_DIM #undef DIM_INFO_TO_SLICE_DIM #define DIM_INFO_TO_FREQ_DIM(di) ( ((di) ) & 0x03 ) #define DIM_INFO_TO_PHASE_DIM(di) ( ((di) >> 2) & 0x03 ) #define DIM_INFO_TO_SLICE_DIM(di) ( ((di) >> 4) & 0x03 ) #undef FPS_INTO_DIM_INFO #define FPS_INTO_DIM_INFO(fd,pd,sd) ( ( ( ((char)(fd)) & 0x03) ) | \ ( ( ((char)(pd)) & 0x03) << 2 ) | \ ( ( ((char)(sd)) & 0x03) << 4 ) ) /*! \defgroup NIFTI1_SLICE_ORDER \brief nifti1 slice order codes, describing the acquisition order of the slices @{ */ #define NIFTI_SLICE_UNKNOWN 0 #define NIFTI_SLICE_SEQ_INC 1 #define NIFTI_SLICE_SEQ_DEC 2 #define NIFTI_SLICE_ALT_INC 3 #define NIFTI_SLICE_ALT_DEC 4 #define NIFTI_SLICE_ALT_INC2 5 /* 05 May 2005: RWCox */ #define NIFTI_SLICE_ALT_DEC2 6 /* 05 May 2005: RWCox */ /* @} */ /*---------------------------------------------------------------------------*/ /* UNUSED FIELDS: ------------- Some of the ANALYZE 7.5 fields marked as ++UNUSED++ may need to be set to particular values for compatibility with other programs. The issue of interoperability of ANALYZE 7.5 files is a murky one -- not all programs require exactly the same set of fields. (Unobscuring this murkiness is a principal motivation behind NIFTI-1.) Some of the fields that may need to be set for other (non-NIFTI aware) software to be happy are: extents dbh.h says this should be 16384 regular dbh.h says this should be the character 'r' glmin, } dbh.h says these values should be the min and max voxel glmax } values for the entire dataset It is best to initialize ALL fields in the NIFTI-1 header to 0 (e.g., with calloc()), then fill in what is needed. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* MISCELLANEOUS C MACROS -----------------------------------------------------------------------------*/ /*.................*/ /*! Given a nifti_1_header struct, check if it has a good magic number. Returns NIFTI version number (1..9) if magic is good, 0 if it is not. */ #define NIFTI_VERSION(h) \ ( ( (h).magic[0]=='n' && (h).magic[3]=='\0' && \ ( (h).magic[1]=='i' || (h).magic[1]=='+' ) && \ ( (h).magic[2]>='1' && (h).magic[2]<='9' ) ) \ ? (h).magic[2]-'0' : 0 ) /*.................*/ /*! Check if a nifti_1_header struct says if the data is stored in the same file or in a separate file. Returns 1 if the data is in the same file as the header, 0 if it is not. */ #define NIFTI_ONEFILE(h) ( (h).magic[1] == '+' ) /*.................*/ /*! Check if a nifti_1_header struct needs to be byte swapped. Returns 1 if it needs to be swapped, 0 if it does not. */ #define NIFTI_NEEDS_SWAP(h) ( (h).dim[0] < 0 || (h).dim[0] > 7 ) /*.................*/ /*! Check if a nifti_1_header struct contains a 5th (vector) dimension. Returns size of 5th dimension if > 1, returns 0 otherwise. */ #define NIFTI_5TH_DIM(h) ( ((h).dim[0]>4 && (h).dim[5]>1) ? (h).dim[5] : 0 ) /*****************************************************************************/ /*=================*/ #ifdef __cplusplus } #endif /*=================*/ #endif /* _NIFTI_HEADER_ */ dcm2niix-1.0.20211006/console/nifti1_io_core.cpp000066400000000000000000000670211412761503300210300ustar00rootroot00000000000000//This unit uses a subset of the functions from the nifti1_io available from // https://sourceforge.net/projects/niftilib/files/nifticlib/ //These functions were extended by Chris Rorden (2014) and maintain the same license /*****===================================================================*****/ /***** Sample functions to deal with NIFTI-1 and ANALYZE files *****/ /*****...................................................................*****/ /***** This code is released to the public domain. *****/ /*****...................................................................*****/ /***** Author: Robert W Cox, SSCC/DIRP/NIMH/NIH/DHHS/USA/EARTH *****/ /***** Date: August 2003 *****/ /*****...................................................................*****/ /***** Neither the National Institutes of Health (NIH), nor any of its *****/ /***** employees imply any warranty of usefulness of this software for *****/ /***** any purpose, and do not assume any liability for damages, *****/ /***** incidental or otherwise, caused by any use of this document. *****/ /*****===================================================================*****/ #include //requires VS 2015 or later #include "nifti1_io_core.h" #include #include #include #include #include #include #include #include #ifndef _MSC_VER #include #endif #include "print.h" #ifndef USING_R void nifti_swap_8bytes( size_t n , void *ar ) // 4 bytes at a time { size_t ii ; unsigned char * cp0 = (unsigned char *)ar, * cp1, * cp2 ; unsigned char tval ; for( ii=0 ; ii < n ; ii++ ){ cp1 = cp0; cp2 = cp0+7; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp1++; cp2--; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp1++; cp2--; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp1++; cp2--; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp0 += 8; } return ; } void nifti_swap_4bytes( size_t n , void *ar ) // 4 bytes at a time { size_t ii ; unsigned char * cp0 = (unsigned char *)ar, * cp1, * cp2 ; unsigned char tval ; for( ii=0 ; ii < n ; ii++ ){ cp1 = cp0; cp2 = cp0+3; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp1++; cp2--; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp0 += 4; } return ; } void nifti_swap_2bytes( size_t n , void *ar ) // 2 bytes at a time { size_t ii ; unsigned char * cp1 = (unsigned char *)ar, * cp2 ; unsigned char tval; for( ii=0 ; ii < n ; ii++ ){ cp2 = cp1 + 1; tval = *cp1; *cp1 = *cp2; *cp2 = tval; cp1 += 2; } return ; } /*-------------------------------------------------------------------------*/ /*! Byte swap NIFTI-1 file header in various places and ways. If is_nifti, swap all (even UNUSED) fields of NIfTI header. Else, swap as a nifti_analyze75 struct. *//*---------------------------------------------------------------------- */ void swap_nifti_header( struct nifti_1_header *h ) { /* otherwise, swap all NIFTI fields */ nifti_swap_4bytes(1, &h->sizeof_hdr); nifti_swap_4bytes(1, &h->extents); nifti_swap_2bytes(1, &h->session_error); nifti_swap_2bytes(8, h->dim); nifti_swap_4bytes(1, &h->intent_p1); nifti_swap_4bytes(1, &h->intent_p2); nifti_swap_4bytes(1, &h->intent_p3); nifti_swap_2bytes(1, &h->intent_code); nifti_swap_2bytes(1, &h->datatype); nifti_swap_2bytes(1, &h->bitpix); nifti_swap_2bytes(1, &h->slice_start); nifti_swap_4bytes(8, h->pixdim); nifti_swap_4bytes(1, &h->vox_offset); nifti_swap_4bytes(1, &h->scl_slope); nifti_swap_4bytes(1, &h->scl_inter); nifti_swap_2bytes(1, &h->slice_end); nifti_swap_4bytes(1, &h->cal_max); nifti_swap_4bytes(1, &h->cal_min); nifti_swap_4bytes(1, &h->slice_duration); nifti_swap_4bytes(1, &h->toffset); nifti_swap_4bytes(1, &h->glmax); nifti_swap_4bytes(1, &h->glmin); nifti_swap_2bytes(1, &h->qform_code); nifti_swap_2bytes(1, &h->sform_code); nifti_swap_4bytes(1, &h->quatern_b); nifti_swap_4bytes(1, &h->quatern_c); nifti_swap_4bytes(1, &h->quatern_d); nifti_swap_4bytes(1, &h->qoffset_x); nifti_swap_4bytes(1, &h->qoffset_y); nifti_swap_4bytes(1, &h->qoffset_z); nifti_swap_4bytes(4, h->srow_x); nifti_swap_4bytes(4, h->srow_y); nifti_swap_4bytes(4, h->srow_z); return ; } #endif bool littleEndianPlatform () { uint32_t value = 1; return (*((char *) &value) == 1); } int isSameFloat (float a, float b) { return (fabs (a - b) <= FLT_EPSILON); } int isSameDouble (double a, double b) { return (fabs (a - b) <= DBL_EPSILON); } ivec3 setiVec3(int x, int y, int z) { ivec3 v = {{x, y, z}}; return v; } vec3 setVec3(float x, float y, float z) { vec3 v = {{x, y, z}}; return v; } vec4 setVec4(float x, float y, float z) { vec4 v= {{x, y, z, 1}}; return v; } vec3 crossProduct(vec3 u, vec3 v) { return setVec3(u.v[1]*v.v[2] - v.v[1]*u.v[2], -u.v[0]*v.v[2] + v.v[0]*u.v[2], u.v[0]*v.v[1] - v.v[0]*u.v[1]); } float dotProduct(vec3 u, vec3 v) { return (u.v[0]*v.v[0] + v.v[1]*u.v[1] + v.v[2]*u.v[2]); } vec3 nifti_vect33_norm (vec3 v) { //normalize vector length vec3 vO = v; float vLen = sqrt( (v.v[0]*v.v[0]) + (v.v[1]*v.v[1]) + (v.v[2]*v.v[2])); if (vLen <= FLT_EPSILON) return vO; //avoid divide by zero for (int i = 0; i < 3; i++) vO.v[i] = v.v[i]/vLen; return vO; } vec4 nifti_vect44_norm (vec4 v) { //normalize vector length vec4 vO = v; float vLen = sqrt( (v.v[0]*v.v[0]) + (v.v[1]*v.v[1]) + (v.v[2]*v.v[2]) + (v.v[3]*v.v[3])); if (vLen <= FLT_EPSILON) return vO; //avoid divide by zero for (int i = 0; i < 4; i++) vO.v[i] = v.v[i]/vLen; return vO; } vec3 nifti_vect33mat33_mul(vec3 v, mat33 m ) { //multiply vector * 3x3matrix vec3 vO; for (int i=0; i<3; i++) { //multiply Pcrs * m vO.v[i] = 0; for(int j=0; j<3; j++) vO.v[i] += m.m[i][j]*v.v[j]; } return vO; } vec4 nifti_vect44mat44_mul(vec4 v, mat44 m ) { //multiply vector * 4x4matrix vec4 vO; for (int i=0; i<4; i++) { //multiply Pcrs * m vO.v[i] = 0; for(int j=0; j<4; j++) vO.v[i] += m.m[i][j]*v.v[j]; } return vO; } mat44 nifti_dicom2mat(float orient[7], float patientPosition[4], float xyzMM[4]) { //create NIfTI header based on values from DICOM header //note orient has 6 values, indexed from 1, patient position and xyzMM have 3 values indexed from 1 mat33 Q, diagVox; Q.m[0][0] = orient[1]; Q.m[0][1] = orient[2] ; Q.m[0][2] = orient[3] ; // load Q Q.m[1][0] = orient[4]; Q.m[1][1] = orient[5] ; Q.m[1][2] = orient[6]; //printMessage("Orient %g %g %g %g %g %g\n",orient[1],orient[2],orient[3],orient[4],orient[5],orient[6] ); /* normalize row 1 */ double val = Q.m[0][0]*Q.m[0][0] + Q.m[0][1]*Q.m[0][1] + Q.m[0][2]*Q.m[0][2] ; if( val > 0.0l ){ val = 1.0l / sqrt(val) ; Q.m[0][0] *= (float)val ; Q.m[0][1] *= (float)val ; Q.m[0][2] *= (float)val ; } else { Q.m[0][0] = 1.0l ; Q.m[0][1] = 0.0l ; Q.m[0][2] = 0.0l ; } /* normalize row 2 */ val = Q.m[1][0]*Q.m[1][0] + Q.m[1][1]*Q.m[1][1] + Q.m[1][2]*Q.m[1][2] ; if( val > 0.0l ){ val = 1.0l / sqrt(val) ; Q.m[1][0] *= (float)val ; Q.m[1][1] *= (float)val ; Q.m[1][2] *= (float)val ; } else { Q.m[1][0] = 0.0l ; Q.m[1][1] = 1.0l ; Q.m[1][2] = 0.0l ; } /* row 3 is the cross product of rows 1 and 2*/ Q.m[2][0] = Q.m[0][1]*Q.m[1][2] - Q.m[0][2]*Q.m[1][1] ; /* cross */ Q.m[2][1] = Q.m[0][2]*Q.m[1][0] - Q.m[0][0]*Q.m[1][2] ; /* product */ Q.m[2][2] = Q.m[0][0]*Q.m[1][1] - Q.m[0][1]*Q.m[1][0] ; Q = nifti_mat33_transpose(Q); if (nifti_mat33_determ(Q) < 0.0) { Q.m[0][2] = -Q.m[0][2]; Q.m[1][2] = -Q.m[1][2]; Q.m[2][2] = -Q.m[2][2]; } //next scale matrix LOAD_MAT33(diagVox, xyzMM[1],0.0l,0.0l, 0.0l,xyzMM[2],0.0l, 0.0l,0.0l, xyzMM[3]); Q = nifti_mat33_mul(Q,diagVox); mat44 Q44; //4x4 matrix includes translations LOAD_MAT44(Q44, Q.m[0][0],Q.m[0][1],Q.m[0][2],patientPosition[1], Q.m[1][0],Q.m[1][1],Q.m[1][2],patientPosition[2], Q.m[2][0],Q.m[2][1],Q.m[2][2],patientPosition[3]); return Q44; } #ifndef USING_R float nifti_mat33_determ( mat33 R ) /* determinant of 3x3 matrix */ { double r11,r12,r13,r21,r22,r23,r31,r32,r33 ; /* INPUT MATRIX: */ r11 = R.m[0][0]; r12 = R.m[0][1]; r13 = R.m[0][2]; /* [ r11 r12 r13 ] */ r21 = R.m[1][0]; r22 = R.m[1][1]; r23 = R.m[1][2]; /* [ r21 r22 r23 ] */ r31 = R.m[2][0]; r32 = R.m[2][1]; r33 = R.m[2][2]; /* [ r31 r32 r33 ] */ return (float)(r11*r22*r33-r11*r32*r23-r21*r12*r33 +r21*r32*r13+r31*r12*r23-r31*r22*r13) ; } mat33 nifti_mat33_mul( mat33 A , mat33 B ) /* multiply 2 3x3 matrices */ //see http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c { mat33 C ; int i,j ; for( i=0 ; i < 3 ; i++ ) for( j=0 ; j < 3 ; j++ ) C.m[i][j] = A.m[i][0] * B.m[0][j] + A.m[i][1] * B.m[1][j] + A.m[i][2] * B.m[2][j] ; return C ; } #endif mat44 nifti_mat44_mul( mat44 A , mat44 B ) /* multiply 2 3x3 matrices */ { mat44 C ; int i,j ; for( i=0 ; i < 4 ; i++ ) for( j=0 ; j < 4; j++ ) C.m[i][j] = A.m[i][0] * B.m[0][j] + A.m[i][1] * B.m[1][j] + A.m[i][2] * B.m[2][j] + A.m[i][3] * B.m[3][j]; return C ; } mat33 nifti_mat33_transpose( mat33 A ) /* transpose 3x3 matrix */ //see http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c { mat33 B; int i,j ; for( i=0 ; i < 3 ; i++ ) for( j=0 ; j < 3 ; j++ ) B.m[i][j] = A.m[j][i]; return B; } #ifndef USING_R mat33 nifti_mat33_inverse( mat33 R ) /* inverse of 3x3 matrix */ { double r11,r12,r13,r21,r22,r23,r31,r32,r33 , deti ; mat33 Q ; // INPUT MATRIX: r11 = R.m[0][0]; r12 = R.m[0][1]; r13 = R.m[0][2]; // [ r11 r12 r13 ] r21 = R.m[1][0]; r22 = R.m[1][1]; r23 = R.m[1][2]; // [ r21 r22 r23 ] r31 = R.m[2][0]; r32 = R.m[2][1]; r33 = R.m[2][2]; // [ r31 r32 r33 ] deti = r11*r22*r33-r11*r32*r23-r21*r12*r33 +r21*r32*r13+r31*r12*r23-r31*r22*r13 ; if( deti != 0.0l ) deti = 1.0l / deti ; Q.m[0][0] = deti*( r22*r33-r32*r23) ; Q.m[0][1] = deti*(-r12*r33+r32*r13) ; Q.m[0][2] = deti*( r12*r23-r22*r13) ; Q.m[1][0] = deti*(-r21*r33+r31*r23) ; Q.m[1][1] = deti*( r11*r33-r31*r13) ; Q.m[1][2] = deti*(-r11*r23+r21*r13) ; Q.m[2][0] = deti*( r21*r32-r31*r22) ; Q.m[2][1] = deti*(-r11*r32+r31*r12) ; Q.m[2][2] = deti*( r11*r22-r21*r12) ; return Q ; } float nifti_mat33_rownorm( mat33 A ) // max row norm of 3x3 matrix { float r1,r2,r3 ; r1 = fabs(A.m[0][0])+fabs(A.m[0][1])+fabs(A.m[0][2]) ; r2 = fabs(A.m[1][0])+fabs(A.m[1][1])+fabs(A.m[1][2]) ; r3 = fabs(A.m[2][0])+fabs(A.m[2][1])+fabs(A.m[2][2]) ; if( r1 < r2 ) r1 = r2 ; if( r1 < r3 ) r1 = r3 ; return r1 ; } float nifti_mat33_colnorm( mat33 A ) // max column norm of 3x3 matrix { float r1,r2,r3 ; r1 = fabs(A.m[0][0])+fabs(A.m[1][0])+fabs(A.m[2][0]) ; r2 = fabs(A.m[0][1])+fabs(A.m[1][1])+fabs(A.m[2][1]) ; r3 = fabs(A.m[0][2])+fabs(A.m[1][2])+fabs(A.m[2][2]) ; if( r1 < r2 ) r1 = r2 ; if( r1 < r3 ) r1 = r3 ; return r1 ; } mat33 nifti_mat33_polar( mat33 A ) { mat33 X , Y , Z ; float alp,bet,gam,gmi , dif=1.0 ; int k=0 ; X = A ; // force matrix to be nonsingular gam = nifti_mat33_determ(X) ; while( gam == 0.0 ){ // perturb matrix gam = 0.00001 * ( 0.001 + nifti_mat33_rownorm(X) ) ; X.m[0][0] += gam ; X.m[1][1] += gam ; X.m[2][2] += gam ; gam = nifti_mat33_determ(X) ; } while(1){ Y = nifti_mat33_inverse(X) ; if( dif > 0.3 ){ // far from convergence alp = sqrt( nifti_mat33_rownorm(X) * nifti_mat33_colnorm(X) ) ; bet = sqrt( nifti_mat33_rownorm(Y) * nifti_mat33_colnorm(Y) ) ; gam = sqrt( bet / alp ) ; gmi = 1.0 / gam ; } else gam = gmi = 1.0 ; // close to convergence Z.m[0][0] = 0.5 * ( gam*X.m[0][0] + gmi*Y.m[0][0] ) ; Z.m[0][1] = 0.5 * ( gam*X.m[0][1] + gmi*Y.m[1][0] ) ; Z.m[0][2] = 0.5 * ( gam*X.m[0][2] + gmi*Y.m[2][0] ) ; Z.m[1][0] = 0.5 * ( gam*X.m[1][0] + gmi*Y.m[0][1] ) ; Z.m[1][1] = 0.5 * ( gam*X.m[1][1] + gmi*Y.m[1][1] ) ; Z.m[1][2] = 0.5 * ( gam*X.m[1][2] + gmi*Y.m[2][1] ) ; Z.m[2][0] = 0.5 * ( gam*X.m[2][0] + gmi*Y.m[0][2] ) ; Z.m[2][1] = 0.5 * ( gam*X.m[2][1] + gmi*Y.m[1][2] ) ; Z.m[2][2] = 0.5 * ( gam*X.m[2][2] + gmi*Y.m[2][2] ) ; dif = fabs(Z.m[0][0]-X.m[0][0])+fabs(Z.m[0][1]-X.m[0][1]) +fabs(Z.m[0][2]-X.m[0][2])+fabs(Z.m[1][0]-X.m[1][0]) +fabs(Z.m[1][1]-X.m[1][1])+fabs(Z.m[1][2]-X.m[1][2]) +fabs(Z.m[2][0]-X.m[2][0])+fabs(Z.m[2][1]-X.m[2][1]) +fabs(Z.m[2][2]-X.m[2][2]) ; k = k+1 ; if( k > 100 || dif < 3.e-6 ) break ; // convergence or exhaustion X = Z ; } return Z ; } void nifti_mat44_to_quatern( mat44 R , float *qb, float *qc, float *qd, float *qx, float *qy, float *qz, float *dx, float *dy, float *dz, float *qfac ) { double r11,r12,r13 , r21,r22,r23 , r31,r32,r33 ; double xd,yd,zd , a,b,c,d ; mat33 P,Q ; // offset outputs are read write out of input matrix ASSIF(qx,R.m[0][3]) ; ASSIF(qy,R.m[1][3]) ; ASSIF(qz,R.m[2][3]) ; // load 3x3 matrix into local variables */ r11 = R.m[0][0] ; r12 = R.m[0][1] ; r13 = R.m[0][2] ; r21 = R.m[1][0] ; r22 = R.m[1][1] ; r23 = R.m[1][2] ; r31 = R.m[2][0] ; r32 = R.m[2][1] ; r33 = R.m[2][2] ; // compute lengths of each column; these determine grid spacings xd = sqrt( r11*r11 + r21*r21 + r31*r31 ) ; yd = sqrt( r12*r12 + r22*r22 + r32*r32 ) ; zd = sqrt( r13*r13 + r23*r23 + r33*r33 ) ; // if a column length is zero, patch the trouble if( xd == 0.0l ){ r11 = 1.0l ; r21 = r31 = 0.0l ; xd = 1.0l ; } if( yd == 0.0l ){ r22 = 1.0l ; r12 = r32 = 0.0l ; yd = 1.0l ; } if( zd == 0.0l ){ r33 = 1.0l ; r13 = r23 = 0.0l ; zd = 1.0l ; } // assign the output lengths */ ASSIF(dx,xd) ; ASSIF(dy,yd) ; ASSIF(dz,zd) ; // normalize the columns */ r11 /= xd ; r21 /= xd ; r31 /= xd ; r12 /= yd ; r22 /= yd ; r32 /= yd ; r13 /= zd ; r23 /= zd ; r33 /= zd ; /* At this point, the matrix has normal columns, but we have to allow for the fact that the hideous user may not have given us a matrix with orthogonal columns. So, now find the orthogonal matrix closest to the current matrix. One reason for using the polar decomposition to get this orthogonal matrix, rather than just directly orthogonalizing the columns, is so that inputting the inverse matrix to R will result in the inverse orthogonal matrix at this point. If we just orthogonalized the columns, this wouldn't necessarily hold. */ Q.m[0][0] = r11 ; Q.m[0][1] = r12 ; Q.m[0][2] = r13 ; // load Q Q.m[1][0] = r21 ; Q.m[1][1] = r22 ; Q.m[1][2] = r23 ; Q.m[2][0] = r31 ; Q.m[2][1] = r32 ; Q.m[2][2] = r33 ; P = nifti_mat33_polar(Q) ; // P is orthog matrix closest to Q r11 = P.m[0][0] ; r12 = P.m[0][1] ; r13 = P.m[0][2] ; // unload r21 = P.m[1][0] ; r22 = P.m[1][1] ; r23 = P.m[1][2] ; r31 = P.m[2][0] ; r32 = P.m[2][1] ; r33 = P.m[2][2] ; // [ r11 r12 r13 ] // at this point, the matrix [ r21 r22 r23 ] is orthogonal // [ r31 r32 r33 ] // compute the determinant to determine if it is proper zd = r11*r22*r33-r11*r32*r23-r21*r12*r33 +r21*r32*r13+r31*r12*r23-r31*r22*r13 ; // should be -1 or 1 if( zd > 0 ){ // proper ASSIF(qfac,1.0) ; } else { // improper ==> flip 3rd column ASSIF(qfac,-1.0) ; r13 = -r13 ; r23 = -r23 ; r33 = -r33 ; } // now, compute quaternion parameters a = r11 + r22 + r33 + 1.0l ; if( a > 0.5l ){ // simplest case a = 0.5l * sqrt(a) ; b = 0.25l * (r32-r23) / a ; c = 0.25l * (r13-r31) / a ; d = 0.25l * (r21-r12) / a ; } else { // trickier case xd = 1.0 + r11 - (r22+r33) ; // 4*b*b yd = 1.0 + r22 - (r11+r33) ; // 4*c*c zd = 1.0 + r33 - (r11+r22) ; // 4*d*d if( xd > 1.0 ){ b = 0.5l * sqrt(xd) ; c = 0.25l* (r12+r21) / b ; d = 0.25l* (r13+r31) / b ; a = 0.25l* (r32-r23) / b ; } else if( yd > 1.0 ){ c = 0.5l * sqrt(yd) ; b = 0.25l* (r12+r21) / c ; d = 0.25l* (r23+r32) / c ; a = 0.25l* (r13-r31) / c ; } else { d = 0.5l * sqrt(zd) ; b = 0.25l* (r13+r31) / d ; c = 0.25l* (r23+r32) / d ; a = 0.25l* (r21-r12) / d ; } // if( a < 0.0l ){ b=-b ; c=-c ; d=-d; a=-a; } if( a < 0.0l ){ b=-b ; c=-c ; d=-d; } //a discarded... } ASSIF(qb,b) ; ASSIF(qc,c) ; ASSIF(qd,d) ; return ; } mat44 nifti_quatern_to_mat44( float qb, float qc, float qd, float qx, float qy, float qz, float dx, float dy, float dz, float qfac ) { mat44 R ; double a,b=qb,c=qc,d=qd , xd,yd,zd ; /* last row is always [ 0 0 0 1 ] */ R.m[3][0]=R.m[3][1]=R.m[3][2] = 0.0f ; R.m[3][3]= 1.0f ; /* compute a parameter from b,c,d */ a = 1.0l - (b*b + c*c + d*d) ; if( a < 1.e-7l ){ /* special case */ a = 1.0l / sqrt(b*b+c*c+d*d) ; b *= a ; c *= a ; d *= a ; /* normalize (b,c,d) vector */ a = 0.0l ; /* a = 0 ==> 180 degree rotation */ } else{ a = sqrt(a) ; /* angle = 2*arccos(a) */ } /* load rotation matrix, including scaling factors for voxel sizes */ xd = (dx > 0.0) ? dx : 1.0l ; /* make sure are positive */ yd = (dy > 0.0) ? dy : 1.0l ; zd = (dz > 0.0) ? dz : 1.0l ; if( qfac < 0.0 ) zd = -zd ; /* left handedness? */ R.m[0][0] = (float)( (a*a+b*b-c*c-d*d) * xd) ; R.m[0][1] = 2.0l * (b*c-a*d ) * yd ; R.m[0][2] = 2.0l * (b*d+a*c ) * zd ; R.m[1][0] = 2.0l * (b*c+a*d ) * xd ; R.m[1][1] = (float)( (a*a+c*c-b*b-d*d) * yd) ; R.m[1][2] = 2.0l * (c*d-a*b ) * zd ; R.m[2][0] = 2.0l * (b*d-a*c ) * xd ; R.m[2][1] = 2.0l * (c*d+a*b ) * yd ; R.m[2][2] = (float)( (a*a+d*d-c*c-b*b) * zd) ; /* load offsets */ R.m[0][3] = qx ; R.m[1][3] = qy ; R.m[2][3] = qz ; return R ; } mat44 nifti_mat44_inverse( mat44 R ) { double r11,r12,r13,r21,r22,r23,r31,r32,r33,v1,v2,v3 , deti ; mat44 Q ; /* INPUT MATRIX IS: */ r11 = R.m[0][0]; r12 = R.m[0][1]; r13 = R.m[0][2]; // [ r11 r12 r13 v1 ] r21 = R.m[1][0]; r22 = R.m[1][1]; r23 = R.m[1][2]; // [ r21 r22 r23 v2 ] r31 = R.m[2][0]; r32 = R.m[2][1]; r33 = R.m[2][2]; // [ r31 r32 r33 v3 ] v1 = R.m[0][3]; v2 = R.m[1][3]; v3 = R.m[2][3]; // [ 0 0 0 1 ] deti = r11*r22*r33-r11*r32*r23-r21*r12*r33 +r21*r32*r13+r31*r12*r23-r31*r22*r13 ; if( deti != 0.0l ) deti = 1.0l / deti ; Q.m[0][0] = deti*( r22*r33-r32*r23) ; Q.m[0][1] = deti*(-r12*r33+r32*r13) ; Q.m[0][2] = deti*( r12*r23-r22*r13) ; Q.m[0][3] = deti*(-r12*r23*v3+r12*v2*r33+r22*r13*v3 -r22*v1*r33-r32*r13*v2+r32*v1*r23) ; Q.m[1][0] = deti*(-r21*r33+r31*r23) ; Q.m[1][1] = deti*( r11*r33-r31*r13) ; Q.m[1][2] = deti*(-r11*r23+r21*r13) ; Q.m[1][3] = deti*( r11*r23*v3-r11*v2*r33-r21*r13*v3 +r21*v1*r33+r31*r13*v2-r31*v1*r23) ; Q.m[2][0] = deti*( r21*r32-r31*r22) ; Q.m[2][1] = deti*(-r11*r32+r31*r12) ; Q.m[2][2] = deti*( r11*r22-r21*r12) ; Q.m[2][3] = deti*(-r11*r22*v3+r11*r32*v2+r21*r12*v3 -r21*r32*v1-r31*r12*v2+r31*r22*v1) ; Q.m[3][0] = Q.m[3][1] = Q.m[3][2] = 0.0l ; Q.m[3][3] = (deti == 0.0l) ? 0.0l : 1.0l ; // failure flag if deti == 0 return Q ; } #endif // Eigen decomposition for symmetric 3x3 matrices, port of public domain Java Matrix library JAMA. // Connelly Barnes http://barnesc.blogspot.com/2007/02/eigenvectors-of-3x3-symmetric-matrix.html // see also https://www.geometrictools.com/Documentation/RobustEigenSymmetric3x3.pdf //begin: Connelly Barnes code #include #ifdef MAX #undef MAX #endif #define MAX(a, b) ((a)>(b)?(a):(b)) #define n 3 static double hypot2(double x, double y) { return sqrt(x*x+y*y); } // Symmetric Householder reduction to tridiagonal form. static void tred2(double V[n][n], double d[n], double e[n]) { // This is derived from the Algol procedures tred2 by // Bowdler, Martin, Reinsch, and Wilkinson, Handbook for // Auto. Comp., Vol.ii-Linear Algebra, and the corresponding // Fortran subroutine in EISPACK. for (int j = 0; j < n; j++) { d[j] = V[n-1][j]; } // Householder reduction to tridiagonal form. for (int i = n-1; i > 0; i--) { // Scale to avoid under/overflow. double scale = 0.0; double h = 0.0; for (int k = 0; k < i; k++) { scale = scale + fabs(d[k]); } if (scale == 0.0) { e[i] = d[i-1]; for (int j = 0; j < i; j++) { d[j] = V[i-1][j]; V[i][j] = 0.0; V[j][i] = 0.0; } } else { // Generate Householder vector. for (int k = 0; k < i; k++) { d[k] /= scale; h += d[k] * d[k]; } double f = d[i-1]; double g = sqrt(h); if (f > 0) { g = -g; } e[i] = scale * g; h = h - f * g; d[i-1] = f - g; for (int j = 0; j < i; j++) { e[j] = 0.0; } // Apply similarity transformation to remaining columns. for (int j = 0; j < i; j++) { f = d[j]; V[j][i] = f; g = e[j] + V[j][j] * f; for (int k = j+1; k <= i-1; k++) { g += V[k][j] * d[k]; e[k] += V[k][j] * f; } e[j] = g; } f = 0.0; for (int j = 0; j < i; j++) { e[j] /= h; f += e[j] * d[j]; } double hh = f / (h + h); for (int j = 0; j < i; j++) { e[j] -= hh * d[j]; } for (int j = 0; j < i; j++) { f = d[j]; g = e[j]; for (int k = j; k <= i-1; k++) { V[k][j] -= (f * e[k] + g * d[k]); } d[j] = V[i-1][j]; V[i][j] = 0.0; } } d[i] = h; } // Accumulate transformations. for (int i = 0; i < n-1; i++) { V[n-1][i] = V[i][i]; V[i][i] = 1.0; double h = d[i+1]; if (h != 0.0) { for (int k = 0; k <= i; k++) { d[k] = V[k][i+1] / h; } for (int j = 0; j <= i; j++) { double g = 0.0; for (int k = 0; k <= i; k++) { g += V[k][i+1] * V[k][j]; } for (int k = 0; k <= i; k++) { V[k][j] -= g * d[k]; } } } for (int k = 0; k <= i; k++) { V[k][i+1] = 0.0; } } for (int j = 0; j < n; j++) { d[j] = V[n-1][j]; V[n-1][j] = 0.0; } V[n-1][n-1] = 1.0; e[0] = 0.0; } // Symmetric tridiagonal QL algorithm. static void tql2(double V[n][n], double d[n], double e[n]) { // This is derived from the Algol procedures tql2, by // Bowdler, Martin, Reinsch, and Wilkinson, Handbook for // Auto. Comp., Vol.ii-Linear Algebra, and the corresponding // Fortran subroutine in EISPACK. for (int i = 1; i < n; i++) { e[i-1] = e[i]; } e[n-1] = 0.0; double f = 0.0; double tst1 = 0.0; double eps = pow(2.0,-52.0); for (int l = 0; l < n; l++) { // Find small subdiagonal element tst1 = MAX(tst1,fabs(d[l]) + fabs(e[l])); int m = l; while (m < n) { if (fabs(e[m]) <= eps*tst1) { break; } m++; } // If m == l, d[l] is an eigenvalue, // otherwise, iterate. if (m > l) { int iter = 0; do { iter = iter + 1; // (Could check iteration count here.) // Compute implicit shift double g = d[l]; double p = (d[l+1] - g) / (2.0 * e[l]); double r = hypot2(p,1.0); if (p < 0) { r = -r; } d[l] = e[l] / (p + r); d[l+1] = e[l] * (p + r); double dl1 = d[l+1]; double h = g - d[l]; for (int i = l+2; i < n; i++) { d[i] -= h; } f = f + h; // Implicit QL transformation. p = d[m]; double c = 1.0; double c2 = c; double c3 = c; double el1 = e[l+1]; double s = 0.0; double s2 = 0.0; for (int i = m-1; i >= l; i--) { c3 = c2; c2 = c; s2 = s; g = c * e[i]; h = c * p; r = hypot2(p,e[i]); e[i+1] = s * r; s = e[i] / r; c = p / r; p = c * d[i] - s * g; d[i+1] = h + s * (c * g + s * d[i]); // Accumulate transformation. for (int k = 0; k < n; k++) { h = V[k][i+1]; V[k][i+1] = s * V[k][i] + c * h; V[k][i] = c * V[k][i] - s * h; } } p = -s * s2 * c3 * el1 * e[l] / dl1; e[l] = s * p; d[l] = c * p; // Check for convergence. } while (fabs(e[l]) > eps*tst1); } d[l] = d[l] + f; e[l] = 0.0; } // Sort eigenvalues and corresponding vectors. for (int i = 0; i < n-1; i++) { int k = i; double p = d[i]; for (int j = i+1; j < n; j++) { if (d[j] < p) { k = j; p = d[j]; } } if (k != i) { d[k] = d[i]; d[i] = p; for (int j = 0; j < n; j++) { p = V[j][i]; V[j][i] = V[j][k]; V[j][k] = p; } } } } void eigen_decomposition(double A[n][n], double V[n][n], double d[n]) { double e[n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { V[i][j] = A[i][j]; } } tred2(V, d, e); tql2(V, d, e); } //end: Connelly Barnes code /*void printMat(char ch, double A[3][3]) { printf("%c=[%g %g %g; %g %g %g; %g %g %g];\n", ch, A[0][0],A[0][1],A[0][2], A[1][0],A[1][1],A[1][2], A[2][0],A[2][1],A[2][2]); }*/ vec3 nifti_mat33_eig3(double bxx, double bxy, double bxz, double byy, double byz, double bzz) { double A[3][3]; A[0][0] = bxx; A[0][1] = bxy; A[0][2] = bxz; A[1][0] = bxy; A[1][1] = byy; A[1][2] = byz; A[2][0] = bxz; A[2][1] = byz; A[2][2] = bzz; double V[3][3]; double d[3]; eigen_decomposition(A,V,d); //printMat('A',A); //printf("[V,D] = eig(A) %%where A*V = V*D\n"); //printMat('V',V); //printf("D = [%g 0 0; 0 %g 0; 0 0 %g]\n", d[0], d[1], d[2]); vec3 v3; v3.v[0] = V[0][2]; v3.v[1] = V[1][2]; v3.v[2] = V[2][2]; if (v3.v[0] < 0.0) { //B-matrix underspecified to describe B-vector // Describes direction but not polarity of B-vector // e.g. We get an eigenvector, but eigenvalue can be + or - // https://github.com/rordenlab/dcm2niix/issues/265 // Like B2q We set the vector to have a positive x component by convention. // https://raw.githubusercontent.com/matthew-brett/nibabel/master/nibabel/nicom/dwiparams.py // This ensures eddy will see this as a half shell and not attempt to interpret arbitrary signs v3.v[0] = -v3.v[0]; v3.v[1] = -v3.v[1]; v3.v[2] = -v3.v[2]; } //printf("bvec = [%g 0 0; 0 %g 0; 0 0 %g]\n", v3.v[0], v3.v[1], v3.v[2]); return v3; } dcm2niix-1.0.20211006/console/nifti1_io_core.h000066400000000000000000000065441412761503300205000ustar00rootroot00000000000000//this minimal set of nifti routines is based on nifti1_io without the dependencies (zlib) and a few extra functions // http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.h // http://niftilib.sourceforge.net #ifndef _NIFTI_IO_CORE_HEADER_ #define _NIFTI_IO_CORE_HEADER_ #ifdef USING_R #define STRICT_R_HEADERS #include #include "RNifti.h" #else #include "nifti1.h" #include #endif #ifdef __cplusplus extern "C" { #endif #include //requires VS 2015 or later #include #ifndef USING_R typedef struct { /** 4x4 matrix struct **/ float m[3][3] ; } mat33 ; typedef struct { /** 4x4 matrix struct **/ float m[4][4] ; } mat44 ; #endif typedef struct { /** x4 vector struct **/ float v[4] ; } vec4 ; typedef struct { /** x3 vector struct **/ float v[3] ; } vec3 ; typedef struct { /** x4 vector struct INTEGER**/ int v[3] ; } ivec3 ; #define LOAD_MAT33(AA,a11,a12,a13 ,a21,a22,a23 ,a31,a32,a33) \ ( AA.m[0][0]=a11 , AA.m[0][1]=a12 , AA.m[0][2]=a13 , \ AA.m[1][0]=a21 , AA.m[1][1]=a22 , AA.m[1][2]=a23 , \ AA.m[2][0]=a31 , AA.m[2][1]=a32 , AA.m[2][2]=a33 ) #define LOAD_MAT44(AA,a11,a12,a13,a14,a21,a22,a23,a24,a31,a32,a33,a34) \ ( AA.m[0][0]=a11 , AA.m[0][1]=a12 , AA.m[0][2]=a13 , AA.m[0][3]=a14 , \ AA.m[1][0]=a21 , AA.m[1][1]=a22 , AA.m[1][2]=a23 , AA.m[1][3]=a24 , \ AA.m[2][0]=a31 , AA.m[2][1]=a32 , AA.m[2][2]=a33 , AA.m[2][3]=a34 , \ AA.m[3][0]=AA.m[3][1]=AA.m[3][2]=0.0f , AA.m[3][3]=1.0f ) #undef ASSIF // assign v to *p, if possible #define ASSIF(p,v) if( (p)!=NULL ) *(p) = (v) float dotProduct(vec3 u, vec3 v); float nifti_mat33_determ( mat33 R ) ; int isSameFloat (float a, float b) ; int isSameDouble (double a, double b) ; bool littleEndianPlatform (); vec3 nifti_mat33_eig3(double bxx, double bxy, double bxz, double byy, double byz, double bzz); mat33 nifti_mat33_inverse( mat33 R ); mat33 nifti_mat33_mul( mat33 A , mat33 B ); mat33 nifti_mat33_transpose( mat33 A ) ; mat44 nifti_dicom2mat(float orient[7], float patientPosition[4], float xyzMM[4]); mat44 nifti_mat44_inverse( mat44 R ); mat44 nifti_mat44_mul( mat44 A , mat44 B ); vec3 crossProduct(vec3 u, vec3 v); vec3 nifti_vect33_norm (vec3 v); vec4 nifti_vect44_norm (vec4 v); vec3 nifti_vect33mat33_mul(vec3 v, mat33 m ); 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 // 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) ; #endif vec4 nifti_vect44mat44_mul(vec4 v, mat44 m ); void nifti_swap_2bytes( size_t n , void *ar ); // 2 bytes at a time void nifti_swap_4bytes( size_t n , void *ar ); // 4 bytes at a time void nifti_swap_8bytes( size_t n , void *ar ); // 8 bytes at a time void nifti_mat44_to_quatern( mat44 R , float *qb, float *qc, float *qd, float *qx, float *qy, float *qz, float *dx, float *dy, float *dz, float *qfac ); mat44 nifti_quatern_to_mat44( float qb, float qc, float qd, float qx, float qy, float qz, float dx, float dy, float dz, float qfac ); #ifdef __cplusplus } #endif #endif dcm2niix-1.0.20211006/console/nii_dicom.cpp000066400000000000000000011602701412761503300200720ustar00rootroot00000000000000//#define MY_DEBUG #if defined(_WIN64) || defined(_WIN32) #include //write to registry #endif #ifdef _MSC_VER #include #define getcwd _getcwd #define chdir _chrdir #include "io.h" #include //#define snprintf _snprintf //#define vsnprintf _vsnprintf #define strcasecmp _stricmp #define strncasecmp _strnicmp #ifdef _WIN32 #pragma comment(lib, "advapi32") #endif #else #include #endif //#include //clock() #ifndef USING_R #include "nifti1.h" #endif #include "jpg_0XC3.h" #include "nifti1_io_core.h" #include "nii_dicom.h" #include "print.h" #include //toupper #include #include #include #include #include #include #include #include // discriminate files from folders #include #ifdef USING_R #undef isnan #define isnan ISNAN #endif #ifndef myDisableClassicJPEG #ifdef myTurboJPEG #include #else #include "ujpeg.h" #endif #endif #ifdef myEnableJasper #include #endif #ifndef myDisableOpenJPEG #include "openjpeg.h" #ifdef myEnableJasper ERROR : YOU CAN NOT COMPILE WITH myEnableJasper AND NOT myDisableOpenJPEG OPTIONS SET SIMULTANEOUSLY #endif unsigned char * imagetoimg(opj_image_t *image) { int numcmpts = image->numcomps; int sgnd = image->comps[0].sgnd; int width = image->comps[0].w; int height = image->comps[0].h; int bpp = (image->comps[0].prec + 7) >> 3; //e.g. 12 bits requires 2 bytes int imgbytes = bpp * width * height * numcmpts; bool isOK = true; if (numcmpts > 1) { for (int comp = 1; comp < numcmpts; comp++) { //check RGB data if (image->comps[0].w != image->comps[comp].w) isOK = false; if (image->comps[0].h != image->comps[comp].h) isOK = false; if (image->comps[0].dx != image->comps[comp].dx) isOK = false; if (image->comps[0].dy != image->comps[comp].dy) isOK = false; if (image->comps[0].prec != image->comps[comp].prec) isOK = false; if (image->comps[0].sgnd != image->comps[comp].sgnd) isOK = false; } if (numcmpts != 3) isOK = false; //we only handle Gray and RedGreenBlue, not GrayAlpha or RedGreenBlueAlpha if (image->comps[0].prec != 8) isOK = false; //only 8-bit for RGB data } if ((image->comps[0].prec < 1) || (image->comps[0].prec > 16)) isOK = false; //currently we only handle 1 and 2 byte data if (!isOK) { printMessage("jpeg decode failure w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version()); return NULL; } #ifdef MY_DEBUG printMessage("w*h %d*%d bpp %d sgnd %d components %d OpenJPEG=%s\n", width, height, bpp, sgnd, numcmpts, opj_version()); #endif //extract the data if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) { printError("Catastrophic decompression error\n"); return NULL; } unsigned char *img = (unsigned char *)malloc(imgbytes); uint16_t *img16ui = (uint16_t *)img; //unsigned 16-bit int16_t *img16i = (int16_t *)img; //signed 16-bit if (sgnd) bpp = -bpp; if (bpp == -1) { free(img); printError("Signed 8-bit DICOM?\n"); return NULL; } //n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB int pix = 0; //output pixel for (int cmptno = 0; cmptno < numcmpts; ++cmptno) { int cpix = 0; //component pixel int *v = image->comps[cmptno].data; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { switch (bpp) { case 1: img[pix] = (unsigned char)v[cpix]; break; case 2: img16ui[pix] = (uint16_t)v[cpix]; break; case -2: img16i[pix] = (int16_t)v[cpix]; break; } pix++; cpix++; } //for x } //for y } //for each component return img; } // imagetoimg() typedef struct bufinfo { unsigned char *buf; unsigned char *cur; size_t len; } BufInfo; static void my_stream_free(void *p_user_data) { //do nothing //BufInfo d = (BufInfo) p_user_data; //free(d.buf); } // my_stream_free() static OPJ_UINT32 opj_read_from_buffer(void *p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo *p_file) { OPJ_UINT32 l_nb_read; if (p_file->cur + p_nb_bytes < p_file->buf + p_file->len) { l_nb_read = p_nb_bytes; } else { l_nb_read = (OPJ_UINT32)(p_file->buf + p_file->len - p_file->cur); } memcpy(p_buffer, p_file->cur, l_nb_read); p_file->cur += l_nb_read; return l_nb_read ? l_nb_read : ((OPJ_UINT32)-1); } //opj_read_from_buffer() static OPJ_UINT32 opj_write_from_buffer(void *p_buffer, OPJ_UINT32 p_nb_bytes, BufInfo *p_file) { memcpy(p_file->cur, p_buffer, p_nb_bytes); p_file->cur += p_nb_bytes; p_file->len += p_nb_bytes; return p_nb_bytes; } // opj_write_from_buffer() static OPJ_SIZE_T opj_skip_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo *p_file) { if (p_file->cur + p_nb_bytes < p_file->buf + p_file->len) { p_file->cur += p_nb_bytes; return p_nb_bytes; } p_file->cur = p_file->buf + p_file->len; return (OPJ_SIZE_T)-1; } //opj_skip_from_buffer() //fix for https://github.com/neurolabusc/dcm_qa/issues/5 static OPJ_BOOL opj_seek_from_buffer(OPJ_SIZE_T p_nb_bytes, BufInfo *p_file) { //printf("opj_seek_from_buffer %d + %d -> %d + %d\n", p_file->cur , p_nb_bytes, p_file->buf, p_file->len); if (p_nb_bytes < p_file->len) { p_file->cur = p_file->buf + p_nb_bytes; return OPJ_TRUE; } p_file->cur = p_file->buf + p_file->len; return OPJ_FALSE; } //opj_seek_from_buffer() opj_stream_t *opj_stream_create_buffer_stream(BufInfo *p_file, OPJ_UINT32 p_size, OPJ_BOOL p_is_read_stream) { opj_stream_t *l_stream; if (!p_file) return NULL; l_stream = opj_stream_create(p_size, p_is_read_stream); if (!l_stream) return NULL; opj_stream_set_user_data(l_stream, p_file, my_stream_free); opj_stream_set_user_data_length(l_stream, p_file->len); opj_stream_set_read_function(l_stream, (opj_stream_read_fn)opj_read_from_buffer); opj_stream_set_write_function(l_stream, (opj_stream_write_fn)opj_write_from_buffer); opj_stream_set_skip_function(l_stream, (opj_stream_skip_fn)opj_skip_from_buffer); opj_stream_set_seek_function(l_stream, (opj_stream_seek_fn)opj_seek_from_buffer); return l_stream; } //opj_stream_create_buffer_stream() unsigned char *nii_loadImgCoreOpenJPEG(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) { //OpenJPEG library is not well documented and has changed between versions //Since the JPEG is embedded in a DICOM we need to skip bytes at the start of the file // In theory we might also want to strip data that exists AFTER the image, see gdcmJPEG2000Codec.c unsigned char *ret = NULL; opj_dparameters_t params; opj_codec_t *codec; opj_image_t *jpx; opj_stream_t *stream; FILE *reader = fopen(imgname, "rb"); fseek(reader, 0, SEEK_END); long size = ftell(reader) - dcm.imageStart; if (size <= 8) return NULL; fseek(reader, dcm.imageStart, SEEK_SET); unsigned char *data = (unsigned char *)malloc(size); size_t sz = fread(data, 1, size, reader); fclose(reader); if (sz < size) return NULL; OPJ_CODEC_FORMAT format = OPJ_CODEC_JP2; //DICOM JPEG2k is SUPPOSED to start with codestream, but some vendors include a header if (data[0] == 0xFF && data[1] == 0x4F && data[2] == 0xFF && data[3] == 0x51) format = OPJ_CODEC_J2K; opj_set_default_decoder_parameters(¶ms); BufInfo dx; dx.buf = data; dx.cur = data; dx.len = size; stream = opj_stream_create_buffer_stream(&dx, (OPJ_UINT32)size, true); if (stream == NULL) return NULL; codec = opj_create_decompress(format); // setup the decoder decoding parameters using user parameters if (!opj_setup_decoder(codec, ¶ms)) goto cleanup2; // Read the main header of the codestream and if necessary the JP2 boxes if (!opj_read_header(stream, codec, &jpx)) { printError("OpenJPEG failed to read the header %s (offset %d)\n", imgname, dcm.imageStart); //comment these next lines to abort: include these to create zero-padded slice #ifdef MY_ZEROFILLBROKENJPGS //fix broken slices https://github.com/scitran-apps/dcm2niix/issues/4 printError("Zero-filled slice created\n"); int imgbytes = (hdr.bitpix / 8) * hdr.dim[1] * hdr.dim[2]; ret = (unsigned char *)calloc(imgbytes, 1); #endif goto cleanup2; } // Get the decoded image if (!(opj_decode(codec, stream, jpx) && opj_end_decompress(codec, stream))) { printError("OpenJPEG j2k_to_image failed to decode %s\n", imgname); goto cleanup1; } ret = imagetoimg(jpx); cleanup1: opj_image_destroy(jpx); cleanup2: free(dx.buf); opj_stream_destroy(stream); opj_destroy_codec(codec); return ret; } #endif //myDisableOpenJPEG #ifndef M_PI #define M_PI 3.14159265358979323846 #endif float deFuzz(float v) { if (fabs(v) < 0.00001) return 0; else return v; } #ifdef MY_DEBUG void reportMat33(char *str, mat33 A) { printMessage("%s = [%g %g %g ; %g %g %g; %g %g %g ]\n", str, deFuzz(A.m[0][0]), deFuzz(A.m[0][1]), deFuzz(A.m[0][2]), deFuzz(A.m[1][0]), deFuzz(A.m[1][1]), deFuzz(A.m[1][2]), deFuzz(A.m[2][0]), deFuzz(A.m[2][1]), deFuzz(A.m[2][2])); } void reportMat44(char *str, mat44 A) { //example: reportMat44((char*)"out",*R); printMessage("%s = [%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1]\n", str, deFuzz(A.m[0][0]), deFuzz(A.m[0][1]), deFuzz(A.m[0][2]), deFuzz(A.m[0][3]), deFuzz(A.m[1][0]), deFuzz(A.m[1][1]), deFuzz(A.m[1][2]), deFuzz(A.m[1][3]), deFuzz(A.m[2][0]), deFuzz(A.m[2][1]), deFuzz(A.m[2][2]), deFuzz(A.m[2][3])); } #endif int verify_slice_dir(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, mat44 *R, int isVerbose) { //returns slice direction: 1=sag,2=coronal,3=axial, -= flipped if (h->dim[3] < 2) return 0; //don't care direction for single slice int iSL = 1; //find Z-slice direction: row with highest magnitude of 3rd column if ((fabs(R->m[1][2]) >= fabs(R->m[0][2])) && (fabs(R->m[1][2]) >= fabs(R->m[2][2]))) iSL = 2; // if ((fabs(R->m[2][2]) >= fabs(R->m[0][2])) && (fabs(R->m[2][2]) >= fabs(R->m[1][2]))) iSL = 3; //axial acquisition float pos = NAN; if (!isnan(d2.patientPosition[iSL])) { //patient position fields exist pos = d2.patientPosition[iSL]; if (isSameFloat(pos, d.patientPosition[iSL])) pos = NAN; #ifdef MY_DEBUG if (!isnan(pos)) printMessage("position determined using lastFile %f\n", pos); #endif } if (isnan(pos) && (!isnan(d.patientPositionLast[iSL]))) { //patient position fields exist pos = d.patientPositionLast[iSL]; if (isSameFloat(pos, d.patientPosition[iSL])) pos = NAN; #ifdef MY_DEBUG if (!isnan(pos)) printMessage("position determined using last (4d) %f\n", pos); #endif } if (isnan(pos) && (!isnan(d.stackOffcentre[iSL]))) pos = d.stackOffcentre[iSL]; if (isnan(pos) && (!isnan(d.lastScanLoc))) pos = d.lastScanLoc; vec4 x; x.v[0] = 0.0; x.v[1] = 0.0; x.v[2] = (float)(h->dim[3] - 1.0); x.v[3] = 1.0; vec4 pos1v = nifti_vect44mat44_mul(x, *R); float pos1 = pos1v.v[iSL - 1]; //-1 as C indexed from 0 bool flip = false; if (!isnan(pos)) // we have real SliceLocation for last slice or volume center flip = (pos > R->m[iSL - 1][3]) != (pos1 > R->m[iSL - 1][3]); // same direction?, note C indices from 0 else { // we do some guess work and warn user vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]); vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]); //printMessage("rd %g %g %g\n",readV.v[0],readV.v[1],readV.v[2]); //printMessage("ph %g %g %g\n",phaseV.v[0],phaseV.v[1],phaseV.v[2]); vec3 sliceV = crossProduct(readV, phaseV); //order important: this is our hail mary flip = ((sliceV.v[0] + sliceV.v[1] + sliceV.v[2]) < 0); //printMessage("verify slice dir %g %g %g\n",sliceV.v[0],sliceV.v[1],sliceV.v[2]); if (isVerbose) { //1st pass only if (!d.isDerived) { //do not warn user if image is derived printWarning("Unable to determine slice direction: please check whether slices are flipped\n"); } else { printWarning("Unable to determine slice direction: please check whether slices are flipped (derived image)\n"); } } } if (flip) { for (int i = 0; i < 4; i++) R->m[i][2] = -R->m[i][2]; } if (flip) iSL = -iSL; #ifdef MY_DEBUG printMessage("verify slice dir %d %d %d\n", h->dim[1], h->dim[2], h->dim[3]); //reportMat44((char*)"Rout",*R); printMessage("flip = %d\n", flip); printMessage("sliceDir = %d\n", iSL); printMessage(" pos1 = %f\n", pos1); #endif return iSL; } //verify_slice_dir() mat44 noNaN(mat44 Q44, bool isVerbose, bool *isBogus) //simplify any headers that have NaN values { mat44 ret = Q44; bool isNaN44 = false; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) if (isnan(ret.m[i][j])) isNaN44 = true; if (isNaN44) { *isBogus = true; if (isVerbose) printWarning("Bogus spatial matrix (perhaps non-spatial image): inspect spatial orientation\n"); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) if (i == j) ret.m[i][j] = 1; else ret.m[i][j] = 0; ret.m[1][1] = -1; } //if isNaN detected return ret; } #define kSessionOK 0 #define kSessionBadMatrix 1 void setQSForm(struct nifti_1_header *h, mat44 Q44i, bool isVerbose) { bool isBogus = false; mat44 Q44 = noNaN(Q44i, isVerbose, &isBogus); if ((h->session_error == kSessionBadMatrix) || (isBogus)) { h->session_error = kSessionBadMatrix; h->sform_code = NIFTI_XFORM_UNKNOWN; } else h->sform_code = NIFTI_XFORM_SCANNER_ANAT; h->srow_x[0] = Q44.m[0][0]; h->srow_x[1] = Q44.m[0][1]; h->srow_x[2] = Q44.m[0][2]; h->srow_x[3] = Q44.m[0][3]; h->srow_y[0] = Q44.m[1][0]; h->srow_y[1] = Q44.m[1][1]; h->srow_y[2] = Q44.m[1][2]; h->srow_y[3] = Q44.m[1][3]; h->srow_z[0] = Q44.m[2][0]; h->srow_z[1] = Q44.m[2][1]; h->srow_z[2] = Q44.m[2][2]; h->srow_z[3] = Q44.m[2][3]; float dumdx, dumdy, dumdz; nifti_mat44_to_quatern(Q44, &h->quatern_b, &h->quatern_c, &h->quatern_d, &h->qoffset_x, &h->qoffset_y, &h->qoffset_z, &dumdx, &dumdy, &dumdz, &h->pixdim[0]); h->qform_code = h->sform_code; } //setQSForm() #ifdef my_unused ivec3 maxCol(mat33 R) { //return index of maximum column in 3x3 matrix, e.g. [1 0 0; 0 1 0; 0 0 1] -> 1,2,3 ivec3 ixyz; mat33 foo; for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) foo.m[i][j] = fabs(R.m[i][j]); //ixyz.v[0] : row with largest value in column 1 ixyz.v[0] = 1; if ((foo.m[1][0] > foo.m[0][0]) && (foo.m[1][0] >= foo.m[2][0])) ixyz.v[0] = 2; //2nd column largest column else if ((foo.m[2][0] > foo.m[0][0]) && (foo.m[2][0] > foo.m[1][0])) ixyz.v[0] = 3; //3rd column largest column //ixyz.v[1] : row with largest value in column 2, but not the same row as ixyz.v[1] if (ixyz.v[0] == 1) { ixyz.v[1] = 2; if (foo.m[2][1] > foo.m[1][1]) ixyz.v[1] = 3; } else if (ixyz.v[0] == 2) { ixyz.v[1] = 1; if (foo.m[2][1] > foo.m[0][1]) ixyz.v[1] = 3; } else { //ixyz.v[0] == 3 ixyz.v[1] = 1; if (foo.m[1][1] > foo.m[0][1]) ixyz.v[1] = 2; } //ixyz.v[2] : 3rd row, constrained by previous rows ixyz.v[2] = 6 - ixyz.v[1] - ixyz.v[0]; //sum of 1+2+3 return ixyz; } int sign(float x) { //returns -1,0,1 depending on if X is less than, equal to or greater than zero if (x < 0) return -1; else if (x > 0) return 1; return 0; } // Subfunction: get dicom xform matrix and related info // This is a direct port of Xiangrui Li's dicm2nii function mat44 xform_mat(struct TDICOMdata d) { vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]); vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]); vec3 sliceV = crossProduct(readV, phaseV); mat33 R; LOAD_MAT33(R, readV.v[0], readV.v[1], readV.v[2], phaseV.v[0], phaseV.v[1], phaseV.v[2], sliceV.v[0], sliceV.v[1], sliceV.v[2]); R = nifti_mat33_transpose(R); //reportMat33((char*)"R",R); ivec3 ixyz = maxCol(R); //printMessage("%d %d %d\n", ixyz.v[0], ixyz.v[1], ixyz.v[2]); int iSL = ixyz.v[2]; // 1/2/3 for Sag/Cor/Tra slice float cosSL = R.m[iSL - 1][2]; //printMessage("cosSL\t%g\n", cosSL); //vec3 pixdim = setVec3(d.xyzMM[1], d.xyzMM[2], d.xyzMM[3]); //printMessage("%g %g %g\n", pixdim.v[0], pixdim.v[1], pixdim.v[2]); mat33 pixdim; LOAD_MAT33(pixdim, d.xyzMM[1], 0.0, 0.0, 0.0, d.xyzMM[2], 0.0, 0.0, 0.0, d.xyzMM[3]); R = nifti_mat33_mul(R, pixdim); //reportMat33((char*)"R",R); mat44 R44; LOAD_MAT44(R44, R.m[0][0], R.m[0][1], R.m[0][2], d.patientPosition[1], R.m[1][0], R.m[1][1], R.m[1][2], d.patientPosition[2], R.m[2][0], R.m[2][1], R.m[2][2], d.patientPosition[3]); //reportMat44((char*)"R",R44); //rest are former: R = verify_slice_dir(R, s, dim, iSL) if ((d.xyzDim[3] < 2) && (d.CSA.mosaicSlices < 2)) return R44; //don't care direction for single slice vec3 dim = setVec3(d.xyzDim[1], d.xyzDim[2], d.xyzDim[3]); if (d.CSA.mosaicSlices > 1) { //Siemens mosaic: use dim(1) since no transpose to img float nRowCol = ceil(sqrt((double)d.CSA.mosaicSlices)); dim.v[0] = dim.v[0] / nRowCol; dim.v[1] = dim.v[1] / nRowCol; dim.v[2] = d.CSA.mosaicSlices; vec4 dim4 = setVec4((nRowCol - 1) * dim.v[0] / 2.0f, (nRowCol - 1) * dim.v[1] / 2.0f, 0); vec4 offset = nifti_vect44mat44_mul(dim4, R44); //printMessage("%g %g %g\n", dim.v[0], dim.v[1], dim.v[2]); //printMessage("%g %g %g\n", dim4.v[0], dim4.v[1], dim4.v[2]); //printMessage("%g %g %g %g\n", offset.v[0], offset.v[1], offset.v[2], offset.v[3]); //printMessage("nRowCol\t%g\n", nRowCol); R44.m[0][3] = offset.v[0]; R44.m[1][3] = offset.v[1]; R44.m[2][3] = offset.v[2]; //R44.m[3][3] = offset.v[3]; if (sign(d.CSA.sliceNormV[iSL]) != sign(cosSL)) { R44.m[0][2] = -R44.m[0][2]; R44.m[1][2] = -R44.m[1][2]; R44.m[2][2] = -R44.m[2][2]; R44.m[3][2] = -R44.m[3][2]; } //reportMat44((char*)"iR44",R44); return R44; } else if (true) { //SliceNormalVector TO DO printMessage("Not completed"); #ifndef USING_R exit(2); #endif return R44; } printMessage("Unable to determine spatial transform\n"); #ifndef USING_R exit(1); #else return R44; #endif } mat44 set_nii_header(struct TDICOMdata d) { mat44 R = xform_mat(d); //R(1:2,:) = -R(1:2,:); % dicom LPS to nifti RAS, xform matrix before reorient for (int i = 0; i < 2; i++) for (int j = 0; j < 4; j++) R.m[i][j] = -R.m[i][j]; #ifdef MY_DEBUG reportMat44((char *)"R44", R); #endif } #endif // This code predates Xiangrui Li's set_nii_header function mat44 set_nii_header_x(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int *sliceDir, int isVerbose) { *sliceDir = 0; mat44 Q44 = nifti_dicom2mat(d.orient, d.patientPosition, d.xyzMM); //Q44 = doQuadruped(Q44); if (d.isSegamiOasis == true) { //Segami reconstructions appear to disregard DICOM spatial parameters: assume center of volume is isocenter and no table tilt // Consider sample image with d.orient (0020,0037) = -1 0 0; 0 1 0: this suggests image RAI (L->R, P->A, S->I) but the vendors viewing software suggests LPS //Perhaps we should ignore 0020,0037 and 0020,0032 as they are hidden in sequence 0054,0022, but in this case no positioning is provided // http://www.cs.ucl.ac.uk/fileadmin/cmic/Documents/DavidAtkinson/DICOM.pdf // https://www.slicer.org/wiki/Coordinate_systems LOAD_MAT44(Q44, -h->pixdim[1], 0, 0, 0, 0, -h->pixdim[2], 0, 0, 0, 0, h->pixdim[3], 0); //X and Y dimensions flipped in NIfTI (RAS) vs DICOM (LPS) vec4 originVx = setVec4((h->dim[1] + 1.0f) / 2.0f, (h->dim[2] + 1.0f) / 2.0f, (h->dim[3] + 1.0f) / 2.0f); vec4 originMm = nifti_vect44mat44_mul(originVx, Q44); for (int i = 0; i < 3; i++) Q44.m[i][3] = -originMm.v[i]; //set origin to center voxel if (isVerbose) { //printMessage("origin (vx) %g %g %g\n",originVx.v[0],originVx.v[1],originVx.v[2]); //printMessage("origin (mm) %g %g %g\n",originMm.v[0],originMm.v[1],originMm.v[2]); printWarning("Segami coordinates defy DICOM convention, please check orientation\n"); } return Q44; } //next line only for Siemens mosaic: ignore for UIH grid // https://github.com/xiangruili/dicm2nii/commit/47ad9e6d9bc8a999344cbd487d602d420fb1509f if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.mosaicSlices > 1)) { double nRowCol = ceil(sqrt((double)d.CSA.mosaicSlices)); double lFactorX = (d.xyzDim[1] - (d.xyzDim[1] / nRowCol)) / 2.0; double lFactorY = (d.xyzDim[2] - (d.xyzDim[2] / nRowCol)) / 2.0; Q44.m[0][3] = (float)((Q44.m[0][0] * lFactorX) + (Q44.m[0][1] * lFactorY) + Q44.m[0][3]); Q44.m[1][3] = (float)((Q44.m[1][0] * lFactorX) + (Q44.m[1][1] * lFactorY) + Q44.m[1][3]); Q44.m[2][3] = (float)((Q44.m[2][0] * lFactorX) + (Q44.m[2][1] * lFactorY) + Q44.m[2][3]); for (int c = 0; c < 2; c++) for (int r = 0; r < 4; r++) Q44.m[c][r] = -Q44.m[c][r]; mat33 Q; LOAD_MAT33(Q, d.orient[1], d.orient[4], d.CSA.sliceNormV[1], d.orient[2], d.orient[5], d.CSA.sliceNormV[2], d.orient[3], d.orient[6], d.CSA.sliceNormV[3]); if (nifti_mat33_determ(Q) < 0) { //Siemens sagittal are R>>L, whereas NIfTI is L>>R, we retain Siemens order on disk so ascending is still ascending, but we need to have the spatial transform reflect this. mat44 det; *sliceDir = kSliceOrientMosaicNegativeDeterminant; //we need to handle DTI vectors accordingly LOAD_MAT44(det, 1.0l, 0.0l, 0.0l, 0.0l, 0.0l, 1.0l, 0.0l, 0.0l, 0.0l, 0.0l, -1.0l, 0.0l); //patient_to_tal.m[2][3] = 1-d.CSA.MosaicSlices; Q44 = nifti_mat44_mul(Q44, det); } } else { //not a mosaic *sliceDir = verify_slice_dir(d, d2, h, &Q44, isVerbose); for (int c = 0; c < 4; c++) // LPS to nifti RAS, xform matrix before reorient for (int r = 0; r < 2; r++) //swap rows 1 & 2 Q44.m[r][c] = -Q44.m[r][c]; } #ifdef MY_DEBUG reportMat44((char *)"Q44", Q44); #endif return Q44; } int headerDcm2NiiSForm(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose) { //fill header s and q form //see http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1_io.c //returns sliceDir: 0=unknown,1=sag,2=coro,3=axial,-=reversed slices int sliceDir = 0; if (h->dim[3] < 2) { mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose); setQSForm(h, Q44, isVerbose); return sliceDir; //don't care direction for single slice } h->sform_code = NIFTI_XFORM_UNKNOWN; h->qform_code = NIFTI_XFORM_UNKNOWN; bool isOK = false; for (int i = 1; i <= 6; i++) if (d.orient[i] != 0.0) isOK = true; if (!isOK) { //we will have to guess, assume axial acquisition saved in standard Siemens style? d.orient[1] = 1.0f; d.orient[2] = 0.0f; d.orient[3] = 0.0f; d.orient[1] = 0.0f; d.orient[2] = 1.0f; d.orient[3] = 0.0f; if ((d.isDerived) || ((d.bitsAllocated == 8) && (d.samplesPerPixel == 3) && (d.manufacturer == kMANUFACTURER_SIEMENS))) { printMessage("Unable to determine spatial orientation: 0020,0037 missing (probably not a problem: derived image)\n"); } else { printMessage("Unable to determine spatial orientation: 0020,0037 missing (Type 1 attribute: not a valid DICOM) Series %ld\n", d.seriesNum); } } mat44 Q44 = set_nii_header_x(d, d2, h, &sliceDir, isVerbose); setQSForm(h, Q44, isVerbose); return sliceDir; } //headerDcm2NiiSForm() int headerDcm2Nii2(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose) { //final pass after de-mosaic char txt[1024] = {""}; if (h->slice_code == NIFTI_SLICE_UNKNOWN) h->slice_code = d.CSA.sliceOrder; if (h->slice_code == NIFTI_SLICE_UNKNOWN) h->slice_code = d2.CSA.sliceOrder; //sometimes the first slice order is screwed up https://github.com/eauerbach/CMRR-MB/issues/29 if (d.modality == kMODALITY_MR) sprintf(txt, "TE=%.2g;Time=%.3f", d.TE, d.acquisitionTime); else sprintf(txt, "Time=%.3f", d.acquisitionTime); if (d.CSA.phaseEncodingDirectionPositive >= 0) { char dtxt[1024] = {""}; sprintf(dtxt, ";phase=%d", d.CSA.phaseEncodingDirectionPositive); strcat(txt, dtxt); } //from dicm2nii 20151117 InPlanePhaseEncodingDirection if (d.phaseEncodingRC == 'R') h->dim_info = (3 << 4) + (1 << 2) + 2; if (d.phaseEncodingRC == 'C') h->dim_info = (3 << 4) + (2 << 2) + 1; if (d.CSA.multiBandFactor > 1) { char dtxt[1024] = {""}; sprintf(dtxt, ";mb=%d", d.CSA.multiBandFactor); strcat(txt, dtxt); } // GCC 8 warns about truncation using snprintf // snprintf(h->descrip,80, "%s",txt); memcpy(h->descrip, txt, 79); h->descrip[79] = '\0'; if (strlen(d.imageComments) > 0) snprintf(h->aux_file, 24, "%.23s", d.imageComments); return headerDcm2NiiSForm(d, d2, h, isVerbose); } //headerDcm2Nii2() int dcmStrLen(int len, int kMaxLen) { if (len < kMaxLen) return len + 1; else return kMaxLen; } //dcmStrLen() struct TDICOMdata clear_dicom_data() { struct TDICOMdata d; //d.dti4D = NULL; d.locationsInAcquisition = 0; d.locationsInAcquisitionConflict = 0; //for GE discrepancy between tags 0020,1002; 0021,104F; 0054,0081 d.modality = kMODALITY_UNKNOWN; d.effectiveEchoSpacingGE = 0; for (int i = 0; i < 4; i++) { d.CSA.dtiV[i] = 0; d.patientPosition[i] = NAN; //d.patientPosition2nd[i] = NAN; //used to distinguish XYZT vs XYTZ for Philips 4D d.patientPositionLast[i] = NAN; //used to compute slice direction for Philips 4D d.stackOffcentre[i] = NAN; d.angulation[i] = 0.0f; d.xyzMM[i] = 1; } for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; ++i) d.dimensionIndexValues[i] = 0; //d.CSA.sliceTiming[0] = -1.0f; //impossible value denotes not known for (int z = 0; z < kMaxEPI3D; z++) d.CSA.sliceTiming[z] = -1.0; d.CSA.numDti = 0; for (int i = 0; i < 5; i++) d.xyzDim[i] = 1; for (int i = 0; i < 7; i++) d.orient[i] = 0.0f; strcpy(d.patientName, ""); strcpy(d.patientID, ""); strcpy(d.accessionNumber, ""); strcpy(d.imageType, ""); strcpy(d.imageComments, ""); strcpy(d.imageBaseName, ""); strcpy(d.phaseEncodingDirectionDisplayedUIH, ""); strcpy(d.studyDate, ""); strcpy(d.studyTime, ""); strcpy(d.protocolName, ""); strcpy(d.seriesDescription, ""); strcpy(d.sequenceName, ""); strcpy(d.scanningSequence, ""); strcpy(d.sequenceVariant, ""); strcpy(d.manufacturersModelName, ""); strcpy(d.institutionalDepartmentName, ""); strcpy(d.procedureStepDescription, ""); strcpy(d.institutionName, ""); strcpy(d.referringPhysicianName, ""); strcpy(d.institutionAddress, ""); strcpy(d.deviceSerialNumber, ""); strcpy(d.softwareVersions, ""); strcpy(d.stationName, ""); strcpy(d.scanOptions, ""); //strcpy(d.mrAcquisitionType, ""); strcpy(d.seriesInstanceUID, ""); strcpy(d.instanceUID, ""); strcpy(d.studyID, ""); strcpy(d.studyInstanceUID, ""); strcpy(d.bodyPartExamined, ""); strcpy(d.coilName, ""); strcpy(d.coilElements, ""); strcpy(d.radiopharmaceutical, ""); strcpy(d.convolutionKernel, ""); strcpy(d.parallelAcquisitionTechnique, ""); strcpy(d.imageOrientationText, ""); strcpy(d.unitsPT, ""); strcpy(d.decayCorrection, ""); strcpy(d.attenuationCorrectionMethod, ""); strcpy(d.reconstructionMethod, ""); d.phaseEncodingLines = 0; //~ d.patientPositionSequentialRepeats = 0; //~ d.patientPositionRepeats = 0; d.isHasPhase = false; d.isHasReal = false; d.isHasImaginary = false; d.isHasMagnitude = false; //d.maxGradDynVol = -1; //PAR/REC only d.sliceOrient = kSliceOrientUnknown; d.dateTime = (double)19770703150928.0; d.acquisitionTime = 0.0f; d.acquisitionDate = 0.0f; d.manufacturer = kMANUFACTURER_UNKNOWN; d.isPlanarRGB = false; d.lastScanLoc = NAN; d.TR = 0.0; d.TE = 0.0; d.TI = 0.0; d.flipAngle = 0.0; d.bandwidthPerPixelPhaseEncode = 0.0; d.acquisitionDuration = 0.0; d.imagingFrequency = 0.0; d.numberOfAverages = 0.0; d.fieldStrength = 0.0; d.SAR = 0.0; d.pixelBandwidth = 0.0; d.zSpacing = 0.0; d.zThick = 0.0; //~ d.numberOfDynamicScans = 0; d.echoNum = 1; d.echoTrainLength = 0; d.waterFatShift = 0.0; d.groupDelay = 0.0; d.decayFactor = 0.0; d.percentSampling = 0.0; d.phaseFieldofView = 0.0; d.dwellTime = 0; d.protocolBlockStartGE = 0; d.protocolBlockLengthGE = 0; d.phaseEncodingSteps = 0; d.coilCrc = 0; d.seriesUidCrc = 0; d.instanceUidCrc = 0; d.accelFactPE = 0.0; d.accelFactOOP = 0.0; //d.patientPositionNumPhilips = 0; d.imageBytes = 0; d.intenScale = 1; d.intenScalePhilips = 0; d.intenIntercept = 0; d.gantryTilt = 0.0; d.exposureTimeMs = 0.0; d.xRayTubeCurrent = 0.0; d.radionuclidePositronFraction = 0.0; d.radionuclideHalfLife = 0.0; d.doseCalibrationFactor = 0.0; d.ecat_isotope_halflife = 0.0; d.frameDuration = -1.0; d.ecat_dosage = 0.0; d.radionuclideTotalDose = 0.0; d.seriesNum = 1; d.acquNum = 0; d.imageNum = 1; d.imageStart = 0; d.is3DAcq = false; //e.g. MP-RAGE, SPACE, TFE d.is2DAcq = false; // d.isDerived = false; //0008,0008 = DERIVED,CSAPARALLEL,POSDISP d.isSegamiOasis = false; //these images do not store spatial coordinates d.isBVecWorldCoordinates = false; //bvecs can be in image space (GE) or world coordinates (Siemens) d.isGrayscaleSoftcopyPresentationState = false; d.isRawDataStorage = false; d.isPartialFourier = false; d.isIR = false; d.isEPI = false; d.isDiffusion = false; d.isVectorFromBMatrix = false; d.isStackableSeries = false; //combine DCE series https://github.com/rordenlab/dcm2niix/issues/252 d.isXA10A = false; //https://github.com/rordenlab/dcm2niix/issues/236 d.triggerDelayTime = 0.0; d.RWVScale = 0.0; d.RWVIntercept = 0.0; d.isScaleOrTEVaries = false; d.isScaleVariesEnh = false; //issue363 d.bitsAllocated = 16; //bits d.bitsStored = 0; d.samplesPerPixel = 1; d.pixelPaddingValue = NAN; d.isValid = false; d.isXRay = false; d.isMultiEcho = false; d.isSigned = false; //default is unsigned! d.isFloat = false; //default is for integers, not single or double precision d.isResampled = false; //assume data not resliced to remove gantry tilt problems d.isLocalizer = false; d.isNonParallelSlices = false; d.isCoilVaries = false; d.compressionScheme = 0; //none d.isExplicitVR = true; d.isLittleEndian = true; //DICOM initially always little endian d.converted2NII = 0; d.numberOfDiffusionDirectionGE = -1; d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNKNOWN; d.rtia_timerGE = -1.0; d.rawDataRunNumber = -1; d.maxEchoNumGE = -1; d.epiVersionGE = -1; d.internalepiVersionGE = -1; d.durationLabelPulseGE = -1; d.aslFlags = kASL_FLAG_NONE; d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_UNKNOWN; d.mtState = -1; d.numberOfExcitations = -1; d.numberOfArms = -1; d.numberOfPointsPerArm = -1; d.phaseNumber = - 1; //Philips Multi-Phase ASL d.spoiling = kSPOILING_UNKOWN; d.interp3D = -1; for (int i = 0; i < kMaxOverlay; i++) d.overlayStart[i] = 0; d.isHasOverlay = false; d.isPrivateCreatorRemap = false; d.isRealIsPhaseMapHz = false; d.numberOfImagesInGridUIH = 0; d.phaseEncodingRC = '?'; d.patientSex = '?'; d.patientWeight = 0.0; strcpy(d.patientBirthDate, ""); strcpy(d.patientAge, ""); d.CSA.bandwidthPerPixelPhaseEncode = 0.0; d.CSA.mosaicSlices = 0; d.CSA.sliceNormV[1] = 0.0; d.CSA.sliceNormV[2] = 0.0; d.CSA.sliceNormV[3] = 1.0; //default Siemens Image Numbering is F>>H https://www.mccauslandcenter.sc.edu/crnl/tools/stc d.CSA.sliceOrder = NIFTI_SLICE_UNKNOWN; d.CSA.slice_start = 0; d.CSA.slice_end = 0; d.CSA.protocolSliceNumber1 = 0; d.CSA.phaseEncodingDirectionPositive = -1; //unknown d.CSA.isPhaseMap = false; d.CSA.multiBandFactor = 1; d.CSA.SeriesHeader_offset = 0; d.CSA.SeriesHeader_length = 0; return d; } //clear_dicom_data() int isdigitdot(int c) { //returns true if digit or '.' if (c == '.') return 1; return isdigit(c); } void dcmStrDigitsDotOnlyKey(char key, char *lStr) { //e.g. string "F:2.50" returns 2.50 if key==":" size_t len = strlen(lStr); if (len < 1) return; bool isKey = false; for (int i = 0; i < (int)len; i++) { if (!isdigitdot(lStr[i])) { isKey = (lStr[i] == key); lStr[i] = ' '; } else if (!isKey) lStr[i] = ' '; } } //dcmStrDigitsOnlyKey() void dcmStrDigitsOnlyKey(char key, char *lStr) { //e.g. string "p2s3" returns 2 if key=="p" and 3 if key=="s" size_t len = strlen(lStr); if (len < 1) return; bool isKey = false; for (int i = 0; i < (int)len; i++) { if (!isdigit(lStr[i])) { isKey = (lStr[i] == key); lStr[i] = ' '; } else if (!isKey) lStr[i] = ' '; } } //dcmStrDigitsOnlyKey() void dcmStrDigitsOnly(char *lStr) { //e.g. change "H11" to " 11" size_t len = strlen(lStr); if (len < 1) return; for (int i = 0; i < (int)len; i++) if (!isdigit(lStr[i])) lStr[i] = ' '; } // Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ uint32_t mz_crc32X(unsigned char *ptr, size_t buf_len) { static const uint32_t s_crc32[16] = {0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c}; uint32_t crcu32 = 0; if (!ptr) return crcu32; crcu32 = ~crcu32; while (buf_len--) { uint8_t b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } return ~crcu32; } void dcmStr(int lLength, unsigned char lBuffer[], char *lOut, bool isStrLarge = false) { if (lLength < 1) return; char *cString = (char *)malloc(sizeof(char) * (lLength + 1)); cString[lLength] = 0; memcpy(cString, (char *)&lBuffer[0], lLength); //memcpy(cString, test, lLength); //printMessage("X%dX\n", (unsigned char)d.patientName[1]); #ifdef ISO8859 for (int i = 0; i < 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]; if ((c >= 192) && (c <= 198)) cString[i] = 'A'; if (c == 199) cString[i] = 'C'; if ((c >= 200) && (c <= 203)) cString[i] = 'E'; if ((c >= 204) && (c <= 207)) cString[i] = 'I'; if (c == 208) cString[i] = 'D'; if (c == 209) cString[i] = 'N'; if ((c >= 210) && (c <= 214)) cString[i] = 'O'; if (c == 215) cString[i] = 'x'; if (c == 216) cString[i] = 'O'; if ((c >= 217) && (c <= 220)) cString[i] = 'O'; if (c == 221) cString[i] = 'Y'; if ((c >= 224) && (c <= 230)) cString[i] = 'a'; if (c == 231) cString[i] = 'c'; if ((c >= 232) && (c <= 235)) cString[i] = 'e'; if ((c >= 236) && (c <= 239)) cString[i] = 'i'; if (c == 240) cString[i] = 'o'; if (c == 241) cString[i] = 'n'; if ((c >= 242) && (c <= 246)) cString[i] = 'o'; if (c == 248) cString[i] = 'o'; if ((c >= 249) && (c <= 252)) cString[i] = 'u'; if (c == 253) cString[i] = 'y'; if (c == 255) cString[i] = 'y'; } #endif //we no longer sanitize strings, see issue 425 int len = lLength; if (cString[len - 1] == ' ') len--; //while ((len > 0) && (cString[len]=='_')) len--; //remove trailing '_' cString[len] = 0; //null-terminate, strlcpy does this anyway int maxLen = kDICOMStr; if (isStrLarge) maxLen = kDICOMStrLarge; len = dcmStrLen(len, maxLen); if (len == maxLen) { //we need space for null-termination if (cString[len - 2] == '_') len = len - 2; } memcpy(lOut, cString, len - 1); lOut[len - 1] = 0; free(cString); } //dcmStr() #ifdef MY_OLD //this code works on Intel but not some older systems https://github.com/rordenlab/dcm2niix/issues/327 float dcmFloat(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 32-bit float //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian bool swap = (littleEndian != littleEndianPlatform()); float retVal = 0; if (lByteLength < 4) return retVal; memcpy(&retVal, (char *)&lBuffer[0], 4); if (!swap) return retVal; float swapVal; char *inFloat = (char *)&retVal; char *outFloat = (char *)&swapVal; outFloat[0] = inFloat[3]; outFloat[1] = inFloat[2]; outFloat[2] = inFloat[1]; outFloat[3] = inFloat[0]; //printMessage("swapped val = %f\n",swapVal); return swapVal; } //dcmFloat() double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], const bool littleEndian) { //read binary 64-bit float //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian bool swap = (littleEndian != littleEndianPlatform()); double retVal = 0.0f; if (lByteLength < 8) return retVal; memcpy(&retVal, (char *)&lBuffer[0], 8); if (!swap) return retVal; char *floatToConvert = (char *)&lBuffer; char *returnFloat = (char *)&retVal; //swap the bytes into a temporary buffer returnFloat[0] = floatToConvert[7]; returnFloat[1] = floatToConvert[6]; returnFloat[2] = floatToConvert[5]; returnFloat[3] = floatToConvert[4]; returnFloat[4] = floatToConvert[3]; returnFloat[5] = floatToConvert[2]; returnFloat[6] = floatToConvert[1]; returnFloat[7] = floatToConvert[0]; //printMessage("swapped val = %f\n",retVal); return retVal; } //dcmFloatDouble() #else float dcmFloat(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 32-bit float //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian if (lByteLength < 4) return 0.0; bool swap = (littleEndian != littleEndianPlatform()); union { uint32_t i; float f; uint8_t c[4]; } i, o; memcpy(&i.i, (char *)&lBuffer[0], 4); //printf("%02x%02x%02x%02x\n",i.c[0], i.c[1], i.c[2], i.c[3]); if (!swap) return i.f; o.c[0] = i.c[3]; o.c[1] = i.c[2]; o.c[2] = i.c[1]; o.c[3] = i.c[0]; //printf("swp %02x%02x%02x%02x\n",o.c[0], o.c[1], o.c[2], o.c[3]); return o.f; } //dcmFloat() double dcmFloatDouble(const size_t lByteLength, const unsigned char lBuffer[], const bool littleEndian) { //read binary 64-bit float //http://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian if (lByteLength < 8) return 0.0; bool swap = (littleEndian != littleEndianPlatform()); union { uint32_t i; double d; uint8_t c[8]; } i, o; memcpy(&i.i, (char *)&lBuffer[0], 8); if (!swap) return i.d; o.c[0] = i.c[7]; o.c[1] = i.c[6]; o.c[2] = i.c[5]; o.c[3] = i.c[4]; o.c[4] = i.c[3]; o.c[5] = i.c[2]; o.c[6] = i.c[1]; o.c[7] = i.c[0]; return o.d; } //dcmFloatDouble() #endif int dcmInt(int lByteLength, unsigned char lBuffer[], bool littleEndian) { //read binary 16 or 32 bit integer if (littleEndian) { if (lByteLength <= 3) return lBuffer[0] | (lBuffer[1] << 8); //shortint vs word? return lBuffer[0] + (lBuffer[1] << 8) + (lBuffer[2] << 16) + (lBuffer[3] << 24); //shortint vs word? } if (lByteLength <= 3) return lBuffer[1] | (lBuffer[0] << 8); //shortint vs word? return lBuffer[3] + (lBuffer[2] << 8) + (lBuffer[1] << 16) + (lBuffer[0] << 24); //shortint vs word? } //dcmInt() uint32_t dcmAttributeTag(unsigned char lBuffer[], bool littleEndian) { // read Attribute Tag (AT) value // return in Group + (Element << 16) format if (littleEndian) return lBuffer[0] + (lBuffer[1] << 8) + (lBuffer[2] << 16) + (lBuffer[3] << 24); return lBuffer[1] + (lBuffer[0] << 8) + (lBuffer[3] << 16) + (lBuffer[2] << 24); } //dcmInt() int dcmStrInt(const int lByteLength, const unsigned char lBuffer[]) { //read int stored as a string char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); cString[lByteLength] = 0; memcpy(cString, (const unsigned char *)(&lBuffer[0]), lByteLength); int ret = atoi(cString); free(cString); return ret; } //dcmStrInt() int dcmStrManufacturer(const int lByteLength, unsigned char lBuffer[]) { //read float stored as a string if (lByteLength < 2) return kMANUFACTURER_UNKNOWN; //#ifdef _MSC_VER char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); //#else // char cString[lByteLength + 1]; //#endif int ret = kMANUFACTURER_UNKNOWN; cString[lByteLength] = 0; memcpy(cString, (char *)&lBuffer[0], lByteLength); if ((toupper(cString[0]) == 'S') && (toupper(cString[1]) == 'I')) ret = kMANUFACTURER_SIEMENS; if ((toupper(cString[0]) == 'G') && (toupper(cString[1]) == 'E')) ret = kMANUFACTURER_GE; if ((toupper(cString[0]) == 'H') && (toupper(cString[1]) == 'I')) ret = kMANUFACTURER_HITACHI; if ((toupper(cString[0]) == 'M') && (toupper(cString[1]) == 'E')) ret = kMANUFACTURER_MEDISO; if ((toupper(cString[0]) == 'P') && (toupper(cString[1]) == 'H')) 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]) == '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); //#ifdef _MSC_VER free(cString); //#endif return ret; } //dcmStrManufacturer float csaMultiFloat(unsigned char buff[], int nItems, float Floats[], int *ItemsOK) { //warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats] //if lnItems == 1, returns first item, if lnItems > 1 returns index of final successful conversion TCSAitem itemCSA; *ItemsOK = 0; if (nItems < 1) return 0.0f; Floats[1] = 0; int lPos = 0; for (int lI = 1; lI <= nItems; lI++) { memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); lPos += sizeof(itemCSA); // Storage order is always little-endian, so byte-swap required values if necessary if (!littleEndianPlatform()) nifti_swap_4bytes(1, &itemCSA.xx2_Len); if (itemCSA.xx2_Len > 0) { char *cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len)); memcpy(cString, &buff[lPos], itemCSA.xx2_Len); //TPX memcpy(&cString, &buff[lPos], sizeof(cString)); lPos += ((itemCSA.xx2_Len + 3) / 4) * 4; //printMessage(" %d item length %d = %s\n",lI, itemCSA.xx2_Len, cString); Floats[lI] = (float)atof(cString); *ItemsOK = lI; //some sequences have store empty items free(cString); } } //for each item return Floats[1]; } //csaMultiFloat() bool csaIsPhaseMap(unsigned char buff[], int nItems) { //returns true if the tag "ImageHistory" has an item named "CC:ComplexAdd" TCSAitem itemCSA; if (nItems < 1) return false; int lPos = 0; for (int lI = 1; lI <= nItems; lI++) { memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); lPos += sizeof(itemCSA); // Storage order is always little-endian, so byte-swap required values if necessary if (!littleEndianPlatform()) nifti_swap_4bytes(1, &itemCSA.xx2_Len); if (itemCSA.xx2_Len > 0) { char *cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len + 1)); memcpy(cString, &buff[lPos], sizeof(itemCSA.xx2_Len)); //TPX memcpy(&cString, &buff[lPos], sizeof(cString)); lPos += ((itemCSA.xx2_Len + 3) / 4) * 4; //printMessage(" %d item length %d = %s\n",lI, itemCSA.xx2_Len, cString); if (strcmp(cString, "CC:ComplexAdd") == 0) return true; free(cString); } } //for each item return false; } //csaIsPhaseMap() void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3DAcq) { if ((is3DAcq) || (itemsOK < 1)) //we expect 3D sequences to be simultaneous return; if (itemsOK > kMaxEPI3D) { printError("Please increase kMaxEPI3D and recompile\n"); return; } float maxTimeValue, minTimeValue, timeValue1; minTimeValue = CSA->sliceTiming[0]; for (int z = 0; z < itemsOK; z++) if (CSA->sliceTiming[z] < minTimeValue) minTimeValue = CSA->sliceTiming[z]; //CSA can report negative slice times // https://neurostars.org/t/slice-timing-illegal-values-in-fmriprep/1516/8 // Nov 1, 2018 wrote: // If you have an interleaved dataset we can more definitively validate this formula (aka sliceTime(i) - min(sliceTimes())). if (minTimeValue < 0) { //printWarning("Adjusting for negative MosaicRefAcqTimes (issue 271).\n"); //if uncommented, overwhelming number of warnings (one per DICOM input), better once per series CSA->sliceTiming[kMaxEPI3D - 1] = -2.0; //issue 271: flag for unified warning for (int z = 0; z < itemsOK; z++) CSA->sliceTiming[z] = CSA->sliceTiming[z] - minTimeValue; } CSA->multiBandFactor = 1; timeValue1 = CSA->sliceTiming[0]; int nTimeZero = 0; if (CSA->sliceTiming[0] == 0) nTimeZero++; int minTimeIndex = 0; int maxTimeIndex = minTimeIndex; minTimeValue = CSA->sliceTiming[0]; maxTimeValue = minTimeValue; if (isVerbose > 1) printMessage(" sliceTimes %g\t", CSA->sliceTiming[0]); for (int z = 1; z < itemsOK; z++) { //find index and value of fastest time if (isVerbose > 1) printMessage("%g\t", CSA->sliceTiming[z]); if (CSA->sliceTiming[z] == 0) nTimeZero++; if (CSA->sliceTiming[z] < minTimeValue) { minTimeValue = CSA->sliceTiming[z]; minTimeIndex = (float)z; } if (CSA->sliceTiming[z] > maxTimeValue) { maxTimeValue = CSA->sliceTiming[z]; maxTimeIndex = (float)z; } if (CSA->sliceTiming[z] == timeValue1) CSA->multiBandFactor++; } if (isVerbose > 1) printMessage("\n"); CSA->slice_start = minTimeIndex; CSA->slice_end = maxTimeIndex; if (minTimeIndex == maxTimeIndex) { if (isVerbose) printMessage("No variability in slice times (3D EPI?)\n"); } if (nTimeZero < 2) { //not for multi-band, not 3D if (minTimeIndex == 1) CSA->sliceOrder = NIFTI_SLICE_ALT_INC2; // e.g. 3,1,4,2 else if (minTimeIndex == (itemsOK - 2)) CSA->sliceOrder = NIFTI_SLICE_ALT_DEC2; // e.g. 2,4,1,3 or 5,2,4,1,3 else if ((minTimeIndex == 0) && (CSA->sliceTiming[1] < CSA->sliceTiming[2])) CSA->sliceOrder = NIFTI_SLICE_SEQ_INC; // e.g. 1,2,3,4 else if ((minTimeIndex == 0) && (CSA->sliceTiming[1] > CSA->sliceTiming[2])) CSA->sliceOrder = NIFTI_SLICE_ALT_INC; //e.g. 1,3,2,4 else if ((minTimeIndex == (itemsOK - 1)) && (CSA->sliceTiming[itemsOK - 3] > CSA->sliceTiming[itemsOK - 2])) CSA->sliceOrder = NIFTI_SLICE_SEQ_DEC; //e.g. 4,3,2,1 or 5,4,3,2,1 else if ((minTimeIndex == (itemsOK - 1)) && (CSA->sliceTiming[itemsOK - 3] < CSA->sliceTiming[itemsOK - 2])) CSA->sliceOrder = NIFTI_SLICE_ALT_DEC; //e.g. 4,2,3,1 or 3,5,2,4,1 else { if (!is3DAcq) //we expect 3D sequences to be simultaneous printWarning("Unable to determine slice order from CSA tag MosaicRefAcqTimes\n"); } } if ((CSA->sliceOrder != NIFTI_SLICE_UNKNOWN) && (nTimeZero > 1) && (nTimeZero < itemsOK)) { if (isVerbose) printMessage(" Multiband x%d sequence: setting slice order as UNKNOWN (instead of %d)\n", nTimeZero, CSA->sliceOrder); CSA->sliceOrder = NIFTI_SLICE_UNKNOWN; } } //checkSliceTimes() int readCSAImageHeader(unsigned char *buff, int lLength, struct TCSAdata *CSA, int isVerbose, bool is3DAcq) { //see also http://afni.nimh.nih.gov/pub/dist/src/siemens_dicom_csa.c //printMessage("%c%c%c%c\n",buff[0],buff[1],buff[2],buff[3]); if (lLength < 36) return EXIT_FAILURE; if ((buff[0] != 'S') || (buff[1] != 'V') || (buff[2] != '1') || (buff[3] != '0')) return EXIT_FAILURE; int lPos = 8; //skip 8 bytes of data, 'SV10' plus 2 32-bit values unused1 and unused2 int lnTag = buff[lPos] + (buff[lPos + 1] << 8) + (buff[lPos + 2] << 16) + (buff[lPos + 3] << 24); if (buff[lPos + 4] != 77) return EXIT_FAILURE; lPos += 8; //skip 8 bytes of data, 32-bit lnTag plus 77 00 00 0 TCSAtag tagCSA; TCSAitem itemCSA; int itemsOK; float lFloats[7]; for (int lT = 1; lT <= lnTag; lT++) { memcpy(&tagCSA, &buff[lPos], sizeof(tagCSA)); //read tag lPos += sizeof(tagCSA); // Storage order is always little-endian, so byte-swap required values if necessary if (!littleEndianPlatform()) nifti_swap_4bytes(1, &tagCSA.nitems); if (isVerbose > 1) //extreme verbosity: show every CSA tag printMessage(" %d CSA of %s %d\n", lPos, tagCSA.name, tagCSA.nitems); if (tagCSA.nitems > 0) { if (strcmp(tagCSA.name, "ImageHistory") == 0) CSA->isPhaseMap = csaIsPhaseMap(&buff[lPos], tagCSA.nitems); else if (strcmp(tagCSA.name, "NumberOfImagesInMosaic") == 0) CSA->mosaicSlices = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK)); else if (strcmp(tagCSA.name, "B_value") == 0) { CSA->dtiV[0] = csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK); if (CSA->dtiV[0] < 0.0) { printWarning("(Corrupt) CSA reports negative b-value! %g\n", CSA->dtiV[0]); CSA->dtiV[0] = 0.0; } CSA->numDti = 1; //triggered by b-value, as B0 images do not have DiffusionGradientDirection tag } else if ((strcmp(tagCSA.name, "DiffusionGradientDirection") == 0) && (tagCSA.nitems > 2)) { CSA->dtiV[1] = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK); CSA->dtiV[2] = lFloats[2]; CSA->dtiV[3] = lFloats[3]; if (isVerbose) printMessage("DiffusionGradientDirection %f %f %f\n", lFloats[1], lFloats[2], lFloats[3]); } else if ((strcmp(tagCSA.name, "SliceNormalVector") == 0) && (tagCSA.nitems > 2)) { CSA->sliceNormV[1] = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK); CSA->sliceNormV[2] = lFloats[2]; CSA->sliceNormV[3] = lFloats[3]; if (isVerbose > 1) printMessage(" SliceNormalVector %f %f %f\n", CSA->sliceNormV[1], CSA->sliceNormV[2], CSA->sliceNormV[3]); } else if (strcmp(tagCSA.name, "SliceMeasurementDuration") == 0) CSA->sliceMeasurementDuration = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK); else if (strcmp(tagCSA.name, "BandwidthPerPixelPhaseEncode") == 0) CSA->bandwidthPerPixelPhaseEncode = csaMultiFloat(&buff[lPos], 3, lFloats, &itemsOK); else if ((strcmp(tagCSA.name, "MosaicRefAcqTimes") == 0) && (tagCSA.nitems > 3)) { if (itemsOK > kMaxEPI3D) { printError("Please increase kMaxEPI3D and recompile\n"); } else { float *sliceTimes = (float *)malloc(sizeof(float) * (tagCSA.nitems + 1)); csaMultiFloat(&buff[lPos], tagCSA.nitems, sliceTimes, &itemsOK); for (int z = 0; z < kMaxEPI3D; z++) CSA->sliceTiming[z] = -1.0; for (int z = 0; z < itemsOK; z++) CSA->sliceTiming[z] = sliceTimes[z + 1]; free(sliceTimes); checkSliceTimes(CSA, itemsOK, isVerbose, is3DAcq); } } else if (strcmp(tagCSA.name, "ProtocolSliceNumber") == 0) CSA->protocolSliceNumber1 = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK)); else if (strcmp(tagCSA.name, "PhaseEncodingDirectionPositive") == 0) CSA->phaseEncodingDirectionPositive = (int)round(csaMultiFloat(&buff[lPos], 1, lFloats, &itemsOK)); for (int lI = 1; lI <= tagCSA.nitems; lI++) { memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); lPos += sizeof(itemCSA); // Storage order is always little-endian, so byte-swap required values if necessary if (!littleEndianPlatform()) nifti_swap_4bytes(1, &itemCSA.xx2_Len); lPos += ((itemCSA.xx2_Len + 3) / 4) * 4; } } //if at least 1 item } // for lT 1..lnTag if (CSA->protocolSliceNumber1 > 1) CSA->sliceOrder = NIFTI_SLICE_UNKNOWN; return EXIT_SUCCESS; } // readCSAImageHeader() void dcmMultiShorts(int lByteLength, unsigned char lBuffer[], int lnShorts, uint16_t *lShorts, bool littleEndian) { //read array of unsigned shorts US http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html if ((lnShorts < 1) || (lByteLength != (lnShorts * 2))) return; memcpy(&lShorts[0], (uint16_t *)&lBuffer[0], lByteLength); bool swap = (littleEndian != littleEndianPlatform()); if (swap) nifti_swap_2bytes(lnShorts, &lShorts[0]); } //dcmMultiShorts() void dcmMultiLongs(int lByteLength, unsigned char lBuffer[], int lnLongs, uint32_t *lLongs, bool littleEndian) { //read array of unsigned longs UL http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html if ((lnLongs < 1) || (lByteLength != (lnLongs * 4))) return; memcpy(&lLongs[0], (uint32_t *)&lBuffer[0], lByteLength); bool swap = (littleEndian != littleEndianPlatform()); if (swap) nifti_swap_4bytes(lnLongs, &lLongs[0]); } //dcmMultiLongs() void dcmMultiFloat(int lByteLength, char lBuffer[], int lnFloats, float *lFloats) { //warning: lFloats indexed from 1! will fill lFloats[1]..[nFloats] if ((lnFloats < 1) || (lByteLength < 1)) return; char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); memcpy(cString, (char *)&lBuffer[0], lByteLength); cString[lByteLength] = 0; //null terminate char *temp = (char *)malloc(lByteLength + 1); int f = 0, lStart = 0; bool isOK = false; for (int i = 0; i <= lByteLength; i++) { if ((lBuffer[i] >= '0') && (lBuffer[i] <= '9')) isOK = true; if ((isOK) && ((i == (lByteLength)) || (lBuffer[i] == '/') || (lBuffer[i] == ' ') || (lBuffer[i] == '\\'))) { snprintf(temp, i - lStart + 1, "%s", &cString[lStart]); //printMessage("dcmMultiFloat %s\n",temp); if (f < lnFloats) { f++; lFloats[f] = (float)atof(temp); isOK = false; //printMessage("%d == %f\n", f, atof(temp)); } //if f <= nFloats lStart = i + 1; } //if isOK } //for i to length free(temp); free(cString); } //dcmMultiFloat() float dcmStrFloat(const int lByteLength, const unsigned char lBuffer[]) { //read float stored as a string char *cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); memcpy(cString, (char *)&lBuffer[0], lByteLength); cString[lByteLength] = 0; //null terminate float ret = (float)atof(cString); free(cString); return ret; } //dcmStrFloat() int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeSForm) { memset(h, 0, sizeof(nifti_1_header)); //zero-fill structure so unused items are consistent for (int i = 0; i < 80; i++) h->descrip[i] = 0; for (int i = 0; i < 24; i++) h->aux_file[i] = 0; for (int i = 0; i < 18; i++) h->db_name[i] = 0; for (int i = 0; i < 10; i++) h->data_type[i] = 0; for (int i = 0; i < 16; i++) h->intent_name[i] = 0; if ((d.bitsAllocated == 8) && (d.samplesPerPixel == 3)) { h->intent_code = NIFTI_INTENT_ESTIMATE; //make sure we treat this as RGBRGB...RGB h->datatype = DT_RGB24; } else if ((d.bitsAllocated == 8) && (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)) h->datatype = DT_INT16; else if ((d.bitsAllocated == 16) && (d.samplesPerPixel == 1) && (!d.isSigned)) h->datatype = DT_UINT16; else if ((d.bitsAllocated == 32) && (d.isFloat)) h->datatype = DT_FLOAT32; else if (d.bitsAllocated == 32) h->datatype = DT_INT32; else if ((d.bitsAllocated == 64) && (d.isFloat)) h->datatype = DT_FLOAT64; else { printMessage("Unsupported DICOM bit-depth %d with %d samples per pixel\n", d.bitsAllocated, d.samplesPerPixel); return EXIT_FAILURE; } if ((h->datatype == DT_UINT16) && (d.bitsStored > 0) && (d.bitsStored < 16)) h->datatype = DT_INT16; // DT_INT16 is more widely supported, same representation for values 0..32767 for (int i = 0; i < 8; i++) { h->pixdim[i] = 0.0f; h->dim[i] = 0; } //next items listed as unused in NIfTI format, but zeroed for consistency across runs h->extents = 0; h->session_error = kSessionOK; h->glmin = 0; //unused, but make consistent h->glmax = 0; //unused, but make consistent h->regular = 114; //in legacy Analyze this was always 114 //these are important h->scl_inter = d.intenIntercept; h->scl_slope = d.intenScale; h->cal_max = 0; h->cal_min = 0; h->magic[0] = 'n'; h->magic[1] = '+'; h->magic[2] = '1'; h->magic[3] = '\0'; h->vox_offset = (float)d.imageStart; if (d.bitsAllocated == 12) h->bitpix = 16 * d.samplesPerPixel; else h->bitpix = d.bitsAllocated * d.samplesPerPixel; h->pixdim[1] = d.xyzMM[1]; h->pixdim[2] = d.xyzMM[2]; h->pixdim[3] = d.xyzMM[3]; h->pixdim[4] = d.TR / 1000.0; //TR reported in msec, time is in sec h->dim[1] = d.xyzDim[1]; h->dim[2] = d.xyzDim[2]; h->dim[3] = d.xyzDim[3]; h->dim[4] = d.xyzDim[4]; h->dim[5] = 1; h->dim[6] = 1; h->dim[7] = 1; if (h->dim[4] < 2) h->dim[0] = 3; else h->dim[0] = 4; for (int i = 0; i <= 3; i++) { h->srow_x[i] = 0.0f; h->srow_y[i] = 0.0f; h->srow_z[i] = 0.0f; } h->slice_start = 0; h->slice_end = 0; h->srow_x[0] = -1; h->srow_y[2] = 1; h->srow_z[1] = -1; h->srow_x[3] = ((float)h->dim[1] / 2); h->srow_y[3] = -((float)h->dim[3] / 2); h->srow_z[3] = ((float)h->dim[2] / 2); h->qform_code = NIFTI_XFORM_UNKNOWN; h->sform_code = NIFTI_XFORM_UNKNOWN; h->toffset = 0; h->intent_code = NIFTI_INTENT_NONE; h->dim_info = 0; //Freq, Phase and Slice all unknown h->xyzt_units = NIFTI_UNITS_MM + NIFTI_UNITS_SEC; h->slice_duration = 0; //avoid +inf/-inf, NaN h->intent_p1 = 0; //avoid +inf/-inf, NaN h->intent_p2 = 0; //avoid +inf/-inf, NaN h->intent_p3 = 0; //avoid +inf/-inf, NaN h->pixdim[0] = 1; //QFactor should be 1 or -1 h->sizeof_hdr = 348; //used to signify header does not need to be byte-swapped h->slice_code = d.CSA.sliceOrder; if (isComputeSForm) headerDcm2Nii2(d, d, h, false); return EXIT_SUCCESS; } // headerDcm2Nii() bool isFloatDiff(float a, float b) { return (fabs(a - b) > FLT_EPSILON); } //isFloatDiff() mat33 nifti_mat33_reorder_cols(mat33 m, ivec3 v) { // matlab equivalent ret = m(:, v); where v is 1,2,3 [INDEXED FROM ONE!!!!] mat33 ret; for (int r = 0; r < 3; r++) { for (int c = 0; c < 3; c++) ret.m[r][c] = m.m[r][v.v[c] - 1]; } return ret; } //nifti_mat33_reorder_cols() void changeExt(char *file_name, const char *ext) { char *p_extension; p_extension = strrchr(file_name, '.'); if (p_extension) strcpy(++p_extension, ext); } //changeExt() void cleanStr(char *lOut) { //e.g. strings such as image comments with special characters (e.g. "G/6/2009") can disrupt file saves size_t lLength = strlen(lOut); if (lLength < 1) return; char *cString = (char *)malloc(sizeof(char) * (lLength + 1)); cString[lLength] = 0; memcpy(cString, (char *)&lOut[0], lLength); for (int i = 0; i < 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]; if ((c >= 192) && (c <= 198)) cString[i] = 'A'; if (c == 199) cString[i] = 'C'; if ((c >= 200) && (c <= 203)) cString[i] = 'E'; if ((c >= 204) && (c <= 207)) cString[i] = 'I'; if (c == 208) cString[i] = 'D'; if (c == 209) cString[i] = 'N'; if ((c >= 210) && (c <= 214)) cString[i] = 'O'; if (c == 215) cString[i] = 'x'; if (c == 216) cString[i] = 'O'; if ((c >= 217) && (c <= 220)) cString[i] = 'O'; if (c == 221) cString[i] = 'Y'; if ((c >= 224) && (c <= 230)) cString[i] = 'a'; if (c == 231) cString[i] = 'c'; if ((c >= 232) && (c <= 235)) cString[i] = 'e'; if ((c >= 236) && (c <= 239)) cString[i] = 'i'; if (c == 240) cString[i] = 'o'; if (c == 241) cString[i] = 'n'; if ((c >= 242) && (c <= 246)) cString[i] = 'o'; if (c == 248) cString[i] = 'o'; if ((c >= 249) && (c <= 252)) cString[i] = 'u'; if (c == 253) cString[i] = 'y'; if (c == 255) cString[i] = 'y'; } for (int i = 0; i < 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 "_" if ((cString[i - 1] != '_') || (cString[i] != '_')) { cString[len] = cString[i]; len++; } } //for each item if (cString[len - 1] == '_') len--; cString[len] = 0; //null-terminate, strlcpy does this anyway int maxLen = kDICOMStr; len = dcmStrLen(len, maxLen); if (len == maxLen) { //we need space for null-termination if (cString[len - 2] == '_') len = len - 2; } memcpy(lOut, cString, len - 1); lOut[len - 1] = 0; free(cString); } //cleanStr() int isSameFloatGE(float a, float b) { //Kludge for bug in 0002,0016="DIGITAL_JACKET", 0008,0070="GE MEDICAL SYSTEMS" DICOM data: Orient field (0020:0037) can vary 0.00604261 == 0.00604273 !!! //return (a == b); //niave approach does not have any tolerance for rounding errors return (fabs(a - b) <= 0.0001); } struct TDICOMdata nii_readParRec(char *parname, int isVerbose, struct TDTI4D *dti4D, bool isReadPhase) { struct TDICOMdata d = clear_dicom_data(); dti4D->sliceOrder[0] = -1; dti4D->volumeOnsetTime[0] = -1; dti4D->decayFactor[0] = -1; dti4D->frameDuration[0] = -1; //dti4D->fragmentOffset[0] = -1; dti4D->intenScale[0] = 0.0; strcpy(d.protocolName, ""); //erase dummy with empty strcpy(d.seriesDescription, ""); //erase dummy with empty strcpy(d.sequenceName, ""); //erase dummy with empty strcpy(d.scanningSequence, ""); FILE *fp = fopen(parname, "r"); if (fp == NULL) return d; #define LINESZ 2048 #define kSlice 0 #define kEcho 1 #define kDyn 2 #define kCardiac 3 #define kImageType 4 #define kSequence 5 #define kIndex 6 //V3 only identical for columns 1..6 #define kBitsPerVoxel 7 //V3: not per slice: "Image pixel size [8 or 16 bits]" #define kXdim 9 //V3: not per slice: "Recon resolution (x, y)" #define kYdim 10 //V3: not per slice: "Recon resolution (x, y)" int kRI = 11; //V3: 7 int kRS = 12; //V3: 8 int kSS = 13; //V3: 9 int kAngulationAPs = 16; //V3: 12 int kAngulationFHs = 17; //V3: 13 int kAngulationRLs = 18; //V3: 14 int kPositionAP = 19; //V3: 15 int kPositionFH = 20; //V3: 16 int kPositionRL = 21; //V3: 17 #define kThickmm 22 //V3: not per slice: "Slice thickness [mm]" #define kGapmm 23 //V3: not per slice: "Slice gap [mm]" int kSliceOrients = 25; //V3: 19 int kXmm = 28; //V3: 22 int kYmm = 29; //V3: 23 int kTEcho = 30; //V3: 24 int kDynTime = 31; //V3: 25 int kTriggerTime = 32; //V3: 26 int kbval = 33;//V3: 27 //the following do not exist in V3 #define kInversionDelayMs 40 #define kbvalNumber 41 #define kGradientNumber 42 //the following do not exist in V40 or earlier #define kv1 47 #define kv2 45 #define kv3 46 //the following do not exist in V41 or earlier #define kASL 48 #define kMaxImageType 4 //4 observed image types: real, imag, mag, phase (in theory also subsequent calculation such as B1) printWarning("dcm2niix PAR is not actively supported (hint: use dicm2nii)\n"); if (isReadPhase) printWarning(" Reading phase images from PAR/REC\n"); char buff[LINESZ]; //next values: PAR V3 only int v3BitsPerVoxel = 16; //V3: not per slice: "Image pixel size [8 or 16 bits]" int v3Xdim = 128; //not per slice: "Recon resolution (x, y)" int v3Ydim = 128; //V3: not per slice: "Recon resolution (x, y)" float v3Thickmm = 2.0; //V3: not per slice: "Slice thickness [mm]" float v3Gapmm = 0.0; //V3: not per slice: "Slice gap [mm]" //from top of header int maxNumberOfDiffusionValues = 1; int maxNumberOfGradientOrients = 1; int maxNumberOfCardiacPhases = 1; int maxNumberOfEchoes = 1; int maxNumberOfDynamics = 1; int maxNumberOfMixes = 1; int maxNumberOfLabels = 1; //Number of label types <0=no ASL> float maxBValue = 0.0f; float maxDynTime = 0.0f; float minDynTime = 999999.0f; float TE = 0.0; int minDyn = 32767; int maxDyn = 0; int minSlice = 32767; int maxSlice = 0; bool ADCwarning = false; bool isTypeWarning = false; bool isType4Warning = false; bool isSequenceWarning = false; int numSlice2D = 0; int prevDyn = -1; bool dynNotAscending = false; int parVers = 0; int maxSeq = -1; //maximum value of Seq column int seq1 = -1; //value of Seq volume for first slice int maxEcho = 1; int maxCardiac = 1; int nCols = 26; //int diskSlice = 0; int num3DExpected = 0; //number of 3D volumes in the top part of the header int num2DExpected = 0; //number of 2D slices described in the top part of the header int maxVol = -1; int patientPositionNumPhilips = 0; d.isValid = false; const int kMaxCols = 49; float *cols = (float *)malloc(sizeof(float) * (kMaxCols + 1)); for (int i = 0; i < kMaxCols; i++) cols[i] = 0.0; //old versions of PAR do not fill all columns - beware of buffer overflow char *p = fgets(buff, LINESZ, fp); bool isIntenScaleVaries = false; for (int i = 0; i < kMaxDTI4D; i++) { dti4D->S[i].V[0] = -1.0; dti4D->TE[i] = -1.0; } for (int i = 0; i < kMaxSlice2D; i++) dti4D->sliceOrder[i] = -1; while (p) { if (strlen(buff) < 1) continue; if (buff[0] == '#') { //comment char Comment[7][50]; sscanf(buff, "# %s %s %s %s %s %s V%s\n", Comment[0], Comment[1], Comment[2], Comment[3], Comment[4], Comment[5], Comment[6]); if ((strcmp(Comment[0], "sl") == 0) && (strcmp(Comment[1], "ec") == 0)) { num3DExpected = maxNumberOfGradientOrients * maxNumberOfDiffusionValues * maxNumberOfLabels * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes; num2DExpected = d.xyzDim[3] * num3DExpected; if ((num2DExpected) >= kMaxSlice2D) { printError("Use dicm2nii or increase kMaxSlice2D to be more than %d\n", num2DExpected); printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n", d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); free(cols); return d; } } if (strcmp(Comment[1], "TRYOUT") == 0) { //sscanf(buff, "# %s %s %s %s %s %s V%s\n", Comment[0], Comment[1], Comment[2], Comment[3],Comment[4], Comment[5],Comment[6]); parVers = (int)round(atof(Comment[6]) * 10); //4.2 = 42 etc if (parVers <= 29) { printMessage("Unsupported old PAR version %0.2f (use dicm2nii)\n", parVers / 10.0); return d; //nCols = 26; //e.g. PAR 3.0 has 26 relevant columns } if (parVers < 40) { nCols = 29; // PAR 3.0? kRI = 7; kRS = 8; kSS = 9; kAngulationAPs = 12; kAngulationFHs = 13; kAngulationRLs = 14; kPositionAP = 15; kPositionFH = 16; kPositionRL = 17; kSliceOrients = 19; kXmm = 22; kYmm = 23; kTEcho = 24; kDynTime = 25; kTriggerTime = 26; kbval = 27; } else if (parVers < 41) nCols = kv1; //e.g PAR 4.0 else if (parVers < 42) nCols = kASL; //e.g. PAR 4.1 - last column is final diffusion b-value else nCols = kMaxCols; //e.g. PAR 4.2 } //the following do not exist in V3 p = fgets(buff, LINESZ, fp); //get next line continue; } //process '#' comment if (buff[0] == '.') { //tag char Comment[9][50]; for (int i = 0; i < 9; i++) strcpy(Comment[i], ""); sscanf(buff, ". %s %s %s %s %s %s %s %s %s\n", Comment[0], Comment[1], Comment[2], Comment[3], Comment[4], Comment[5], Comment[6], Comment[7], Comment[8]); if ((strcmp(Comment[0], "Acquisition") == 0) && (strcmp(Comment[1], "nr") == 0)) { d.acquNum = atoi(Comment[3]); d.seriesNum = d.acquNum; } if ((strcmp(Comment[0], "Recon") == 0) && (strcmp(Comment[1], "resolution") == 0)) { v3Xdim = (int)atoi(Comment[5]); v3Ydim = (int)atoi(Comment[6]); //printMessage("recon %d,%d\n", v3Xdim,v3Ydim); } if ((strcmp(Comment[1], "pixel") == 0) && (strcmp(Comment[2], "size") == 0)) { v3BitsPerVoxel = (int)atoi(Comment[8]); //printMessage("bits %d\n", v3BitsPerVoxel); } if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "gap") == 0)) { v3Gapmm = (float)atof(Comment[4]); //printMessage("gap %g\n", v3Gapmm); } if ((strcmp(Comment[0], "Slice") == 0) && (strcmp(Comment[1], "thickness") == 0)) { v3Thickmm = (float)atof(Comment[4]); //printMessage("thick %g\n", v3Thickmm); } if ((strcmp(Comment[0], "Repetition") == 0) && (strcmp(Comment[1], "time") == 0)) d.TR = (float)atof(Comment[4]); if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "name") == 0)) { strcpy(d.patientName, Comment[3]); strcat(d.patientName, Comment[4]); strcat(d.patientName, Comment[5]); strcat(d.patientName, Comment[6]); strcat(d.patientName, Comment[7]); cleanStr(d.patientName); //printMessage("%s\n",d.patientName); } if ((strcmp(Comment[0], "Technique") == 0) && (strcmp(Comment[1], ":") == 0)) { strcpy(d.patientID, Comment[2]); strcat(d.patientID, Comment[3]); strcat(d.patientID, Comment[4]); strcat(d.patientID, Comment[5]); strcat(d.patientID, Comment[6]); strcat(d.patientID, Comment[7]); cleanStr(d.patientID); } if ((strcmp(Comment[0], "Protocol") == 0) && (strcmp(Comment[1], "name") == 0)) { strcpy(d.protocolName, Comment[3]); strcat(d.protocolName, Comment[4]); strcat(d.protocolName, Comment[5]); strcat(d.protocolName, Comment[6]); strcat(d.protocolName, Comment[7]); cleanStr(d.protocolName); } if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "name") == 0)) { strcpy(d.imageComments, Comment[3]); strcat(d.imageComments, Comment[4]); strcat(d.imageComments, Comment[5]); strcat(d.imageComments, Comment[6]); strcat(d.imageComments, Comment[7]); cleanStr(d.imageComments); } if ((strcmp(Comment[0], "Series") == 0) && (strcmp(Comment[1], "Type") == 0)) { strcpy(d.seriesDescription, Comment[3]); strcat(d.seriesDescription, Comment[4]); strcat(d.seriesDescription, Comment[5]); strcat(d.seriesDescription, Comment[6]); strcat(d.seriesDescription, Comment[7]); cleanStr(d.seriesDescription); } if ((strcmp(Comment[0], "Examination") == 0) && (strcmp(Comment[1], "date/time") == 0)) { if ((strlen(Comment[3]) >= 10) && (strlen(Comment[5]) >= 8)) { //DICOM date format is YYYYMMDD, but PAR stores YYYY.MM.DD 2016.03.25 d.studyDate[0] = Comment[3][0]; d.studyDate[1] = Comment[3][1]; d.studyDate[2] = Comment[3][2]; d.studyDate[3] = Comment[3][3]; d.studyDate[4] = Comment[3][5]; d.studyDate[5] = Comment[3][6]; d.studyDate[6] = Comment[3][8]; d.studyDate[7] = Comment[3][9]; d.studyDate[8] = '\0'; //DICOM time format is HHMMSS.FFFFFF, but PAR stores HH:MM:SS, e.g. 18:00:42 or 09:34:16 d.studyTime[0] = Comment[5][0]; d.studyTime[1] = Comment[5][1]; d.studyTime[2] = Comment[5][3]; d.studyTime[3] = Comment[5][4]; d.studyTime[4] = Comment[5][6]; d.studyTime[5] = Comment[5][7]; d.studyTime[6] = '\0'; d.dateTime = (atof(d.studyDate) * 1000000) + atof(d.studyTime); } } if ((strcmp(Comment[0], "Off") == 0) && (strcmp(Comment[1], "Centre") == 0)) { //Off Centre midslice(ap,fh,rl) [mm] d.stackOffcentre[2] = (float)atof(Comment[5]); d.stackOffcentre[3] = (float)atof(Comment[6]); d.stackOffcentre[1] = (float)atof(Comment[7]); } if ((strcmp(Comment[0], "Patient") == 0) && (strcmp(Comment[1], "position") == 0)) { //Off Centre midslice(ap,fh,rl) [mm] d.patientOrient[0] = toupper(Comment[3][0]); d.patientOrient[1] = toupper(Comment[4][0]); d.patientOrient[2] = toupper(Comment[5][0]); d.patientOrient[3] = 0; } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "slices/locations") == 0)) { d.xyzDim[3] = atoi(Comment[5]); } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "diffusion") == 0)) { maxNumberOfDiffusionValues = atoi(Comment[6]); //if (maxNumberOfDiffusionValues > 1) maxNumberOfDiffusionValues -= 1; //if two listed, one is B=0 } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "gradient") == 0)) { maxNumberOfGradientOrients = atoi(Comment[6]); //Warning ISOTROPIC scans may be stored that are not reported here! 32 directions plus isotropic = 33 volumes } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "cardiac") == 0)) { maxNumberOfCardiacPhases = atoi(Comment[6]); } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "echoes") == 0)) { maxNumberOfEchoes = atoi(Comment[5]); if (maxNumberOfEchoes > 1) d.isMultiEcho = true; } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "dynamics") == 0)) { maxNumberOfDynamics = atoi(Comment[5]); } if ((strcmp(Comment[0], "Max.") == 0) && (strcmp(Comment[3], "mixes") == 0)) { maxNumberOfMixes = atoi(Comment[5]); if (maxNumberOfMixes > 1) printError("maxNumberOfMixes > 1. Please update this software to support these images\n"); } if ((strcmp(Comment[0], "Number") == 0) && (strcmp(Comment[2], "label") == 0)) { maxNumberOfLabels = atoi(Comment[7]); if (maxNumberOfLabels < 1) maxNumberOfLabels = 1; } p = fgets(buff, LINESZ, fp); //get next line continue; } //process '.' tag if (strlen(buff) < 24) { //empty line p = fgets(buff, LINESZ, fp); //get next line continue; } if (parVers < 20) { printError("PAR files should have 'CLINICAL TRYOUT' line with a version from 2.0-4.2: %s\n", parname); free(cols); return d; } for (int i = 0; i <= nCols; i++) cols[i] = strtof(p, &p); // p+1 skip comma, read a float //printMessage("xDim %dv%d yDim %dv%d bits %dv%d\n", d.xyzDim[1],(int)cols[kXdim], d.xyzDim[2], (int)cols[kYdim], d.bitsAllocated, (int)cols[kBitsPerVoxel]); if ((int)cols[kSlice] == 0) { //line does not contain attributes p = fgets(buff, LINESZ, fp); //get next line continue; } //diskSlice ++; bool isADC = false; if ((maxNumberOfGradientOrients >= 2) && (cols[kbval] > 50) && isSameFloat(0.0, cols[kv1]) && isSameFloat(0.0, cols[kv2]) && isSameFloat(0.0, cols[kv3])) { isADC = true; ADCwarning = true; } if (numSlice2D < 1) { d.xyzMM[1] = cols[kXmm]; d.xyzMM[2] = cols[kYmm]; if (parVers < 40) { //v3 does things differently //cccc d.xyzDim[1] = v3Xdim; d.xyzDim[2] = v3Ydim; d.xyzMM[3] = v3Thickmm + v3Gapmm; d.bitsAllocated = v3BitsPerVoxel; d.bitsStored = v3BitsPerVoxel; } else { d.xyzDim[1] = (int)cols[kXdim]; d.xyzDim[2] = (int)cols[kYdim]; d.xyzMM[3] = cols[kThickmm] + cols[kGapmm]; d.bitsAllocated = (int)cols[kBitsPerVoxel]; d.bitsStored = (int)cols[kBitsPerVoxel]; } d.patientPosition[1] = cols[kPositionRL]; d.patientPosition[2] = cols[kPositionAP]; d.patientPosition[3] = cols[kPositionFH]; d.angulation[1] = cols[kAngulationRLs]; d.angulation[2] = cols[kAngulationAPs]; d.angulation[3] = cols[kAngulationFHs]; d.sliceOrient = (int)cols[kSliceOrients]; d.TE = cols[kTEcho]; d.echoNum = cols[kEcho]; d.TI = cols[kInversionDelayMs]; d.intenIntercept = cols[kRI]; d.intenScale = cols[kRS]; d.intenScalePhilips = cols[kSS]; } else { if (parVers >= 40) { if ((d.xyzDim[1] != cols[kXdim]) || (d.xyzDim[2] != cols[kYdim]) || (d.bitsAllocated != cols[kBitsPerVoxel])) { printError("Slice dimensions or bit depth varies %s\n", parname); printError("xDim %dv%d yDim %dv%d bits %dv%d\n", d.xyzDim[1], (int)cols[kXdim], d.xyzDim[2], (int)cols[kYdim], d.bitsAllocated, (int)cols[kBitsPerVoxel]); return d; } } if ((d.intenScale != cols[kRS]) || (d.intenIntercept != cols[kRI])) isIntenScaleVaries = true; } if (cols[kImageType] == 0) d.isHasMagnitude = true; if (cols[kImageType] != 0) d.isHasPhase = true; if (isSameFloat(cols[kImageType], 18)) { //printWarning("Field map in Hz will be saved as the 'real' image.\n"); //isTypeWarning = true; d.isRealIsPhaseMapHz = true; } else if (((cols[kImageType] < 0.0) || (cols[kImageType] > 4.0)) && (!isTypeWarning)) { printError("Unknown type %g: not magnitude[0], real[1], imaginary[2] or phase[3].\n", cols[kImageType]); isTypeWarning = true; } if (cols[kDyn] > maxDyn) maxDyn = (int)cols[kDyn]; if (cols[kDyn] < minDyn) minDyn = (int)cols[kDyn]; if (cols[kDyn] < prevDyn) dynNotAscending = true; prevDyn = cols[kDyn]; if (cols[kDynTime] > maxDynTime) maxDynTime = cols[kDynTime]; if (cols[kDynTime] < minDynTime) minDynTime = cols[kDynTime]; if (cols[kEcho] > maxEcho) maxEcho = cols[kEcho]; if (cols[kCardiac] > maxCardiac) maxCardiac = cols[kCardiac]; if ((cols[kEcho] == 1) && (cols[kDyn] == 1) && (cols[kCardiac] == 1) && (cols[kGradientNumber] == 1)) { if (cols[kSlice] == 1) { d.patientPosition[1] = cols[kPositionRL]; d.patientPosition[2] = cols[kPositionAP]; d.patientPosition[3] = cols[kPositionFH]; } patientPositionNumPhilips++; } if (true) { //for every slice int slice = (int)cols[kSlice]; if (slice < minSlice) minSlice = slice; if (slice > maxSlice) { maxSlice = slice; d.patientPositionLast[1] = cols[kPositionRL]; d.patientPositionLast[2] = cols[kPositionAP]; d.patientPositionLast[3] = cols[kPositionFH]; } int volStep = maxNumberOfDynamics; int vol = ((int)cols[kDyn] - 1); #ifdef old int gradDynVol = (int)cols[kGradientNumber] - 1; if (gradDynVol < 0) gradDynVol = 0; //old PAREC without cols[kGradientNumber] vol = vol + (volStep * (gradDynVol)); if (vol < 0) vol = 0; volStep = volStep * maxNumberOfGradientOrients; int bval = (int)cols[kbvalNumber]; if (bval > 2) //b=0 is 0, b=1000 is 1, b=2000 is 2 - b=0 does not have multiple directions bval = bval - 1; else bval = 1; //if (slice == 1) printMessage("bVal %d bVec %d isADC %d nbVal %d nGrad %d\n",(int) cols[kbvalNumber], (int)cols[kGradientNumber], isADC, maxNumberOfDiffusionValues, maxNumberOfGradientOrients); vol = vol + (volStep * (bval - 1)); volStep = volStep * (maxNumberOfDiffusionValues - 1); if (isADC) vol = volStep + (bval - 1); #else if (maxNumberOfDiffusionValues > 1) { int grad = (int)cols[kGradientNumber] - 1; if (grad < 0) grad = 0; //old v4 does not have this tag int bval = (int)cols[kbvalNumber] - 1; if (bval < 0) bval = 0; //old v4 does not have this tag if (isADC) vol = vol + (volStep * maxNumberOfDiffusionValues * maxNumberOfGradientOrients) + bval; else vol = vol + (volStep * grad) + (bval * maxNumberOfGradientOrients); volStep = volStep * (maxNumberOfDiffusionValues + 1) * maxNumberOfGradientOrients; //if (slice == 1) printMessage("vol %d step %d bVal %d bVec %d isADC %d nbVal %d nGrad %d\n", vol, volStep, (int) cols[kbvalNumber], (int)cols[kGradientNumber], isADC, maxNumberOfDiffusionValues, maxNumberOfGradientOrients); } #endif vol = vol + (volStep * ((int)cols[kEcho] - 1)); volStep = volStep * maxNumberOfEchoes; vol = vol + (volStep * ((int)cols[kCardiac] - 1)); volStep = volStep * maxNumberOfCardiacPhases; int ASL = (int)cols[kASL]; if (ASL < 1) ASL = 1; vol = vol + (volStep * (ASL - 1)); volStep = volStep * maxNumberOfLabels; //if ((int)cols[kSequence] > 0) int seq = (int)cols[kSequence]; if (seq1 < 0) seq1 = seq; if (seq > maxSeq) maxSeq = seq; if (seq != seq1) { //sequence varies within this PAR file if (!isSequenceWarning) { isSequenceWarning = true; printWarning("'scanning sequence' column varies within a single file. This behavior is not described at the top of the header.\n"); } vol = vol + (volStep * 1); volStep = volStep * 2; } //if (slice == 1) printMessage("%d\t%d\t%d\t%d\t%d\n", isADC,(int)cols[kbvalNumber], (int)cols[kGradientNumber], bval, vol); if (vol > maxVol) maxVol = vol; bool isReal = (cols[kImageType] == 1); bool isImaginary = (cols[kImageType] == 2); bool isPhase = (cols[kImageType] == 3); if (cols[kImageType] == 18) { isReal = true; d.isRealIsPhaseMapHz = true; } if (cols[kImageType] == 4) { if (!isType4Warning) { printWarning("Unknown image type (4). Be aware the 'phase' image is of an unknown type.\n"); isType4Warning = true; } isPhase = true; //2019 } if ((cols[kImageType] != 18) && ((cols[kImageType] < 0.0) || (cols[kImageType] > 3.0))) { if (!isType4Warning) { printWarning("Unknown image type (%g). Be aware the 'phase' image is of an unknown type.\n", round(cols[kImageType])); isType4Warning = true; } isReal = true; //<- this is not correct, kludge for bug in ROGERS_20180526_WIP_B0_NS_8_1.PAR } if (isReal) vol += num3DExpected; if (isImaginary) vol += (2 * num3DExpected); if (isPhase) vol += (3 * num3DExpected); if (vol >= kMaxDTI4D) { printError("Use dicm2nii or increase kMaxDTI4D (currently %d)to be more than %d\n", kMaxDTI4D, kMaxImageType * num2DExpected); printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n", d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); free(cols); return d; } // dti4D->S[vol].V[0] = cols[kbval]; //dti4D->gradDynVol[vol] = gradDynVol; dti4D->TE[vol] = cols[kTEcho]; if (isSameFloatGE(cols[kTEcho], 0)) dti4D->TE[vol] = TE; //kludge for cols[kImageType]==18 where TE set as 0 else TE = cols[kTEcho]; dti4D->triggerDelayTime[vol] = cols[kTriggerTime]; if (dti4D->TE[vol] < 0) dti4D->TE[vol] = 0; //used to detect sparse volumes //dti4D->intenIntercept[vol] = cols[kRI]; //dti4D->intenScale[vol] = cols[kRS]; //dti4D->intenScalePhilips[vol] = cols[kSS]; dti4D->isReal[vol] = isReal; dti4D->isImaginary[vol] = isImaginary; dti4D->isPhase[vol] = isPhase; if ((maxNumberOfGradientOrients > 1) && (parVers > 40)) { dti4D->S[vol].V[0] = cols[kbval]; dti4D->S[vol].V[1] = cols[kv1]; dti4D->S[vol].V[2] = cols[kv2]; dti4D->S[vol].V[3] = cols[kv3]; if ((vol + 1) > d.CSA.numDti) d.CSA.numDti = vol + 1; } if (numSlice2D < kMaxDTI4D) { //issue 363: intensity can vary with each 2D slice of 4D volume dti4D->intenIntercept[numSlice2D] = cols[kRI]; dti4D->intenScale[numSlice2D] = cols[kRS]; dti4D->intenScalePhilips[numSlice2D] = cols[kSS]; } //if (slice == 1) printWarning("%d\n", (int)cols[kEcho]); slice = slice + (vol * d.xyzDim[3]); //offset images by type: mag+0,real+1, imag+2,phase+3 //if (cols[kImageType] != 0) //yikes - phase maps! // slice = slice + numExpected; //printWarning("%d\t%d\n", slice -1, numSlice2D); if ((slice >= 0) && (slice < kMaxSlice2D) && (numSlice2D < kMaxSlice2D) && (numSlice2D >= 0)) { dti4D->sliceOrder[slice - 1] = numSlice2D; //printMessage("%d\t%d\t%d\n", numSlice2D, slice, (int)cols[kSlice],(int)vol); } numSlice2D++; } //printMessage("%f %f %lu\n",cols[9],cols[kGradientNumber], strlen(buff)) p = fgets(buff, LINESZ, fp); //get next line } free(cols); fclose(fp); if ((parVers <= 0) || (numSlice2D < 1)) { printError("Invalid PAR format header (unable to detect version or slices) %s\n", parname); return d; } if (numSlice2D > kMaxSlice2D) { //check again after reading, as top portion of header does not report image types or isotropics printError("Increase kMaxSlice2D from %d to at least %d (or use dicm2nii).\n", kMaxSlice2D, numSlice2D); return d; } if (numSlice2D > kMaxDTI4D) { //since issue460, kMaxSlice2D == kMaxSlice4D, so we should never get here printError("Increase kMaxDTI4D from %d to at least %d (or use dicm2nii).\n", kMaxDTI4D, numSlice2D); return d; } d.manufacturer = kMANUFACTURER_PHILIPS; d.isValid = true; d.isSigned = true; //remove unused volumes - this will happen if unless we have all 4 image types: real, imag, mag, phase maxVol = 0; for (int i = 0; i < kMaxDTI4D; i++) { if (dti4D->TE[i] > -1.0) { dti4D->TE[maxVol] = dti4D->TE[i]; dti4D->triggerDelayTime[maxVol] = dti4D->triggerDelayTime[i]; //dti4D->intenIntercept[maxVol] = dti4D->intenIntercept[i]; //dti4D->intenScale[maxVol] = dti4D->intenScale[i]; //dti4D->intenScalePhilips[maxVol] = dti4D->intenScalePhilips[i]; dti4D->isReal[maxVol] = dti4D->isReal[i]; dti4D->isImaginary[maxVol] = dti4D->isImaginary[i]; dti4D->isPhase[maxVol] = dti4D->isPhase[i]; dti4D->S[maxVol].V[0] = dti4D->S[i].V[0]; dti4D->S[maxVol].V[1] = dti4D->S[i].V[1]; dti4D->S[maxVol].V[2] = dti4D->S[i].V[2]; dti4D->S[maxVol].V[3] = dti4D->S[i].V[3]; maxVol = maxVol + 1; } } if (d.CSA.numDti > 0) d.CSA.numDti = maxVol; //e.g. gradient 2 can skip B=0 but include isotropic //remove unused slices - this will happen if unless we have all 4 image types: real, imag, mag, phase int slice = 0; for (int i = 0; i < kMaxSlice2D; i++) { if (dti4D->sliceOrder[i] > -1) { //this slice was populated dti4D->sliceOrder[slice] = dti4D->sliceOrder[i]; slice = slice + 1; } } if (slice != numSlice2D) { printError("Catastrophic error: found %d but expected %d slices. %s\n", slice, numSlice2D, parname); printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d\n", d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); d.isValid = false; } for (int i = 0; i < numSlice2D; i++) { //issue363 if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) d.isScaleVariesEnh = true; if (dti4D->intenScale[i] != dti4D->intenScale[0]) d.isScaleVariesEnh = true; if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) d.isScaleVariesEnh = true; //printf("%g --> %g\n", dti4D->intenIntercept[i], dti4D->intenScale[i]); } if (d.isScaleVariesEnh) { //juggle to sorted order, required for subsequent rescaling printWarning("PAR/REC intensity scaling varies between slices (please validate output).\n"); TDTI4D tmp; for (int i = 0; i < numSlice2D; i++) { //issue363 tmp.intenIntercept[i] = dti4D->intenIntercept[i]; tmp.intenScale[i] = dti4D->intenScale[i]; tmp.intenScalePhilips[i] = dti4D->intenScalePhilips[i]; } for (int i = 0; i < numSlice2D; i++) { int j = dti4D->sliceOrder[i]; dti4D->intenIntercept[i] = tmp.intenIntercept[j]; dti4D->intenScale[i] = tmp.intenScale[j]; dti4D->intenScalePhilips[i] = tmp.intenScalePhilips[j]; } } d.isScaleOrTEVaries = true; if (numSlice2D > kMaxSlice2D) { printError("Overloaded slice re-ordering. Number of slices (%d) exceeds kMaxSlice2D (%d)\n", numSlice2D, kMaxSlice2D); dti4D->sliceOrder[0] = -1; dti4D->intenScale[0] = 0.0; } if ((maxSlice - minSlice + 1) != d.xyzDim[3]) { int numSlice = (maxSlice - minSlice) + 1; printWarning("Expected %d slices, but found %d (%d..%d). %s\n", d.xyzDim[3], numSlice, minSlice, maxSlice, parname); if (numSlice <= 0) d.isValid = false; d.xyzDim[3] = numSlice; num2DExpected = d.xyzDim[3] * num3DExpected; } if ((maxBValue <= 0.0f) && (maxDyn > minDyn) && (maxDynTime > minDynTime)) { //use max vs min Dyn instead of && (d.CSA.numDti > 1) int numDyn = (maxDyn - minDyn) + 1; if (numDyn != maxNumberOfDynamics) { printWarning("Expected %d dynamics, but found %d (%d..%d).\n", maxNumberOfDynamics, numDyn, minDyn, maxDyn); maxNumberOfDynamics = numDyn; num3DExpected = maxNumberOfGradientOrients * maxNumberOfDiffusionValues * maxNumberOfLabels * maxNumberOfCardiacPhases * maxNumberOfEchoes * maxNumberOfDynamics * maxNumberOfMixes; num2DExpected = d.xyzDim[3] * num3DExpected; } float TRms = 1000.0f * (maxDynTime - minDynTime) / (float)(numDyn - 1); //-1 for fence post if (fabs(TRms - d.TR) > 0.005f) printWarning("Reported TR=%gms, measured TR=%gms (prospect. motion corr.?)\n", d.TR, TRms); d.TR = TRms; } if ((isTypeWarning) && ((numSlice2D % num2DExpected) != 0) && ((numSlice2D % d.xyzDim[3]) == 0)) { num2DExpected = numSlice2D; } if (((numSlice2D % num2DExpected) != 0) && ((numSlice2D % d.xyzDim[3]) == 0)) { num2DExpected = d.xyzDim[3] * (int)(numSlice2D / d.xyzDim[3]); if (!ADCwarning) printWarning("More volumes than described in header (ADC or isotropic?)\n"); } if ((numSlice2D % num2DExpected) != 0) { printMessage("Found %d slices, but expected divisible by %d: slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d %s\n", numSlice2D, num2DExpected, d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels, parname); d.isValid = false; } if (dynNotAscending) { printWarning("PAR file volumes not saved in ascending temporal order (please check re-ordering)\n"); } if ((slice % d.xyzDim[3]) != 0) { printError("Total number of slices (%d) not divisible by slices per 3D volume (%d) [acquisition aborted]. Try dicm2nii or R2AGUI: %s\n", slice, d.xyzDim[3], parname); d.isValid = false; return d; } d.xyzDim[4] = slice / d.xyzDim[3]; d.locationsInAcquisition = d.xyzDim[3]; if (ADCwarning) printWarning("PAR/REC dataset includes derived (isotropic, ADC, etc) map(s) that could disrupt analysis. Please remove volume and ensure vectors are reported correctly\n"); if (isIntenScaleVaries) printWarning("Intensity slope/intercept varies between slices! [check resulting images]\n"); if ((isVerbose) && (d.isValid)) { printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*labels = %d*%d*%d*%d*%d*%d*%d*%d\n", d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); } if ((d.xyzDim[3] > 1) && (minSlice == 1) && (maxSlice > minSlice)) { //issue 273 float dx[4]; dx[1] = (d.patientPosition[1] - d.patientPositionLast[1]); dx[2] = (d.patientPosition[2] - d.patientPositionLast[2]); dx[3] = (d.patientPosition[3] - d.patientPositionLast[3]); //compute error using 3D pythagorean theorm float sliceMM = sqrt(pow(dx[1], 2) + pow(dx[2], 2) + pow(dx[3], 2)); sliceMM = sliceMM / (maxSlice - minSlice); if (!(isSameFloatGE(sliceMM, d.xyzMM[3]))) { //if (d.xyzMM[3] > 0.0) printWarning("Distance between slices reported by slice gap+thick does not match estimate from slice positions (issue 273).\n"); d.xyzMM[3] = sliceMM; } } //issue 273 printMessage("Done reading PAR header version %.1f, with %d slices\n", (float)parVers / 10, numSlice2D); //see Xiangrui Li 's dicm2nii (also BSD license) // http://www.mathworks.com/matlabcentral/fileexchange/42997-dicom-to-nifti-converter // Rotation order and signs are figured out by trial and error, not 100% sure float d2r = (float)(M_PI / 180.0); vec3 ca = setVec3(cos(d.angulation[1] * d2r), cos(d.angulation[2] * d2r), cos(d.angulation[3] * d2r)); vec3 sa = setVec3(sin(d.angulation[1] * d2r), sin(d.angulation[2] * d2r), sin(d.angulation[3] * d2r)); mat33 rx, ry, rz; LOAD_MAT33(rx, 1.0f, 0.0f, 0.0f, 0.0f, ca.v[0], -sa.v[0], 0.0f, sa.v[0], ca.v[0]); LOAD_MAT33(ry, ca.v[1], 0.0f, sa.v[1], 0.0f, 1.0f, 0.0f, -sa.v[1], 0.0f, ca.v[1]); LOAD_MAT33(rz, ca.v[2], -sa.v[2], 0.0f, sa.v[2], ca.v[2], 0.0f, 0.0f, 0.0f, 1.0f); mat33 R = nifti_mat33_mul(rx, ry); R = nifti_mat33_mul(R, rz); ivec3 ixyz = setiVec3(1, 2, 3); if (d.sliceOrient == kSliceOrientSag) { ixyz = setiVec3(2, 3, 1); for (int r = 0; r < 3; r++) for (int c = 0; c < 3; c++) if (c != 1) R.m[r][c] = -R.m[r][c]; //invert first and final columns } else if (d.sliceOrient == kSliceOrientCor) { ixyz = setiVec3(1, 3, 2); for (int r = 0; r < 3; r++) R.m[r][2] = -R.m[r][2]; //invert rows of final column } R = nifti_mat33_reorder_cols(R, ixyz); //dicom rotation matrix d.orient[1] = R.m[0][0]; d.orient[2] = R.m[1][0]; d.orient[3] = R.m[2][0]; d.orient[4] = R.m[0][1]; d.orient[5] = R.m[1][1]; d.orient[6] = R.m[2][1]; mat33 diag; LOAD_MAT33(diag, d.xyzMM[1], 0.0f, 0.0f, 0.0f, d.xyzMM[2], 0.0f, 0.0f, 0.0f, d.xyzMM[3]); R = nifti_mat33_mul(R, diag); mat44 R44; LOAD_MAT44(R44, R.m[0][0], R.m[0][1], R.m[0][2], d.stackOffcentre[1], R.m[1][0], R.m[1][1], R.m[1][2], d.stackOffcentre[2], R.m[2][0], R.m[2][1], R.m[2][2], d.stackOffcentre[3]); vec3 x; if (parVers > 40) //guess x = setVec3(((float)d.xyzDim[1] - 1) / 2, ((float)d.xyzDim[2] - 1) / 2, ((float)d.xyzDim[3] - 1) / 2); else x = setVec3((float)d.xyzDim[1] / 2, (float)d.xyzDim[2] / 2, ((float)d.xyzDim[3] - 1) / 2); mat44 eye; LOAD_MAT44(eye, 1.0f, 0.0f, 0.0f, x.v[0], 0.0f, 1.0f, 0.0f, x.v[1], 0.0f, 0.0f, 1.0f, x.v[2]); eye = nifti_mat44_inverse(eye); //we wish to compute R/eye, so compute invEye and calculate R*invEye R44 = nifti_mat44_mul(R44, eye); vec4 y; y.v[0] = 0.0f; y.v[1] = 0.0f; y.v[2] = (float)d.xyzDim[3] - 1.0f; y.v[3] = 1.0f; y = nifti_vect44mat44_mul(y, R44); int iOri = 2; //for axial, slices are 3rd dimenson (indexed from 0) (k) if (d.sliceOrient == kSliceOrientSag) iOri = 0; //for sagittal, slices are 1st dimension (i) if (d.sliceOrient == kSliceOrientCor) iOri = 1; //for coronal, slices are 2nd dimension (j) if (d.xyzDim[3] > 1) { //detect and fix Philips Bug //Est: assuming "image offcentre (ap,fh,rl in mm )" is correct float stackOffcentreEst[4]; stackOffcentreEst[1] = (d.patientPosition[1] + d.patientPositionLast[1]) * 0.5; stackOffcentreEst[2] = (d.patientPosition[2] + d.patientPositionLast[2]) * 0.5; stackOffcentreEst[3] = (d.patientPosition[3] + d.patientPositionLast[3]) * 0.5; //compute error using 3D pythagorean theorm stackOffcentreEst[0] = sqrt(pow(stackOffcentreEst[1] - d.stackOffcentre[1], 2) + pow(stackOffcentreEst[2] - d.stackOffcentre[2], 2) + pow(stackOffcentreEst[3] - d.stackOffcentre[3], 2)); //Est: assuming "image offcentre (ap,fh,rl in mm )" is stored in order rl,ap,fh float stackOffcentreRev[4]; stackOffcentreRev[1] = (d.patientPosition[2] + d.patientPositionLast[2]) * 0.5; stackOffcentreRev[2] = (d.patientPosition[3] + d.patientPositionLast[3]) * 0.5; stackOffcentreRev[3] = (d.patientPosition[1] + d.patientPositionLast[1]) * 0.5; //compute error using 3D pythagorean theorm stackOffcentreRev[0] = sqrt(pow(stackOffcentreRev[1] - d.stackOffcentre[1], 2) + pow(stackOffcentreRev[2] - d.stackOffcentre[2], 2) + pow(stackOffcentreRev[3] - d.stackOffcentre[3], 2)); //detect, report and fix error if ((stackOffcentreEst[0] > 1.0) && (stackOffcentreRev[0] < stackOffcentreEst[0])) { //error detected: the ">1.0" handles the low precision of the "Off Centre" values printMessage("Order of 'image offcentre (ap,fh,rl in mm )' appears incorrect (assuming rl,ap,fh)\n"); printMessage(" err[ap,fh,rl]= %g (%g %g %g) \n", stackOffcentreEst[0], stackOffcentreEst[1], stackOffcentreEst[2], stackOffcentreEst[3]); printMessage(" err[rl,ap,fh]= %g (%g %g %g) \n", stackOffcentreRev[0], stackOffcentreRev[1], stackOffcentreRev[2], stackOffcentreRev[3]); printMessage(" orient\t%d\tOffCentre 1st->mid->nth\t%g\t%g\t%g\t->\t%g\t%g\t%g\t->\t%g\t%g\t%g\t=\t%g\t%s\n", iOri, d.patientPosition[1], d.patientPosition[2], d.patientPosition[3], d.stackOffcentre[1], d.stackOffcentre[2], d.stackOffcentre[3], d.patientPositionLast[1], d.patientPositionLast[2], d.patientPositionLast[3], (d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]), parname); //correct patientPosition for (int i = 1; i < 4; i++) stackOffcentreRev[i] = d.patientPosition[i]; d.patientPosition[1] = stackOffcentreRev[2]; d.patientPosition[2] = stackOffcentreRev[3]; d.patientPosition[3] = stackOffcentreRev[1]; //correct patientPositionLast for (int i = 1; i < 4; i++) stackOffcentreRev[i] = d.patientPositionLast[i]; d.patientPositionLast[1] = stackOffcentreRev[2]; d.patientPositionLast[2] = stackOffcentreRev[3]; d.patientPositionLast[3] = stackOffcentreRev[1]; } //if bug: report and fix } //if 3D data bool flip = false; //assume head first supine if ((iOri == 0) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) > 0))) flip = true; //6/2018 : TODO, not sure if this is >= or > if ((iOri == 1) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) <= 0))) flip = true; //<= not <, leslie_dti_6_1.PAR if ((iOri == 2) && (((d.patientPosition[iOri + 1] - d.patientPositionLast[iOri + 1]) <= 0))) flip = true; //<= not <, see leslie_dti_3_1.PAR if (flip) { //if ((d.patientPosition[iOri+1] - d.patientPositionLast[iOri+1]) < 0) { //if (( (y.v[iOri]-R44.m[iOri][3])>0 ) == ( (y.v[iOri]-d.stackOffcentre[iOri+1])>0 ) ) { d.patientPosition[1] = R44.m[0][3]; d.patientPosition[2] = R44.m[1][3]; d.patientPosition[3] = R44.m[2][3]; d.patientPositionLast[1] = y.v[0]; d.patientPositionLast[2] = y.v[1]; d.patientPositionLast[3] = y.v[2]; //printWarning(" Flipping slice order: please verify %s\n", parname); } else { //printWarning(" NOT Flipping slice order: please verify %s\n", parname); d.patientPosition[1] = y.v[0]; d.patientPosition[2] = y.v[1]; d.patientPosition[3] = y.v[2]; d.patientPositionLast[1] = R44.m[0][3]; d.patientPositionLast[2] = R44.m[1][3]; d.patientPositionLast[3] = R44.m[2][3]; } //finish up changeExt(parname, "REC"); #ifndef _MSC_VER //Linux is case sensitive, #include if (access(parname, F_OK) != 0) changeExt(parname, "rec"); #endif d.locationsInAcquisition = d.xyzDim[3]; d.imageStart = 0; if (d.CSA.numDti >= kMaxDTI4D) { printError("Unable to convert DTI [increase kMaxDTI4D] found %d directions\n", d.CSA.numDti); printMessage(" slices*grad*bval*cardiac*echo*dynamic*mix*label = %d*%d*%d*%d*%d*%d*%d*%d\n", d.xyzDim[3], maxNumberOfGradientOrients, maxNumberOfDiffusionValues, maxNumberOfCardiacPhases, maxNumberOfEchoes, maxNumberOfDynamics, maxNumberOfMixes, maxNumberOfLabels); d.CSA.numDti = 0; }; //check if dimensions vary if (maxVol > 0) { //maxVol indexed from 0 for (int i = 1; i <= maxVol; i++) { //if (dti4D->gradDynVol[i] > d.maxGradDynVol) d.maxGradDynVol = dti4D->gradDynVol[i]; //issue363 slope/intercept can vary for each 2D slice, not only between 3D volumes in a 4D time series //if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) d.isScaleOrTEVaries = true; //if (dti4D->intenScale[i] != dti4D->intenScale[0]) d.isScaleOrTEVaries = true; //if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) d.isScaleOrTEVaries = true; if (dti4D->isPhase[i] != dti4D->isPhase[0]) d.isScaleOrTEVaries = true; if (dti4D->isReal[i] != dti4D->isReal[0]) d.isScaleOrTEVaries = true; if (dti4D->isImaginary[i] != dti4D->isImaginary[0]) d.isScaleOrTEVaries = true; if (dti4D->triggerDelayTime[i] != dti4D->triggerDelayTime[0]) d.isScaleOrTEVaries = true; } //if (d.isScaleOrTEVaries) // printWarning("Varying dimensions (echoes, phase maps, intensity scaling) will require volumes to be saved separately (hint: you may prefer dicm2nii output)\n"); } //if (d.CSA.numDti > 1) // for (int i = 0; i < d.CSA.numDti; i++) // printMessage("%d\tb=\t%g\tv=\t%g\t%g\t%g\n",i,dti4D->S[i].V[0],dti4D->S[i].V[1],dti4D->S[i].V[2],dti4D->S[i].V[3]); //check DTI makes sense if (d.CSA.numDti > 1) { bool v1varies = false; bool v2varies = false; bool v3varies = false; for (int i = 1; i < d.CSA.numDti; i++) { if (dti4D->S[0].V[1] != dti4D->S[i].V[1]) v1varies = true; if (dti4D->S[0].V[2] != dti4D->S[i].V[2]) v2varies = true; if (dti4D->S[0].V[3] != dti4D->S[i].V[3]) v3varies = true; } if ((!v1varies) || (!v2varies) || (!v3varies)) printError("Bizarre b-vectors %s\n", parname); } if ((maxEcho > 1) || (maxCardiac > 1)) printWarning("Multiple Echo (%d) or Cardiac (%d). Carefully inspect output\n", maxEcho, maxCardiac); if ((maxEcho > 1) || (maxCardiac > 1)) d.isScaleOrTEVaries = true; return d; } //nii_readParRec() size_t nii_SliceBytes(struct nifti_1_header hdr) { //size of 2D slice size_t imgsz = hdr.bitpix / 8; for (int i = 1; i < 3; i++) if (hdr.dim[i] > 1) imgsz = imgsz * hdr.dim[i]; return imgsz; } //nii_SliceBytes() size_t nii_ImgBytes(struct nifti_1_header hdr) { size_t imgsz = hdr.bitpix / 8; for (int i = 1; i < 8; i++) if (hdr.dim[i] > 1) imgsz = imgsz * hdr.dim[i]; return imgsz; } //nii_ImgBytes() //unsigned char * nii_demosaic(unsigned char* inImg, struct nifti_1_header *hdr, int nMosaicSlices, int ProtocolSliceNumber1) { unsigned char *nii_demosaic(unsigned char *inImg, struct nifti_1_header *hdr, int nMosaicSlices, bool isUIH) { //demosaic http://nipy.org/nibabel/dicom/dicom_mosaic.html if (nMosaicSlices < 2) return inImg; //Byte inImg[ [img length] ]; //[img getBytes:&inImg length:[img length]]; int nCol = (int)ceil(sqrt((double)nMosaicSlices)); int nRow = nCol; //n.b. Siemens store 20 images as 5x5 grid, UIH as 5rows, 4 Col https://github.com/rordenlab/dcm2niix/issues/225 if (isUIH) nRow = ceil((float)nMosaicSlices / (float)nCol); //printf("%d = %dx%d\n", nMosaicSlices, nCol, nRow); int colBytes = hdr->dim[1] / nCol * hdr->bitpix / 8; int lineBytes = hdr->dim[1] * hdr->bitpix / 8; int rowBytes = hdr->dim[1] * hdr->dim[2] / nRow * hdr->bitpix / 8; int col = 0; int row = 0; int lOutPos = 0; hdr->dim[1] = hdr->dim[1] / nCol; hdr->dim[2] = hdr->dim[2] / nRow; hdr->dim[3] = nMosaicSlices; size_t imgsz = nii_ImgBytes(*hdr); unsigned char *outImg = (unsigned char *)malloc(imgsz); for (int m = 1; m <= nMosaicSlices; m++) { int lPos = (row * rowBytes) + (col * colBytes); for (int y = 0; y < hdr->dim[2]; y++) { memcpy(&outImg[lOutPos], &inImg[lPos], colBytes); // dest, src, bytes lPos += lineBytes; lOutPos += colBytes; } col++; if (col >= nCol) { row++; col = 0; } //start new column } //for m = each mosaic slice free(inImg); return outImg; } // nii_demosaic() unsigned char *nii_flipImgY(unsigned char *bImg, struct nifti_1_header *hdr) { //DICOM row order opposite from NIfTI int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; size_t lineBytes = hdr->dim[1] * hdr->bitpix / 8; if ((hdr->datatype == DT_RGB24) && (hdr->bitpix == 24) && (hdr->intent_code == NIFTI_INTENT_NONE)) { //we use the intent code to indicate planar vs triplet... lineBytes = hdr->dim[1]; dim3to7 = dim3to7 * 3; } //rgb data saved planar (RRR..RGGGG..GBBB..B unsigned char *line = (unsigned char *)malloc(sizeof(unsigned char) * (lineBytes)); size_t sliceBytes = hdr->dim[2] * lineBytes; int halfY = hdr->dim[2] / 2; //note truncated toward zero, so halfY=2 regardless of 4 or 5 columns for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice size_t slBottom = (size_t)sl * sliceBytes; size_t slTop = (((size_t)sl + 1) * sliceBytes) - lineBytes; for (int y = 0; y < halfY; y++) { //swap order of lines memcpy(line, &bImg[slBottom], lineBytes); //memcpy(&line, &bImg[slBottom], lineBytes); memcpy(&bImg[slBottom], &bImg[slTop], lineBytes); memcpy(&bImg[slTop], line, lineBytes); //tpx memcpy(&bImg[slTop], &line, lineBytes); slTop -= lineBytes; slBottom += lineBytes; } //for y } //for each slice free(line); return bImg; } // nii_flipImgY() unsigned char *nii_flipImgZ(unsigned char *bImg, struct nifti_1_header *hdr) { //DICOM row order opposite from NIfTI int halfZ = hdr->dim[3] / 2; //note truncated toward zero, so halfY=2 regardless of 4 or 5 columns if (halfZ < 1) return bImg; int dim4to7 = 1; for (int i = 4; i < 8; i++) if (hdr->dim[i] > 1) dim4to7 = dim4to7 * hdr->dim[i]; size_t sliceBytes = hdr->dim[1] * hdr->dim[2] * hdr->bitpix / 8; size_t volBytes = sliceBytes * hdr->dim[3]; unsigned char *slice = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes)); for (int vol = 0; vol < dim4to7; vol++) { //for each 2D slice size_t slBottom = vol * volBytes; size_t slTop = ((vol + 1) * volBytes) - sliceBytes; for (int z = 0; z < halfZ; z++) { //swap order of lines memcpy(slice, &bImg[slBottom], sliceBytes); //TPX memcpy(&slice, &bImg[slBottom], sliceBytes); memcpy(&bImg[slBottom], &bImg[slTop], sliceBytes); memcpy(&bImg[slTop], slice, sliceBytes); //TPX slTop -= sliceBytes; slBottom += sliceBytes; } //for Z } //for each volume free(slice); return bImg; } // nii_flipImgZ() unsigned char *nii_flipZ(unsigned char *bImg, struct nifti_1_header *h) { //flip slice order if (h->dim[3] < 2) return bImg; mat33 s; mat44 Q44; LOAD_MAT33(s, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_y[0], h->srow_y[1], h->srow_y[2], h->srow_z[0], h->srow_z[1], h->srow_z[2]); LOAD_MAT44(Q44, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_x[3], h->srow_y[0], h->srow_y[1], h->srow_y[2], h->srow_y[3], h->srow_z[0], h->srow_z[1], h->srow_z[2], h->srow_z[3]); vec4 v = setVec4(0.0f, 0.0f, (float)h->dim[3] - 1.0f); v = nifti_vect44mat44_mul(v, Q44); //after flip this voxel will be the origin mat33 mFlipZ; LOAD_MAT33(mFlipZ, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f); s = nifti_mat33_mul(s, mFlipZ); LOAD_MAT44(Q44, s.m[0][0], s.m[0][1], s.m[0][2], v.v[0], s.m[1][0], s.m[1][1], s.m[1][2], v.v[1], s.m[2][0], s.m[2][1], s.m[2][2], v.v[2]); //printMessage(" ----------> %f %f %f\n",v.v[0],v.v[1],v.v[2]); setQSForm(h, Q44, true); //printMessage("nii_flipImgY dims %dx%dx%d %d \n",h->dim[1],h->dim[2], dim3to7,h->bitpix/8); return nii_flipImgZ(bImg, h); } // nii_flipZ() unsigned char *nii_flipY(unsigned char *bImg, struct nifti_1_header *h) { mat33 s; mat44 Q44; LOAD_MAT33(s, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_y[0], h->srow_y[1], h->srow_y[2], h->srow_z[0], h->srow_z[1], h->srow_z[2]); LOAD_MAT44(Q44, h->srow_x[0], h->srow_x[1], h->srow_x[2], h->srow_x[3], h->srow_y[0], h->srow_y[1], h->srow_y[2], h->srow_y[3], h->srow_z[0], h->srow_z[1], h->srow_z[2], h->srow_z[3]); vec4 v = setVec4(0, (float)h->dim[2] - 1, 0); v = nifti_vect44mat44_mul(v, Q44); //after flip this voxel will be the origin mat33 mFlipY; LOAD_MAT33(mFlipY, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f); s = nifti_mat33_mul(s, mFlipY); LOAD_MAT44(Q44, s.m[0][0], s.m[0][1], s.m[0][2], v.v[0], s.m[1][0], s.m[1][1], s.m[1][2], v.v[1], s.m[2][0], s.m[2][1], s.m[2][2], v.v[2]); setQSForm(h, Q44, true); //printMessage("nii_flipImgY dims %dx%d %d \n",h->dim[1],h->dim[2], h->bitpix/8); return nii_flipImgY(bImg, h); } // nii_flipY() 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; int i12 = floor(i * 1.5); uint16_t val; if ((i % 2) != 1) { val = img[i12 + 1] + (img[i12 + 0] << 8); val = val >> 4; } else { val = img[i12 + 0] + (img[i12 + 1] << 8); } img[i16 + 0] = val & 0xFF; img[i16 + 1] = (val >> 8) & 0xFF; } } //conv12bit16bit() unsigned char *nii_loadImgCore(char *imgname, struct nifti_1_header hdr, int bitsAllocated, int imageStart32) { size_t imgsz = nii_ImgBytes(hdr); size_t imgszRead = imgsz; size_t imageStart = imageStart32; if (bitsAllocated == 12) imgszRead = round(imgsz * 0.75); FILE *file = fopen(imgname, "rb"); if (!file) { printError("Unable to open '%s'\n", imgname); return NULL; } fseek(file, 0, SEEK_END); long fileLen = ftell(file); 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); printWarning("File not large enough to store image data: %s\n", imgname); return NULL; } fseek(file, (long)imageStart, SEEK_SET); unsigned char *bImg = (unsigned char *)malloc(imgsz); //int i = 0; //while (bImg[i] == 0) i++; //printMessage("%d %d<\n",i,bImg[i]); size_t sz = fread(bImg, 1, imgszRead, file); fclose(file); if (sz < imgszRead) { printError("Only loaded %zu of %zu bytes for %s\n", sz, imgszRead, imgname); return NULL; } if (bitsAllocated == 12) conv12bit16bit(bImg, hdr); return bImg; } //nii_loadImgCore() unsigned char *nii_planar2rgb(unsigned char *bImg, struct nifti_1_header *hdr, int isPlanar) { //DICOM data saved in triples RGBRGBRGB, NIfTI RGB saved in planes RRR..RGGG..GBBBB..B if (bImg == NULL) return NULL; if (hdr->datatype != DT_RGB24) return bImg; if (isPlanar == 0) return bImg; int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int sliceBytes8 = hdr->dim[1] * hdr->dim[2]; int sliceBytes24 = sliceBytes8 * 3; unsigned char *slice24 = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes24)); int sliceOffsetRGB = 0; int sliceOffsetR = 0; int sliceOffsetG = sliceOffsetR + sliceBytes8; int sliceOffsetB = sliceOffsetR + 2 * sliceBytes8; //printMessage("planar->rgb %dx%dx%d\n", hdr->dim[1],hdr->dim[2], dim3to7); int i = 0; for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice memcpy(slice24, &bImg[sliceOffsetRGB], sliceBytes24); for (int rgb = 0; rgb < sliceBytes8; rgb++) { bImg[i++] = slice24[sliceOffsetR + rgb]; bImg[i++] = slice24[sliceOffsetG + rgb]; bImg[i++] = slice24[sliceOffsetB + rgb]; } sliceOffsetRGB += sliceBytes24; } //for each slice free(slice24); return bImg; } //nii_planar2rgb() unsigned char *nii_rgb2planar(unsigned char *bImg, struct nifti_1_header *hdr, int isPlanar) { //DICOM data saved in triples RGBRGBRGB, Analyze RGB saved in planes RRR..RGGG..GBBBB..B if (bImg == NULL) return NULL; if (hdr->datatype != DT_RGB24) return bImg; if (isPlanar == 1) return bImg; //return nii_bgr2rgb(bImg,hdr); int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int sliceBytes8 = hdr->dim[1] * hdr->dim[2]; int sliceBytes24 = sliceBytes8 * 3; unsigned char *slice24 = (unsigned char *)malloc(sizeof(unsigned char) * (sliceBytes24)); //printMessage("rgb->planar %dx%dx%d\n", hdr->dim[1],hdr->dim[2], dim3to7); int sliceOffsetR = 0; for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice memcpy(slice24, &bImg[sliceOffsetR], sliceBytes24); //TPX memcpy(&slice24, &bImg[sliceOffsetR], sliceBytes24); int sliceOffsetG = sliceOffsetR + sliceBytes8; int sliceOffsetB = sliceOffsetR + 2 * sliceBytes8; int i = 0; int j = 0; for (int rgb = 0; rgb < sliceBytes8; rgb++) { bImg[sliceOffsetR + j] = slice24[i++]; bImg[sliceOffsetG + j] = slice24[i++]; bImg[sliceOffsetB + j] = slice24[i++]; j++; } sliceOffsetR += sliceBytes24; } //for each slice free(slice24); return bImg; } //nii_rgb2Planar() unsigned char *nii_iVaries(unsigned char *img, struct nifti_1_header *hdr, struct TDTI4D *dti4D) { //each DICOM image can have its own intensity scaling, whereas NIfTI requires the same scaling for all images in a file //WARNING: do this BEFORE nii_check16bitUnsigned!!!! //if (hdr->datatype != DT_INT16) return img; int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; if (nVox < 1) return img; float *img32 = (float *)malloc(nVox * sizeof(float)); if (hdr->datatype == DT_UINT8) { uint8_t *img8i = (uint8_t *)img; for (int i = 0; i < nVox; i++) img32[i] = img8i[i]; } else if (hdr->datatype == DT_UINT16) { uint16_t *img16ui = (uint16_t *)img; for (int i = 0; i < nVox; i++) img32[i] = img16ui[i]; } else if (hdr->datatype == DT_INT16) { int16_t *img16i = (int16_t *)img; for (int i = 0; i < nVox; i++) img32[i] = img16i[i]; } else if (hdr->datatype == DT_INT32) { int32_t *img32i = (int32_t *)img; for (int i = 0; i < nVox; i++) img32[i] = (float)img32i[i]; } 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 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) //printf("vol\tRS(0028,1053)\tRI(0028,1052)\tSS(2005,100E)\trwS(0040,9225)\trwI(0040,9224)\n"); for (int i = 0; i < nVox; i++) { //issue 363 if ((i % dim1to2) == 0) { slice++; //printf("%d\t%g\t%g\t%g\t%g\t%g\n", slice, dti4D->intenScale[slice], dti4D->intenIntercept[slice],dti4D->intenScalePhilips[slice], dti4D->RWVScale[slice], dti4D->RWVIntercept[slice]); } img32[i] = (img32[i] * dti4D->intenScale[slice]) + dti4D->intenIntercept[slice]; } } else { // for (int i = 0; i < nVox; i++) img32[i] = (img32[i] * hdr->scl_slope) + hdr->scl_inter; } hdr->scl_slope = 1; hdr->scl_inter = 0; hdr->datatype = DT_FLOAT; hdr->bitpix = 32; return (unsigned char *)img32; } //nii_iVaries() unsigned char *nii_reorderSlicesX(unsigned char *bImg, struct nifti_1_header *hdr, struct TDTI4D *dti4D) { //Philips can save slices in any random order... rearrange all of them int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; if (dim3to7 < 2) return bImg; if (dim3to7 > kMaxSlice2D) return bImg; uint64_t imgSz = nii_ImgBytes(*hdr); uint64_t sliceBytes = hdr->dim[1] * hdr->dim[2] * hdr->bitpix / 8; unsigned char *outImg = (unsigned char *)malloc(imgSz); memcpy(&outImg[0], &bImg[0], imgSz); for (int i = 0; i < dim3to7; i++) { //for each volume int fromSlice = dti4D->sliceOrder[i]; //if (i < 10) printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", i, toSlice); //printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", fromSlice, i); if ((i < 0) || (fromSlice >= dim3to7)) { printError("Re-ordered slice out-of-volume %d\n", fromSlice); } else if (i != fromSlice) { uint64_t inPos = fromSlice * sliceBytes; uint64_t outPos = i * sliceBytes; memcpy(&bImg[outPos], &outImg[inPos], sliceBytes); } } free(outImg); return bImg; } unsigned char *nii_byteswap(unsigned char *img, struct nifti_1_header *hdr) { if (hdr->bitpix < 9) return img; uint64_t nvox = nii_ImgBytes(*hdr) / (hdr->bitpix / 8); void *ar = (void *)img; if (hdr->bitpix == 16) nifti_swap_2bytes(nvox, ar); if (hdr->bitpix == 32) nifti_swap_4bytes(nvox, ar); if (hdr->bitpix == 64) nifti_swap_8bytes(nvox, ar); return img; } //nii_byteswap() #ifdef myEnableJasper unsigned char *nii_loadImgCoreJasper(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int compressFlag) { if (jas_init()) { return NULL; } jas_stream_t *in; jas_image_t *image; jas_setdbglevel(0); if (!(in = jas_stream_fopen(imgname, "rb"))) { printError("Cannot open input image file %s\n", imgname); return NULL; } //int isSeekable = jas_stream_isseekable(in); jas_stream_seek(in, dcm.imageStart, 0); int infmt = jas_image_getfmt(in); if (infmt < 0) { printError("Input image has unknown format %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes); return NULL; } char opt[] = "\0"; char *inopts = opt; if (!(image = jas_image_decode(in, infmt, inopts))) { printError("Cannot decode image data %s offset %d bytes %d\n", imgname, dcm.imageStart, dcm.imageBytes); return NULL; } int numcmpts; int cmpts[4]; switch (jas_clrspc_fam(jas_image_clrspc(image))) { case JAS_CLRSPC_FAM_RGB: if (jas_image_clrspc(image) != JAS_CLRSPC_SRGB) printWarning("Inaccurate color\n"); numcmpts = 3; if ((cmpts[0] = jas_image_getcmptbytype(image, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R))) < 0 || (cmpts[1] = jas_image_getcmptbytype(image, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G))) < 0 || (cmpts[2] = jas_image_getcmptbytype(image, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B))) < 0) { printError("Missing color component\n"); return NULL; } break; case JAS_CLRSPC_FAM_GRAY: if (jas_image_clrspc(image) != JAS_CLRSPC_SGRAY) printWarning("Inaccurate color\n"); numcmpts = 1; if ((cmpts[0] = jas_image_getcmptbytype(image, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_GRAY_Y))) < 0) { printError("Missing color component\n"); return NULL; } break; default: printError("Unsupported color space\n"); return NULL; break; } int width = jas_image_cmptwidth(image, cmpts[0]); int height = jas_image_cmptheight(image, cmpts[0]); int prec = jas_image_cmptprec(image, cmpts[0]); int sgnd = jas_image_cmptsgnd(image, cmpts[0]); #ifdef MY_DEBUG printMessage("offset %d w*h %d*%d bpp %d sgnd %d components %d '%s' Jasper=%s\n", dcm.imageStart, width, height, prec, sgnd, numcmpts, imgname, jas_getversion()); #endif for (int cmptno = 0; cmptno < numcmpts; ++cmptno) { if (jas_image_cmptwidth(image, cmpts[cmptno]) != width || jas_image_cmptheight(image, cmpts[cmptno]) != height || jas_image_cmptprec(image, cmpts[cmptno]) != prec || jas_image_cmptsgnd(image, cmpts[cmptno]) != sgnd || jas_image_cmpthstep(image, cmpts[cmptno]) != jas_image_cmpthstep(image, 0) || jas_image_cmptvstep(image, cmpts[cmptno]) != jas_image_cmptvstep(image, 0) || jas_image_cmpttlx(image, cmpts[cmptno]) != jas_image_cmpttlx(image, 0) || jas_image_cmpttly(image, cmpts[cmptno]) != jas_image_cmpttly(image, 0)) { printMessage("The NIfTI format cannot be used to represent an image with this geometry.\n"); return NULL; } } //extract the data int bpp = (prec + 7) >> 3; //e.g. 12 bits requires 2 bytes int imgbytes = bpp * width * height * numcmpts; if ((bpp < 1) || (bpp > 2) || (width < 1) || (height < 1) || (imgbytes < 1)) { printError("Catastrophic decompression error\n"); return NULL; } jas_seqent_t v; unsigned char *img = (unsigned char *)malloc(imgbytes); uint16_t *img16ui = (uint16_t *)img; //unsigned 16-bit int16_t *img16i = (int16_t *)img; //signed 16-bit if (sgnd) bpp = -bpp; if (bpp == -1) { printError("Signed 8-bit DICOM?\n"); return NULL; } jas_matrix_t *data; jas_seqent_t *d; data = 0; int cmptno, y, x; int pix = 0; for (cmptno = 0; cmptno < numcmpts; ++cmptno) { if (!(data = jas_matrix_create(1, width))) { free(img); return NULL; } } //n.b. Analyze rgb-24 are PLANAR e.g. RRR..RGGG..GBBB..B not RGBRGBRGB...RGB for (cmptno = 0; cmptno < numcmpts; ++cmptno) { for (y = 0; y < height; ++y) { if (jas_image_readcmpt(image, cmpts[cmptno], 0, y, width, 1, data)) { free(img); return NULL; } d = jas_matrix_getref(data, 0, 0); for (x = 0; x < width; ++x) { v = *d; switch (bpp) { case 1: img[pix] = v; break; case 2: img16ui[pix] = v; break; case -2: img16i[pix] = v; break; } pix++; ++d; } //for x } //for y } //for each component jas_matrix_destroy(data); jas_image_destroy(image); jas_image_clearfmts(); return img; } //nii_loadImgCoreJasper() #endif struct TJPEG { long offset; long size; }; TJPEG *decode_JPEG_SOF_0XC3_stack(const char *fn, int skipBytes, int isVerbose, int frames, bool isLittleEndian) { #define abortGoto() free(lOffsetRA); return NULL; TJPEG *lOffsetRA = (TJPEG *)malloc(frames * sizeof(TJPEG)); FILE *reader = fopen(fn, "rb"); fseek(reader, 0, SEEK_END); long lRawSz = ftell(reader) - skipBytes; if (lRawSz <= 8) { printError("Unable to open %s\n", fn); abortGoto(); //read failure } fseek(reader, skipBytes, SEEK_SET); unsigned char *lRawRA = (unsigned char *)malloc(lRawSz); size_t lSz = fread(lRawRA, 1, lRawSz, reader); fclose(reader); if (lSz < (size_t)lRawSz) { printError("Unable to read %s\n", fn); abortGoto(); //read failure } long lRawPos = 0; //starting position int frame = 0; while ((frame < frames) && ((lRawPos + 10) < lRawSz)) { int tag = dcmInt(4, &lRawRA[lRawPos], isLittleEndian); lRawPos += 4; //read tag int tagLength = dcmInt(4, &lRawRA[lRawPos], isLittleEndian); long tagEnd = lRawPos + tagLength + 4; if (isVerbose) printMessage("Frame %d Tag %#x length %d end at %ld\n", frame + 1, tag, tagLength, tagEnd + skipBytes); lRawPos += 4; //read tag length if ((lRawRA[lRawPos] != 0xFF) || (lRawRA[lRawPos + 1] != 0xD8) || (lRawRA[lRawPos + 2] != 0xFF)) { if (isVerbose) printWarning("JPEG signature 0xFFD8FF not found at offset %d of %s\n", skipBytes, fn); } else { lOffsetRA[frame].offset = lRawPos + skipBytes; lOffsetRA[frame].size = tagLength; frame++; } lRawPos = tagEnd; } free(lRawRA); if (frame < frames) { printMessage("Only found %d of %d JPEG fragments. Please use dcmdjpeg or gdcmconv to uncompress data.\n", frame, frames); abortGoto(); } return lOffsetRA; } unsigned char *nii_loadImgJPEGC3(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm, int isVerbose) { //arcane and inefficient lossless compression method popularized by dcmcjpeg, examples at http://www.osirix-viewer.com/resources/dicom-image-library/ int dimX, dimY, bits, frames; //clock_t start = clock(); // https://github.com/rii-mango/JPEGLosslessDecoderJS/blob/master/tests/data/jpeg_lossless_sel1-8bit.dcm //N.B. this current code can not extract a 2D image that is saved as multiple fragments, for example see the JPLL files at // ftp://medical.nema.org/MEDICAL/Dicom/DataSets/WG04/ //Live javascript code that can handle these is at // https://github.com/chafey/cornerstoneWADOImageLoader //I have never seen these segmented images in the wild, so we will simply warn the user if we encounter such a file //int Sz = JPEG_SOF_0XC3_sz (imgname, (dcm.imageStart - 4), dcm.isLittleEndian); //printMessage("Sz %d %d\n", Sz, dcm.imageBytes ); //This behavior is legal but appears extremely rare //ftp://medical.nema.org/medical/dicom/final/cp900_ft.pdf if (65536 == dcm.imageBytes) printError("One frame may span multiple fragments. SOFxC3 lossless JPEG. Please extract with dcmdjpeg or gdcmconv.\n"); unsigned char *ret = decode_JPEG_SOF_0XC3(imgname, dcm.imageStart, isVerbose, &dimX, &dimY, &bits, &frames, 0); if (ret == NULL) { printMessage("Unable to decode JPEG. Please use dcmdjpeg to uncompress data.\n"); return NULL; } if (hdr.dim[3] != frames) { //multi-slice image saved as multiple image fragments rather than a single image //printMessage("Unable to decode all slices (%d/%d). Please use dcmdjpeg to uncompress data.\n", frames, hdr.dim[3]); if (ret != NULL) free(ret); TJPEG *offsetRA = decode_JPEG_SOF_0XC3_stack(imgname, dcm.imageStart - 8, isVerbose, hdr.dim[3], dcm.isLittleEndian); if (offsetRA == NULL) return NULL; size_t slicesz = nii_SliceBytes(hdr); size_t imgsz = slicesz * hdr.dim[3]; size_t pos = 0; unsigned char *bImg = (unsigned char *)malloc(imgsz); for (int frame = 0; frame < hdr.dim[3]; frame++) { if (isVerbose) printMessage("JPEG frame %d has %ld bytes @ %ld\n", frame, offsetRA[frame].size, offsetRA[frame].offset); unsigned char *ret = decode_JPEG_SOF_0XC3(imgname, (int)offsetRA[frame].offset, false, &dimX, &dimY, &bits, &frames, (int)offsetRA[frame].size); if (ret == NULL) { printMessage("Unable to decode JPEG. Please use dcmdjpeg to uncompress data.\n"); free(bImg); return NULL; } memcpy(&bImg[pos], ret, slicesz); //dest, src, size free(ret); pos += slicesz; } free(offsetRA); return bImg; } return ret; } #ifndef F_OK #define F_OK 0 /* existence check */ #endif #ifndef myDisableClassicJPEG #ifdef myTurboJPEG //if turboJPEG instead of nanoJPEG for classic JPEG decompression //unsigned char * nii_loadImgJPEG50(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { unsigned char *nii_loadImgJPEG50(char *imgname, struct TDICOMdata dcm) { //decode classic JPEG using nanoJPEG //printMessage("50 offset %d\n", dcm.imageStart); if ((dcm.samplesPerPixel != 1) && (dcm.samplesPerPixel != 3)) { printError("%d components (expected 1 or 3) in a JPEG image '%s'\n", dcm.samplesPerPixel, imgname); return NULL; } if (access(imgname, F_OK) == -1) { printError("Unable to find '%s'\n", imgname); return NULL; } //load compressed data FILE *f = fopen(imgname, "rb"); fseek(f, 0, SEEK_END); long unsigned int _jpegSize = (long unsigned int)ftell(f); _jpegSize = _jpegSize - dcm.imageStart; if (_jpegSize < 8) { printError("File too small\n"); fclose(f); return NULL; } unsigned char *_compressedImage = (unsigned char *)malloc(_jpegSize); fseek(f, dcm.imageStart, SEEK_SET); _jpegSize = (long unsigned int)fread(_compressedImage, 1, _jpegSize, f); fclose(f); int jpegSubsamp, width, height; //printMessage("Decoding with turboJPEG\n"); tjhandle _jpegDecompressor = tjInitDecompress(); tjDecompressHeader2(_jpegDecompressor, _compressedImage, _jpegSize, &width, &height, &jpegSubsamp); int COLOR_COMPONENTS = dcm.samplesPerPixel; //printMessage("turboJPEG h*w %d*%d sampling %d components %d\n", width, height, jpegSubsamp, COLOR_COMPONENTS); if ((jpegSubsamp == TJSAMP_GRAY) && (COLOR_COMPONENTS != 1)) { printError("Grayscale jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname); } if ((jpegSubsamp != TJSAMP_GRAY) && (COLOR_COMPONENTS != 3)) { printError("Color jpegs should not have %d components '%s'\n", COLOR_COMPONENTS, imgname); } //unsigned char bImg[width*height*COLOR_COMPONENTS]; //!< will contain the decompressed image unsigned char *bImg = (unsigned char *)malloc(width * height * COLOR_COMPONENTS); if (COLOR_COMPONENTS == 1) //TJPF_GRAY tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0 /*pitch*/, height, TJPF_GRAY, TJFLAG_FASTDCT); else tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, bImg, width, 0 /*pitch*/, height, TJPF_RGB, TJFLAG_FASTDCT); //printMessage("turboJPEG h*w %d*%d (sampling %d)\n", width, height, jpegSubsamp); tjDestroy(_jpegDecompressor); return bImg; } #else //if turboJPEG else use nanojpeg... //unsigned char * nii_loadImgJPEG50(char* imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { unsigned char *nii_loadImgJPEG50(char *imgname, struct TDICOMdata dcm) { //decode classic JPEG using nanoJPEG //printMessage("50 offset %d\n", dcm.imageStart); if (access(imgname, F_OK) == -1) { printError("Unable to find '%s'\n", imgname); return NULL; } //load compressed data FILE *f = fopen(imgname, "rb"); fseek(f, 0, SEEK_END); int size = (int)ftell(f); size = size - dcm.imageStart; if (size < 8) { printError("File too small '%s'\n", imgname); fclose(f); return NULL; } char *buf = (char *)malloc(size); fseek(f, dcm.imageStart, SEEK_SET); size = (int)fread(buf, 1, size, f); fclose(f); //decode njInit(); if (njDecode(buf, size)) { printError("Unable to decode JPEG image.\n"); return NULL; } free(buf); unsigned char *bImg = (unsigned char *)malloc(njGetImageSize()); memcpy(bImg, njGetImage(), njGetImageSize()); //dest, src, size njDone(); return bImg; } #endif #endif uint32_t rleInt(int lIndex, unsigned char lBuffer[], bool swap) { //read binary 32-bit integer uint32_t retVal = 0; memcpy(&retVal, (char *)&lBuffer[lIndex * 4], 4); if (!swap) return retVal; uint32_t swapVal; char *inInt = (char *)&retVal; char *outInt = (char *)&swapVal; outInt[0] = inInt[3]; outInt[1] = inInt[2]; outInt[2] = inInt[1]; outInt[3] = inInt[0]; return swapVal; } //rleInt() unsigned char *nii_loadImgPMSCT_RLE1(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { //Transfer Syntax 1.3.46.670589.33.1.4.1 also handled by TomoVision and GDCM's rle2img //https://github.com/malaterre/GDCM/blob/a923f206060e85e8d81add565ae1b9dd7b210481/Examples/Cxx/rle2img.cxx //see rle2img: Philips/ELSCINT1 run-length compression 07a1,1011= PMSCT_RLE1 if (dcm.imageBytes < 66) { //64 for header+ 2 byte minimum image printError("%d is not enough bytes for PMSCT_RLE1 compression '%s'\n", dcm.imageBytes, imgname); return NULL; } int bytesPerSample = dcm.samplesPerPixel * (dcm.bitsAllocated / 8); if (bytesPerSample != 2) { //there is an RGB variation of this format, but we have not seen it in the wild printError("PMSCT_RLE1 should be 16-bits per sample (please report on Github and use pmsct_rgb1).\n"); return NULL; } FILE *file = fopen(imgname, "rb"); if (!file) { printError("Unable to open %s\n", imgname); return NULL; } fseek(file, 0, SEEK_END); long fileLen = ftell(file); if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) { printMessage("File not large enough to store image data: %s\n", imgname); fclose(file); return NULL; } fseek(file, (long)dcm.imageStart, SEEK_SET); size_t imgsz = nii_ImgBytes(hdr); char *cImg = (char *)malloc(dcm.imageBytes); //compressed input size_t sz = fread(cImg, 1, dcm.imageBytes, file); fclose(file); if (sz < (size_t)dcm.imageBytes) { printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); free(cImg); return NULL; } if (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) { if (cImg[i] == (char)0xa5) { int repeat = (unsigned char)cImg[i + 1] + 1; char value = cImg[i + 2]; while (repeat) { tImg[o] = value; o++; --repeat; } i += 2; } else { tImg[o] = cImg[i]; o++; } } //for i free(cImg); int 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; unsigned short *bImg16 = (unsigned short *)bImg; for (size_t i = 0; i < tempsize; ++i) { if (tImg[i] == (unsigned char)0x5a) { unsigned char v1 = (unsigned char)tImg[i + 1]; unsigned char v2 = (unsigned char)tImg[i + 2]; unsigned short value = (unsigned short)(v2 * 256 + v1); if (o < n16) bImg16[o] = value; o++; delta = value; i += 2; } else { unsigned short value = (unsigned short)(tImg[i] + delta); if (o < n16) bImg16[o] = value; o++; delta = value; } } //for i //printMessage("Delta %d -> %d (of %d)\n", tempsize, 2*(o-1), imgsz); free(tImg); return bImg; } // nii_loadImgPMSCT_RLE1() unsigned char *nii_loadImgRLE(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { //decompress PackBits run-length encoding https://en.wikipedia.org/wiki/PackBits if (dcm.imageBytes < 66) { //64 for header+ 2 byte minimum image printError("%d is not enough bytes for RLE compression '%s'\n", dcm.imageBytes, imgname); return NULL; } FILE *file = fopen(imgname, "rb"); if (!file) { printError("Unable to open %s\n", imgname); return NULL; } fseek(file, 0, SEEK_END); long fileLen = ftell(file); if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) { printMessage("File not large enough to store image data: %s\n", imgname); fclose(file); return NULL; } fseek(file, (long)dcm.imageStart, SEEK_SET); size_t imgsz = nii_ImgBytes(hdr); unsigned char *cImg = (unsigned char *)malloc(dcm.imageBytes); //compressed input size_t sz = fread(cImg, 1, dcm.imageBytes, file); fclose(file); if (sz < (size_t)dcm.imageBytes) { printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); free(cImg); return NULL; } //read header http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_G.3.html bool swap = (dcm.isLittleEndian != littleEndianPlatform()); int bytesPerSample = dcm.samplesPerPixel * (dcm.bitsAllocated / 8); uint32_t bytesPerSampleRLE = rleInt(0, cImg, swap); if ((bytesPerSample < 0) || (bytesPerSampleRLE != (uint32_t)bytesPerSample)) { printError("RLE header corrupted %d != %d\n", bytesPerSampleRLE, bytesPerSample); free(cImg); return NULL; } unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output for (size_t i = 0; i < imgsz; i++) bImg[i] = 0; for (int i = 0; i < bytesPerSample; i++) { uint32_t offset = rleInt(i + 1, cImg, swap); if ((dcm.imageBytes < 0) || (offset > (uint32_t)dcm.imageBytes)) { printError("RLE header error\n"); free(cImg); free(bImg); return NULL; } //save in platform's endian: // The first Segment is generated by stripping off the most significant byte of each Padded Composite Pixel Code... size_t vx = i; if ((dcm.samplesPerPixel == 1) && (littleEndianPlatform())) //endian, except for RGB vx = (bytesPerSample - 1) - i; while (vx < imgsz) { int8_t n = (int8_t)cImg[offset]; offset++; //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_G.3.html //if ((n >= 0) && (n <= 127)) { //not needed: int8_t always <=127 if (n >= 0) { //literal bytes int reps = 1 + (int)n; for (int r = 0; r < reps; r++) { int8_t v = cImg[offset]; offset++; if (vx >= imgsz) ; //printMessage("literal overflow %d %d\n", r, reps); else bImg[vx] = v; vx = vx + bytesPerSample; } } else if ((n <= -1) && (n >= -127)) { //repeated run int8_t v = cImg[offset]; offset++; int reps = -(int)n + 1; for (int r = 0; r < reps; r++) { if (vx >= imgsz) ; //printMessage("repeat overflow %d\n", reps); else bImg[vx] = v; vx = vx + bytesPerSample; } }; //n.b. we ignore -128! } //while vx < imgsz } //for i < bytesPerSample free(cImg); return bImg; } // nii_loadImgRLE() #ifdef myDisableOpenJPEG #ifndef myEnableJasper //avoid compiler warning, see https://stackoverflow.com/questions/3599160/unused-parameter-warnings-in-c #define UNUSED(x) (void)(x) #endif #endif #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) //Support for JPEG-LS //JPEG-LS: Transfer Syntaxes 1.2.840.10008.1.2.4.80 1.2.840.10008.1.2.4.81 #ifdef myEnableJPEGLS1 //use CharLS v1.* requires c++03 //-std=c++03 -DmyEnableJPEGLS1 charls1/header.cpp charls1/jpegls.cpp charls1/jpegmarkersegment.cpp charls1/interface.cpp charls1/jpegstreamwriter.cpp #include "charls1/interface.h" #else //use latest release of CharLS: CharLS 2.x requires c++14 //-std=c++14 -DmyEnableJPEGLS charls/jpegls.cpp charls/jpegmarkersegment.cpp charls/interface.cpp charls/jpegstreamwriter.cpp charls/jpegstreamreader.cpp #include "charls/charls.h" #endif #include "charls/publictypes.h" unsigned char *nii_loadImgJPEGLS(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { //load compressed data FILE *file = fopen(imgname, "rb"); if (!file) { printError("Unable to open %s\n", imgname); return NULL; } fseek(file, 0, SEEK_END); long fileLen = ftell(file); if ((fileLen < 1) || (dcm.imageBytes < 1) || (fileLen < (dcm.imageBytes + dcm.imageStart))) { printMessage("File not large enough to store JPEG-LS data: %s\n", imgname); fclose(file); return NULL; } fseek(file, (long)dcm.imageStart, SEEK_SET); unsigned char *cImg = (unsigned char *)malloc(dcm.imageBytes); //compressed input size_t sz = fread(cImg, 1, dcm.imageBytes, file); fclose(file); if (sz < (size_t)dcm.imageBytes) { printError("Only loaded %zu of %d bytes for %s\n", sz, dcm.imageBytes, imgname); free(cImg); return NULL; } //create buffer for uncompressed data size_t imgsz = nii_ImgBytes(hdr); unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output JlsParameters params = {}; #ifdef myEnableJPEGLS1 if (JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms) != OK) { #else using namespace charls; if (JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) { #endif printMessage("CharLS failed to read header.\n"); return NULL; } #ifdef myEnableJPEGLS1 if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms) != OK) { #else if (JpegLsDecode(&bImg[0], imgsz, &cImg[0], dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) { #endif free(bImg); printMessage("CharLS failed to read image.\n"); return NULL; } return (bImg); } #endif unsigned char *nii_loadImgXL(char *imgname, struct nifti_1_header *hdr, struct TDICOMdata dcm, bool iVaries, int compressFlag, int isVerbose, struct TDTI4D *dti4D) { //provided with a filename (imgname) and DICOM header (dcm), creates NIfTI header (hdr) and img if (headerDcm2Nii(dcm, hdr, true) == EXIT_FAILURE) return NULL; //TOFU unsigned char *img; if (dcm.compressionScheme == kCompress50) { #ifdef myDisableClassicJPEG printMessage("Software not compiled to decompress classic JPEG DICOM images\n"); return NULL; #else //img = nii_loadImgJPEG50(imgname, *hdr, dcm); img = nii_loadImgJPEG50(imgname, dcm); if (hdr->datatype == DT_RGB24) //convert to planar img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped #endif } else if (dcm.compressionScheme == kCompressJPEGLS) { #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) img = nii_loadImgJPEGLS(imgname, *hdr, dcm); if (hdr->datatype == DT_RGB24) //convert to planar img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped #else printMessage("Software not compiled to decompress JPEG-LS DICOM images\n"); return NULL; #endif } else if (dcm.compressionScheme == kCompressPMSCT_RLE1) { img = nii_loadImgPMSCT_RLE1(imgname, *hdr, dcm); } else if (dcm.compressionScheme == kCompressRLE) { img = nii_loadImgRLE(imgname, *hdr, dcm); if (hdr->datatype == DT_RGB24) //convert to planar img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped } else if (dcm.compressionScheme == kCompressC3) img = nii_loadImgJPEGC3(imgname, *hdr, dcm, isVerbose); else #ifndef myDisableOpenJPEG if (((dcm.compressionScheme == kCompress50) || (dcm.compressionScheme == kCompressYes)) && (compressFlag != kCompressNone)) img = nii_loadImgCoreOpenJPEG(imgname, *hdr, dcm, compressFlag); else #else #ifdef myEnableJasper if ((dcm.compressionScheme == kCompressYes) && (compressFlag != kCompressNone)) img = nii_loadImgCoreJasper(imgname, *hdr, dcm, compressFlag); else #endif #endif if (dcm.compressionScheme == kCompressYes) { printMessage("Software not set up to decompress DICOM\n"); return NULL; } else img = nii_loadImgCore(imgname, *hdr, dcm.bitsAllocated, dcm.imageStart); if (img == NULL) return img; if ((dcm.compressionScheme == kCompressNone) && (dcm.isLittleEndian != littleEndianPlatform()) && (hdr->bitpix > 8)) img = nii_byteswap(img, hdr); if ((dcm.compressionScheme == kCompressNone) && (hdr->datatype == DT_RGB24)) img = nii_rgb2planar(img, hdr, dcm.isPlanarRGB); //do this BEFORE Y-Flip, or RGB order can be flipped dcm.isPlanarRGB = true; if (dcm.CSA.mosaicSlices > 1) { img = nii_demosaic(img, hdr, dcm.CSA.mosaicSlices, (dcm.manufacturer == kMANUFACTURER_UIH)); //, dcm.CSA.protocolSliceNumber1); } if ((dti4D == NULL) && (!dcm.isFloat) && (iVaries)) //must do afte img = nii_iVaries(img, hdr, NULL); int nAcq = dcm.locationsInAcquisition; if ((nAcq > 1) && (hdr->dim[0] < 4) && ((hdr->dim[3] % nAcq) == 0) && (hdr->dim[3] > nAcq)) { hdr->dim[4] = hdr->dim[3] / nAcq; hdr->dim[3] = nAcq; hdr->dim[0] = 4; } if ((dti4D != NULL) && (dti4D->sliceOrder[0] >= 0)) img = nii_reorderSlicesX(img, hdr, dti4D); if ((dti4D != NULL) && (!dcm.isFloat) && (iVaries)) img = nii_iVaries(img, hdr, dti4D); headerDcm2NiiSForm(dcm, dcm, hdr, false); return img; } //nii_loadImgXL() int isSQ(uint32_t groupElement) { //Detect sequence VR ("SQ") for implicit tags static const int array_size = 35; uint32_t array[array_size] = {0x2005 + (uint32_t(0x140F) << 16), 0x0008 + (uint32_t(0x1111) << 16), 0x0008 + (uint32_t(0x1115) << 16), 0x0008 + (uint32_t(0x1140) << 16), 0x0008 + (uint32_t(0x1199) << 16), 0x0008 + (uint32_t(0x2218) << 16), 0x0008 + (uint32_t(0x9092) << 16), 0x0018 + (uint32_t(0x9006) << 16), 0x0018 + (uint32_t(0x9042) << 16), 0x0018 + (uint32_t(0x9045) << 16), 0x0018 + (uint32_t(0x9049) << 16), 0x0018 + (uint32_t(0x9112) << 16), 0x0018 + (uint32_t(0x9114) << 16), 0x0018 + (uint32_t(0x9115) << 16), 0x0018 + (uint32_t(0x9117) << 16), 0x0018 + (uint32_t(0x9119) << 16), 0x0018 + (uint32_t(0x9125) << 16), 0x0018 + (uint32_t(0x9152) << 16), 0x0018 + (uint32_t(0x9176) << 16), 0x0018 + (uint32_t(0x9226) << 16), 0x0018 + (uint32_t(0x9239) << 16), 0x0020 + (uint32_t(0x9071) << 16), 0x0020 + (uint32_t(0x9111) << 16), 0x0020 + (uint32_t(0x9113) << 16), 0x0020 + (uint32_t(0x9116) << 16), 0x0020 + (uint32_t(0x9221) << 16), 0x0020 + (uint32_t(0x9222) << 16), 0x0028 + (uint32_t(0x9110) << 16), 0x0028 + (uint32_t(0x9132) << 16), 0x0028 + (uint32_t(0x9145) << 16), 0x0040 + (uint32_t(0x0260) << 16), 0x0040 + (uint32_t(0x0555) << 16), 0x0040 + (uint32_t(0xa170) << 16), 0x5200 + (uint32_t(0x9229) << 16), 0x5200 + (uint32_t(0x9230) << 16)}; for (int i = 0; i < array_size; i++) { //if (array[i] == groupElement) printMessage(" implicitSQ %04x,%04x\n", groupElement & 65535,groupElement>>16); if (array[i] == groupElement) return 1; } return 0; } //isSQ() int isDICOMfile(const char *fname) { //0=NotDICOM, 1=DICOM, 2=Maybe(not Part 10 compliant) //Someday: it might be worthwhile to detect "IMGF" at offset 3228 to warn user if they attempt to convert Signa data FILE *fp = fopen(fname, "rb"); if (!fp) return 0; fseek(fp, 0, SEEK_END); long fileLen = ftell(fp); if (fileLen < 256) { fclose(fp); return 0; } fseek(fp, 0, SEEK_SET); unsigned char buffer[256]; size_t sz = fread(buffer, 1, 256, fp); fclose(fp); if (sz < 256) return 0; if ((buffer[128] == 'D') && (buffer[129] == 'I') && (buffer[130] == 'C') && (buffer[131] == 'M')) return 1; //valid DICOM if ((buffer[0] == 8) && (buffer[1] == 0) && (buffer[3] == 0)) return 2; //not valid Part 10 file, perhaps DICOM object return 0; } //isDICOMfile() //START RIR 12/2017 Robert I. Reid // Gathering spot for all the info needed to get the b value and direction // for a volume. struct TVolumeDiffusion { struct TDICOMdata *pdd; // The multivolume struct TDTI4D *pdti4D; // permanent records. uint8_t manufacturer; // kMANUFACTURER_UNKNOWN, kMANUFACTURER_SIEMENS, etc. //void set_manufacturer(const uint8_t m) {manufacturer = m; update();} // unnecessary // Everything after this in the structure would be private if it were a C++ // class, but it has been rewritten as a struct for C compatibility. I am // using _ as a hint of that, although _ for privacy is not really a // universal convention in C. Privacy is desired because immediately // any of these are updated _update_tvd() should be called. bool _isAtFirstPatientPosition; // Limit b vals and vecs to 1 per volume. //float bVal0018_9087; // kDiffusion_b_value, always present in Philips/Siemens. //float bVal2001_1003; // kDiffusionBFactor // float dirRL2005_10b0; // kDiffusionDirectionRL // float dirAP2005_10b1; // kDiffusionDirectionAP // float dirFH2005_10b2; // kDiffusionDirectionFH // Philips diffusion scans tend to have a "trace" (average of the diffusion // weighted volumes) volume tacked on, usually but not always at the end, // so b is > 0, but the direction is meaningless. Most software versions // explicitly set the direction to 0, but version 3 does not, making (0x18, // 0x9075) necessary. bool _isPhilipsNonDirectional; //char _directionality0018_9075[16]; // DiffusionDirectionality, not in Philips 2.6. // float _orientation0018_9089[3]; // kDiffusionOrientation, always present in Philips/Siemens for volumes with a direction. //char _seq0018_9117[64]; // MRDiffusionSequence, not in Philips 2.6. float _dtiV[4]; double _symBMatrix[6]; //uint16_t numDti; }; struct TVolumeDiffusion initTVolumeDiffusion(struct TDICOMdata *ptdd, struct TDTI4D *dti4D); void clear_volume(struct TVolumeDiffusion *ptvd); // Blank the volume-specific members or set them to impossible values. void set_directionality0018_9075(struct TVolumeDiffusion *ptvd, unsigned char *inbuf); void set_orientation0018_9089(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, bool isLittleEndian); void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion *ptvd, bool iafpp); int set_bValGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf); void set_diffusion_directionPhilips(struct TVolumeDiffusion *ptvd, float vec, const int axis); void set_diffusion_directionGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, int axis); void set_bVal(struct TVolumeDiffusion *ptvd, float b); void set_bMatrix(struct TVolumeDiffusion *ptvd, float b, int component); void _update_tvd(struct TVolumeDiffusion *ptvd); struct TVolumeDiffusion initTVolumeDiffusion(struct TDICOMdata *ptdd, struct TDTI4D *dti4D) { struct TVolumeDiffusion tvd; tvd.pdd = ptdd; tvd.pdti4D = dti4D; clear_volume(&tvd); return tvd; } //initTVolumeDiffusion() void clear_volume(struct TVolumeDiffusion *ptvd) { ptvd->_isAtFirstPatientPosition = false; ptvd->manufacturer = kMANUFACTURER_UNKNOWN; //bVal0018_9087 = -1; //ptvd->_directionality0018_9075[0] = 0; //ptvd->seq0018_9117[0] = 0; //bVal2001_1003 = -1; // dirRL2005_10b0 = 2; // dirAP2005_10b1 = 2; // dirFH2005_10b2 = 2; ptvd->_isPhilipsNonDirectional = false; ptvd->_dtiV[0] = -1; for (int i = 1; i < 4; ++i) ptvd->_dtiV[i] = 2; for (int i = 1; i < 6; ++i) ptvd->_symBMatrix[i] = NAN; //numDti = 0; } //clear_volume() void set_directionality0018_9075(struct TVolumeDiffusion *ptvd, unsigned char *inbuf) { //if(!strncmp(( char*)(inbuf), "BMATRIX", 4)) printf("FOUND BMATRIX----%s\n",inbuf ); //n.b. strncmp returns 0 if the contents of both strings are equal, for boolean 0 = false! // elsewhere we use strstr() which returns 0/null if match is not present if (strncmp((char *)(inbuf), "DIRECTIONAL", 11) && // strncmp = 0 for ==. //strncmp(( char*)(inbuf), "NONE", 4) && //issue 256 strncmp((char *)(inbuf), "BMATRIX", 7)) { // Siemens XA10 ptvd->_isPhilipsNonDirectional = true; // Explicitly set the direction to 0 now, because there may // not be a 0018,9089 for this frame. for (int i = 1; i < 4; ++i) // 1-3 is intentional. ptvd->_dtiV[i] = 0.0; } else { ptvd->_isPhilipsNonDirectional = false; // Wait for 0018,9089 to get the direction. } _update_tvd(ptvd); } //set_directionality0018_9075() 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); ptvd->_dtiV[0] = bVal; //printf("(0043,1039) '%s' Slop_int_6 -->%d \n", inbuf, bVal); //dd.CSA.numDti = 1; // Always true for GE. _update_tvd(ptvd); return bVal; } //set_bValGE() // axis: 0 -> x, 1 -> y , 2 -> z void set_diffusion_directionPhilips(struct TVolumeDiffusion *ptvd, float vec, const int axis) { ptvd->_dtiV[axis + 1] = vec; //printf("(2005,10b0..2) v[%d]=%g\n", axis, ptvd->_dtiV[axis + 1]); _update_tvd(ptvd); } //set_diffusion_directionPhilips() // axis: 0 -> x, 1 -> y , 2 -> z void set_diffusion_directionGE(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, const int axis) { ptvd->_dtiV[axis + 1] = dcmStrFloat(lLength, inbuf); //printf("(0019,10bb..d) v[%d]=%g\n", axis, ptvd->_dtiV[axis + 1]); _update_tvd(ptvd); } //set_diffusion_directionGE() void dcmMultiFloatDouble(size_t lByteLength, unsigned char lBuffer[], size_t lnFloats, float *lFloats, bool isLittleEndian) { size_t floatlen = lByteLength / lnFloats; for (size_t i = 0; i < lnFloats; ++i) lFloats[i] = dcmFloatDouble((int)floatlen, lBuffer + i * floatlen, isLittleEndian); } //dcmMultiFloatDouble() void set_orientation0018_9089(struct TVolumeDiffusion *ptvd, int lLength, unsigned char *inbuf, bool isLittleEndian) { if (ptvd->_isPhilipsNonDirectional) { for (int i = 1; i < 4; ++i) // Deliberately ignore inbuf; it might be nonsense. ptvd->_dtiV[i] = 0.0; } else dcmMultiFloatDouble(lLength, inbuf, 3, ptvd->_dtiV + 1, isLittleEndian); _update_tvd(ptvd); } //set_orientation0018_9089() void set_bVal(struct TVolumeDiffusion *ptvd, const float b) { ptvd->_dtiV[0] = b; _update_tvd(ptvd); } //set_bVal() void set_bMatrix(struct TVolumeDiffusion *ptvd, double b, int idx) { if ((idx < 0) || (idx > 5)) return; ptvd->_symBMatrix[idx] = b; _update_tvd(ptvd); } void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion *ptvd, const bool iafpp) { ptvd->_isAtFirstPatientPosition = iafpp; _update_tvd(ptvd); } //set_isAtFirstPatientPosition_tvd() // Update the diffusion info in dd and *pdti4D for a volume once all the // diffusion info for that volume has been read into pvd. // // Note that depending on the scanner software the diffusion info can arrive in // different tags, in different orders (because of enclosing sequence tags), // and the values in some tags may be invalid, or may be essential, depending // on the presence of other tags. Thus it is best to gather all the diffusion // info for a volume (frame) before taking action on it. // // On the other hand, dd and *pdti4D need to be updated as soon as the // diffusion info is ready, before diffusion info for the next volume is read // in. void _update_tvd(struct TVolumeDiffusion *ptvd) { // Figure out if we have both the b value and direction (if any) for this // volume, and if isFirstPosition. // // GE (software version 27) is liable to NOT include kDiffusion_b_value for the // // slice if it is 0, but should still have kDiffusionBFactor, which comes // // after PatientPosition. // if(isAtFirstPatientPosition && manufacturer == kMANUFACTURER_GE && dtiV[0] < 0) // dtiV[0] = 0; // Implied 0. bool isReady = (ptvd->_isAtFirstPatientPosition && (ptvd->_dtiV[0] >= 0)); if (!isReady) return; //no B=0 if (isReady) { for (int i = 1; i < 4; ++i) { if (ptvd->_dtiV[i] > 1) { isReady = false; break; } } } if (!isReady) { //bvecs NOT filled: see if symBMatrix filled isReady = true; for (int i = 1; i < 6; ++i) if (isnan(ptvd->_symBMatrix[i])) isReady = false; if (!isReady) return; // symBMatrix not filled //START BRUKER KLUDGE //see issue 265: Bruker stores xx,xy,xz,yx,yy,yz instead of xx,xy,xz,yy,yz,zz // we can recover since xx+yy+zz = bval // since any value squared is positive, a negative diagonal reveals fault double xx = ptvd->_symBMatrix[0]; //x*x double xy = ptvd->_symBMatrix[1]; //x*y double xz = ptvd->_symBMatrix[2]; //x*z double yy = ptvd->_symBMatrix[3]; //y*y double yz = ptvd->_symBMatrix[4]; //y*z double zz = ptvd->_symBMatrix[5]; //z*z bool isBrukerBug = false; if ((xx < 0.0) || (yy < 0.0) || (zz < 0.0)) isBrukerBug = true; double sumDiag = ptvd->_symBMatrix[0] + ptvd->_symBMatrix[3] + ptvd->_symBMatrix[5]; //if correct xx+yy+zz = bval double bVecError = fabs(sumDiag - ptvd->pdd->CSA.dtiV[0]); if (bVecError > 0.5) isBrukerBug = true; //next: check diagonals double x = sqrt(xx); double y = sqrt(yy); double z = sqrt(zz); if ((fabs((x * y) - xy)) > 0.5) isBrukerBug = true; if ((fabs((x * z) - xz)) > 0.5) isBrukerBug = true; if ((fabs((y * z) - yz)) > 0.5) isBrukerBug = true; if (isBrukerBug) printWarning("Fixing corrupt bmat (issue 265). [%g %g %g %g %g %g]\n", xx, xy, xz, yy, yz, zz); if (isBrukerBug) { ptvd->_symBMatrix[3] = ptvd->_symBMatrix[4]; ptvd->_symBMatrix[4] = ptvd->_symBMatrix[5]; //next: solve for zz given bvalue, xx, and yy ptvd->_symBMatrix[5] = ptvd->_dtiV[0] - ptvd->_symBMatrix[0] - ptvd->_symBMatrix[3]; if ((ptvd->_symBMatrix[0] < 0.0) || (ptvd->_symBMatrix[5] < 0.0)) printError("DICOM BMatrix corrupt.\n"); } //END BRUKER_KLUDGE vec3 bVec = nifti_mat33_eig3(ptvd->_symBMatrix[0], ptvd->_symBMatrix[1], ptvd->_symBMatrix[2], ptvd->_symBMatrix[3], ptvd->_symBMatrix[4], ptvd->_symBMatrix[5]); ptvd->_dtiV[1] = bVec.v[0]; ptvd->_dtiV[2] = bVec.v[1]; ptvd->_dtiV[3] = bVec.v[2]; //printf("bmat=[%g %g %g %g %g %g %g %g %g]\n", ptvd->_symBMatrix[0],ptvd->_symBMatrix[1],ptvd->_symBMatrix[2], ptvd->_symBMatrix[1],ptvd->_symBMatrix[3],ptvd->_symBMatrix[4], ptvd->_symBMatrix[2],ptvd->_symBMatrix[4],ptvd->_symBMatrix[5]); //printf("bmats=[%g %g %g %g %g %g];\n", ptvd->_symBMatrix[0],ptvd->_symBMatrix[1],ptvd->_symBMatrix[2],ptvd->_symBMatrix[3],ptvd->_symBMatrix[4],ptvd->_symBMatrix[5]); //printf("bvec=[%g %g %g];\n", ptvd->_dtiV[1], ptvd->_dtiV[2], ptvd->_dtiV[3]); //printf("bval=%g;\n\n", ptvd->_dtiV[0]); } if (!isReady) return; // If still here, update dd and *pdti4D. ptvd->pdd->CSA.numDti++; if (ptvd->pdd->CSA.numDti == 2) { // First time we know that this is a 4D DTI dataset; for (int i = 0; i < 4; ++i) // Start *pdti4D before ptvd->pdd->CSA.dtiV ptvd->pdti4D->S[0].V[i] = ptvd->pdd->CSA.dtiV[i]; // is updated. } for (int i = 0; i < 4; ++i) // Update pdd ptvd->pdd->CSA.dtiV[i] = ptvd->_dtiV[i]; if ((ptvd->pdd->CSA.numDti > 1) && (ptvd->pdd->CSA.numDti < kMaxDTI4D)) { // Update *pdti4D //d.dti4D = (TDTI *)malloc(kMaxDTI4D * sizeof(TDTI)); for (int i = 0; i < 4; ++i) ptvd->pdti4D->S[ptvd->pdd->CSA.numDti - 1].V[i] = ptvd->_dtiV[i]; } clear_volume(ptvd); // clear the slate for the next volume. } //_update_tvd() //END RIR struct TDCMdim { //DimensionIndexValues uint32_t dimIdx[MAX_NUMBER_OF_DIMENSIONS]; uint32_t diskPos; float triggerDelayTime, TE, intenScale, intenIntercept, intenScalePhilips, RWVScale, RWVIntercept; float V[4]; bool isPhase; bool isReal; bool isImaginary; }; void getFileNameX(char *pathParent, const char *path, int maxLen) { //if path is c:\d1\d2 then filename is 'd2' const char *filename = strrchr(path, '/'); //UNIX const char *filenamew = strrchr(path, '\\'); //Windows if (filename == NULL) filename = filenamew; //if ((filename != NULL) && (filenamew != NULL)) filename = std::max(filename, filenamew); if ((filename != NULL) && (filenamew != NULL) && (filenamew > filename)) filename = filenamew; //for mixed file separators, e.g. "C:/dir\filenane.tmp" if (filename == NULL) { //no path separator strcpy(pathParent, path); return; } filename++; strncpy(pathParent, filename, maxLen - 1); } void getFileName(char *pathParent, const char *path) { //if path is c:\d1\d2 then filename is 'd2' getFileNameX(pathParent, path, kDICOMStr); } struct fidx { float value; int index; }; int fcmp(const void *a, const void *b) { struct fidx *a1 = (struct fidx *)a; struct fidx *a2 = (struct fidx *)b; if ((*a1).value > (*a2).value) return 1; else if ((*a1).value < (*a2).value) return -1; else return 0; } #ifdef USING_R // True iff dcm1 sorts *before* dcm2 bool compareTDCMdim(const TDCMdim &dcm1, const TDCMdim &dcm2) { for (int i = MAX_NUMBER_OF_DIMENSIONS - 1; i >= 0; i--) { if (dcm1.dimIdx[i] < dcm2.dimIdx[i]) return true; else if (dcm1.dimIdx[i] > dcm2.dimIdx[i]) return false; } return false; } //compareTDCMdim() bool compareTDCMdimRev(const TDCMdim &dcm1, const TDCMdim &dcm2) { for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) { if (dcm1.dimIdx[i] < dcm2.dimIdx[i]) return true; else if (dcm1.dimIdx[i] > dcm2.dimIdx[i]) return false; } return false; } //compareTDCMdimRev() #else int compareTDCMdim(void const *item1, void const *item2) { struct TDCMdim const *dcm1 = (const struct TDCMdim *)item1; struct TDCMdim const *dcm2 = (const struct TDCMdim *)item2; //for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) { for (int i = MAX_NUMBER_OF_DIMENSIONS - 1; i >= 0; i--) { if (dcm1->dimIdx[i] < dcm2->dimIdx[i]) return -1; else if (dcm1->dimIdx[i] > dcm2->dimIdx[i]) return 1; } return 0; } //compareTDCMdim() int compareTDCMdimRev(void const *item1, void const *item2) { struct TDCMdim const *dcm1 = (const struct TDCMdim *)item1; struct TDCMdim const *dcm2 = (const struct TDCMdim *)item2; for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) { if (dcm1->dimIdx[i] < dcm2->dimIdx[i]) return -1; else if (dcm1->dimIdx[i] > dcm2->dimIdx[i]) return 1; } return 0; } //compareTDCMdimRev() #endif // USING_R struct TDICOMdata readDICOMx(char *fname, struct TDCMprefs *prefs, struct TDTI4D *dti4D) { //struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) { int isVerbose = prefs->isVerbose; int compressFlag = prefs->compressFlag; struct TDICOMdata d = clear_dicom_data(); d.imageNum = 0; //not set strcpy(d.protocolName, ""); //erase dummy with empty strcpy(d.protocolName, ""); //erase dummy with empty strcpy(d.seriesDescription, ""); //erase dummy with empty strcpy(d.sequenceName, ""); //erase dummy with empty //do not read folders - code specific to GCC (LLVM/Clang seems to recognize a small file size) dti4D->sliceOrder[0] = -1; dti4D->volumeOnsetTime[0] = -1; dti4D->decayFactor[0] = -1; dti4D->frameDuration[0] = -1; //dti4D->fragmentOffset[0] = -1; dti4D->intenScale[0] = 0.0; struct TVolumeDiffusion volDiffusion = initTVolumeDiffusion(&d, dti4D); struct stat s; if (stat(fname, &s) == 0) { if (!(s.st_mode & S_IFREG)) { printMessage("DICOM read fail: not a valid file (perhaps a directory) %s\n", fname); return d; } } bool isPart10prefix = true; int isOK = isDICOMfile(fname); if (isOK == 0) return d; if (isOK == 2) { d.isExplicitVR = false; isPart10prefix = false; } FILE *file = fopen(fname, "rb"); if (!file) { printMessage("Unable to open file %s\n", fname); return d; } fseek(file, 0, SEEK_END); long fileLen = ftell(file); //Get file length if (fileLen < 256) { printMessage("File too small to be a DICOM image %s\n", fname); return d; } //Since size of DICOM header is unknown, we will load it in 1mb segments //This uses less RAM and makes is faster for computers with slow disk access //Benefit is largest for 4D images. //To disable caching and load entire file to RAM, compile with "-dmyLoadWholeFileToReadHeader" //To implement the segments, we define these variables: // fileLen = size of file in bytes // MaxBufferSz = maximum size of buffer in bytes // Buffer = array with n elements, where n is smaller of fileLen or MaxBufferSz // lPos = position in Buffer (indexed from 0), 0..(n-1) // lFileOffset = offset of Buffer in file: true file position is lOffset+lPos (initially 0) #ifdef myLoadWholeFileToReadHeader size_t MaxBufferSz = fileLen; #else size_t MaxBufferSz = 1000000; //ideally size of DICOM header, but this varies from 2D to 4D files #endif if (MaxBufferSz > (size_t)fileLen) MaxBufferSz = fileLen; //printf("%d -> %d\n", MaxBufferSz, fileLen); long lFileOffset = 0; fseek(file, 0, SEEK_SET); //Allocate memory unsigned char *buffer = (unsigned char *)malloc(MaxBufferSz + 1); if (!buffer) { printError("Memory exhausted!"); fclose(file); return d; } //Read file contents into buffer size_t sz = fread(buffer, 1, MaxBufferSz, file); if (sz < MaxBufferSz) { printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname); fclose(file); return d; } #ifdef myLoadWholeFileToReadHeader fclose(file); #endif //DEFINE DICOM TAGS #define kUnused 0x0001 + (0x0001 << 16) #define kStart 0x0002 + (0x0000 << 16) #define kMediaStorageSOPClassUID 0x0002 + (0x0002 << 16) #define kMediaStorageSOPInstanceUID 0x0002 + (0x0003 << 16) #define kTransferSyntax 0x0002 + (0x0010 << 16) #define kImplementationVersionName 0x0002 + (0x0013 << 16) #define kSourceApplicationEntityTitle 0x0002 + (0x0016 << 16) #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 // 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) #define kAcquisitionDateTime 0x0008 + (0x002A << 16) #define kStudyTime 0x0008 + (0x0030 << 16) #define kSeriesTime 0x0008 + (0x0031 << 16) #define kAcquisitionTime 0x0008 + (0x0032 << 16) //TM //#define kContentTime 0x0008+(0x0033 << 16 ) //TM #define kModality 0x0008 + (0x0060 << 16) //CS #define kManufacturer 0x0008 + (0x0070 << 16) #define kInstitutionName 0x0008 + (0x0080 << 16) #define kInstitutionAddress 0x0008 + (0x0081 << 16) #define kReferringPhysicianName 0x0008 + (0x0090 << 16) #define kStationName 0x0008 + (0x1010 << 16) #define kSeriesDescription 0x0008 + (0x103E << 16) // '0008' '103E' 'LO' 'SeriesDescription' #define kInstitutionalDepartmentName 0x0008 + (0x1040 << 16) #define kManufacturersModelName 0x0008 + (0x1090 << 16) #define kDerivationDescription 0x0008 + (0x2111 << 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) #define kPatientID 0x0010 + (0x0020 << 16) #define kAccessionNumber 0x0008 + (0x0050 << 16) #define kPatientBirthDate 0x0010 + (0x0030 << 16) #define kPatientSex 0x0010 + (0x0040 << 16) #define kPatientAge 0x0010 + (0x1010 << 16) #define kPatientWeight 0x0010 + (0x1030 << 16) #define kAnatomicalOrientationType 0x0010 + (0x2210 << 16) #define kDeidentificationMethod 0x0012 + (0x0063 << 16) //[DICOMANON, issue 383 #define kBodyPartExamined 0x0018 + (0x0015 << 16) #define kBodyPartExamined 0x0018 + (0x0015 << 16) #define kScanningSequence 0x0018 + (0x0020 << 16) #define kSequenceVariant 0x0018 + (0x0021 << 16) #define kScanOptions 0x0018 + (0x0022 << 16) #define kMRAcquisitionType 0x0018 + (0x0023 << 16) #define kSequenceName 0x0018 + (0x0024 << 16) #define kRadiopharmaceutical 0x0018 + (0x0031 << 16) //LO #define kZThick 0x0018 + (0x0050 << 16) #define kTR 0x0018 + (0x0080 << 16) #define kTE 0x0018 + (0x0081 << 16) #define kTI 0x0018 + (0x0082 << 16) // Inversion time #define kNumberOfAverages 0x0018 + (0x0083 << 16) //DS #define kImagingFrequency 0x0018 + (0x0084 << 16) //DS //#define kEffectiveTE 0x0018+(0x9082 << 16 ) #define kEchoNum 0x0018 + (0x0086 << 16) //IS #define kMagneticFieldStrength 0x0018 + (0x0087 << 16) //DS #define kZSpacing 0x0018 + (0x0088 << 16) //'DS' 'SpacingBetweenSlices' #define kPhaseEncodingSteps 0x0018 + (0x0089 << 16) //'IS' #define kEchoTrainLength 0x0018 + (0x0091 << 16) //IS #define kPercentSampling 0x0018 + (0x0093 << 16) //'DS' #define kPhaseFieldofView 0x0018 + (0x0094 << 16) //'DS' #define kPixelBandwidth 0x0018 + (0x0095 << 16) //'DS' 'PixelBandwidth' #define kDeviceSerialNumber 0x0018 + (0x1000 << 16) //LO #define kSoftwareVersions 0x0018 + (0x1020 << 16) //LO #define kProtocolName 0x0018 + (0x1030 << 16) #define kTriggerTime 0x0018 + (0x1060 << 16) //DS #define kRadionuclideTotalDose 0x0018 + (0x1074 << 16) #define kRadionuclideHalfLife 0x0018 + (0x1075 << 16) #define kRadionuclidePositronFraction 0x0018 + (0x1076 << 16) #define kGantryTilt 0x0018 + (0x1120 << 16) #define kXRayTimeMS 0x0018 + (0x1150 << 16) //IS #define kXRayTubeCurrent 0x0018 + (0x1151 << 16) //IS #define kXRayExposure 0x0018 + (0x1152 << 16) #define kConvolutionKernel 0x0018 + (0x1210 << 16) //SH #define kFrameDuration 0x0018 + (0x1242 << 16) //IS #define kReceiveCoilName 0x0018 + (0x1250 << 16) // SH //#define kTransmitCoilName 0x0018 + (0x1251 << 16) // SH issue527 #define kAcquisitionMatrix 0x0018 + (0x1310 << 16) //US #define kFlipAngle 0x0018 + (0x1314 << 16) #define kInPlanePhaseEncodingDirection 0x0018 + (0x1312 << 16) //CS #define kSAR 0x0018 + (0x1316 << 16) //'DS' 'SAR' #define kPatientOrient 0x0018 + (0x5100 << 16) //0018,5100. patient orientation - 'HFS' #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' #define kMagnetizationTransferAttribute 0x0018 + uint32_t(0x9020 << 16) //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE' #define kRectilinearPhaseEncodeReordering 0x0018 + uint32_t(0x9034 << 16) //'CS' 'REVERSE_LINEAR'/'LINEAR' #define kPartialFourierDirection 0x0018 + uint32_t(0x9036 << 16) //'CS' #define kCardiacSynchronizationTechnique 0x0018 + uint32_t(0x9037 << 16) //'CS' #define kParallelReductionFactorInPlane 0x0018 + uint32_t(0x9069 << 16) //FD #define kAcquisitionDuration 0x0018 + uint32_t(0x9073 << 16) //FD //#define kFrameAcquisitionDateTime 0x0018+uint32_t(0x9074<< 16 ) //DT "20181019212528.232500" #define kDiffusionDirectionality 0x0018 + uint32_t(0x9075 << 16) // NONE, ISOTROPIC, or DIRECTIONAL #define kParallelAcquisitionTechnique 0x0018 + uint32_t(0x9078 << 16) //CS: SENSE, SMASH #define kInversionTimes 0x0018 + uint32_t(0x9079 << 16) //FD #define kPartialFourier 0x0018 + uint32_t(0x9081 << 16) //CS const uint32_t kEffectiveTE = 0x0018 + (0x9082 << 16); //#define kDiffusionBFactorSiemens 0x0019+(0x100C<< 16 ) // 0019;000C;SIEMENS MR HEADER;B_value #define kDiffusion_bValue 0x0018 + uint32_t(0x9087 << 16) // FD #define kDiffusionOrientation 0x0018 + uint32_t(0x9089 << 16) // FD, seen in enhanced DICOM from Philips 5.* and Siemens XA10. #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 kDiffusionBValueXX 0x0018 + uint32_t(0x9602 << 16) //FD #define kDiffusionBValueXY 0x0018 + uint32_t(0x9603 << 16) //FD #define kDiffusionBValueXZ 0x0018 + uint32_t(0x9604 << 16) //FD #define kDiffusionBValueYY 0x0018 + uint32_t(0x9605 << 16) //FD #define kDiffusionBValueYZ 0x0018 + uint32_t(0x9606 << 16) //FD #define kDiffusionBValueZZ 0x0018 + uint32_t(0x9607 << 16) //FD #define kMREchoSequence 0x0018 + uint32_t(0x9114 << 16) //SQ #define kMRAcquisitionPhaseEncodingStepsInPlane 0x0018 + uint32_t(0x9231 << 16) //US #define kNumberOfImagesInMosaic 0x0019 + (0x100A << 16) //US NumberOfImagesInMosaic //https://nmrimaging.wordpress.com/2011/12/20/when-we-process/ // https://nciphub.org/groups/qindicom/wiki/DiffusionrelatedDICOMtags:experienceacrosssites?action=pdf #define kDiffusion_bValueSiemens 0x0019 + (0x100C << 16) //IS #define kDiffusionGradientDirectionSiemens 0x0019 + (0x100E << 16) //FD #define kSeriesPlaneGE 0x0019 + (0x1017 << 16) //SS #define kDwellTime 0x0019 + (0x1018 << 16) //IS in NSec, see https://github.com/rordenlab/dcm2niix/issues/127 #define kLastScanLoc 0x0019 + (0x101B << 16) #define kBandwidthPerPixelPhaseEncode 0x0019 + (0x1028 << 16) //FD #define kSliceTimeSiemens 0x0019 + (0x1029 << 16) ///FD #define kPulseSequenceNameGE 0x0019 + (0x109C << 16) //LO 'epiRT' or 'epi' #define kInternalPulseSequenceNameGE 0x0019 + (0x109E << 16) //LO 'EPI' or 'EPI2' #define kRawDataRunNumberGE 0x0019 + (0x10A2 << 16)//SL #define kMaxEchoNumGE 0x0019 + (0x10A9 << 16) //DS #define kUserData12GE 0x0019 + (0x10B3 << 16) //DS phase diffusion direction #define kDiffusionDirectionGEX 0x0019 + (0x10BB << 16) //DS phase diffusion direction #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 kStudyID 0x0020 + (0x0010 << 16) #define kSeriesNum 0x0020 + (0x0011 << 16) #define kAcquNum 0x0020 + (0x0012 << 16) #define kImageNum 0x0020 + (0x0013 << 16) #define kStudyInstanceUID 0x0020 + (0x000D << 16) #define kSeriesInstanceUID 0x0020 + (0x000E << 16) #define kImagePositionPatient 0x0020 + (0x0032 << 16) // Actually ! #define kOrientationACR 0x0020 + (0x0035 << 16) #define kOrientation 0x0020 + (0x0037 << 16) #define kTemporalPosition 0x0020+(0x0100 << 16 ) //IS //#define kNumberOfTemporalPositions 0x0020+(0x0105 << 16 ) //IS public tag for NumberOfDynamicScans #define kTemporalResolution 0x0020 + (0x0110 << 16) //DS #define kImagesInAcquisition 0x0020 + (0x1002 << 16) //IS //#define kSliceLocation 0x0020+(0x1041 << 16 ) //DS would be useful if not optional type 3 #define kImageComments 0x0020 + (0x4000 << 16) // '0020' '4000' 'LT' 'ImageComments' #define kFrameContentSequence 0x0020 + uint32_t(0x9111 << 16) //SQ #define kTriggerDelayTime 0x0020 + uint32_t(0x9153 << 16) //FD #define kDimensionIndexValues 0x0020 + uint32_t(0x9157 << 16) // UL n-dimensional index of frame. #define kInStackPositionNumber 0x0020 + uint32_t(0x9057 << 16) // UL can help determine slices in volume #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 kPATModeText 0x0021 + (0x1009 << 16) //LO, see kImaPATModeText #define kTimeAfterStart 0x0021 + (0x1104 << 16) //DS #define kPhaseEncodingDirectionPositiveSiemens 0x0021 + (0x111C << 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 //Private Group 21 as used by GE: #define kLocationsInAcquisitionGE 0x0021 + (0x104F << 16) //SS 'LocationsInAcquisitionGE' #define kRTIA_timer 0x0021 + (0x105E << 16) //DS #define kProtocolDataBlockGE 0x0025 + (0x101B << 16) //OB #define kNumberOfPointsPerArm 0x0027 + (0x1060 << 16) //FL #define kNumberOfArms 0x0027 + (0x1061 << 16) //FL #define kNumberOfExcitations 0x0027 + (0x1062 << 16) //FL #define kSamplesPerPixel 0x0028 + (0x0002 << 16) #define kPhotometricInterpretation 0x0028 + (0x0004 << 16) #define kPlanarRGB 0x0028 + (0x0006 << 16) #define kDim3 0x0028 + (0x0008 << 16) //number of frames - for Philips this is Dim3*Dim4 #define kDim2 0x0028 + (0x0010 << 16) #define kDim1 0x0028 + (0x0011 << 16) #define kXYSpacing 0x0028 + (0x0030 << 16) //DS 'PixelSpacing' #define kBitsAllocated 0x0028 + (0x0100 << 16) #define kBitsStored 0x0028 + (0x0101 << 16) //US 'BitsStored' #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 #define kIntercept 0x0028 + (0x1052 << 16) #define kSlope 0x0028 + (0x1053 << 16) //#define kRescaleType 0x0028+(0x1053 << 16 ) //LO e.g. for Philips Fieldmap: [Hz] //#define kSpectroscopyDataPointColumns 0x0028+(0x9002 << 16 ) //IS #define kGeiisFlag 0x0029 + (0x0010 << 16) //warn user if dreaded GEIIS was used to process image #define kCSAImageHeaderInfo 0x0029 + (0x1010 << 16) #define kCSASeriesHeaderInfo 0x0029 + (0x1020 << 16) #define kStudyComments 0x0032 + (0x4000 << 16) //LT StudyComments //#define kObjectGraphics 0x0029+(0x1210 << 16 ) //0029,1210 syngoPlatformOOGInfo Object Oriented Graphics #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 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 #define kDiffusion_bValueGE 0x0043 + (0x1039 << 16) //IS dicm2nii's SlopInt_6_9 #define kEpiRTGroupDelayGE 0x0043 + (0x107C << 16) //FL #define kAssetRFactorsGE 0x0043 + (0x1083 << 16) //DS #define kASLContrastTechniqueGE 0x0043 + (0x10A3 << 16) //CS #define kASLLabelingTechniqueGE 0x0043 + (0x10A4 << 16) //LO #define kDurationLabelPulseGE 0x0043 + (0x10A5 << 16) //IS #define kMultiBandGE 0x0043 + (0x10B6 << 16) //LO #define kAcquisitionMatrixText 0x0051 + (0x100B << 16) //LO #define kImageOrientationText 0x0051 + (0x100E << 16) // #define kCoilSiemens 0x0051 + (0x100F << 16) #define kImaPATModeText 0x0051 + (0x1011 << 16) #define kLocationsInAcquisition 0x0054 + (0x0081 << 16) #define kUnitsPT 0x0054 + (0x1001 << 16) //CS #define kAttenuationCorrectionMethod 0x0054 + (0x1101 << 16) //LO #define kDecayCorrection 0x0054 + (0x1102 << 16) //CS #define kReconstructionMethod 0x0054 + (0x1103 << 16) //LO #define kDecayFactor 0x0054 + (0x1321 << 16) //LO //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 ) #define kDoseCalibrationFactor 0x0054 + (0x1322 << 16) #define kPETImageIndex 0x0054 + (0x1330 << 16) #define kPEDirectionDisplayedUIH 0x0065 + (0x1005 << 16) //SH #define kDiffusion_bValueUIH 0x0065 + (0x1009 << 16) //FD #define kParallelInformationUIH 0x0065 + (0x100D << 16) //SH #define kNumberOfImagesInGridUIH 0x0065 + (0x1050 << 16) //DS #define kDiffusionGradientDirectionUIH 0x0065 + (0x1037 << 16) //FD //#define kMRVFrameSequenceUIH 0x0065+(0x1050<< 16 ) //SQ #define kPhaseEncodingDirectionPositiveUIH 0x0065 + (0x1058 << 16) //IS issue410 #define kIconImageSequence 0x0088 + (0x0200 << 16) #define kElscintIcon 0x07a3 + (0x10ce << 16) //see kGeiisFlag and https://github.com/rordenlab/dcm2niix/issues/239 #define kPMSCT_RLE1 0x07a1 + (0x100a << 16) //Elscint/Philips compression #define kPrivateCreator 0x2001 + (0x0010 << 16) // LO (Private creator is any tag where group is odd and element is x0010-x00FF #define kDiffusion_bValuePhilips 0x2001 + (0x1003 << 16) // FL #define kPhaseNumber 0x2001 + (0x1008 << 16) //IS #define kCardiacSync 0x2001 + (0x1010 << 16) //CS //#define kDiffusionDirectionPhilips 0x2001+(0x1004 << 16 )//CS Diffusion Direction #define kSliceNumberMrPhilips 0x2001 + (0x100A << 16) //IS Slice_Number_MR #define kSliceOrient 0x2001 + (0x100B << 16) //2001,100B Philips slice orientation (TRANSVERSAL, AXIAL, SAGITTAL) #define kEPIFactorPhilips 0x2001 + (0x1013 << 16) //SL #define kPrepulseDelay 0x2001 + (0x101B << 16) //FL #define kPrepulseType 0x2001 + (0x101C << 16) //CS #define kRespirationSync 0x2001 + (0x101F << 16) //CS #define kNumberOfSlicesMrPhilips 0x2001 + (0x1018 << 16) //SL 0x2001, 0x1018 ), "Number_of_Slices_MR" #define kPartialMatrixScannedPhilips 0x2001 + (0x1019 << 16) // CS #define kWaterFatShiftPhilips 0x2001 + (0x1022 << 16) //FL //#define kMRSeriesAcquisitionNumber 0x2001+(0x107B << 16 ) //IS //#define kNumberOfLocationsPhilips 0x2001+(0x1015 << 16 ) //SS //#define kStackSliceNumber 0x2001+(0x1035 << 16 )//? Potential way to determine slice order for Philips? #define kNumberOfDynamicScans 0x2001 + (0x1081 << 16) //'2001' '1081' 'IS' 'NumberOfDynamicScans' #define kMRfMRIStatusIndicationPhilips 0x2005 + (0x1063 << 16) #define kMRAcquisitionTypePhilips 0x2005 + (0x106F << 16) //SS #define kAngulationAP 0x2005 + (0x1071 << 16) //'2005' '1071' 'FL' 'MRStackAngulationAP' #define kAngulationFH 0x2005 + (0x1072 << 16) //'2005' '1072' 'FL' 'MRStackAngulationFH' #define kAngulationRL 0x2005 + (0x1073 << 16) //'2005' '1073' 'FL' 'MRStackAngulationRL' #define kMRStackOffcentreAP 0x2005 + (0x1078 << 16) #define kMRStackOffcentreFH 0x2005 + (0x1079 << 16) #define kMRStackOffcentreRL 0x2005 + (0x107A << 16) #define kPhilipsSlope 0x2005 + (0x100E << 16) #define kMRImageDynamicScanBeginTime 0x2005 + (0x10a0 << 16) //FL #define kDiffusionDirectionRL 0x2005 + (0x10B0 << 16) #define kDiffusionDirectionAP 0x2005 + (0x10B1 << 16) #define kDiffusionDirectionFH 0x2005 + (0x10B2 << 16) #define kPrivatePerFrameSq 0x2005 + (0x140F << 16) #define kMRImageDiffBValueNumber 0x2005 + (0x1412 << 16) //IS #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 kSharedFunctionalGroupsSequence 0x5200 + uint32_t(0x9229 << 16) // SQ #define kPerFrameFunctionalGroupsSequence 0x5200 + uint32_t(0x9230 << 16) // SQ #define kWaveformSq 0x5400 + (0x0100 << 16) #define kSpectroscopyData 0x5600 + (0x0020 << 16) //OF #define kImageStart 0x7FE0 + (0x0010 << 16) #define kImageStartFloat 0x7FE0 + (0x0008 << 16) #define kImageStartDouble 0x7FE0 + (0x0009 << 16) uint32_t kItemTag = 0xFFFE + (0xE000 << 16); uint32_t kItemDelimitationTag = 0xFFFE + (0xE00D << 16); uint32_t kSequenceDelimitationItemTag = 0xFFFE + (0xE0DD << 16); #define salvageAgfa #ifdef salvageAgfa //issue435 // handle PrivateCreator renaming e.g. 0021,10xx -> 0021,11xx // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/siemens.tpl // https://github.com/neurolabusc/dcm_qa_agfa // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.8.html #define kMaxRemaps 16 //no vendor uses more than 5 private creator groups //we need to keep track of multiple remappings, e.g. issue 437 2005,0014->2005,0012; 2005,0015->2005,0011 int nRemaps = 0; uint32_t privateCreatorMasks[kMaxRemaps]; //0 -> none uint32_t privateCreatorRemaps[kMaxRemaps]; //0 -> none #endif double TE = 0.0; //most recent echo time recorded float temporalResolutionMS = 0.0; float MRImageDynamicScanBeginTime = 0.0; bool is2005140FSQ = false; bool overlayOK = true; int userData12GE = 0; int overlayRows = 0; int overlayCols = 0; bool isNeologica = false; bool isTriggerSynced = false; bool isProspectiveSynced = false; bool isDICOMANON = false; //issue383 bool isMATLAB = false; //issue383 bool isASL = false; //double contentTime = 0.0; int echoTrainLengthPhil = 0; int philMRImageDiffBValueNumber = 0; int philMRImageDiffVolumeNumber = -1; int sqDepth = 0; int acquisitionTimesGE_UIH = 0; int sqDepth00189114 = -1; bool hasDwiDirectionality = false; //float sliceLocation = INFINITY; //useless since this tag is optional //int numFirstPatientPosition = 0; int nDimIndxVal = -1; //tracks Philips kDimensionIndexValues int locationsInAcquisitionGE = 0; int PETImageIndex = 0; int inStackPositionNumber = 0; bool isKludgeIssue533 = false; uint32_t dimensionIndexPointer[MAX_NUMBER_OF_DIMENSIONS]; size_t dimensionIndexPointerCounter = 0; int maxInStackPositionNumber = 0; int temporalPositionIndex = 0; int maxTemporalPositionIndex = 0; //int temporalPositionIdentifier = 0; int locationsInAcquisitionPhilips = 0; int imagesInAcquisition = 0; //int sumSliceNumberMrPhilips = 0; int sliceNumberMrPhilips = 0; int volumeNumber = -1; int gradientOrientationNumberPhilips = -1; int numberOfFrames = 0; //int MRImageGradientOrientationNumber = 0; //int minGradNum = kMaxDTI4D + 1; //int maxGradNum = -1; int numberOfDynamicScans = 0; //int mRSeriesAcquisitionNumber = 0; uint32_t lLength; uint32_t groupElement; long lPos = 0; bool isPhilipsDerived = false; //bool isPhilipsDiffusion = false; if (isPart10prefix) { //for part 10 files, skip preamble and prefix lPos = 128 + 4; //4-byte signature starts at 128 groupElement = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); if (groupElement != kStart) printMessage("DICOM appears corrupt: first group:element should be 0x0002:0x0000 '%s'\n", fname); } else { //no isPart10prefix - need to work out if this is explicit VR! if (isVerbose > 1) printMessage("DICOM preamble and prefix missing: this is not a valid DICOM image.\n"); //See Toshiba Aquilion images from https://www.aliza-dicom-viewer.com/download/datasets lLength = buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24); if (lLength > fileLen) { if (isVerbose > 1) printMessage("Guessing this is an explicit VR image.\n"); d.isExplicitVR = true; } } char vr[2]; //float intenScalePhilips = 0.0; char seriesTimeTxt[kDICOMStr] = ""; char acquisitionDateTimeTxt[kDICOMStr] = ""; char imageType1st[kDICOMStr] = ""; bool isEncapsulatedData = false; int multiBandFactor = 0; int frequencyRows = 0; int numberOfImagesInMosaic = 0; int encapsulatedDataFragments = 0; int encapsulatedDataFragmentStart = 0; //position of first FFFE,E000 for compressed images int encapsulatedDataImageStart = 0; //position of 7FE0,0010 for compressed images (where actual image start should be start of first fragment) bool isOrient = false; //bool isDcm4Che = false; bool isMoCo = false; bool isPaletteColor = false; bool isInterpolated = false; bool isIconImageSequence = false; int sqDepthIcon = -1; bool isSwitchToImplicitVR = false; bool isSwitchToBigEndian = false; bool isAtFirstPatientPosition = false; //for 3d and 4d files: flag is true for slices at same position as first slice bool isMosaic = false; bool isGEfieldMap = false; //issue501 int patientPositionNum = 0; float B0Philips = -1.0; float vRLPhilips = 0.0; float vAPPhilips = 0.0; float vFHPhilips = 0.0; bool isPhase = false; bool isReal = false; bool isImaginary = false; bool isMagnitude = false; d.seriesNum = -1; //start issue 372: vec3 sliceV; //cross-product of kOrientation 0020,0037 sliceV.v[0] = NAN; float sliceMM[kMaxSlice2D]; int nSliceMM = 0; float minSliceMM = INFINITY; float maxSliceMM = -INFINITY; float minDynamicScanBeginTime = INFINITY; float maxDynamicScanBeginTime = -INFINITY; float minPatientPosition[4] = {NAN, NAN, NAN, NAN}; float maxPatientPosition[4] = {NAN, NAN, NAN, NAN}; //end issue 372 //float frameAcquisitionDuration = 0.0; //issue369 float patientPositionPrivate[4] = {NAN, NAN, NAN, NAN}; float patientPosition[4] = {NAN, NAN, NAN, NAN}; //used to compute slice direction for Philips 4D //float patientPositionPublic[4] = {NAN, NAN, NAN, NAN}; //used to compute slice direction for Philips 4D float patientPositionEndPhilips[4] = {NAN, NAN, NAN, NAN}; float patientPositionStartPhilips[4] = {NAN, NAN, NAN, NAN}; //struct TDTI philDTI[kMaxDTI4D]; //for (int i = 0; i < kMaxDTI4D; i++) // philDTI[i].V[0] = -1; //array for storing DimensionIndexValues int numDimensionIndexValues = 0; #ifdef USING_R // Allocating a large array on the stack, as below, vexes valgrind and may cause overflow std::vector dcmDim(kMaxSlice2D); #else TDCMdim dcmDim[kMaxSlice2D]; #endif for (int i = 0; i < kMaxSlice2D; i++) { dcmDim[i].diskPos = i; for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; j++) dcmDim[i].dimIdx[j] = 0; } //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html //The array nestPos tracks explicit lengths for Data Element Tag of Value (FFFE,E000) //a delimiter (fffe,e000) can have an explicit length, in which case there is no delimiter (fffe,e00d) // fffe,e000 can provide explicit lengths, to demonstrate ./dcmconv +ti ex.DCM im.DCM #define kMaxNestPost 128 int nNestPos = 0; size_t nestPos[kMaxNestPost]; while ((d.imageStart == 0) && ((lPos + 8 + lFileOffset) < fileLen)) { #ifndef myLoadWholeFileToReadHeader //read one segment at a time if ((size_t)(lPos + 128) > MaxBufferSz) { //avoid overreading the file lFileOffset = lFileOffset + lPos; if ((lFileOffset + MaxBufferSz) > (size_t)fileLen) MaxBufferSz = fileLen - lFileOffset; fseek(file, lFileOffset, SEEK_SET); size_t sz = fread(buffer, 1, MaxBufferSz, file); if (sz < MaxBufferSz) { printError("Only loaded %zu of %zu bytes for %s\n", sz, MaxBufferSz, fname); fclose(file); return d; } lPos = 0; } #endif if (d.isLittleEndian) groupElement = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); else groupElement = buffer[lPos + 1] | (buffer[lPos] << 8) | (buffer[lPos + 3] << 16) | (buffer[lPos + 2] << 24); if ((isSwitchToBigEndian) && ((groupElement & 0xFFFF) != 2)) { isSwitchToBigEndian = false; d.isLittleEndian = false; groupElement = buffer[lPos + 1] | (buffer[lPos] << 8) | (buffer[lPos + 3] << 16) | (buffer[lPos + 2] << 24); } //transfer syntax requests switching endian after group 0002 if ((isSwitchToImplicitVR) && ((groupElement & 0xFFFF) != 2)) { isSwitchToImplicitVR = false; d.isExplicitVR = false; } //transfer syntax requests switching VR after group 0001 //uint32_t group = (groupElement & 0xFFFF); lPos += 4; //issue409 - icons can have their own sub-sections... keep reading until we get to the icon image? //if ((groupElement == kItemDelimitationTag) || (groupElement == kSequenceDelimitationItemTag)) isIconImageSequence = false; //if (groupElement == kItemTag) sqDepth++; bool unNest = false; while ((nNestPos > 0) && (nestPos[nNestPos] <= (lFileOffset + lPos))) { nNestPos--; sqDepth--; unNest = true; if ((sqDepthIcon >= 0) && (sqDepth <= sqDepthIcon)) { //issue415 sqDepthIcon = -1; isIconImageSequence = false; } } if (groupElement == kItemDelimitationTag) { //end of item with undefined length sqDepth--; unNest = true; if ((sqDepthIcon >= 0) && (sqDepth <= sqDepthIcon)) { //issue415 sqDepthIcon = -1; isIconImageSequence = false; } } if (unNest) { is2005140FSQ = false; if (sqDepth < 0) sqDepth = 0; //should not happen, but protect for faulty anonymization //if we leave the folder MREchoSequence 0018,9114 if ((nDimIndxVal > 0) && ((d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_BRUKER) || (d.manufacturer == kMANUFACTURER_PHILIPS)) && (sqDepth00189114 >= sqDepth)) { 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)) { isKludgeIssue533 = true; for (int i = 0; i < nDimIndxVal; i++) d.dimensionIndexValues[i] = 0; int phase = d.phaseNumber; if (d.phaseNumber < 0) phase = 0; //if not set: we are saving as UINT d.dimensionIndexValues[0] = inStackPositionNumber; //dim[3] slice changes fastest d.dimensionIndexValues[1] = phase; //dim[4] successive volumes are phase d.dimensionIndexValues[2] = d.aslFlags == kASL_FLAG_PHILIPS_LABEL; //dim[5] Control/Label d.dimensionIndexValues[3] = volumeNumber; //dim[6] Repeat changes slowest 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); } int ndim = nDimIndxVal; if (inStackPositionNumber > 0) { //for images without SliceNumberMrPhilips (2001,100A) int sliceNumber = inStackPositionNumber; //printf("slice %d \n", sliceNumber); if ((sliceNumber == 1) && (!isnan(patientPosition[1]))) { for (int k = 0; k < 4; k++) patientPositionStartPhilips[k] = patientPosition[k]; } else if ((sliceNumber == 1) && (!isnan(patientPositionPrivate[1]))) { for (int k = 0; k < 4; k++) patientPositionStartPhilips[k] = patientPositionPrivate[k]; } if ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPosition[1]))) { for (int k = 0; k < 4; k++) patientPositionEndPhilips[k] = patientPosition[k]; } else if ((sliceNumber == maxInStackPositionNumber) && (!isnan(patientPositionPrivate[1]))) { for (int k = 0; k < 4; k++) patientPositionEndPhilips[k] = patientPositionPrivate[k]; } patientPosition[1] = NAN; patientPositionPrivate[1] = NAN; } inStackPositionNumber = 0; if (numDimensionIndexValues >= kMaxSlice2D) { printError("Too many slices to track dimensions. Only up to %d are supported\n", kMaxSlice2D); break; } uint32_t dimensionIndexOrder[MAX_NUMBER_OF_DIMENSIONS]; for (size_t 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 //Canon and Bruker reverse dimensionIndexItem order relative to Philips: new versions introduce compareTDCMdimRev //printf("%d: %d %d %d %d\n", ndim, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2], d.dimensionIndexValues[3]); if ((philMRImageDiffVolumeNumber > 0) && (sliceNumberMrPhilips > 0)) { //issue546: 2005,1596 provides temporal order dcmDim[numDimensionIndexValues].dimIdx[0] = 1; dcmDim[numDimensionIndexValues].dimIdx[1] = sliceNumberMrPhilips; dcmDim[numDimensionIndexValues].dimIdx[2] = philMRImageDiffVolumeNumber; } else { for (int i = 0; i < ndim; i++) dcmDim[numDimensionIndexValues].dimIdx[i] = d.dimensionIndexValues[dimensionIndexOrder[i]]; } dcmDim[numDimensionIndexValues].TE = TE; dcmDim[numDimensionIndexValues].intenScale = d.intenScale; dcmDim[numDimensionIndexValues].intenIntercept = d.intenIntercept; dcmDim[numDimensionIndexValues].isPhase = isPhase; dcmDim[numDimensionIndexValues].isReal = isReal; dcmDim[numDimensionIndexValues].isImaginary = isImaginary; dcmDim[numDimensionIndexValues].intenScalePhilips = d.intenScalePhilips; dcmDim[numDimensionIndexValues].RWVScale = d.RWVScale; dcmDim[numDimensionIndexValues].RWVIntercept = d.RWVIntercept; //printf("%d %d %g????\n", isTriggerSynced, isProspectiveSynced, d.triggerDelayTime); //TODO533: isKludgeIssue533 alias Philips ASL as FrameDuration? //if ((d.triggerDelayTime > 0.0) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (d.aslFlags != kASL_FLAG_NONE)) //printf(">>>%g\n", d.triggerDelayTime); //if ((isASL) || (d.aslFlags != kASL_FLAG_NONE)) d.triggerDelayTime = 0.0; //see dcm_qa_philips_asl //if ((d.manufacturer == kMANUFACTURER_PHILIPS) && ((!isTriggerSynced) || (!isProspectiveSynced)) ) //issue408 // d.triggerDelayTime = 0.0; if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime) ) dcmDim[numDimensionIndexValues].triggerDelayTime = 0.0; //issue395 else dcmDim[numDimensionIndexValues].triggerDelayTime = d.triggerDelayTime; dcmDim[numDimensionIndexValues].V[0] = -1.0; #ifdef MY_DEBUG if (numDimensionIndexValues < 19) { printMessage("dimensionIndexValues0020x9157[%d] = [", numDimensionIndexValues); for (int i = 0; i < ndim; i++) printMessage("%d ", d.dimensionIndexValues[i]); printMessage("]\n"); //printMessage("B0= %g num=%d\n", B0Philips, gradNum); } else return d; #endif //next: add diffusion if reported if (B0Philips >= 0.0) { //diffusion parameters // Philips does not always provide 2005,1413 (MRImageGradientOrientationNumber) and sometimes after dimensionIndexValues /*int gradNum = 0; for (int i = 0; i < ndim; i++) if (d.dimensionIndexValues[i] > 0) gradNum = d.dimensionIndexValues[i]; if (gradNum <= 0) break; With Philips 51.0 both ADC and B=0 are saved as same direction, though they vary in another dimension (0018,9075) CS [ISOTROPIC] (0020,9157) UL 1\2\1\33 << ADC MAP (0018,9075) CS [NONE] (0020,9157) UL 1\1\2\33 next two lines attempt to skip ADC maps we could also increment gradNum for ADC if we wanted... */ if (isPhilipsDerived) { //gradNum ++; B0Philips = 2000.0; vRLPhilips = 0.0; vAPPhilips = 0.0; vFHPhilips = 0.0; } if (B0Philips == 0.0) { //printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips); vRLPhilips = 0.0; vAPPhilips = 0.0; vFHPhilips = 0.0; } //if ((MRImageGradientOrientationNumber > 0) && ((gradNum != MRImageGradientOrientationNumber)) break; /*if (gradNum < minGradNum) minGradNum = gradNum; if (gradNum >= maxGradNum) maxGradNum = gradNum; if (gradNum >= kMaxDTI4D) { printError("Number of DTI gradients exceeds 'kMaxDTI4D (%d).\n", kMaxDTI4D); } else { gradNum = gradNum - 1; //index from 0 philDTI[gradNum].V[0] = B0Philips; philDTI[gradNum].V[1] = vRLPhilips; philDTI[gradNum].V[2] = vAPPhilips; philDTI[gradNum].V[3] = vFHPhilips; }*/ dcmDim[numDimensionIndexValues].V[0] = B0Philips; dcmDim[numDimensionIndexValues].V[1] = vRLPhilips; dcmDim[numDimensionIndexValues].V[2] = vAPPhilips; dcmDim[numDimensionIndexValues].V[3] = vFHPhilips; isPhilipsDerived = false; //printMessage(" DimensionIndexValues grad %d b=%g vec=%gx%gx%g\n", gradNum, B0Philips, vRLPhilips, vAPPhilips, vFHPhilips); //!!! 16032018 : next line as well as definition of B0Philips may need to be set to zero if Philips omits DiffusionBValue tag for B=0 B0Philips = -1.0; //Philips may skip reporting B-values for B=0 volumes, so zero these vRLPhilips = 0.0; vAPPhilips = 0.0; vFHPhilips = 0.0; //MRImageGradientOrientationNumber = 0; } //diffusion parameters numDimensionIndexValues++; nDimIndxVal = -1; //we need DimensionIndexValues } //record dimensionIndexValues slice information } //groupElement == kItemDelimitationTag : delimit item exits folder if (groupElement == kItemTag) { uint32_t slen = dcmInt(4, &buffer[lPos], d.isLittleEndian); uint32_t kUndefinedLen = 0xFFFFFFFF; if (slen != kUndefinedLen) { nNestPos++; if (nNestPos >= kMaxNestPost) nNestPos = kMaxNestPost - 1; nestPos[nNestPos] = slen + lFileOffset + lPos; } lLength = 4; sqDepth++; //return d; } else if (((groupElement == kItemDelimitationTag) || (groupElement == kSequenceDelimitationItemTag)) && (!isEncapsulatedData)) { vr[0] = 'N'; vr[1] = 'A'; lLength = 4; } else if (d.isExplicitVR) { vr[0] = buffer[lPos]; vr[1] = buffer[lPos + 1]; if (buffer[lPos + 1] < 'A') { //implicit vr with 32-bit length if (d.isLittleEndian) lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); else lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24); lPos += 4; } else if (((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'N')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'C')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'R')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'T')) || ((buffer[lPos] == 'U') && (buffer[lPos + 1] == 'V')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'B')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'D')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'F')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'L')) | ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'V')) || ((buffer[lPos] == 'O') && (buffer[lPos + 1] == 'W')) || ((buffer[lPos] == 'S') && (buffer[lPos + 1] == 'V'))) { //VR= UN, OB, OW, SQ || ((buffer[lPos] == 'S') && (buffer[lPos+1] == 'Q')) //for example of UC/UR/UV/OD/OF/OL/OV/SV see VR conformance test https://www.aliza-dicom-viewer.com/download/datasets lPos = lPos + 4; //skip 2 byte VR string and 2 reserved bytes = 4 bytes if (d.isLittleEndian) lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); else lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24); lPos = lPos + 4; //skip 4 byte length } else if ((buffer[lPos] == 'S') && (buffer[lPos + 1] == 'Q')) { lLength = 8; //Sequence Tag //printMessage(" !!!SQ\t%04x,%04x\n", groupElement & 65535,groupElement>>16); } else { //explicit VR with 16-bit length if ((d.isLittleEndian)) lLength = buffer[lPos + 2] | (buffer[lPos + 3] << 8); else lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8); lPos += 4; //skip 2 byte VR string and 2 length bytes = 4 bytes } } else { //implicit VR vr[0] = 'U'; vr[1] = 'N'; if (d.isLittleEndian) lLength = buffer[lPos] | (buffer[lPos + 1] << 8) | (buffer[lPos + 2] << 16) | (buffer[lPos + 3] << 24); else lLength = buffer[lPos + 3] | (buffer[lPos + 2] << 8) | (buffer[lPos + 1] << 16) | (buffer[lPos] << 24); lPos += 4; //we have loaded the 32-bit length if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isSQ(groupElement))) { //https://github.com/rordenlab/dcm2niix/issues/144 vr[0] = 'S'; vr[1] = 'Q'; lLength = 0; //Do not skip kItemTag - required to determine nesting of Philips Enhanced } if ((d.manufacturer != kMANUFACTURER_PHILIPS) && (isSQ(groupElement))) { //https://github.com/rordenlab/dcm2niix/issues/144 vr[0] = 'S'; vr[1] = 'Q'; lLength = 0; //Do not skip kItemTag - required to determine nesting of Philips Enhanced } } //if explicit else implicit VR if (lLength == 0xFFFFFFFF) { lLength = 8; //SQ (Sequences) use 0xFFFFFFFF [4294967295] to denote unknown length //09032018 - do not count these as SQs: Horos does not count even groups //uint32_t special = dcmInt(4,&buffer[lPos],d.isLittleEndian); //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html //if (special != ksqDelim) { vr[0] = 'S'; vr[1] = 'Q'; //} } if ((groupElement == kItemTag) && (isEncapsulatedData)) { //use this to find image fragment for compressed datasets, e.g. JPEG transfer syntax d.imageBytes = dcmInt(4, &buffer[lPos], d.isLittleEndian); lPos = lPos + 4; lLength = d.imageBytes; if (d.imageBytes > 128) { /*if (encapsulatedDataFragments < kMaxDTI4D) { dti4D->fragmentOffset[encapsulatedDataFragments] = (int)lPos + (int)lFileOffset; dti4D->fragmentLength[encapsulatedDataFragments] = lLength; }*/ encapsulatedDataFragments++; if (encapsulatedDataFragmentStart == 0) encapsulatedDataFragmentStart = (int)lPos + (int)lFileOffset; } } if ((isIconImageSequence) && ((groupElement & 0x0028) == 0x0028)) groupElement = kUnused; //ignore icon dimensions #ifdef salvageAgfa //issue435 //Handle remapping using integers, and slower but simpler approach is with strings: // https://github.com/pydicom/pydicom/blob/master/pydicom/_private_dict.py if (((groupElement & 65535) % 2) == 0) goto skipRemap; //remap odd (private) groups //printf("tag %04x,%04x\n", groupElement & 65535, groupElement >> 16); if (((groupElement >> 16) >= 0x10) && ((groupElement >> 16) <= 0xFF)) { //tags (gggg,0010-00FF) may define new remapping //if remapping tag //first: see if this remapping overwrites existing tag uint32_t privateCreatorMask = 0; //0 -> none uint32_t privateCreatorRemap = 0; //0 -> none privateCreatorMask = (groupElement & 65535) + ((groupElement & 0xFFFF0000) << 8); if (nRemaps > 0) { int j = 0; for (int i = 0; i < nRemaps; i++) //remove duplicate remapping //copy all remaps except exact match if (privateCreatorMasks[i] != privateCreatorMask) { privateCreatorMasks[j] = privateCreatorMasks[i]; privateCreatorRemaps[j] = privateCreatorRemaps[i]; j++; } nRemaps = j; } //see if this is known private vendor tag privateCreatorRemap = 0; char privateCreator[kDICOMStr]; dcmStr(lLength, &buffer[lPos], privateCreator); //next lines determine remapping, append as needed //Siemens https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/siemens.tpl if (strstr(privateCreator, "SIEMENS MR HEADER") != NULL) privateCreatorRemap = 0x0019 + (0x1000 << 16); if (strstr(privateCreator, "SIEMENS MR SDS 01") != NULL) privateCreatorRemap = 0x0021 + (0x1000 << 16); if (strstr(privateCreator, "SIEMENS MR SDI 02") != NULL) privateCreatorRemap = 0x0021 + (0x1100 << 16); if (strstr(privateCreator, "SIEMENS CSA HEADER") != NULL) privateCreatorRemap = 0x0029 + (0x1000 << 16); if (strstr(privateCreator, "SIEMENS MR HEADER") != NULL) privateCreatorRemap = 0x0051 + (0x1000 << 16); //GE https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/gems.tpl if (strstr(privateCreator, "GEMS_ACQU_01") != NULL) privateCreatorRemap = 0x0019 + (0x1000 << 16); if (strstr(privateCreator, "GEMS_RELA_01") != NULL) privateCreatorRemap = 0x0021 + (0x1000 << 16); if (strstr(privateCreator, "GEMS_SERS_01") != NULL) privateCreatorRemap = 0x0025 + (0x1000 << 16); if (strstr(privateCreator, "GEMS_PARM_01") != NULL) privateCreatorRemap = 0x0043 + (0x1000 << 16); //ELSCINT https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/elscint.tpl int grp = (groupElement & 65535); if ((grp == 0x07a1) && (strstr(privateCreator, "ELSCINT1") != NULL)) privateCreatorRemap = 0x07a1 + (0x1000 << 16); if ((grp == 0x07a3) && (strstr(privateCreator, "ELSCINT1") != NULL)) privateCreatorRemap = 0x07a3 + (0x1000 << 16); //Philips https://github.com/dcm4che/dcm4che/blob/master/dcm4che-dict/src/main/dicom3tools/libsrc/standard/elmdict/philips.tpl if (strstr(privateCreator, "PHILIPS IMAGING DD 001") != NULL) privateCreatorRemap = 0x2001 + (0x1000 << 16); if (strstr(privateCreator, "Philips Imaging DD 001") != NULL) privateCreatorRemap = 0x2001 + (0x1000 << 16); if (strstr(privateCreator, "PHILIPS MR IMAGING DD 001") != NULL) privateCreatorRemap = 0x2005 + (0x1000 << 16); if (strstr(privateCreator, "Philips MR Imaging DD 001") != NULL) privateCreatorRemap = 0x2005 + (0x1000 << 16); if (strstr(privateCreator, "PHILIPS MR IMAGING DD 005") != NULL) privateCreatorRemap = 0x2005 + (0x1400 << 16); if (strstr(privateCreator, "Philips MR Imaging DD 005") != NULL) privateCreatorRemap = 0x2005 + (0x1400 << 16); //UIH https://github.com/neurolabusc/dcm_qa_uih if (strstr(privateCreator, "Image Private Header") != NULL) privateCreatorRemap = 0x0065 + (0x1000 << 16); //sanity check: group should match if (grp != (privateCreatorRemap & 65535)) privateCreatorRemap = 0; if (privateCreatorRemap == 0) goto skipRemap; //this is not a known private group if (privateCreatorRemap == privateCreatorMask) goto skipRemap; //the remapping and mask are identical 2005,1000 -> 2005,1000 if ((nRemaps + 1) >= kMaxRemaps) goto skipRemap; //all slots full (should never happen) //add new remapping privateCreatorMasks[nRemaps] = privateCreatorMask; privateCreatorRemaps[nRemaps] = privateCreatorRemap; //printf("new remapping %04x,%04x -> %04x,%04x\n", privateCreatorMask & 65535, privateCreatorMask >> 16, privateCreatorRemap & 65535, privateCreatorRemap >> 16); if (isVerbose > 1) printMessage("new remapping (%d) %04x,%02xxy -> %04x,%02xxy\n", nRemaps, privateCreatorMask & 65535, privateCreatorMask >> 24, privateCreatorRemap & 65535, privateCreatorRemap >> 24); nRemaps += 1; //for (int i = 0; i < nRemaps; i++) // printf(" %d = %04x,%02xxy -> %04x,%02xxy\n", i, privateCreatorMasks[i] & 65535, privateCreatorMasks[i] >> 24, privateCreatorRemaps[i] & 65535, privateCreatorRemaps[i] >> 24); goto skipRemap; } if (nRemaps < 1) goto skipRemap; { uint32_t remappedGroupElement = 0; for (int i = 0; i < nRemaps; i++) if ((groupElement & 0xFF00FFFF) == (privateCreatorMasks[i] & 0xFF00FFFF)) remappedGroupElement = privateCreatorRemaps[i] + (groupElement & 0x00FF0000); if (remappedGroupElement == 0) goto skipRemap; if (isVerbose > 1) printMessage("remapping %04x,%04x -> %04x,%04x\n", groupElement & 65535, groupElement >> 16, remappedGroupElement & 65535, remappedGroupElement >> 16); groupElement = remappedGroupElement; } skipRemap: #endif // salvageAgfa 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 // d.isValid = false; //return d; } switch (groupElement) { case kMediaStorageSOPClassUID: { char mediaUID[kDICOMStr]; 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.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) d.isRawDataStorage = true; //Private MR Series Data Storage if (strstr(mediaUID, "1.3.46.670589.11.0.0.12.4") != NULL) d.isRawDataStorage = true; //Private MR Examcard Storage if (d.isRawDataStorage) d.isDerived = true; if (d.isRawDataStorage) printMessage("Skipping non-image DICOM: %s\n", fname); //Philips "PS_" files if (strstr(mediaUID, "1.2.840.10008.5.1.4.1.1.11.1") != NULL) d.isGrayscaleSoftcopyPresentationState = true; if (d.isGrayscaleSoftcopyPresentationState) d.isDerived = true; break; } case kMediaStorageSOPInstanceUID: { // 0002, 0003 //char SOPInstanceUID[kDICOMStr]; dcmStr(lLength, &buffer[lPos], d.instanceUID); //printMessage(">>%s\n", d.seriesInstanceUID); d.instanceUidCrc = mz_crc32X((unsigned char *)&d.instanceUID, strlen(d.instanceUID)); break; } case kTransferSyntax: { char transferSyntax[kDICOMStr]; strcpy(transferSyntax, ""); dcmStr(lLength, &buffer[lPos], transferSyntax); if (strcmp(transferSyntax, "1.2.840.10008.1.2.1") == 0) ; //default isExplicitVR=true; //d.isLittleEndian=true else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.50") == 0) { d.compressionScheme = kCompress50; //printMessage("Lossy JPEG: please decompress with Osirix or dcmdjpg. %s\n", transferSyntax); //d.imageStart = 1; //abort as invalid (imageStart MUST be >128) } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.51") == 0) { d.compressionScheme = kCompress50; } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.57") == 0) { //d.isCompressed = true; //https://www.medicalconnections.co.uk/kb/Transfer_Syntax should be SOF = 0xC3 d.compressionScheme = kCompressC3; //printMessage("Ancient JPEG-lossless (SOF type 0xc3): please check conversion\n"); } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.70") == 0) { d.compressionScheme = kCompressC3; } else if ((strcmp(transferSyntax, "1.2.840.10008.1.2.4.80") == 0) || (strcmp(transferSyntax, "1.2.840.10008.1.2.4.81") == 0)) { #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) d.compressionScheme = kCompressJPEGLS; #else printWarning("Unsupported transfer syntax '%s' (decode with 'dcmdjpls jpg.dcm raw.dcm' or 'gdcmconv -w jpg.dcm raw.dcm', or recompile dcm2niix with JPEGLS support)\n", transferSyntax); d.imageStart = 1; //abort as invalid (imageStart MUST be >128) #endif } else if (strcmp(transferSyntax, "1.3.46.670589.33.1.4.1") == 0) { d.compressionScheme = kCompressPMSCT_RLE1; //printMessage("Unsupported transfer syntax '%s' (decode with rle2img)\n",transferSyntax); //d.imageStart = 1; //abort as invalid (imageStart MUST be >128) } else if ((compressFlag != kCompressNone) && (strcmp(transferSyntax, "1.2.840.10008.1.2.4.90") == 0)) { d.compressionScheme = kCompressYes; //printMessage("JPEG2000 Lossless support is new: please validate conversion\n"); } else if ((strcmp(transferSyntax, "1.2.840.10008.1.2.1.99") == 0)) { //n.b. Deflate compression applied applies to the encoding of the **entire** DICOM Data Set, not just image data // see https://www.medicalconnections.co.uk/kb/Transfer-Syntax/ //#ifndef myDisableZLib //d.compressionScheme = kCompressDeflate; //#else printWarning("Unsupported transfer syntax '%s' (inflate files with 'dcmconv +te gz.dcm raw.dcm' or 'gdcmconv -w gz.dcm raw.dcm)'\n", transferSyntax); d.imageStart = 1; //abort as invalid (imageStart MUST be >128) //#endif } else if ((compressFlag != kCompressNone) && (strcmp(transferSyntax, "1.2.840.10008.1.2.4.91") == 0)) { d.compressionScheme = kCompressYes; //printMessage("JPEG2000 support is new: please validate conversion\n"); } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.5") == 0) d.compressionScheme = kCompressRLE; //run length else if (strcmp(transferSyntax, "1.2.840.10008.1.2.2") == 0) isSwitchToBigEndian = true; //isExplicitVR=true; else if (strcmp(transferSyntax, "1.2.840.10008.1.2") == 0) isSwitchToImplicitVR = true; //d.isLittleEndian=true else { if (lLength < 1) //"1.2.840.10008.1.2" printWarning("Missing transfer syntax: assuming default (1.2.840.10008.1.2)\n"); else { printWarning("Unsupported transfer syntax '%s' (see www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage)\n", transferSyntax); d.imageStart = 1; //abort as invalid (imageStart MUST be >128) } } break; } //{} provide scope for variable 'transferSyntax case kImplementationVersionName: { char impTxt[kDICOMStr]; dcmStr(lLength, &buffer[lPos], impTxt); int slen = (int)strlen(impTxt); if ((slen > 5) && (strstr(impTxt, "MATLAB") != NULL)) isMATLAB = true; if ((slen < 5) || (strstr(impTxt, "XA10A") == NULL)) break; d.isXA10A = true; break; } case kSourceApplicationEntityTitle: { char saeTxt[kDICOMStr]; dcmStr(lLength, &buffer[lPos], saeTxt); int slen = (int)strlen(saeTxt); if ((slen < 5) || (strstr(saeTxt, "oasis") == NULL)) break; d.isSegamiOasis = true; break; } case kDirectoryRecordSequence: { d.isRawDataStorage = true; break; } case kImageTypeTag: { bool is1st = strlen(d.imageType) == 0; dcmStr(lLength, &buffer[lPos], d.imageType, false); //<-distinguish spaces from pathdelim: [ORIGINAL\PHASE MAP\FFE] should return "PHASE MAP" not "PHASE_MAP" int slen; slen = (int)strlen(d.imageType); if (slen > 1) { for (int i = 0; i < slen; i++) if (d.imageType[i] == '\\') d.imageType[i] = '_'; } if (is1st) strcpy(imageType1st, d.imageType); if ((slen > 5) && strstr(d.imageType, "_MOCO_")) { //d.isDerived = true; //this would have 'i- y' skip MoCo images isMoCo = true; } if ((slen > 5) && strstr(d.imageType, "B0") && strstr(d.imageType, "MAP")) d.isRealIsPhaseMapHz = true; if ((slen > 5) && strstr(d.imageType, "_ADC_")) d.isDerived = true; if ((slen > 5) && strstr(d.imageType, "_TRACEW_")) d.isDerived = true; if ((slen > 5) && strstr(d.imageType, "_TRACE_")) d.isDerived = true; if ((slen > 5) && strstr(d.imageType, "_FA_")) d.isDerived = true; if ((slen > 12) && strstr(d.imageType, "_DIFFUSION_")) d.isDiffusion = true; //if (strcmp(transferSyntax, "ORIGINAL_PRIMARY_M_ND_MOSAIC") == 0) if ((slen > 5) && !strcmp(d.imageType + slen - 6, "MOSAIC")) isMosaic = true; //const char* prefix = "MOSAIC"; const char *pos = strstr(d.imageType, "MOSAIC"); //const char p = (const char *) d.imageType; //p = (const char) strstr(d.imageType, "MOSAIC"); //const char* p = strstr(d.imageType, "MOSAIC"); if (pos != NULL) isMosaic = true; //isNonImage 0008,0008 = DERIVED,CSAPARALLEL,POSDISP // sometime ComplexImageComponent 0008,9208 is missing - see ADNI data // attempt to detect non-images, see https://github.com/scitran/data/blob/a516fdc39d75a6e4ac75d0e179e18f3a5fc3c0af/scitran/data/medimg/dcm/mr/siemens.py //For Philips combinations see Table 3-28 Table 3-28: Valid combinations of Image Type applied values // http://incenter.medical.philips.com/doclib/enc/fetch/2000/4504/577242/577256/588723/5144873/5144488/5144982/DICOM_Conformance_Statement_Intera_R7%2c_R8_and_R9.pdf%3fnodeid%3d5147977%26vernum%3d-2 if ((slen > 3) && (strstr(d.imageType, "_R_") != NULL)) { d.isHasReal = true; isReal = true; } if ((slen > 3) && (strstr(d.imageType, "_M_") != NULL)) { d.isHasMagnitude = true; isMagnitude = true; } if ((slen > 3) && (strstr(d.imageType, "_I_") != NULL)) { d.isHasImaginary = true; isImaginary = true; } if ((slen > 3) && (strstr(d.imageType, "_P_") != NULL)) { d.isHasPhase = true; isPhase = true; } if ((slen > 6) && (strstr(d.imageType, "_REAL_") != NULL)) { d.isHasReal = true; isReal = true; } if ((slen > 11) && (strstr(d.imageType, "_MAGNITUDE_") != NULL)) { d.isHasMagnitude = true; isMagnitude = true; } if ((slen > 11) && (strstr(d.imageType, "_IMAGINARY_") != NULL)) { d.isHasImaginary = true; isImaginary = true; } if ((slen > 6) && (strstr(d.imageType, "PHASE") != NULL)) { d.isHasPhase = true; isPhase = true; } if ((slen > 6) && (strstr(d.imageType, "DERIVED") != NULL)) d.isDerived = true; //if((slen > 4) && (strstr(typestr, "DIS2D") != NULL) ) // d.isNonImage = true; //not mutually exclusive: possible for Philips enhanced DICOM to store BOTH magnitude and phase in the same image break; } case kAcquisitionDate: char acquisitionDateTxt[kDICOMStr]; dcmStr(lLength, &buffer[lPos], acquisitionDateTxt); d.acquisitionDate = atof(acquisitionDateTxt); break; case kAcquisitionDateTime: //char acquisitionDateTimeTxt[kDICOMStr]; dcmStr(lLength, &buffer[lPos], acquisitionDateTimeTxt); //printMessage("%s\n",acquisitionDateTimeTxt); break; case kStudyDate: dcmStr(lLength, &buffer[lPos], d.studyDate); if (((int)strlen(d.studyDate) > 7) && (strstr(d.studyDate, "19000101") != NULL)) isNeologica = true; break; case kModality: if (lLength < 2) break; if ((buffer[lPos] == 'C') && (toupper(buffer[lPos + 1]) == 'R')) d.modality = kMODALITY_CR; else if ((buffer[lPos] == 'C') && (toupper(buffer[lPos + 1]) == 'T')) d.modality = kMODALITY_CT; if ((buffer[lPos] == 'M') && (toupper(buffer[lPos + 1]) == 'R')) d.modality = kMODALITY_MR; if ((buffer[lPos] == 'P') && (toupper(buffer[lPos + 1]) == 'T')) d.modality = kMODALITY_PT; if ((buffer[lPos] == 'U') && (toupper(buffer[lPos + 1]) == 'S')) d.modality = kMODALITY_US; break; case kManufacturer: if (d.manufacturer == kMANUFACTURER_UNKNOWN) d.manufacturer = dcmStrManufacturer(lLength, &buffer[lPos]); volDiffusion.manufacturer = d.manufacturer; break; case kInstitutionName: dcmStr(lLength, &buffer[lPos], d.institutionName); break; case kInstitutionAddress: //VR is "ST": 1024 chars maximum dcmStr(lLength, &buffer[lPos], d.institutionAddress); break; case kReferringPhysicianName: dcmStr(lLength, &buffer[lPos], d.referringPhysicianName); 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 if (lLength < 2) break; //issue 256: Philips files report real ComplexImageComponent but Magnitude ImageType https://github.com/rordenlab/dcm2niix/issues/256 isPhase = false; isReal = false; isImaginary = false; isMagnitude = false; //see Table C.8-85 http://dicom.nema.org/medical/Dicom/2017c/output/chtml/part03/sect_C.8.13.3.html if ((buffer[lPos] == 'R') && (toupper(buffer[lPos + 1]) == 'E')) isReal = true; if ((buffer[lPos] == 'I') && (toupper(buffer[lPos + 1]) == 'M')) isImaginary = true; if ((buffer[lPos] == 'P') && (toupper(buffer[lPos + 1]) == 'H')) isPhase = true; if ((buffer[lPos] == 'M') && (toupper(buffer[lPos + 1]) == 'A')) isMagnitude = true; //not mutually exclusive: possible for Philips enhanced DICOM to store BOTH magnitude and phase in the same image if (isPhase) d.isHasPhase = true; if (isReal) d.isHasReal = true; if (isImaginary) d.isHasImaginary = true; if (isMagnitude) d.isHasMagnitude = true; break; case kAcquisitionContrast: char acqContrast[kDICOMStr]; 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 break; case kAcquisitionTime: { char acquisitionTimeTxt[kDICOMStr]; dcmStr(lLength, &buffer[lPos], acquisitionTimeTxt); d.acquisitionTime = atof(acquisitionTimeTxt); if (d.manufacturer != kMANUFACTURER_UIH) break; //UIH slice timing- do not use for Siemens as Siemens de-identification can corrupt this field https://github.com/rordenlab/dcm2niix/issues/236 d.CSA.sliceTiming[acquisitionTimesGE_UIH] = d.acquisitionTime; acquisitionTimesGE_UIH++; break; } case kSeriesTime: dcmStr(lLength, &buffer[lPos], seriesTimeTxt); break; case kStudyTime: if (strlen(d.studyTime) < 2) dcmStr(lLength, &buffer[lPos], d.studyTime); break; case kPatientName: dcmStr(lLength, &buffer[lPos], d.patientName); break; case kAnatomicalOrientationType: { char aotTxt[kDICOMStr]; //ftp://dicom.nema.org/MEDICAL/dicom/2015b/output/chtml/part03/sect_C.7.6.2.html#sect_C.7.6.2.1.1 dcmStr(lLength, &buffer[lPos], aotTxt); int slen = (int)strlen(aotTxt); if ((slen < 9) || (strstr(aotTxt, "QUADRUPED") == NULL)) break; printError("Anatomical Orientation Type (0010,2210) is QUADRUPED: rotate coordinates accordingly\n"); break; } case kDeidentificationMethod: { //issue 383 char anonTxt[kDICOMStr]; dcmStr(lLength, &buffer[lPos], anonTxt); int slen = (int)strlen(anonTxt); if ((slen < 10) || (strstr(anonTxt, "DICOMANON") == NULL)) break; isDICOMANON = true; printWarning("Matlab DICOMANON can scramble SeriesInstanceUID (0020,000e) and remove crucial data (see issue 383). \n"); break; } case kPatientID: if (strlen(d.patientID) > 1) break; dcmStr(lLength, &buffer[lPos], d.patientID); break; case kAccessionNumber: dcmStr(lLength, &buffer[lPos], d.accessionNumber); break; case kPatientBirthDate: dcmStr(lLength, &buffer[lPos], d.patientBirthDate); break; case kPatientSex: { //must be M,F,O: http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.2.html char patientSex = toupper(buffer[lPos]); if ((patientSex == 'M') || (patientSex == 'F') || (patientSex == 'O')) d.patientSex = patientSex; break; } case kPatientAge: dcmStr(lLength, &buffer[lPos], d.patientAge); break; case kPatientWeight: d.patientWeight = dcmStrFloat(lLength, &buffer[lPos]); break; case kStationName: dcmStr(lLength, &buffer[lPos], d.stationName); break; case kSeriesDescription: dcmStr(lLength, &buffer[lPos], d.seriesDescription); break; case kInstitutionalDepartmentName: dcmStr(lLength, &buffer[lPos], d.institutionalDepartmentName); break; case kManufacturersModelName: dcmStr(lLength, &buffer[lPos], d.manufacturersModelName); break; case kDerivationDescription: { //strcmp(transferSyntax, "1.2.840.10008.1.2") char derivationDescription[kDICOMStr]; dcmStr(lLength, &buffer[lPos], derivationDescription); //strcasecmp, strcmp if (strcasecmp(derivationDescription, "MEDCOM_RESAMPLED") == 0) d.isResampled = true; break; } case kDeviceSerialNumber: { dcmStr(lLength, &buffer[lPos], d.deviceSerialNumber); break; } case kSoftwareVersions: { dcmStr(lLength, &buffer[lPos], d.softwareVersions); int slen = (int)strlen(d.softwareVersions); if ((slen > 4) && (strstr(d.softwareVersions, "XA11") != NULL)) d.isXA10A = true; if ((slen > 4) && (strstr(d.softwareVersions, "XA20") != NULL)) d.isXA10A = true; if ((slen > 4) && (strstr(d.softwareVersions, "XA30") != NULL)) d.isXA10A = true; if ((slen < 5) || (strstr(d.softwareVersions, "XA10") == NULL)) break; d.isXA10A = true; break; } case kProtocolName: { dcmStr(lLength, &buffer[lPos], d.protocolName); break; } case kPatientOrient: dcmStr(lLength, &buffer[lPos], d.patientOrient); break; case kInversionRecovery: // CS [YES],[NO] if (lLength < 2) break; if (toupper(buffer[lPos]) == 'Y') d.isIR = true; break; case kSpoiling: // CS 0018,9016 if (lLength < 2) break; char sTxt[kDICOMStr]; dcmStr(lLength, &buffer[lPos], sTxt); if ((strstr(sTxt, "RF") != NULL) && (strstr(sTxt, "RF") != NULL)) d.spoiling = kSPOILING_RF_AND_GRADIENT; else if (strstr(sTxt, "RF") != NULL) d.spoiling = kSPOILING_RF; else if (strstr(sTxt, "GRADIENT") != NULL) d.spoiling = kSPOILING_GRADIENT; else if (strstr(sTxt, "NONE") != NULL) d.spoiling = kSPOILING_NONE; break; case kEchoPlanarPulseSequence: // CS [YES],[NO] if (lLength < 2) break; if (toupper(buffer[lPos]) == 'Y') d.isEPI = true; break; case kMagnetizationTransferAttribute: //'CS' 'ON_RESONANCE','OFF_RESONANCE','NONE' if (lLength < 2) break; //https://github.com/bids-standard/bids-specification/pull/681 if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos+1]) == 'F')) // OFF_RESONANCE d.mtState = 1; //TRUE if ((toupper(buffer[lPos]) == 'O') && (toupper(buffer[lPos+1]) == 'N')) // ON_RESONANCE and NONE d.mtState = 0; //FALSE if ((toupper(buffer[lPos]) == 'N') && (toupper(buffer[lPos+1]) == 'O')) // ON_RESONANCE and NONE d.mtState = 0; //FALSE break; case kRectilinearPhaseEncodeReordering: { //'CS' [REVERSE_LINEAR],[LINEAR],[CENTRIC],[REVERSE_CENTRIC] if (d.manufacturer != kMANUFACTURER_GE) break; //only found in GE software beginning with RX27 if (lLength < 2) break; if (toupper(buffer[lPos]) == 'L') d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; if (toupper(buffer[lPos]) == 'R') d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; break; } case kPartialFourierDirection: { //'CS' PHASE FREQUENCY SLICE_SELECT COMBINATION if (lLength < 2) break; if (toupper(buffer[lPos]) == 'P') d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_PHASE; if (toupper(buffer[lPos]) == 'F') d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_FREQUENCY; if (toupper(buffer[lPos]) == 'S') d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT; if (toupper(buffer[lPos]) == 'C') d.partialFourierDirection = kPARTIAL_FOURIER_DIRECTION_COMBINATION; break; } case kCardiacSynchronizationTechnique: if (toupper(buffer[lPos]) == 'P') isProspectiveSynced = true; break; case kParallelReductionFactorInPlane: if (d.manufacturer == kMANUFACTURER_SIEMENS) break; d.accelFactPE = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); break; case kAcquisitionDuration: //n.b. used differently by different vendors https://github.com/rordenlab/dcm2niix/issues/225 d.acquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); break; //in theory, 0018,9074 could provide XA10 slice time information, but scrambled by XA10 de-identification: better to use 0021,1104 //case kFrameAcquisitionDateTime: { // //(0018,9074) DT [20190621095516.140000] YYYYMMDDHHMMSS // //see https://github.com/rordenlab/dcm2niix/issues/303 // char dateTime[kDICOMStr]; // dcmStr(lLength, &buffer[lPos], dateTime); // printf("%s\tkFrameAcquisitionDateTime\n", dateTime); //} case kDiffusionDirectionality: { // 0018, 9075 set_directionality0018_9075(&volDiffusion, (&buffer[lPos])); if ((d.manufacturer != kMANUFACTURER_PHILIPS) || (lLength < 10)) break; char dir[kDICOMStr]; dcmStr(lLength, &buffer[lPos], dir); if (strcmp(dir, "ISOTROPIC") == 0) isPhilipsDerived = true; break; } case kParallelAcquisitionTechnique: //CS dcmStr(lLength, &buffer[lPos], d.parallelAcquisitionTechnique); break; case kInversionTimes: { //issue 380 if ((lLength < 8) || ((lLength % 8) != 0)) break; d.TI = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); break; } case kPartialFourier: //(0018,9081) CS [YES],[NO] if (lLength < 2) break; if (toupper(buffer[lPos]) == 'Y') d.isPartialFourier = true; break; case kMREchoSequence: if (d.manufacturer != kMANUFACTURER_BRUKER) break; if (sqDepth == 0) sqDepth = 1; //should not happen, in case faulty anonymization sqDepth00189114 = sqDepth - 1; break; case kMRAcquisitionPhaseEncodingStepsInPlane: d.phaseEncodingLines = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kNumberOfImagesInMosaic: if (d.manufacturer == kMANUFACTURER_SIEMENS) numberOfImagesInMosaic = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kSeriesPlaneGE: //SS 2=Axi, 4=Sag, 8=Cor, 16=Obl, 256=3plane if (d.manufacturer != kMANUFACTURER_GE) break; if (dcmInt(lLength, &buffer[lPos], d.isLittleEndian) == 256) d.isLocalizer = true; break; case kDwellTime: d.dwellTime = dcmStrInt(lLength, &buffer[lPos]); break; case kDiffusion_bValueSiemens: if (d.manufacturer != kMANUFACTURER_SIEMENS) break; //issue409 B0Philips = dcmStrInt(lLength, &buffer[lPos]); set_bVal(&volDiffusion, B0Philips); break; case kSliceTimeSiemens: { //Array of FD (64-bit double) if (d.manufacturer != kMANUFACTURER_SIEMENS) break; if ((lLength < 8) || ((lLength % 8) != 0)) break; int nSlicesTimes = lLength / 8; if (nSlicesTimes > kMaxEPI3D) break; d.CSA.mosaicSlices = nSlicesTimes; //printf(">>>> %d\n", nSlicesTimes); //issue 296: for images de-identified to remove readCSAImageHeader for (int z = 0; z < nSlicesTimes; z++) d.CSA.sliceTiming[z] = dcmFloatDouble(8, &buffer[lPos + (z * 8)], d.isLittleEndian); //for (int z = 0; z < nSlicesTimes; z++) // printf("%d>>>%g\n", z+1, d.CSA.sliceTiming[z]); checkSliceTimes(&d.CSA, nSlicesTimes, isVerbose, d.is3DAcq); //d.CSA.dtiV[0] = dcmStrInt(lLength, &buffer[lPos]); //d.CSA.numDti = 1; break; } case kDiffusionGradientDirectionSiemens: { if (d.manufacturer != kMANUFACTURER_SIEMENS) break; float v[4]; dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v); //printf(">>>%g %g %g\n", v[0], v[1], v[2]); d.CSA.dtiV[1] = v[0]; d.CSA.dtiV[2] = v[1]; d.CSA.dtiV[3] = v[2]; break; } case kNumberOfDiffusionDirectionGE: { if (d.manufacturer != kMANUFACTURER_GE) break; float f = dcmStrFloat(lLength, &buffer[lPos]); d.numberOfDiffusionDirectionGE = round(f); break; } case kLastScanLoc: d.lastScanLoc = dcmStrFloat(lLength, &buffer[lPos]); break; //GE bug: multiple echos can create identical instance numbers // in theory, one could detect as kRawDataRunNumberGE varies // sliceN of echoE will have the same value for all timepoints // this value does not appear indexed // different echoes record same echo time. // use multiEchoSortGEDICOM.py to salvage case kRawDataRunNumberGE: if (d.manufacturer != kMANUFACTURER_GE) break; d.rawDataRunNumber = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kMaxEchoNumGE: if (d.manufacturer != kMANUFACTURER_GE) break; d.maxEchoNumGE = round(dcmStrFloat(lLength, &buffer[lPos])); break; case kUserData12GE: { if (d.manufacturer != kMANUFACTURER_GE) break; userData12GE = round(dcmStrFloat(lLength, &buffer[lPos])); //printf("%d<<<<\n", userData12GE); break; } case kDiffusionDirectionGEX: if (d.manufacturer == kMANUFACTURER_GE) set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 0); break; case kDiffusionDirectionGEY: if (d.manufacturer == kMANUFACTURER_GE) set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 1); break; case kDiffusionDirectionGEZ: if (d.manufacturer == kMANUFACTURER_GE) set_diffusion_directionGE(&volDiffusion, lLength, (&buffer[lPos]), 2); break; 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) { d.epiVersionGE = kGE_EPI_PEPOLAR_FWD; //n.b. combine with 0019,10B3 } else if (strstr(epiStr, "epi2") != NULL) { d.epiVersionGE = kGE_EPI_EPI2; //-1 = not epi, 0 = epi, 1 = epiRT, 2 = epi2 } else if (strstr(epiStr, "epiRT") != NULL) { d.epiVersionGE = kGE_EPI_EPIRT; //-1 = not epi, 0 = epi, 1 = epiRT } else if (strstr(epiStr, "epi") != NULL) { d.epiVersionGE = kGE_EPI_EPI; //-1 = not epi, 0 = epi, 1 = epiRT } if (strcmp(epiStr, "3db0map") == 0) { isGEfieldMap = true; //issue501 } break; } case kInternalPulseSequenceNameGE: { //LO 'EPI'(gradient echo)/'EPI2'(spin echo): if (d.manufacturer != kMANUFACTURER_GE) break; char epiStr[kDICOMStr]; dcmStr(lLength, &buffer[lPos], epiStr); if ((d.epiVersionGE < kGE_EPI_PEPOLAR_FWD) && (strcmp(epiStr, "EPI") == 0)) { d.internalepiVersionGE = 1; //-1 = not EPI, 1 = EPI, 2 = EPI2 if (d.epiVersionGE != 1) { // 1 = epiRT by kEpiRTGroupDelayGE or kPulseSequenceNameGE d.epiVersionGE = 0; // 0 = epi (multi-phase epi) } } if (strcmp(epiStr, "EPI2") == 0) { d.internalepiVersionGE = 2; //-1 = not epi, 1 = EPI, 2 = EPI2 } if (strcmp(epiStr, "B0map") == 0) { isGEfieldMap = true; //issue501 } break; } case kBandwidthPerPixelPhaseEncode: d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); break; case kStudyInstanceUID: // 0020,000D dcmStr(lLength, &buffer[lPos], d.studyInstanceUID); break; case kSeriesInstanceUID: // 0020,000E dcmStr(lLength, &buffer[lPos], d.seriesInstanceUID); //printMessage(">>%s\n", d.seriesInstanceUID); d.seriesUidCrc = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID)); break; case kImagePositionPatient: { if (is2005140FSQ) { dcmMultiFloat(lLength, (char *)&buffer[lPos], 3, &patientPositionPrivate[0]); break; } patientPositionNum++; isAtFirstPatientPosition = true; //char dx[kDICOMStr]; //dcmStr(lLength, &buffer[lPos], dx); //printMessage("*%s*", dx); dcmMultiFloat(lLength, (char *)&buffer[lPos], 3, &patientPosition[0]); //slice position if (isnan(d.patientPosition[1])) { //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &d.patientPosition[0]); //slice position for (int k = 0; k < 4; k++) d.patientPosition[k] = patientPosition[k]; } else { //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, &d.patientPositionLast[0]); //slice direction for 4D for (int k = 0; k < 4; k++) d.patientPositionLast[k] = patientPosition[k]; if ((isFloatDiff(d.patientPositionLast[1], d.patientPosition[1])) || (isFloatDiff(d.patientPositionLast[2], d.patientPosition[2])) || (isFloatDiff(d.patientPositionLast[3], d.patientPosition[3]))) { isAtFirstPatientPosition = false; //this slice is not at position of 1st slice //if (d.patientPositionSequentialRepeats == 0) //this is the first slice with different position // d.patientPositionSequentialRepeats = patientPositionNum-1; } //if different position from 1st slice in file } //if not first slice in file set_isAtFirstPatientPosition_tvd(&volDiffusion, isAtFirstPatientPosition); //if (isAtFirstPatientPosition) numFirstPatientPosition++; if (isVerbose > 0) //verbose > 1 will report full DICOM tag printMessage(" Patient Position 0020,0032 (#,@,X,Y,Z)\t%d\t%ld\t%g\t%g\t%g\n", patientPositionNum, lPos, patientPosition[1], patientPosition[2], patientPosition[3]); if ((isOrient) && (nSliceMM < kMaxSlice2D)) { vec3 pos = setVec3(patientPosition[1], patientPosition[2], patientPosition[3]); sliceMM[nSliceMM] = dotProduct(pos, sliceV); if (sliceMM[nSliceMM] < minSliceMM) { minSliceMM = sliceMM[nSliceMM]; for (int k = 0; k < 4; k++) minPatientPosition[k] = patientPosition[k]; } if (sliceMM[nSliceMM] > maxSliceMM) { maxSliceMM = sliceMM[nSliceMM]; for (int k = 0; k < 4; k++) maxPatientPosition[k] = patientPosition[k]; } nSliceMM++; } break; } case kInPlanePhaseEncodingDirection: d.phaseEncodingRC = toupper(buffer[lPos]); //first character is either 'R'ow or 'C'ol break; case kSAR: d.SAR = dcmStrFloat(lLength, &buffer[lPos]); break; case kStudyID: dcmStr(lLength, &buffer[lPos], d.studyID); break; case kSeriesNum: d.seriesNum = dcmStrInt(lLength, &buffer[lPos]); break; case kAcquNum: d.acquNum = dcmStrInt(lLength, &buffer[lPos]); break; 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 if (d.imageNum < 1) d.imageNum = dcmStrInt(lLength, &buffer[lPos]); //Philips renames each image again in 2001,9000, which can lead to duplicates break; case kInStackPositionNumber: if ((d.manufacturer != kMANUFACTURER_CANON) && (d.manufacturer != kMANUFACTURER_HITACHI) && (d.manufacturer != kMANUFACTURER_UNKNOWN) && (d.manufacturer != kMANUFACTURER_PHILIPS) && (d.manufacturer != kMANUFACTURER_BRUKER)) break; inStackPositionNumber = dcmInt(4, &buffer[lPos], d.isLittleEndian); //if (inStackPositionNumber == 1) numInStackPositionNumber1 ++; //printf("<%d>\n",inStackPositionNumber); if (inStackPositionNumber > maxInStackPositionNumber) maxInStackPositionNumber = inStackPositionNumber; break; case kTemporalPositionIndex: temporalPositionIndex = dcmInt(4, &buffer[lPos], d.isLittleEndian); if (temporalPositionIndex > maxTemporalPositionIndex) maxTemporalPositionIndex = temporalPositionIndex; break; case kDimensionIndexPointer: dimensionIndexPointer[dimensionIndexPointerCounter++] = dcmAttributeTag(&buffer[lPos], d.isLittleEndian); break; case kFrameContentSequence: //if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //see https://github.com/rordenlab/dcm2niix/issues/241 if (sqDepth == 0) sqDepth = 1; //should not happen, in case faulty anonymization sqDepth00189114 = sqDepth - 1; break; case kTriggerDelayTime: { //0x0020+uint32_t(0x9153<< 16 ) //FD if (prefs->isIgnoreTriggerTimes) break; //issue499 if (d.manufacturer != kMANUFACTURER_PHILIPS) break; //if (isVerbose < 2) break; double trigger = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); d.triggerDelayTime = trigger; if (isSameFloatGE(d.triggerDelayTime, 0.0)) d.triggerDelayTime = 0.0; //double to single break; } case kDimensionIndexValues: { // kImageNum is not enough for 4D series from Philips 5.*. if (lLength < 4) break; nDimIndxVal = lLength / 4; if (nDimIndxVal > MAX_NUMBER_OF_DIMENSIONS) { printError("%d is too many dimensions. Only up to %d are supported\n", nDimIndxVal, MAX_NUMBER_OF_DIMENSIONS); nDimIndxVal = MAX_NUMBER_OF_DIMENSIONS; // Truncate } dcmMultiLongs(4 * nDimIndxVal, &buffer[lPos], nDimIndxVal, d.dimensionIndexValues, d.isLittleEndian); break; } case kPhotometricInterpretation: { char interp[kDICOMStr]; dcmStr(lLength, &buffer[lPos], interp); if (strcmp(interp, "PALETTE_COLOR") == 0) isPaletteColor = true; //printError("Photometric Interpretation 'PALETTE COLOR' not supported\n"); break; } case kPlanarRGB: d.isPlanarRGB = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kDim3: d.xyzDim[3] = dcmStrInt(lLength, &buffer[lPos]); numberOfFrames = d.xyzDim[3]; break; case kSamplesPerPixel: d.samplesPerPixel = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kDim2: d.xyzDim[2] = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kDim1: d.xyzDim[1] = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; //order is Row,Column e.g. YX case kXYSpacing: { float yx[3]; dcmMultiFloat(lLength, (char *)&buffer[lPos], 2, yx); d.xyzMM[1] = yx[2]; d.xyzMM[2] = yx[1]; break; } case kImageComments: dcmStr(lLength, &buffer[lPos], d.imageComments, true); break; //group 21: siemens //g21 case kPATModeText: { //e.g. Siemens iPAT x2 listed as "p2" char accelStr[kDICOMStr]; dcmStr(lLength, &buffer[lPos], accelStr); char *ptr; dcmStrDigitsOnlyKey('p', accelStr); //e.g. if "p2s4" return "2", if "s4" return "" d.accelFactPE = (float)strtof(accelStr, &ptr); if (*ptr != '\0') d.accelFactPE = 0.0; //between slice accel dcmStr(lLength, &buffer[lPos], accelStr); dcmStrDigitsOnlyKey('s', accelStr); //e.g. if "p2s4" return "4", if "p2" return "" multiBandFactor = (int)strtol(accelStr, &ptr, 10); if (*ptr != '\0') multiBandFactor = 0.0; //printMessage("p%gs%d\n", d.accelFactPE, multiBandFactor); break; } case kTimeAfterStart: //0021,1104 see https://github.com/rordenlab/dcm2niix/issues/303 // 0021,1104 6@159630 DS 4.635 // 0021,1104 2@161164 DS 0 if (d.manufacturer != kMANUFACTURER_SIEMENS) break; if (acquisitionTimesGE_UIH >= kMaxEPI3D) break; d.CSA.sliceTiming[acquisitionTimesGE_UIH] = dcmStrFloat(lLength, &buffer[lPos]); d.CSA.sliceTiming[acquisitionTimesGE_UIH] *= 1000.0; //convert sec to msec //printf("x\t%d\t%g\tkTimeAfterStart\n", acquisitionTimesGE_UIH, d.CSA.sliceTiming[acquisitionTimesGE_UIH]); acquisitionTimesGE_UIH++; break; case kPhaseEncodingDirectionPositiveSiemens: { if (d.manufacturer != kMANUFACTURER_SIEMENS) break; int ph = dcmStrInt(lLength, &buffer[lPos]); if (ph == 0) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; if (ph == 1) 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 kBandwidthPerPixelPhaseEncode21: if (d.manufacturer != kMANUFACTURER_SIEMENS) break; d.bandwidthPerPixelPhaseEncode = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); break; case kCoilElements: if (d.manufacturer != kMANUFACTURER_SIEMENS) break; dcmStr(lLength, &buffer[lPos], d.coilElements); break; //group 21: GE case kLocationsInAcquisitionGE: locationsInAcquisitionGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kRTIA_timer: if (d.manufacturer != kMANUFACTURER_GE) break; //see dicm2nii slice timing from 0021,105E DS RTIA_timer d.rtia_timerGE = dcmStrFloat(lLength, &buffer[lPos]); //RefAcqTimes = t/10; end % in ms //printf("%s\t%g\n", fname, d.rtia_timerGE); break; case kProtocolDataBlockGE: if (d.manufacturer != kMANUFACTURER_GE) break; d.protocolBlockLengthGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); d.protocolBlockStartGE = (int)lPos + (int)lFileOffset + 4; //printError("ProtocolDataBlockGE %d @ %d\n", d.protocolBlockLengthGE, d.protocolBlockStartGE); break; case kNumberOfExcitations: //FL if (d.manufacturer != kMANUFACTURER_GE) break; d.numberOfExcitations = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); break; case kNumberOfArms: //FL if (d.manufacturer != kMANUFACTURER_GE) break; d.numberOfArms = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); break; case kNumberOfPointsPerArm: //FL if (d.manufacturer != kMANUFACTURER_GE) break; d.numberOfPointsPerArm = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); break; case kDoseCalibrationFactor: d.doseCalibrationFactor = dcmStrFloat(lLength, &buffer[lPos]); break; case kPETImageIndex: PETImageIndex = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kPEDirectionDisplayedUIH: if (d.manufacturer != kMANUFACTURER_UIH) break; dcmStr(lLength, &buffer[lPos], d.phaseEncodingDirectionDisplayedUIH); break; case kDiffusion_bValueUIH: { if (d.manufacturer != kMANUFACTURER_UIH) break; float v[4]; dcmMultiFloatDouble(lLength, &buffer[lPos], 1, v, d.isLittleEndian); B0Philips = v[0]; set_bVal(&volDiffusion, v[0]); break; } case kParallelInformationUIH: { //SENSE factor (0065,100d) SH [F:2S] if (d.manufacturer != kMANUFACTURER_UIH) break; char accelStr[kDICOMStr]; dcmStr(lLength, &buffer[lPos], accelStr); //char *ptr; dcmStrDigitsDotOnlyKey(':', accelStr); //e.g. if "p2s4" return "2", if "s4" return "" d.accelFactPE = atof(accelStr); break; } case kNumberOfImagesInGridUIH: if (d.manufacturer != kMANUFACTURER_UIH) break; d.numberOfImagesInGridUIH = dcmStrFloat(lLength, &buffer[lPos]); d.CSA.mosaicSlices = d.numberOfImagesInGridUIH; break; case kPhaseEncodingDirectionPositiveUIH: { if (d.manufacturer != kMANUFACTURER_UIH) break; int ph = dcmStrInt(lLength, &buffer[lPos]); if (ph == 1) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; if (ph == 0) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; break; } case kDiffusionGradientDirectionUIH: { if (d.manufacturer != kMANUFACTURER_UIH) break; float v[4]; dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v); //printf(">>>%g %g %g\n", v[0], v[1], v[2]); d.CSA.dtiV[1] = v[0]; d.CSA.dtiV[2] = v[1]; d.CSA.dtiV[3] = v[2]; //vRLPhilips = v[0]; //vAPPhilips = v[1]; //vFHPhilips = v[2]; break; } case kBitsAllocated: d.bitsAllocated = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kBitsStored: d.bitsStored = 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; case kPixelPaddingValue: // According to the DICOM standard, this can be either unsigned (US) or signed (SS). Currently this // is used only in nii_saveNII3Dtilt() which only allows DT_INT16, so treat it as signed. d.pixelPaddingValue = (float)(short)dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kFloatPixelPaddingValue: d.pixelPaddingValue = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); break; case kTR: d.TR = dcmStrFloat(lLength, &buffer[lPos]); break; case kTE: TE = dcmStrFloat(lLength, &buffer[lPos]); if (d.TE <= 0.0) d.TE = TE; break; case kNumberOfAverages: d.numberOfAverages = dcmStrFloat(lLength, &buffer[lPos]); break; case kImagingFrequency: d.imagingFrequency = dcmStrFloat(lLength, &buffer[lPos]); break; case kTriggerTime: { if (prefs->isIgnoreTriggerTimes) break; //issue499 //untested method to detect slice timing for GE PSD “epi” with multiphase option // will not work for current PSD “epiRT” (BrainWave RT, fMRI/DTI package provided by Medical Numerics) if ((d.manufacturer != kMANUFACTURER_GE) && (d.manufacturer != kMANUFACTURER_PHILIPS)) break; //issue384 d.triggerDelayTime = dcmStrFloat(lLength, &buffer[lPos]); //???? issue 336 if (d.manufacturer != kMANUFACTURER_GE) break; d.CSA.sliceTiming[acquisitionTimesGE_UIH] = d.triggerDelayTime; //printf("%g\n", d.CSA.sliceTiming[acquisitionTimesGE_UIH]); acquisitionTimesGE_UIH++; break; } case kRadionuclideTotalDose: d.radionuclideTotalDose = dcmStrFloat(lLength, &buffer[lPos]); break; case kEffectiveTE: { TE = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); if (d.TE <= 0.0) d.TE = TE; break; } case kTI: d.TI = dcmStrFloat(lLength, &buffer[lPos]); break; case kEchoNum: d.echoNum = dcmStrInt(lLength, &buffer[lPos]); break; case kMagneticFieldStrength: d.fieldStrength = dcmStrFloat(lLength, &buffer[lPos]); break; case kZSpacing: d.zSpacing = dcmStrFloat(lLength, &buffer[lPos]); break; case kPhaseEncodingSteps: d.phaseEncodingSteps = dcmStrInt(lLength, &buffer[lPos]); break; case kEchoTrainLength: d.echoTrainLength = dcmStrInt(lLength, &buffer[lPos]); break; case kPercentSampling: d.percentSampling = dcmStrFloat(lLength, &buffer[lPos]); case kPhaseFieldofView: d.phaseFieldofView = dcmStrFloat(lLength, &buffer[lPos]); break; case kPixelBandwidth: d.pixelBandwidth = dcmStrFloat(lLength, &buffer[lPos]); //printWarning(" PixelBandwidth (0018,0095)====> %g @%d\n", d.pixelBandwidth, lPos); break; case kAcquisitionMatrix: if (lLength == 8) { uint16_t acquisitionMatrix[4]; dcmMultiShorts(lLength, &buffer[lPos], 4, &acquisitionMatrix[0], d.isLittleEndian); //slice position //phaseEncodingLines stored in either image columns or rows if (acquisitionMatrix[3] > 0) d.phaseEncodingLines = acquisitionMatrix[3]; if (acquisitionMatrix[2] > 0) d.phaseEncodingLines = acquisitionMatrix[2]; if (acquisitionMatrix[1] > 0) frequencyRows = acquisitionMatrix[1]; if (acquisitionMatrix[0] > 0) frequencyRows = acquisitionMatrix[0]; } break; case kFlipAngle: d.flipAngle = dcmStrFloat(lLength, &buffer[lPos]); break; case kRadionuclideHalfLife: d.radionuclideHalfLife = dcmStrFloat(lLength, &buffer[lPos]); break; case kRadionuclidePositronFraction: d.radionuclidePositronFraction = dcmStrFloat(lLength, &buffer[lPos]); break; case kGantryTilt: d.gantryTilt = dcmStrFloat(lLength, &buffer[lPos]); break; case kXRayTimeMS: //IS d.exposureTimeMs = dcmStrInt(lLength, &buffer[lPos]);; break; case kXRayTubeCurrent: //IS d.xRayTubeCurrent = dcmStrInt(lLength, &buffer[lPos]);; break; case kXRayExposure: //CTs do not have echo times, we use this field to detect different exposures: https://github.com/neurolabusc/dcm2niix/pull/48 if (d.TE == 0) { // for CT we will use exposure (0018,1152) whereas for MR we use echo time (0018,0081) d.isXRay = true; d.TE = dcmStrFloat(lLength, &buffer[lPos]); } break; case kConvolutionKernel: //CS dcmStr(lLength, &buffer[lPos], d.convolutionKernel); break; case kFrameDuration: d.frameDuration = dcmStrInt(lLength, &buffer[lPos]); break; case kReceiveCoilName: dcmStr(lLength, &buffer[lPos], d.coilName); if (strlen(d.coilName) < 1) break; d.coilCrc = mz_crc32X((unsigned char *)&d.coilName, strlen(d.coilName)); break; case kSlope: d.intenScale = dcmStrFloat(lLength, &buffer[lPos]); break; //case kSpectroscopyDataPointColumns : // d.xyzDim[4] = dcmInt(4,&buffer[lPos],d.isLittleEndian); // break; case kPhilipsSlope: if ((lLength == 4) && (d.manufacturer == kMANUFACTURER_PHILIPS)) d.intenScalePhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); break; case kMRImageDynamicScanBeginTime: { //FL if (lLength != 4) break; MRImageDynamicScanBeginTime = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); if (MRImageDynamicScanBeginTime < minDynamicScanBeginTime) minDynamicScanBeginTime = MRImageDynamicScanBeginTime; if (MRImageDynamicScanBeginTime > maxDynamicScanBeginTime) maxDynamicScanBeginTime = MRImageDynamicScanBeginTime; break; } case kIntercept: d.intenIntercept = dcmStrFloat(lLength, &buffer[lPos]); break; case kRadiopharmaceutical: dcmStr(lLength, &buffer[lPos], d.radiopharmaceutical); break; case kZThick: d.xyzMM[3] = dcmStrFloat(lLength, &buffer[lPos]); d.zThick = d.xyzMM[3]; break; case kAcquisitionMatrixText21: //fall through to kAcquisitionMatrixText case kAcquisitionMatrixText: { if (d.manufacturer == kMANUFACTURER_SIEMENS) { char matStr[kDICOMStr]; dcmStr(lLength, &buffer[lPos], matStr); char *pPosition = strchr(matStr, 'I'); if (pPosition != NULL) isInterpolated = true; } break; } case kImageOrientationText: //issue522 if (d.manufacturer != kMANUFACTURER_SIEMENS) break; dcmStr(lLength, &buffer[lPos], d.imageOrientationText); break; case kCoilSiemens: { if (d.manufacturer == kMANUFACTURER_SIEMENS) { //see if image from single coil "H12" or an array "HEA;HEP" //char coilStr[kDICOMStr]; //int coilNum; dcmStr(lLength, &buffer[lPos], d.coilName); if (strlen(d.coilName) < 1) break; //printf("-->%s\n", coilStr); //d.coilName = coilStr; //if (coilStr[0] == 'C') break; //kludge as Nova 32-channel defaults to "C:A32" https://github.com/rordenlab/dcm2niix/issues/187 //char *ptr; //dcmStrDigitsOnly(coilStr); //coilNum = (int)strtol(coilStr, &ptr, 10); d.coilCrc = mz_crc32X((unsigned char *)&d.coilName, strlen(d.coilName)); //printf("%d:%s\n", d.coilNum, coilStr); //if (*ptr != '\0') // d.coilNum = 0; } break; } case kImaPATModeText: { //e.g. Siemens iPAT x2 listed as "p2" char accelStr[kDICOMStr]; dcmStr(lLength, &buffer[lPos], accelStr); char *ptr; dcmStrDigitsOnlyKey('p', accelStr); //e.g. if "p2s4" return "2", if "s4" return "" d.accelFactPE = (float)strtof(accelStr, &ptr); if (*ptr != '\0') d.accelFactPE = 0.0; //between slice accel dcmStr(lLength, &buffer[lPos], accelStr); dcmStrDigitsOnlyKey('s', accelStr); //e.g. if "p2s4" return "4", if "p2" return "" multiBandFactor = (int)strtol(accelStr, &ptr, 10); if (*ptr != '\0') multiBandFactor = 0.0; break; } case kLocationsInAcquisition: d.locationsInAcquisition = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kUnitsPT: //CS dcmStr(lLength, &buffer[lPos], d.unitsPT); break; case kAttenuationCorrectionMethod: //LO dcmStr(lLength, &buffer[lPos], d.attenuationCorrectionMethod); break; case kDecayCorrection: //CS dcmStr(lLength, &buffer[lPos], d.decayCorrection); break; case kReconstructionMethod: //LO dcmStr(lLength, &buffer[lPos], d.reconstructionMethod); break; case kDecayFactor: d.decayFactor = dcmStrFloat(lLength, &buffer[lPos]); break; case kIconImageSequence: isIconImageSequence = true; if (sqDepthIcon < 0) sqDepthIcon = sqDepth; break; //case kMRSeriesAcquisitionNumber: // 0x2001+(0x107B << 16 ) //IS // mRSeriesAcquisitionNumber = dcmStrInt(lLength, &buffer[lPos]); // break; case kNumberOfDynamicScans: //~d.numberOfDynamicScans = dcmStrInt(lLength, &buffer[lPos]); numberOfDynamicScans = dcmStrInt(lLength, &buffer[lPos]); break; case kMRAcquisitionType: //detect 3D acquisition: we can reorient these without worrying about slice time correct or BVEC/BVAL orientation if (lLength > 1) d.is2DAcq = (buffer[lPos] == '2') && (toupper(buffer[lPos + 1]) == 'D'); if (lLength > 1) d.is3DAcq = (buffer[lPos] == '3') && (toupper(buffer[lPos + 1]) == 'D'); //dcmStr(lLength, &buffer[lPos], d.mrAcquisitionType); break; case kBodyPartExamined: { dcmStr(lLength, &buffer[lPos], d.bodyPartExamined); break; } case kScanningSequence: { dcmStr(lLength, &buffer[lPos], d.scanningSequence); //According to the DICOM standard 0018,9018 is REQUIRED for EPI raw data // http://dicom.nema.org/MEDICAL/Dicom/2015c/output/chtml/part03/sect_C.8.13.4.html //In practice, this is not the case for all vendors //Fortunately, the combination of 0018,0020 and 0018,9018 appears to reliably detect EPI data //Siemens (pre-XA) omits 0018,9018, but reports [EP] for 0018,0020 (regardless of SE/GR) //Siemens (XA) reports 0018,9018 but omits 0018,0020 //Canon/Toshiba omits 0018,9018, but reports [SE\EP];[GR\EP] for 0018,0020 //GE omits 0018,9018, but reports [EP\GR];[EP\SE] for 0018,0020 //Philips reports 0018,9018, but reports [SE];[GR] for 0018,0020 if ((lLength > 1) && (strstr(d.scanningSequence, "IR") != NULL)) d.isIR = true; if ((lLength > 1) && (strstr(d.scanningSequence, "EP") != NULL)) d.isEPI = true; break; //warp } case kSequenceVariant21: if (d.manufacturer != kMANUFACTURER_SIEMENS) break; //see GE dataset in dcm_qa_nih //fall through... case kSequenceVariant: { dcmStr(lLength, &buffer[lPos], d.sequenceVariant); break; } case kScanOptions: dcmStr(lLength, &buffer[lPos], d.scanOptions); if ((lLength > 1) && (strstr(d.scanOptions, "PFF") != NULL)) d.isPartialFourier = true; //e.g. GE does not populate (0018,9081) break; case kSequenceName: { dcmStr(lLength, &buffer[lPos], d.sequenceName); break; } case kMRfMRIStatusIndicationPhilips: {//fmri volume number if (d.manufacturer != kMANUFACTURER_PHILIPS) break; int i = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); if (i > 0) //only if positive value, see Magdeburg_2014 sample data from dcm_qa_philips Philips MR 51.0 volumeNumber = i; break; } case kMRAcquisitionTypePhilips: //kMRAcquisitionType if (lLength > 1) d.is3DAcq = (buffer[lPos] == '3') && (toupper(buffer[lPos + 1]) == 'D'); break; case kAngulationRL: d.angulation[1] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); break; case kAngulationAP: d.angulation[2] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); break; case kAngulationFH: d.angulation[3] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); break; case kMRStackOffcentreRL: d.stackOffcentre[1] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); break; case kMRStackOffcentreAP: d.stackOffcentre[2] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); break; case kMRStackOffcentreFH: d.stackOffcentre[3] = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); break; case kSliceOrient: { char orientStr[kDICOMStr]; orientStr[0] = 'X'; //avoid compiler warning: orientStr filled by dcmStr dcmStr(lLength, &buffer[lPos], orientStr); if (toupper(orientStr[0]) == 'S') d.sliceOrient = kSliceOrientSag; //sagittal else if (toupper(orientStr[0]) == 'C') d.sliceOrient = kSliceOrientCor; //coronal else d.sliceOrient = kSliceOrientTra; //transverse (axial) break; } case kElscintIcon: printWarning("Assuming icon SQ 07a3,10ce.\n"); isIconImageSequence = true; if (sqDepthIcon < 0) sqDepthIcon = sqDepth; break; case kPMSCT_RLE1: //https://groups.google.com/forum/#!topic/comp.protocols.dicom/8HuP_aNy9Pc //https://discourse.slicer.org/t/fail-to-load-pet-ct-gemini/8158/3 // d.compressionScheme = kCompressPMSCT_RLE1; //force RLE if (d.compressionScheme != kCompressPMSCT_RLE1) break; d.imageStart = (int)lPos + (int)lFileOffset; d.imageBytes = lLength; break; case kPrivateCreator: { if (d.manufacturer != kMANUFACTURER_UNKNOWN) break; d.manufacturer = dcmStrManufacturer(lLength, &buffer[lPos]); volDiffusion.manufacturer = d.manufacturer; break; } case kDiffusion_bValuePhilips: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; B0Philips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); set_bVal(&volDiffusion, B0Philips); break; case kPhaseNumber: //IS issue529 if (d.manufacturer != kMANUFACTURER_PHILIPS) break; d.phaseNumber = dcmStrInt(lLength, &buffer[lPos]); //see dcm_qa_philips_asl break; case kCardiacSync: //CS [TRIGGERED],[NO] if (lLength < 2) break; if (toupper(buffer[lPos]) != 'N') isTriggerSynced = true; break; case kDiffusion_bValue: // 0018,9087 if (d.manufacturer == kMANUFACTURER_UNKNOWN) { d.manufacturer = kMANUFACTURER_PHILIPS; printWarning("Found 0018,9087 but manufacturer (0008,0070) unknown: assuming Philips.\n"); } // Note that this is ahead of kImagePositionPatient (0020,0032), so // isAtFirstPatientPosition is not necessarily set yet. // Philips uses this tag too, at least as of 5.1, but they also // use kDiffusionBFactor (see above), and we do not want to // double count. More importantly, with Philips this tag // (sometimes?) gets repeated in a nested sequence with the // value *unset*! // GE started using this tag in 27, and annoyingly, NOT including // the b value if it is 0 for the slice. //if((d.manufacturer != kMANUFACTURER_PHILIPS) || !is2005140FSQ){ // d.CSA.numDti++; // if (d.CSA.numDti == 2) { //First time we know that this is a 4D DTI dataset // dti4D->S[0].V[0] = d.CSA.dtiV[0]; // dti4D->S[0].V[1] = d.CSA.dtiV[1]; // dti4D->S[0].V[2] = d.CSA.dtiV[2]; // dti4D->S[0].V[3] = d.CSA.dtiV[3]; //} B0Philips = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); //d.CSA.dtiV[0] = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); set_bVal(&volDiffusion, dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian)); // if ((d.CSA.numDti > 1) && (d.CSA.numDti < kMaxDTI4D)) // dti4D->S[d.CSA.numDti-1].V[0] = d.CSA.dtiV[0]; //} break; case kDiffusionOrientation: // 0018, 9089 // Note that this is ahead of kImagePositionPatient (0020,0032), so // isAtFirstPatientPosition is not necessarily set yet. // Philips uses this tag too, at least as of 5.1, but they also // use kDiffusionDirectionRL, etc., and we do not want to double // count. More importantly, with Philips this tag (sometimes?) // gets repeated in a nested sequence with the value *unset*! // if (((d.manufacturer == kMANUFACTURER_SIEMENS) || // ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ)) && // (isAtFirstPatientPosition || isnan(d.patientPosition[1]))) //if((d.manufacturer == kMANUFACTURER_SIEMENS) || ((d.manufacturer == kMANUFACTURER_PHILIPS) && !is2005140FSQ)) if ((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON) || (d.manufacturer == kMANUFACTURER_HITACHI) || (d.manufacturer == kMANUFACTURER_SIEMENS) || (d.manufacturer == kMANUFACTURER_PHILIPS)) { //for kMANUFACTURER_HITACHI see https://nciphub.org/groups/qindicom/wiki/StandardcompliantenhancedmultiframeDWI float v[4]; //dcmMultiFloat(lLength, (char*)&buffer[lPos], 3, v); //dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); dcmMultiFloatDouble(lLength, &buffer[lPos], 3, v, d.isLittleEndian); vRLPhilips = v[0]; vAPPhilips = v[1]; vFHPhilips = v[2]; //printMessage("><>< 0018,9089:\t%g\t%g\t%g\n", v[0], v[1], v[2]); //https://github.com/rordenlab/dcm2niix/issues/256 //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); } break; case kImagingFrequency2: d.imagingFrequency = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); break; case kParallelReductionFactorOutOfPlane: if (d.manufacturer == kMANUFACTURER_SIEMENS) break; d.accelFactOOP = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); break; //case kFrameAcquisitionDuration : // frameAcquisitionDuration = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); //issue369 // break; case kDiffusionBValueXX: { if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); set_bMatrix(&volDiffusion, bMat, 0); break; } case kDiffusionBValueXY: { if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); set_bMatrix(&volDiffusion, bMat, 1); break; } case kDiffusionBValueXZ: { if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); set_bMatrix(&volDiffusion, bMat, 2); break; } case kDiffusionBValueYY: { if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); set_bMatrix(&volDiffusion, bMat, 3); break; } case kDiffusionBValueYZ: { if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); set_bMatrix(&volDiffusion, bMat, 4); break; } case kDiffusionBValueZZ: { if (!(d.manufacturer == kMANUFACTURER_BRUKER)) break; //other manufacturers provide bvec directly, rather than bmatrix double bMat = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); set_bMatrix(&volDiffusion, bMat, 5); d.isVectorFromBMatrix = true; break; } case kSliceNumberMrPhilips: { if (d.manufacturer != kMANUFACTURER_PHILIPS) break; sliceNumberMrPhilips = dcmStrInt(lLength, &buffer[lPos]); int sliceNumber = sliceNumberMrPhilips; //use public patientPosition if it exists - fall back to private patient position if ((sliceNumber == 1) && (!isnan(patientPosition[1]))) { for (int k = 0; k < 4; k++) patientPositionStartPhilips[k] = patientPosition[k]; } else if ((sliceNumber == 1) && (!isnan(patientPositionPrivate[1]))) { for (int k = 0; k < 4; k++) patientPositionStartPhilips[k] = patientPositionPrivate[k]; } if ((sliceNumber == locationsInAcquisitionPhilips) && (!isnan(patientPosition[1]))) { for (int k = 0; k < 4; k++) patientPositionEndPhilips[k] = patientPosition[k]; } else if ((sliceNumber == locationsInAcquisitionPhilips) && (!isnan(patientPositionPrivate[1]))) { for (int k = 0; k < 4; k++) patientPositionEndPhilips[k] = patientPositionPrivate[k]; } break; } case kEPIFactorPhilips: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; echoTrainLengthPhil = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kPrepulseDelay: //FL if (d.manufacturer != kMANUFACTURER_PHILIPS) break; d.TI = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); break; case kPrepulseType: //CS [INV] if (d.manufacturer != kMANUFACTURER_PHILIPS) break; if (lLength < 3) break; if ((toupper(buffer[lPos]) != 'I') && (toupper(buffer[lPos + 1]) != 'N') && (toupper(buffer[lPos + 2]) != 'V')) d.isIR = true; break; case kRespirationSync: //CS [TRIGGERED],[NO] if (lLength < 2) break; if (toupper(buffer[lPos]) != 'N') isTriggerSynced = true; break; case kNumberOfSlicesMrPhilips: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; locationsInAcquisitionPhilips = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); //printMessage("====> locationsInAcquisitionPhilips\t%d\n", locationsInAcquisitionPhilips); break; case kPartialMatrixScannedPhilips: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; if (lLength < 2) break; if (toupper(buffer[lPos]) == 'Y') d.isPartialFourier = true; break; case kWaterFatShiftPhilips: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; d.waterFatShift = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); break; case kDiffusionDirectionRL: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; vRLPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); set_diffusion_directionPhilips(&volDiffusion, vRLPhilips, 0); break; case kDiffusionDirectionAP: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; vAPPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); set_diffusion_directionPhilips(&volDiffusion, vAPPhilips, 1); break; case kDiffusionDirectionFH: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; vFHPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); set_diffusion_directionPhilips(&volDiffusion, vFHPhilips, 2); break; case kPrivatePerFrameSq: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; //if ((vr[0] == 'S') && (vr[1] == 'Q')) break; //if (!is2005140FSQwarned) // printWarning("expected VR of 2005,140F to be 'SQ' (prior DICOM->DICOM conversion error?)\n"); is2005140FSQ = true; //is2005140FSQwarned = true; break; case kMRImageGradientOrientationNumber : if (d.manufacturer == kMANUFACTURER_PHILIPS) gradientOrientationNumberPhilips = dcmStrInt(lLength, &buffer[lPos]); break; case kMRImageLabelType : //CS ??? LBL CTL if ((d.manufacturer != kMANUFACTURER_PHILIPS) || (lLength < 2)) break; //TODO529: issue529 for ASL LBL/CTL "LABEL" //if (toupper(buffer[lPos]) == 'L') isLabel = true; if (toupper(buffer[lPos]) == 'L') d.aslFlags = kASL_FLAG_PHILIPS_LABEL; if (toupper(buffer[lPos]) == 'C') d.aslFlags = kASL_FLAG_PHILIPS_CONTROL; break; case kMRImageDiffBValueNumber: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; philMRImageDiffBValueNumber = dcmStrInt(lLength, &buffer[lPos]); break; case kMRImageDiffVolumeNumber: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; philMRImageDiffVolumeNumber = dcmStrInt(lLength, &buffer[lPos]); 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.imageStart = (int)lPos + (int)lFileOffset; break; case kCSAImageHeaderInfo: if ((lPos + lLength) > fileLen) break; readCSAImageHeader(&buffer[lPos], lLength, &d.CSA, isVerbose, d.is3DAcq); //, dti4D); if (!d.isHasPhase) d.isHasPhase = d.CSA.isPhaseMap; break; case kCSASeriesHeaderInfo: if ((lPos + lLength) > fileLen) break; d.CSA.SeriesHeader_offset = (int)lPos; d.CSA.SeriesHeader_length = lLength; break; case kRealWorldIntercept: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; d.RWVIntercept = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); if (isSameFloat(0.0, d.intenIntercept)) //give precedence to standard value d.intenIntercept = d.RWVIntercept; break; case kRealWorldSlope: if (d.manufacturer != kMANUFACTURER_PHILIPS) break; d.RWVScale = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); if (d.RWVScale > 1.0E38) d.RWVScale = 0.0; else if (isSameFloat(1.0, d.intenScale)) //give precedence to standard value d.intenScale = d.RWVScale; break; case kUserDefineDataGE: { //0043,102A if ((d.manufacturer != kMANUFACTURER_GE) || (lLength < 128)) break; #define MY_DEBUG_GE // <- uncomment this to use following code to infer GE phase encoding direction #ifdef MY_DEBUG_GE int isVerboseX = isVerbose; //for debugging only - in standard release we will enable user defined "isVerbose" //int isVerboseX = 2; if (isVerboseX > 1) printMessage(" UserDefineDataGE file offset/length %ld %u\n", lFileOffset + lPos, lLength); if (lLength < 916) { //minimum size is hdr_offset=0, read 0x0394 printMessage(" GE header too small to be valid (A)\n"); break; } //debug code to export binary data /* char str[kDICOMStr]; sprintf(str, "%s_ge.bin",fname); FILE *pFile = fopen(str, "wb"); fwrite(&buffer[lPos], 1, lLength, pFile); fclose (pFile); */ if ((size_t)(lPos + lLength) > MaxBufferSz) { //we could re-read the buffer in this case, however in practice GE headers are concise so we never see this issue printMessage(" GE header overflows buffer\n"); break; } uint16_t hdr_offset = dcmInt(2, &buffer[lPos + 24], true); if (isVerboseX > 1) printMessage(" header offset: %d\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; } //size_t hdr = lPos+hdr_offset; float version = dcmFloat(4, &buffer[lPos + hdr_offset], true); if (isVerboseX > 1) printMessage(" version %g\n", version); if (version < 5.0 || version > 40.0) { //printMessage(" GE header file format incorrect %g\n", version); break; } //char const *hdr = &buffer[lPos + hdr_offset]; char *hdr = (char *)&buffer[lPos + hdr_offset]; int epi_chk_off = 0x003a; int pepolar_off = 0x0030; int kydir_off = 0x0394; if (version >= 25.002) { hdr += 0x004c; kydir_off -= 0x008c; } //int seqOrInter =dcmInt(2,(unsigned char*)(hdr + pepolar_off-638),true); //int seqOrInter2 =dcmInt(2,(unsigned char*)(hdr + kydir_off-638),true); //printf("%d %d<<<\n", seqOrInter,seqOrInter2); //check if EPI if (true) { //int check = *(short const *)(hdr + epi_chk_off) & 0x800; int check = dcmInt(2, (unsigned char *)hdr + epi_chk_off, true) & 0x800; if (check == 0) { if (isVerboseX > 1) printMessage("%s: Warning: Data is not EPI\n", fname); break; } } //Check for PE polarity // int flag1 = *(short const *)(hdr + pepolar_off) & 0x0004; //Check for ky direction (view order) // int flag2 = *(int const *)(hdr + kydir_off); int phasePolarityFlag = dcmInt(2, (unsigned char *)hdr + pepolar_off, true) & 0x0004; //Check for ky direction (view order) int sliceOrderFlag = dcmInt(2, (unsigned char *)hdr + kydir_off, true); if (isVerboseX > 1) printMessage(" GE phasePolarity/sliceOrder flags %d %d\n", phasePolarityFlag, sliceOrderFlag); if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_FLIPPED) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP) { //https://cfmriweb.ucsd.edu/Howto/3T/operatingtips.html if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; else d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; } //if (sliceOrderFlag == kGE_SLICE_ORDER_TOP_DOWN) // d.sliceOrderGE = kGE_SLICE_ORDER_TOP_DOWN; //if (sliceOrderFlag == kGE_SLICE_ORDER_BOTTOM_UP) // d.sliceOrderGE = kGE_SLICE_ORDER_BOTTOM_UP; #endif break; } case kEffectiveEchoSpacingGE: if (d.manufacturer == kMANUFACTURER_GE) d.effectiveEchoSpacingGE = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case kImageTypeGE: { //0/1/2/3 for magnitude/phase/real/imaginary if (d.manufacturer != kMANUFACTURER_GE) break; int dt = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); if (dt == 0) d.isHasMagnitude = true; if (dt == 1) d.isHasPhase = true; if (dt == 2) d.isHasReal = true; if (dt == 3) d.isHasImaginary = true; break; } case kDiffusion_bValueGE: if (d.manufacturer == kMANUFACTURER_GE) { d.CSA.dtiV[0] = (float)set_bValGE(&volDiffusion, lLength, &buffer[lPos]); d.CSA.numDti = 1; } break; case kEpiRTGroupDelayGE: //FL if (d.manufacturer != kMANUFACTURER_GE) break; d.groupDelay = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); d.groupDelay *= 1000.0; //sec -> ms // If kEpiRTGroupDelayGE (0043,107C) exists, epiRT d.epiVersionGE = 1; //-1 = not epi, 0 = epi, 1 = epiRT break; case kAssetRFactorsGE: { //DS issue427GE if (d.manufacturer != kMANUFACTURER_GE) break; float PhaseSlice[3]; dcmMultiFloat(lLength, (char *)&buffer[lPos], 2, PhaseSlice); if (PhaseSlice[1] > 0.0) d.accelFactPE = 1.0f / PhaseSlice[1]; if (PhaseSlice[2] > 0.0) d.accelFactOOP = 1.0f / PhaseSlice[2]; break; } case kASLContrastTechniqueGE: { //CS if (d.manufacturer != kMANUFACTURER_GE) break; 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); break; } case kASLLabelingTechniqueGE: { //LO issue427GE if (d.manufacturer != kMANUFACTURER_GE) break; char st[kDICOMStr]; dcmStr(lLength, &buffer[lPos], st); if (strstr(st, "3D continuous") != NULL) d.aslFlags = (d.aslFlags | kASL_FLAG_GE_3DCASL); if (strstr(st, "3D pulsed continuous") != NULL) d.aslFlags = (d.aslFlags | kASL_FLAG_GE_3DPCASL); break; } case kDurationLabelPulseGE: { //IS if (d.manufacturer != kMANUFACTURER_GE) break; d.durationLabelPulseGE = dcmStrInt(lLength, &buffer[lPos]); break; } case kMultiBandGE: { //LO issue427GE if (d.manufacturer != kMANUFACTURER_GE) break; //LO array: Value 1 = Multiband factor, Value 2 = Slice FOV Shift Factor, Value 3 = Calibration method int mb = dcmStrInt(lLength, &buffer[lPos]); if (mb > 1) d.CSA.multiBandFactor = mb; break; } case kGeiisFlag: if ((lLength > 4) && (buffer[lPos] == 'G') && (buffer[lPos + 1] == 'E') && (buffer[lPos + 2] == 'I') && (buffer[lPos + 3] == 'I')) { //read a few digits, as bug is specific to GEIIS, while GEMS are fine printWarning("GEIIS violates the DICOM standard. Inspect results and admonish your vendor.\n"); isIconImageSequence = true; if (sqDepthIcon < 0) sqDepthIcon = sqDepth; //geiisBug = true; //compressed thumbnails do not follow transfer syntax! GE should not re-use pulbic tags for these proprietary images http://sonca.kasshin.net/gdcm/Doc/GE_ImageThumbnails } break; case kStudyComments: { //char commentStr[kDICOMStr]; //dcmStr(lLength, &buffer[lPos], commentStr); //printf(">> %s\n", commentStr); break; } case kProcedureStepDescription: dcmStr(lLength, &buffer[lPos], d.procedureStepDescription); break; case kOrientationACR: //use in emergency if kOrientation is not present! if (!isOrient) dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, d.orient); break; case kOrientation: { if (isOrient) { //already read orient - read for this slice to see if it varies (localizer) float orient[7]; dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, orient); if ((!isSameFloatGE(d.orient[1], orient[1]) || !isSameFloatGE(d.orient[2], orient[2]) || !isSameFloatGE(d.orient[3], orient[3]) || !isSameFloatGE(d.orient[4], orient[4]) || !isSameFloatGE(d.orient[5], orient[5]) || !isSameFloatGE(d.orient[6], orient[6]))) { if (!d.isLocalizer) printMessage("slice orientation varies (localizer?) [%g %g %g %g %g %g] != [%g %g %g %g %g %g]\n", d.orient[1], d.orient[2], d.orient[3], d.orient[4], d.orient[5], d.orient[6], orient[1], orient[2], orient[3], orient[4], orient[5], orient[6]); d.isLocalizer = true; } } dcmMultiFloat(lLength, (char *)&buffer[lPos], 6, d.orient); vec3 readV = setVec3(d.orient[1], d.orient[2], d.orient[3]); vec3 phaseV = setVec3(d.orient[4], d.orient[5], d.orient[6]); sliceV = crossProduct(readV, phaseV); //printf("sliceV %g %g %g\n", sliceV.v[0], sliceV.v[1], sliceV.v[2]); isOrient = true; break; } case kTemporalPosition: //fall through, both kSliceNumberMrPhilips (2001,100A) and kTemporalPosition are is volumeNumber = dcmStrInt(lLength, &buffer[lPos]); //temporalPositionIdentifier = volumeNumber; break; case kTemporalResolution: temporalResolutionMS = dcmStrFloat(lLength, &buffer[lPos]); break; case kImagesInAcquisition: imagesInAcquisition = dcmStrInt(lLength, &buffer[lPos]); break; //case kSliceLocation : //optional so useless, infer from image position patient (0020,0032) and image orientation (0020,0037) // sliceLocation = dcmStrFloat(lLength, &buffer[lPos]); // break; case kImageStart: //if ((!geiisBug) && (!isIconImageSequence)) //do not exit for proprietary thumbnails if (isIconImageSequence) { //20200116 see example from Tashrif Bilah that saves GEIIS thumbnails uncompressed // therefore, the next couple lines are not a perfect detection for GEIIS thumbnail icons //int imgBytes = (d.xyzDim[1] * d.xyzDim[2] * int(d.bitsAllocated / 8)); //if (imgBytes == lLength) // isIconImageSequence = false; if ((isIconImageSequence) && (sqDepth < 1)) printWarning("Assuming 7FE0,0010 refers to an icon not the main image\n"); } if ((d.compressionScheme == kCompressNone) && (!isIconImageSequence)) //do not exit for proprietary thumbnails d.imageStart = (int)lPos + (int)lFileOffset; //geiisBug = false; //http://www.dclunie.com/medical-image-faq/html/part6.html //unlike raw data, Encapsulated data is stored as Fragments contained in Items that are the Value field of Pixel Data if ((d.compressionScheme != kCompressNone) && (!isIconImageSequence)) { isEncapsulatedData = true; encapsulatedDataImageStart = (int)lPos + (int)lFileOffset; lLength = 0; } isIconImageSequence = false; break; case kImageStartFloat: d.isFloat = true; if (!isIconImageSequence) //do not exit for proprietary thumbnails d.imageStart = (int)lPos + (int)lFileOffset; isIconImageSequence = false; break; case kImageStartDouble: printWarning("Double-precision DICOM conversion untested: please provide samples to developer\n"); d.isFloat = true; if (!isIconImageSequence) //do not exit for proprietary thumbnails d.imageStart = (int)lPos + (int)lFileOffset; isIconImageSequence = false; break; } //switch/case for groupElement if ((((groupElement >> 8) & 0xFF) == 0x60) && (groupElement % 2 == 0) && ((groupElement & 0xFF) < 0x1E)) { //Group 60xx: OverlayGroup http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.9.html //even group numbers 0x6000..0x601E int overlayN = ((groupElement & 0xFF) >> 1); //printf("%08x %d %d\n", groupElement, (groupElement & 0xFF), overlayN); int element = groupElement >> 16; switch (element) { case 0x0010: //US OverlayRows overlayRows = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case 0x0011: //US OverlayColumns overlayCols = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); break; case 0x0050: { //SSx2! OverlayOrigin if (lLength != 4) break; int row = dcmInt(2, &buffer[lPos], d.isLittleEndian); int col = dcmInt(2, &buffer[lPos + 2], d.isLittleEndian); if ((row == 1) && (col == 1)) break; printMessage("Unsupported overlay origin %d/%d\n", row, col); overlayOK = false; break; } case 0x0100: { //US OverlayBitsAllocated int bits = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); if (bits == 1) break; //old style Burned-In printMessage("Illegal/Obsolete DICOM: Overlay Bits Allocated must be 1, not %d\n", bits); overlayOK = false; break; } case 0x0102: { //US OverlayBitPosition int pos = dcmInt(lLength, &buffer[lPos], d.isLittleEndian); if (pos == 0) break; //old style Burned-In printMessage("Illegal/Obsolete DICOM: Overlay Bit Position shall be 0, not %d\n", pos); overlayOK = false; break; } case 0x3000: { d.overlayStart[overlayN] = (int)lPos + (int)lFileOffset; d.isHasOverlay = true; break; } } } //Group 60xx even values 0x6000..0x601E https://www.medicalconnections.co.uk/kb/Number-Of-Overlays-In-Image/ #ifndef USING_R if (isVerbose > 1) { //dcm2niix i fast because it does not use a dictionary. // this is a very incomplete DICOM header report, and not a substitute for tools like dcmdump // the purpose is to see how dcm2niix has parsed the image for diagnostics // this section will report very little for implicit data //if (d.isHasReal) printf("r");else printf("m"); char str[kDICOMStr]; 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]); if ((vr[0] == 'F') && (vr[1] == 'D')) sprintf(str, "%s%g ", str, dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'F') && (vr[1] == 'L')) sprintf(str, "%s%g ", str, dcmFloat(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'S') && (vr[1] == 'S')) sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'S') && (vr[1] == 'L')) sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'U') && (vr[1] == 'S')) sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'U') && (vr[1] == 'L')) sprintf(str, "%s%d ", str, dcmInt(lLength, &buffer[lPos], d.isLittleEndian)); if ((vr[0] == 'A') && (vr[1] == 'E')) isStr = true; if ((vr[0] == 'A') && (vr[1] == 'S')) isStr = true; if ((vr[0] == 'C') && (vr[1] == 'S')) isStr = true; if ((vr[0] == 'D') && (vr[1] == 'A')) isStr = true; if ((vr[0] == 'D') && (vr[1] == 'S')) isStr = true; if ((vr[0] == 'D') && (vr[1] == 'T')) isStr = true; if ((vr[0] == 'I') && (vr[1] == 'S')) isStr = true; if ((vr[0] == 'L') && (vr[1] == 'O')) 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')) isStr = true; if ((vr[0] == 'S') && (vr[1] == 'T')) isStr = true; if ((vr[0] == 'T') && (vr[1] == 'M')) isStr = true; if ((vr[0] == 'U') && (vr[1] == 'I')) isStr = true; if ((vr[0] == 'U') && (vr[1] == 'T')) isStr = true; } 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); } 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 strcpy(tagStr, ""); if (lLength > 0) dcmStr(lLength, &buffer[lPos], tagStr); if (strlen(tagStr) > 1) { for (size_t pos = 0; pos < strlen(tagStr); pos++) if ((tagStr[pos] == '<') || (tagStr[pos] == '>') || (tagStr[pos] == ':') || (tagStr[pos] == '"') || (tagStr[pos] == '\\') || (tagStr[pos] == '/') || (tagStr[pos] < 32) //issue398 //|| (tagStr[pos] == '^') || (tagStr[pos] < 33) || (tagStr[pos] == '*') || (tagStr[pos] == '|') || (tagStr[pos] == '?')) tagStr[pos] = '_'; } printMessage("%s %s\n", str, tagStr); } else printMessage("%s\n", str); //if (d.isExplicitVR) printMessage(" VR=%c%c\n", vr[0], vr[1]); } //printMessage(" tag=%04x,%04x length=%u pos=%ld %c%c nest=%d\n", groupElement & 65535,groupElement>>16, lLength, lPos,vr[0], vr[1], nest); #endif lPos = lPos + (lLength); } //while d.imageStart == 0 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) { if ((encapsulatedDataFragments > 1) && (encapsulatedDataFragments == numberOfFrames) && (encapsulatedDataFragments < kMaxDTI4D)) { printWarning("Compressed image stored as %d fragments: if conversion fails decompress with gdcmconv, Osirix, dcmdjpeg or dcmjp2k %s\n", encapsulatedDataFragments, fname); d.imageStart = encapsulatedDataFragmentStart; } else if (encapsulatedDataFragments > 1) { printError("Compressed image stored as %d fragments: decompress with gdcmconv, Osirix, dcmdjpeg or dcmjp2k %s\n", encapsulatedDataFragments, fname); } else { d.imageStart = encapsulatedDataFragmentStart; //dti4D->fragmentOffset[0] = -1; } } else if ((isEncapsulatedData) && (d.imageStart < 128)) { //http://www.dclunie.com/medical-image-faq/html/part6.html //Uncompressed data (unencapsulated) is sent in DICOM as a series of raw bytes or words (little or big endian) in the Value field of the Pixel Data element (7FE0,0010). Encapsulated data on the other hand is sent not as raw bytes or words but as Fragments contained in Items that are the Value field of Pixel Data printWarning("DICOM violation (contact vendor): compressed image without image fragments, assuming image offset defined by 0x7FE0,x0010: %s\n", fname); d.imageStart = encapsulatedDataImageStart; } if ((d.manufacturer == kMANUFACTURER_GE) && (d.groupDelay > 0.0)) d.TR += d.groupDelay; //Strangely, for GE the sample rate is (0018,0080) + ((0043,107c) * 1000.0) if ((d.modality == kMODALITY_PT) && (PETImageIndex > 0)) { d.imageNum = PETImageIndex; //https://github.com/rordenlab/dcm2niix/issues/184 //printWarning("PET scan using 0054,1330 for image number %d\n", PETImageIndex); } if (d.isHasOverlay) { if ((overlayCols > 0) && (d.xyzDim[1] != overlayCols)) overlayOK = false; if ((overlayRows > 0) && (d.xyzDim[2] != overlayRows)) overlayOK = false; if (!overlayOK) d.isHasOverlay = false; } //Recent Philips images include DateTime (0008,002A) but not separate date and time (0008,0022 and 0008,0032) #define kYYYYMMDDlen 8 //how many characters to encode year,month,day in "YYYYDDMM" format if ((strlen(acquisitionDateTimeTxt) > (kYYYYMMDDlen + 5)) && (!isFloatDiff(d.acquisitionTime, 0.0f)) && (!isFloatDiff(d.acquisitionDate, 0.0f))) { // 20161117131643.80000 -> date 20161117 time 131643.80000 //printMessage("acquisitionDateTime %s\n",acquisitionDateTimeTxt); char acquisitionDateTxt[kDICOMStr]; memcpy(acquisitionDateTxt, acquisitionDateTimeTxt, kYYYYMMDDlen); acquisitionDateTxt[kYYYYMMDDlen] = '\0'; // IMPORTANT! d.acquisitionDate = atof(acquisitionDateTxt); char acquisitionTimeTxt[kDICOMStr]; int timeLen = (int)strlen(acquisitionDateTimeTxt) - kYYYYMMDDlen; strncpy(acquisitionTimeTxt, &acquisitionDateTimeTxt[kYYYYMMDDlen], timeLen); acquisitionTimeTxt[timeLen] = '\0'; // IMPORTANT! d.acquisitionTime = atof(acquisitionTimeTxt); } d.dateTime = (atof(d.studyDate) * 1000000) + atof(d.studyTime); //printMessage("slices in Acq %d %d %g %g\n",locationsInAcquisitionGE, d.locationsInAcquisition, d.xyzMM[3], d.zSpacing); if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.locationsInAcquisition == 0)) d.locationsInAcquisition = locationsInAcquisitionPhilips; if ((d.manufacturer == kMANUFACTURER_GE) && (imagesInAcquisition > 0)) d.locationsInAcquisition = imagesInAcquisition; //e.g. if 72 slices acquired but interpolated as 144 if ((d.manufacturer == kMANUFACTURER_GE) && (d.locationsInAcquisition > 0) && (locationsInAcquisitionGE > 0) && (d.locationsInAcquisition != locationsInAcquisitionGE)) { if (isVerbose) printMessage("Check number of slices, discrepancy between tags (0020,1002; 0021,104F; 0054,0081) (%d vs %d) %s\n", locationsInAcquisitionGE, d.locationsInAcquisition, fname); /* SAH.start: Fix for ZIP2 */ int zipFactor = (int)roundf(d.xyzMM[3] / d.zSpacing); if (zipFactor > 1) { d.interp3D = zipFactor; //printMessage("Issue 373: Check for ZIP2 Factor: %d SliceThickness+SliceGap: %f, SpacingBetweenSlices: %f \n", zipFactor, d.xyzMM[3], d.zSpacing); locationsInAcquisitionGE *= zipFactor; // Multiply number of slices by ZIP factor. Do this prior to checking for conflict below (?). } if (isGEfieldMap) { //issue501 : to do check zip factor //Volume 1) derived phase field map [Hz] and 2) magnitude volume. d.isDerived = (d.imageNum <= locationsInAcquisitionGE); //first volume d.isRealIsPhaseMapHz = d.isDerived; d.isHasReal = d.isDerived; } /* SAH.end */ if (locationsInAcquisitionGE < d.locationsInAcquisition) { d.locationsInAcquisitionConflict = d.locationsInAcquisition; d.locationsInAcquisition = locationsInAcquisitionGE; } else d.locationsInAcquisitionConflict = locationsInAcquisitionGE; } if ((d.manufacturer == kMANUFACTURER_GE) && (d.locationsInAcquisition == 0)) d.locationsInAcquisition = locationsInAcquisitionGE; if (d.zSpacing > 0.0) d.xyzMM[3] = d.zSpacing; //use zSpacing if provided: depending on vendor, kZThick may or may not include a slice gap //printMessage("patientPositions = %d XYZT = %d slicePerVol = %d numberOfDynamicScans %d\n",patientPositionNum,d.xyzDim[3], d.locationsInAcquisition, d.numberOfDynamicScans); if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (patientPositionNum > d.xyzDim[3])) { d.CSA.numDti = d.xyzDim[3]; //issue506 printMessage("Please check slice thicknesses: Philips R3.2.2 bug can disrupt estimation (%d positions reported for %d slices)\n", patientPositionNum, d.xyzDim[3]); //Philips reported different positions for each slice! } if ((d.imageStart > 144) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1)) d.isValid = true; //if ((d.imageStart > 144) && (d.xyzDim[1] >= 1) && (d.xyzDim[2] >= 1) && (d.xyzDim[4] > 1)) //Spectroscopy // d.isValid = true; if ((d.xyzMM[1] > FLT_EPSILON) && (d.xyzMM[2] < FLT_EPSILON)) { printMessage("Please check voxel size\n"); d.xyzMM[2] = d.xyzMM[1]; } if ((d.xyzMM[2] > FLT_EPSILON) && (d.xyzMM[1] < FLT_EPSILON)) { printMessage("Please check voxel size\n"); d.xyzMM[1] = d.xyzMM[2]; } if ((d.xyzMM[3] < FLT_EPSILON)) { printMessage("Unable to determine slice thickness: please check voxel size\n"); d.xyzMM[3] = 1.0; } //printMessage("Patient Position\t%g\t%g\t%g\tThick\t%g\n",d.patientPosition[1],d.patientPosition[2],d.patientPosition[3], d.xyzMM[3]); //printMessage("Patient Position\t%g\t%g\t%g\tThick\t%g\tStart\t%d\n",d.patientPosition[1],d.patientPosition[2],d.patientPosition[3], d.xyzMM[3], d.imageStart); // printMessage("ser %ld\n", d.seriesNum); //int kEchoMult = 100; //For Siemens/GE Series 1,2,3... save 2nd echo as 201, 3rd as 301, etc //if (d.seriesNum > 100) // kEchoMult = 10; //For Philips data Saved as Series 101,201,301... save 2nd echo as 111, 3rd as 121, etc //if (coilNum > 0) //segment images with multiple coils // d.seriesNum = d.seriesNum + (100*coilNum); //if (d.echoNum > 1) //segment images with multiple echoes // d.seriesNum = d.seriesNum + (kEchoMult*d.echoNum); if (isPaletteColor) { d.isValid = false; d.isDerived = true; //to my knowledge, palette images always derived printWarning("Photometric Interpretation 'PALETTE COLOR' not supported\n"); } if ((d.compressionScheme == kCompress50) && (d.bitsAllocated > 8)) { //dcmcjpg with +ee can create .51 syntax images that are 8,12,16,24-bit: we can only decode 8/24-bit printError("Unable to decode %d-bit images with Transfer Syntax 1.2.840.10008.1.2.4.51, decompress with dcmdjpg or gdcmconv\n", d.bitsAllocated); d.isValid = false; } if ((isMosaic) && (d.CSA.mosaicSlices < 1) && (numberOfImagesInMosaic < 1) && (!isInterpolated) && (d.phaseEncodingLines > 0) && (frequencyRows > 0) && ((d.xyzDim[1] % frequencyRows) == 0) && ((d.xyzDim[1] / frequencyRows) > 2) && ((d.xyzDim[2] % d.phaseEncodingLines) == 0) && ((d.xyzDim[2] / d.phaseEncodingLines) > 2)) { //n.b. in future check if frequency is in row or column direction (and same with phase) // >2 avoids detecting interpolated as mosaic, in future perhaps check "isInterpolated" numberOfImagesInMosaic = (d.xyzDim[1] / frequencyRows) * (d.xyzDim[2] / d.phaseEncodingLines); printWarning("Guessing this is a mosaic up to %d slices (issue 337).\n", numberOfImagesInMosaic); } if ((numberOfImagesInMosaic > 1) && (d.CSA.mosaicSlices < 1)) d.CSA.mosaicSlices = numberOfImagesInMosaic; if (d.isXA10A) d.manufacturer = kMANUFACTURER_SIEMENS; //XA10A mosaics omit Manufacturer 0008,0070! if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (isMosaic) && (d.CSA.mosaicSlices < 1) && (d.phaseEncodingSteps > 0) && ((d.xyzDim[1] % d.phaseEncodingSteps) == 0) && ((d.xyzDim[2] % d.phaseEncodingSteps) == 0)) { d.CSA.mosaicSlices = (d.xyzDim[1] / d.phaseEncodingSteps) * (d.xyzDim[2] / d.phaseEncodingSteps); printWarning("Mosaic inferred without CSA header (check number of slices and spatial orientation)\n"); } if ((d.isXA10A) && (isMosaic) && (d.CSA.mosaicSlices < 1)) d.CSA.mosaicSlices = -1; //mark as bogus DICOM if ((!d.isXA10A) && (isMosaic) && (d.CSA.mosaicSlices < 1)) //See Erlangen Vida dataset - never reports "XA10" but mosaics have no attributes 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.protocolName) < 1) && (strlen(d.seriesDescription) > 1)) strcpy(d.protocolName, d.seriesDescription); if ((strlen(d.protocolName) > 1) && (isMoCo)) strcat(d.protocolName, "_MoCo"); //disambiguate MoCo https://github.com/neurolabusc/MRIcroGL/issues/31 if ((strlen(d.protocolName) < 1) && (strlen(d.sequenceName) > 1) && (d.manufacturer != kMANUFACTURER_SIEMENS)) strcpy(d.protocolName, d.sequenceName); //protocolName (0018,1030) optional, sequence name (0018,0024) is not a good substitute for Siemens as it can vary per volume: *ep_b0 *ep_b1000#1, *ep_b1000#2, etc https://www.nitrc.org/forum/forum.php?thread_id=8771&forum_id=4703 if (numberOfFrames == 0) numberOfFrames = d.xyzDim[3]; if ((locationsInAcquisitionPhilips > 0) && ((d.xyzDim[3] % locationsInAcquisitionPhilips) == 0)) { d.xyzDim[4] = d.xyzDim[3] / locationsInAcquisitionPhilips; d.xyzDim[3] = locationsInAcquisitionPhilips; } else if ((numberOfDynamicScans > 1) && (d.xyzDim[4] < 2) && (d.xyzDim[3] > 1) && ((d.xyzDim[3] % numberOfDynamicScans) == 0)) { d.xyzDim[3] = d.xyzDim[3] / numberOfDynamicScans; d.xyzDim[4] = numberOfDynamicScans; } if ((maxInStackPositionNumber > 1) && (d.xyzDim[4] < 2) && (d.xyzDim[3] > 1) && ((d.xyzDim[3] % maxInStackPositionNumber) == 0)) { d.xyzDim[4] = d.xyzDim[3] / maxInStackPositionNumber; d.xyzDim[3] = maxInStackPositionNumber; } if ((!isnan(patientPositionStartPhilips[1])) && (!isnan(patientPositionEndPhilips[1]))) { for (int k = 0; k < 4; k++) { d.patientPosition[k] = patientPositionStartPhilips[k]; d.patientPositionLast[k] = patientPositionEndPhilips[k]; } } if ((numberOfFrames > 1) && (locationsInAcquisitionPhilips > 0) && ((numberOfFrames % locationsInAcquisitionPhilips) != 0)) { //issue515 printWarning("Number of frames (%d) not divisible by locations in acquisition (2001,1018) %d (issue 515)\n", numberOfFrames, locationsInAcquisitionPhilips); d.xyzDim[4] = d.xyzDim[3] / locationsInAcquisitionPhilips; d.xyzDim[3] = locationsInAcquisitionPhilips; d.xyzDim[0] = numberOfFrames; } if ((B0Philips >= 0) && (d.CSA.numDti == 0)) { d.CSA.dtiV[0] = B0Philips; d.CSA.numDti = 1; } //issue409 Siemens XA saved as classic 2D not enhanced if (!isnan(patientPositionStartPhilips[1])) //for Philips data without for (int k = 0; k < 4; k++) d.patientPosition[k] = patientPositionStartPhilips[k]; if (isVerbose) { printMessage("DICOM file: %s\n", fname); printMessage(" patient position (0020,0032)\t%g\t%g\t%g\n", d.patientPosition[1], d.patientPosition[2], d.patientPosition[3]); if (!isnan(patientPositionEndPhilips[1])) printMessage(" patient position end (0020,0032)\t%g\t%g\t%g\n", patientPositionEndPhilips[1], patientPositionEndPhilips[2], patientPositionEndPhilips[3]); printMessage(" orient (0020,0037)\t%g\t%g\t%g\t%g\t%g\t%g\n", d.orient[1], d.orient[2], d.orient[3], d.orient[4], d.orient[5], d.orient[6]); printMessage(" acq %d img %d ser %ld dim %dx%dx%dx%d mm %gx%gx%g offset %d loc %d valid %d ph %d mag %d nDTI %d 3d %d bits %d littleEndian %d echo %d coilCRC %d TE %g TR %g\n", d.acquNum, d.imageNum, d.seriesNum, d.xyzDim[1], d.xyzDim[2], d.xyzDim[3], d.xyzDim[4], d.xyzMM[1], d.xyzMM[2], d.xyzMM[3], d.imageStart, d.locationsInAcquisition, d.isValid, d.isHasPhase, d.isHasMagnitude, d.CSA.numDti, d.is3DAcq, d.bitsAllocated, d.isLittleEndian, d.echoNum, d.coilCrc, d.TE, d.TR); if (d.CSA.dtiV[0] > 0) 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 ((d.isValid) && (d.xyzDim[1] > 1) && (d.xyzDim[2] > 1) && (d.imageStart < 132) && (!d.isRawDataStorage)) { //20190524: Philips MR 55.1 creates non-image files that report kDim1/kDim2 - we can detect them since 0008,0016 reports "RawDataStorage" //see https://neurostars.org/t/dcm2niix-error-from-philips-dicom-qsm-data-can-this-be-skipped/4883 printError("Conversion aborted due to corrupt file: %s %dx%d %d\n", fname, d.xyzDim[1], d.xyzDim[2], d.imageStart); #ifdef USING_R Rf_error("Irrecoverable error during conversion"); #else exit(kEXIT_CORRUPT_FILE_FOUND); #endif } if ((numberOfFrames > 1) && (numDimensionIndexValues == 0) && (numberOfFrames == nSliceMM)) { //issue 372 fidx *objects = (fidx *)malloc(sizeof(struct fidx) * numberOfFrames); for (int i = 0; i < numberOfFrames; i++) { objects[i].value = sliceMM[i]; objects[i].index = i; } qsort(objects, numberOfFrames, sizeof(struct fidx), fcmp); numDimensionIndexValues = numberOfFrames; for (int i = 0; i < numberOfFrames; i++) { // printf("%d > %g\n", objects[i].index, objects[i].value); dcmDim[objects[i].index].dimIdx[0] = i; } for (int i = 0; i < 4; i++) { d.patientPosition[i] = minPatientPosition[i]; d.patientPositionLast[i] = maxPatientPosition[i]; } //printf("%g -> %g\n", objects[0].value, objects[numberOfFrames-1].value); //printf("%g %g %g -> %g %g %g\n", d.patientPosition[1], d.patientPosition[2], d.patientPosition[3], d.patientPositionLast[1], d.patientPositionLast[2], d.patientPositionLast[3]); free(objects); } //issue 372 if ((d.echoTrainLength == 0) && (echoTrainLengthPhil)) d.echoTrainLength = echoTrainLengthPhil; //+1 ?? to convert "EPI factor" to echo train length, see issue 377 if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.xyzDim[4] > 1) && (d.is3DAcq) && (d.echoTrainLength > 1) && (minDynamicScanBeginTime < maxDynamicScanBeginTime)) { //issue369 float TR = 1000.0 * ((maxDynamicScanBeginTime - minDynamicScanBeginTime) / (d.xyzDim[4] - 1)); //-1 : fence post problem if (fabs(TR - d.TR) > 0.001) { printWarning("Assuming TR = %gms, not 0018,0080 = %gms (see issue 369)\n", TR, d.TR); d.TR = TR; } } // printWarning("3D EPI with FrameAcquisitionDuration = %gs volumes = %d (see issue 369)\n", frameAcquisitionDuration/1000.0, d.xyzDim[4]); //printf("%g %g\n", minDynamicScanBeginTime, maxDynamicScanBeginTime); //if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (d.xyzDim[4] > 1) && (d.is3DAcq) && (d.echoTrainLength > 1) && (frameAcquisitionDuration > 0.0)) //issue369 // printWarning("3D EPI with FrameAcquisitionDuration = %gs volumes = %d (see issue 369)\n", frameAcquisitionDuration/1000.0, d.xyzDim[4]); if (numDimensionIndexValues > 1) strcpy(d.imageType, imageType1st); //for multi-frame datasets, return name of book, not name of last chapter if ((numDimensionIndexValues > 1) && (numDimensionIndexValues == numberOfFrames)) { //Philips enhanced datasets can have custom slice orders and pack images with different TE, Phase/Magnitude/Etc. int maxVariableItem = 0; int nVariableItems = 0; if (true) { // int mn[MAX_NUMBER_OF_DIMENSIONS]; int mx[MAX_NUMBER_OF_DIMENSIONS]; for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; j++) { mx[j] = dcmDim[0].dimIdx[j]; mn[j] = mx[j]; for (int i = 0; i < numDimensionIndexValues; i++) { if (mx[j] < dcmDim[i].dimIdx[j]) mx[j] = dcmDim[i].dimIdx[j]; if (mn[j] > dcmDim[i].dimIdx[j]) mn[j] = dcmDim[i].dimIdx[j]; } if (mx[j] != mn[j]) { maxVariableItem = j; nVariableItems++; } } if (isVerbose > 1) { printMessage(" DimensionIndexValues (0020,9157), dimensions with variability:\n"); for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; i++) if (mn[i] != mx[i]) printMessage(" Dimension %d Range: %d..%d\n", i, mn[i], mx[i]); } } //verbose > 1 //see http://dicom.nema.org/medical/Dicom/2018d/output/chtml/part03/sect_C.8.24.3.3.html //Philips puts spatial position as lower item than temporal position, the reverse is true for Bruker and Canon int stackPositionItem = 0; if (dimensionIndexPointerCounter > 0) for (size_t i = 0; i < dimensionIndexPointerCounter; i++) if (dimensionIndexPointer[i] == kInStackPositionNumber) 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); int stackTimeItem = 0; if (stackPositionItem == 0) { maxVariableItem++; stackTimeItem++; //e.g. slot 0 = space, slot 1 = time } int vol = 0; for (int i = 0; i < numDimensionIndexValues; i++) { if (1 == dcmDim[i].dimIdx[stackPositionItem]) vol++; dcmDim[i].dimIdx[stackTimeItem] = vol; //printf("vol %d slice %d\n", dcmDim[i].dimIdx[stackTimeItem], dcmDim[i].dimIdx[stackPositionItem]); } } //Kuldge for corrupted CANON 0020,9157 /* //issue533: this code fragment will replicate dicm2nii (reverse order of volume hierarchy) https://github.com/xiangruili/dicm2nii/blob/f918366731162e6895be6e5ee431444642e0e7f9/dicm2nii.m#L2205 if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (stackPositionItem == 1) && ( maxVariableItem > 2)) { //replicate dicm2nii: s2.DimensionIndexValues([3:end 1]) uint32_t tmp[MAX_NUMBER_OF_DIMENSIONS]; for (int i = 0; i < numDimensionIndexValues; i++) { for (int j = 2; j <= maxVariableItem; j++) tmp[j] = dcmDim[i].dimIdx[j]; for (int j = 2; j <= maxVariableItem; j++) dcmDim[i].dimIdx[j] = tmp[maxVariableItem - j + 2]; } }*/ if ((isKludgeIssue533) && (numDimensionIndexValues > 1)) printWarning("Guessing temporal order for Philips enhanced DICOM ASL (issue 532).\n"); //sort dimensions #ifdef USING_R if (stackPositionItem < maxVariableItem) std::sort(dcmDim.begin(), dcmDim.begin() + numberOfFrames, compareTDCMdim); else std::sort(dcmDim.begin(), dcmDim.begin() + numberOfFrames, compareTDCMdimRev); #else if (stackPositionItem < maxVariableItem) qsort(dcmDim, numberOfFrames, sizeof(struct TDCMdim), compareTDCMdim); else qsort(dcmDim, numberOfFrames, sizeof(struct TDCMdim), compareTDCMdimRev); #endif //for (int i = 0; i < numberOfFrames; i++) // printf("i %d diskPos= %d dimIdx= %d %d %d %d TE= %g\n", i, dcmDim[i].diskPos, dcmDim[i].dimIdx[0], dcmDim[i].dimIdx[1], dcmDim[i].dimIdx[2], dcmDim[i].dimIdx[3], dti4D->TE[i]); for (int i = 0; i < numberOfFrames; i++) { dti4D->sliceOrder[i] = dcmDim[i].diskPos; dti4D->intenScale[i] = dcmDim[i].intenScale; dti4D->intenIntercept[i] = dcmDim[i].intenIntercept; dti4D->intenScalePhilips[i] = dcmDim[i].intenScalePhilips; dti4D->RWVIntercept[i] = dcmDim[i].RWVIntercept; dti4D->RWVScale[i] = dcmDim[i].RWVScale; if (dti4D->intenIntercept[i] != dti4D->intenIntercept[0]) d.isScaleVariesEnh = true; if (dti4D->intenScale[i] != dti4D->intenScale[0]) d.isScaleVariesEnh = true; if (dti4D->intenScalePhilips[i] != dti4D->intenScalePhilips[0]) d.isScaleVariesEnh = true; } if (!(d.manufacturer == kMANUFACTURER_BRUKER && d.isDiffusion) && (d.xyzDim[4] > 1) && (d.xyzDim[4] < kMaxDTI4D)) { //record variations in TE d.isScaleOrTEVaries = false; bool isTEvaries = false; bool isScaleVaries = false; //setting j = 1 in next few lines is a hack, just in case TE/scale/intercept listed AFTER dimensionIndexValues int j = 0; for (int i = 0; i < d.xyzDim[4]; i++) { int slice = j + (i * d.xyzDim[3]); //dti4D->gradDynVol[i] = 0; //only PAR/REC dti4D->TE[i] = dcmDim[slice].TE; dti4D->isPhase[i] = dcmDim[slice].isPhase; dti4D->isReal[i] = dcmDim[slice].isReal; dti4D->isImaginary[i] = dcmDim[slice].isImaginary; dti4D->triggerDelayTime[i] = dcmDim[slice].triggerDelayTime; dti4D->S[i].V[0] = dcmDim[slice].V[0]; dti4D->S[i].V[1] = dcmDim[slice].V[1]; dti4D->S[i].V[2] = dcmDim[slice].V[2]; dti4D->S[i].V[3] = dcmDim[slice].V[3]; //printf("te=\t%g\tscl=\t%g\tintercept=\t%g\n",dti4D->TE[i], dti4D->intenScale[i],dti4D->intenIntercept[i]); if ((!isSameFloatGE(dti4D->TE[i], 0.0)) && (dti4D->TE[i] != d.TE)) isTEvaries = true; if (dti4D->isPhase[i] != isPhase) d.isScaleOrTEVaries = true; if (dti4D->triggerDelayTime[i] != d.triggerDelayTime) d.isScaleOrTEVaries = true; if (dti4D->isReal[i] != isReal) d.isScaleOrTEVaries = true; if (dti4D->isImaginary[i] != isImaginary) d.isScaleOrTEVaries = true; /*Philips can vary intensity scalings for separate slices within a volume! dti4D->intenScale[i] = dcmDim[slice].intenScale; dti4D->intenIntercept[i] = dcmDim[slice].intenIntercept; dti4D->intenScalePhilips[i] = dcmDim[slice].intenScalePhilips; dti4D->RWVIntercept[i] = dcmDim[slice].RWVIntercept; dti4D->RWVScale[i] = dcmDim[slice].RWVScale; if (dti4D->intenScale[i] != d.intenScale) isScaleVaries = true; if (dti4D->intenIntercept[i] != d.intenIntercept) isScaleVaries = true;*/ } if ((isScaleVaries) || (isTEvaries)) d.isScaleOrTEVaries = true; if (isTEvaries) d.isMultiEcho = true; //if echoVaries,count number of echoes /*int echoNum = 1; for (int i = 1; i < d.xyzDim[4]; i++) { if (dti4D->TE[i-1] != dti4D->TE[i]) }*/ if ((isVerbose) && (d.isScaleOrTEVaries)) { printMessage("Parameters vary across 3D volumes packed in single DICOM file:\n"); for (int i = 0; i < d.xyzDim[4]; i++) { int slice = (i * d.xyzDim[3]); printMessage(" %d TE=%g Slope=%g Inter=%g PhilipsScale=%g Phase=%d\n", i, dti4D->TE[i], dti4D->intenScale[slice], dti4D->intenIntercept[slice], dti4D->intenScalePhilips[slice], dti4D->isPhase[i]); } } } if ((d.xyzDim[3] == maxInStackPositionNumber) && (maxInStackPositionNumber > 1) && (d.zSpacing <= 0.0)) { float dx = sqrt(pow(d.patientPosition[1] - d.patientPositionLast[1], 2) + pow(d.patientPosition[2] - d.patientPositionLast[2], 2) + pow(d.patientPosition[3] - d.patientPositionLast[3], 2)); dx = dx / (maxInStackPositionNumber - 1); if ((dx > 0.0) && (!isSameFloatGE(dx, d.xyzMM[3]))) //patientPosition has some rounding error d.xyzMM[3] = dx; } //d.zSpacing <= 0.0: Bruker does not populate 0018,0088 https://github.com/rordenlab/dcm2niix/issues/241 } //if numDimensionIndexValues > 1 : enhanced DICOM if (d.CSA.numDti >= kMaxDTI4D) { printError("Unable to convert DTI [recompile with increased kMaxDTI4D] detected=%d, max = %d\n", d.CSA.numDti, kMaxDTI4D); d.CSA.numDti = 0; } 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 //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); d.imageNum = abs((int)d.instanceUidCrc) % 2147483647; //INT_MAX; if (d.imageNum == 0) d.imageNum = 1; //https://github.com/rordenlab/dcm2niix/issues/341 //d.imageNum = 1; //not set } if ((numDimensionIndexValues < 1) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (d.seriesNum > 99999) && (philMRImageDiffBValueNumber > 0)) { //Ugly kludge to distinguish Philips classic DICOM dti // images from a single sequence can have identical series number, instance number, gradient number // the ONLY way to distinguish slices is using the private tag MRImageDiffBValueNumber // confusingly, the same sequence can also generate MULTIPLE series numbers! // for examples see https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging d.seriesNum += (philMRImageDiffBValueNumber * 1000); } //if (contentTime != 0.0) && (numDimensionIndexValues < (MAX_NUMBER_OF_DIMENSIONS - 1)){ // uint_32t timeCRC = mz_crc32X((unsigned char*) &contentTime, sizeof(double)); //} //if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strcmp(d.sequenceName, "fldyn3d1")== 0)) { if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strstr(d.sequenceName, "fldyn3d1") != NULL)) { //combine DCE series https://github.com/rordenlab/dcm2niix/issues/252 d.isStackableSeries = true; d.imageNum += (d.seriesNum * 1000); strcpy(d.seriesInstanceUID, d.studyInstanceUID); d.seriesUidCrc = mz_crc32X((unsigned char *)&d.protocolName, strlen(d.protocolName)); } //TODO533: alias Philips ASL PLD as frameDuration? isKludgeIssue533 //if ((d.manufacturer == kMANUFACTURER_PHILIPS) && ((!isTriggerSynced) || (!isProspectiveSynced)) ) //issue408 // d.triggerDelayTime = 0.0; //Philips ASL use "(0018,9037) CS [NONE]" but "(2001,1010) CS [TRIGGERED]", a situation not described in issue408 if (isSameFloat(MRImageDynamicScanBeginTime * 1000.0, d.triggerDelayTime)) //issue395 d.triggerDelayTime = 0.0; if (d.phaseNumber > 0) //Philips TurboQUASAR set this uniquely for each slice d.triggerDelayTime = 0.0; //printf("%d\t%g\t%g\t%g\n", d.imageNum, d.acquisitionTime, d.triggerDelayTime, MRImageDynamicScanBeginTime); if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strlen(seriesTimeTxt) > 1) && (d.isXA10A) && (d.xyzDim[3] == 1) && (d.xyzDim[4] < 2)) { //printWarning(">>Ignoring series number of XA data saved as classic DICOM (issue 394)\n"); d.isStackableSeries = true; d.imageNum += (d.seriesNum * 1000); strcpy(d.seriesInstanceUID, seriesTimeTxt); // dest <- src d.seriesUidCrc = mz_crc32X((unsigned char *)&seriesTimeTxt, strlen(seriesTimeTxt)); } if (((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON)) && (B0Philips > 0.0)) { //issue 388 char txt[1024] = {""}; sprintf(txt, "b=%d(", (int)round(B0Philips)); if (strstr(d.imageComments, txt) != NULL) { //printf("%s>>>%s %g\n", txt, d.imageComments, B0Philips); int len = strlen(txt); strcpy(txt, (char *)&d.imageComments[len]); len = strlen(txt); for (int i = 0; i <= len; i++) { if ((txt[i] >= '0') && (txt[i] <= '9')) continue; if ((txt[i] == '.') || (txt[i] == '-')) continue; txt[i] = ' '; } float v[4]; dcmMultiFloat(len, (char *)&txt[0], 3, &v[0]); d.CSA.dtiV[0] = B0Philips; #ifdef swizzleCanon //see issue422 and dcm_qa_canon d.CSA.dtiV[1] = v[2]; d.CSA.dtiV[2] = v[1]; d.CSA.dtiV[3] = -v[3]; #else d.CSA.dtiV[1] = v[2]; d.CSA.dtiV[2] = v[1]; d.CSA.dtiV[3] = v[3]; d.manufacturer = kMANUFACTURER_CANON; #endif //d.CSA.dtiV[1] = v[1]; //d.CSA.dtiV[2] = v[2]; //d.CSA.dtiV[3] = v[3]; d.CSA.numDti = 1; } } if ((isDICOMANON) && (isMATLAB)) { //issue 383 strcpy(d.seriesInstanceUID, d.studyDate); // This check is unlikely to be important in practice, but it silences a warning from GCC with -Wrestrict if (strlen(d.studyDate) < kDICOMStr) { strncat(d.seriesInstanceUID, d.studyTime, kDICOMStr - strlen(d.studyDate)); } d.seriesUidCrc = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID)); } if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (strstr(d.sequenceName, "fl2d1") != NULL)) { d.isLocalizer = true; } //detect pepolar https://github.com/nipy/heudiconv/issues/479 if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD) && (userData12GE == 1)) d.epiVersionGE = kGE_EPI_PEPOLAR_REV; if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD) && (userData12GE == 2)) d.epiVersionGE = kGE_EPI_PEPOLAR_REV_FWD; if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD) && (userData12GE == 3)) d.epiVersionGE = kGE_EPI_PEPOLAR_FWD_REV; if ((d.epiVersionGE == kGE_EPI_PEPOLAR_REV_FWD) && (volumeNumber > 0) && ((volumeNumber % 2) == 1)) d.epiVersionGE = kGE_EPI_PEPOLAR_REV_FWD_FLIP; if ((d.epiVersionGE == kGE_EPI_PEPOLAR_FWD_REV) && (volumeNumber > 0) && ((volumeNumber % 2) == 0)) d.epiVersionGE = kGE_EPI_PEPOLAR_FWD_REV_FLIP; #ifndef myDisableGEPEPolarFlip //e.g. to disable patch for issue 532 "make CFLAGS=-DmyDisableGEPEPolarFlip" if ((d.epiVersionGE == kGE_EPI_PEPOLAR_REV) || (d.epiVersionGE == kGE_EPI_PEPOLAR_FWD_REV_FLIP) || (d.epiVersionGE == kGE_EPI_PEPOLAR_REV_FWD_FLIP)) { if (d.epiVersionGE != kGE_EPI_PEPOLAR_REV) d.seriesNum += 1000; if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_FLIPPED; else if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED) d.phaseEncodingGE = kGE_PHASE_ENCODING_POLARITY_UNFLIPPED; } #endif //UIH 3D T1 scans report echo train length, which is interpreted as 3D EPI if ((d.manufacturer == kMANUFACTURER_UIH) && (strstr(d.sequenceName, "gre_fsp") != NULL)) d.echoTrainLength = 0; //printf(">>%s\n", d.sequenceName); d.isValid = false; // Andrey Fedorov has requested keeping GE bvalues, see issue 264 //if ((d.CSA.numDti > 0) && (d.manufacturer == kMANUFACTURER_GE) && (d.numberOfDiffusionDirectionGE < 1)) // d.CSA.numDti = 0; //https://github.com/rordenlab/dcm2niix/issues/264 if ((!d.isLocalizer) && (isInterpolated) && (d.imageNum <= 1)) printWarning("interpolated protocol '%s' may be unsuitable for dwidenoise/mrdegibbs. %s\n", d.protocolName, fname); if (((numDimensionIndexValues + 3) < MAX_NUMBER_OF_DIMENSIONS) && (d.rawDataRunNumber > 0)) d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 4] = d.rawDataRunNumber; if ((numDimensionIndexValues + 2) < MAX_NUMBER_OF_DIMENSIONS) d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 3] = d.instanceUidCrc; if ((numDimensionIndexValues + 1) < MAX_NUMBER_OF_DIMENSIONS) d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 2] = d.echoNum; if (numDimensionIndexValues < MAX_NUMBER_OF_DIMENSIONS) //https://github.com/rordenlab/dcm2niix/issues/221 d.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 1] = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID)); if ((d.isValid) && (d.seriesUidCrc == 0)) { if (d.seriesNum < 1) d.seriesUidCrc = 1; //no series information else d.seriesUidCrc = d.seriesNum; //file does not have Series UID, use series number instead } if (d.seriesNum < 1) //https://github.com/rordenlab/dcm2niix/issues/218 d.seriesNum = mz_crc32X((unsigned char *)&d.seriesInstanceUID, strlen(d.seriesInstanceUID)); getFileName(d.imageBaseName, fname); if (multiBandFactor > d.CSA.multiBandFactor) d.CSA.multiBandFactor = multiBandFactor; //SMS reported in 0051,1011 but not CSA header #ifndef myLoadWholeFileToReadHeader fclose(file); #endif if ((temporalResolutionMS > 0.0) && (isSameFloatGE(d.TR, temporalResolutionMS))) { //do something profound //in practice 0020,0110 not used //https://github.com/bids-standard/bep001/blob/repetitiontime/Proposal_RepetitionTime.md } //issue 542 if ((d.manufacturer == kMANUFACTURER_GE) && (isNeologica) && (!isSameFloat(d.CSA.dtiV[0], 0.0f)) && ((isSameFloat(d.CSA.dtiV[1], 0.0f)) && (isSameFloat(d.CSA.dtiV[2], 0.0f)) && (isSameFloat(d.CSA.dtiV[3], 0.0f)) ) ) printWarning("GE DWI vectors may have been removed by Neologica DICOM Anonymizer Pro (Issue 542)\n"); //start: issue529 TODO JJJJ if ((!isSameFloat(d.CSA.dtiV[0], 0.0f)) && ((isSameFloat(d.CSA.dtiV[1], 0.0f)) && (isSameFloat(d.CSA.dtiV[2], 0.0f)) && (isSameFloat(d.CSA.dtiV[3], 0.0f)) ) ) gradientOrientationNumberPhilips = kMaxDTI4D + 1; //Philips includes derived Trace/ADC images into raw DTI, these should be removed... //if (sliceNumberMrPhilips == 1) printf("instance\t %d\ttime\t%g\tvolume\t%d\tgradient\t%d\tphase\t%d\tisLabel\t%d\n", d.imageNum, MRImageDynamicScanBeginTime, volumeNumber, gradientOrientationNumberPhilips, d.phaseNumber, d.aslFlags == kASL_FLAG_PHILIPS_LABEL); //if (sliceNumberMrPhilips == 1) printf("ACQtime\t%g\tinstance\t %d\ttime\t%g\tvolume\t%d\tphase\t%d\tisLabel\t%d\n", d.acquisitionTime, d.imageNum, MRImageDynamicScanBeginTime, volumeNumber, d.phaseNumber, d.aslFlags == kASL_FLAG_PHILIPS_LABEL); //if (sliceNumberMrPhilips == 1) printf("%d\t%d\t%g\t%d\n", philMRImageDiffBValueNumber, gradientOrientationNumberPhilips, d.CSA.dtiV[0], philMRImageDiffVolumeNumber); //issue546 d.phaseNumber = (d.phaseNumber > philMRImageDiffBValueNumber) ? d.phaseNumber : philMRImageDiffBValueNumber; //we need both BValueNumber(2005,1412) and GradientOrientationNumber(2005,1413) to resolve volumes: issue546 d.rawDataRunNumber = (d.rawDataRunNumber > volumeNumber) ? d.rawDataRunNumber : volumeNumber; d.rawDataRunNumber = (d.rawDataRunNumber > gradientOrientationNumberPhilips) ? d.rawDataRunNumber : gradientOrientationNumberPhilips; if ((d.rawDataRunNumber < 0) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (nDimIndxVal > 1) && (d.dimensionIndexValues[nDimIndxVal - 1] > 0)) d.rawDataRunNumber = d.dimensionIndexValues[nDimIndxVal - 1]; //Philips enhanced scans converted to classic with dcuncat if (philMRImageDiffVolumeNumber > 0) { //use 2005,1596 for Philips DWI >= R5.6 d.rawDataRunNumber = philMRImageDiffVolumeNumber; d.phaseNumber = 0; } // d.rawDataRunNumber = (d.rawDataRunNumber > d.phaseNumber) ? d.rawDataRunNumber : d.phaseNumber; //will not work: conflict for MultiPhase ASL with multiple averages //end: issue529 if (hasDwiDirectionality) d.isVectorFromBMatrix = false; //issue 265: Philips/Siemens have both directionality and bmatrix, Bruker only has bmatrix //printf("%s\t%s\t%s\t%s\t%s_%s\n",d.patientBirthDate, d.procedureStepDescription,d.patientName, fname, d.studyDate, d.studyTime); //d.isValid = false; //printMessage(" patient position (0020,0032)\t%g\t%g\t%g\n", d.patientPosition[1],d.patientPosition[2],d.patientPosition[3]); //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); return d; } // readDICOM() void setDefaultPrefs(struct TDCMprefs *prefs) { prefs->isVerbose = false; prefs->compressFlag = kCompressSupport; prefs->isIgnoreTriggerTimes = false; } struct TDICOMdata readDICOMv(char *fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) { struct TDCMprefs prefs; setDefaultPrefs(&prefs); prefs.isVerbose = isVerbose; prefs.compressFlag = compressFlag; TDICOMdata ret = readDICOMx(fname, &prefs, dti4D); return ret; } struct TDICOMdata readDICOM(char *fname) { struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); //unused TDICOMdata ret = readDICOMv(fname, false, kCompressSupport, dti4D); free(dti4D); return ret; } // readDICOM() dcm2niix-1.0.20211006/console/nii_dicom.h000066400000000000000000000317301412761503300175340ustar00rootroot00000000000000#include //requires VS 2015 or later #include #include #include "nifti1_io_core.h" #ifndef USING_R #include "nifti1.h" #endif #ifndef MRIpro_nii_dcm_h #define MRIpro_nii_dcm_h #ifdef __cplusplus extern "C" { #endif #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) #if defined(myEnableJPEGLS) || defined(myEnableJPEGLS1) #define kLSsuf " (JP-LS:CharLS)" #else #define kLSsuf "" #endif #ifdef myEnableJasper #define kJP2suf " (JP2:JasPer)" #else #ifdef myDisableOpenJPEG #define kJP2suf "" #else #define kJP2suf " (JP2:OpenJPEG)" #endif #endif #if defined(__ICC) || defined(__INTEL_COMPILER) #define kCCsuf " IntelCC" STR(__INTEL_COMPILER) #elif defined(_MSC_VER) #define kCCsuf " MSC" STR(_MSC_VER) #elif defined(__clang__) #define kCCsuf " Clang" STR(__clang_major__) "." STR(__clang_minor__) "." STR(__clang_patchlevel__) #elif defined(__GNUC__) || defined(__GNUG__) #define kCCsuf " GCC" STR(__GNUC__) "." STR(__GNUC_MINOR__) "." STR(__GNUC_PATCHLEVEL__) #else #define kCCsuf " CompilerNA" //unknown compiler! #endif #if defined(__arm__) || defined(__ARM_ARCH) #define kCPUsuf " ARM" #elif defined(__x86_64) #define kCPUsuf " x86-64" #else #define kCPUsuf " " //unknown CPU #endif #define kDCMdate "v1.0.20211006" #define kDCMvers kDCMdate " " kJP2suf kLSsuf kCCsuf kCPUsuf static const int kMaxEPI3D = 1024; //maximum number of EPI images in Siemens Mosaic static const int kMaxSlice2D = 65535; //issue460 maximum number of 2D slices in 4D (Philips) images static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI directions for 4D (Philips) images, also maximum number of 2D slices for Enhanced DICOM and PAR/REC #define kDICOMStr 66 //64 characters plus NULL https://github.com/rordenlab/dcm2niix/issues/268 #define kDICOMStrLarge 256 #define kMANUFACTURER_UNKNOWN 0 #define kMANUFACTURER_SIEMENS 1 #define kMANUFACTURER_GE 2 #define kMANUFACTURER_PHILIPS 3 #define kMANUFACTURER_TOSHIBA 4 #define kMANUFACTURER_UIH 5 #define kMANUFACTURER_BRUKER 6 #define kMANUFACTURER_HITACHI 7 #define kMANUFACTURER_CANON 8 #define kMANUFACTURER_MEDISO 9 //note: note a complete modality list, e.g. XA,PX, etc #define kMODALITY_UNKNOWN 0 #define kMODALITY_CR 1 #define kMODALITY_CT 2 #define kMODALITY_MR 3 #define kMODALITY_PT 4 #define kMODALITY_US 5 // PartialFourierDirection 0018,9036 #define kPARTIAL_FOURIER_DIRECTION_UNKNOWN 0 #define kPARTIAL_FOURIER_DIRECTION_PHASE 1 #define kPARTIAL_FOURIER_DIRECTION_FREQUENCY 2 #define kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT 3 #define kPARTIAL_FOURIER_DIRECTION_COMBINATION 4 //GE EPI settings #define kGE_EPI_UNKNOWN -1 #define kGE_EPI_EPI 0 #define kGE_EPI_EPIRT 1 #define kGE_EPI_EPI2 2 #define kGE_EPI_PEPOLAR_FWD 3 #define kGE_EPI_PEPOLAR_REV 4 #define kGE_EPI_PEPOLAR_REV_FWD 5 #define kGE_EPI_PEPOLAR_FWD_REV 6 #define kGE_EPI_PEPOLAR_REV_FWD_FLIP 7 #define kGE_EPI_PEPOLAR_FWD_REV_FLIP 8 //GE phase encoding #define kGE_PHASE_ENCODING_POLARITY_UNKNOWN -1 #define kGE_PHASE_ENCODING_POLARITY_UNFLIPPED 0 #define kGE_PHASE_ENCODING_POLARITY_FLIPPED 4 #define kGE_SLICE_ORDER_UNKNOWN -1 #define kGE_SLICE_ORDER_TOP_DOWN 0 #define kGE_SLICE_ORDER_BOTTOM_UP 2 //#define kGE_PHASE_DIRECTION_CENTER_OUT_REV 3 //#define kGE_PHASE_DIRECTION_CENTER_OUT 4 //EXIT_SUCCESS 0 //EXIT_FAILURE 1 #define kEXIT_NO_VALID_FILES_FOUND 2 #define kEXIT_REPORT_VERSION 3 #define kEXIT_CORRUPT_FILE_FOUND 4 #define kEXIT_INPUT_FOLDER_INVALID 5 #define kEXIT_OUTPUT_FOLDER_INVALID 6 #define kEXIT_OUTPUT_FOLDER_READ_ONLY 7 #define kEXIT_SOME_OK_SOME_BAD 8 #define kEXIT_RENAME_ERROR 9 #define kEXIT_INCOMPLETE_VOLUMES_FOUND 10 //issue 515 #define kEXIT_NOMINAL 11 //did not expect to convert files //0043,10A3 ---: PSEUDOCONTINUOUS //0043,10A4 ---: 3D pulsed continuous ASL technique #define kASL_FLAG_NONE 0 #define kASL_FLAG_GE_3DPCASL 1 #define kASL_FLAG_GE_3DCASL 2 #define kASL_FLAG_GE_PSEUDOCONTINUOUS 4 #define kASL_FLAG_GE_CONTINUOUS 8 #define kASL_FLAG_PHILIPS_CONTROL 16 #define kASL_FLAG_PHILIPS_LABEL 32 //for spoiling 0018,9016 #define kSPOILING_UNKOWN -1 #define kSPOILING_NONE 0 #define kSPOILING_RF 1 #define kSPOILING_GRADIENT 2 #define kSPOILING_RF_AND_GRADIENT 3 static const int kSliceOrientUnknown = 0; static const int kSliceOrientTra = 1; static const int kSliceOrientSag = 2; static const int kSliceOrientCor = 3; static const int kSliceOrientMosaicNegativeDeterminant = 4; static const int kCompressNone = 0; static const int kCompressYes = 1; static const int kCompressC3 = 2; //obsolete JPEG lossless static const int kCompress50 = 3; //obsolete JPEG lossy static const int kCompressRLE = 4; //run length encoding static const int kCompressPMSCT_RLE1 = 5; //see rle2img: Philips/ELSCINT1 run-length compression 07a1,1011= PMSCT_RLE1 static const int kCompressJPEGLS = 6; //LoCo JPEG-LS static const int kMaxOverlay = 16; //even group values 0x6000..0x601E #ifdef myEnableJasper static const int kCompressSupport = kCompressYes; //JASPER for JPEG2000 #else #ifdef myDisableOpenJPEG static const int kCompressSupport = kCompressNone; //no decompressor #else static const int kCompressSupport = kCompressYes; //OPENJPEG for JPEG2000 #endif #endif // Maximum number of dimensions for .dimensionIndexValues, i.e. possibly the // number of axes in the output .nii. static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; struct TDTI { float V[4]; //int totalSlicesIn4DOrder; }; struct TDTI4D { struct TDTI S[kMaxDTI4D]; 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]; bool isReal[kMaxDTI4D]; bool isImaginary[kMaxDTI4D]; bool isPhase[kMaxDTI4D]; float repetitionTimeExcitation, repetitionTimeInversion; }; #ifdef _MSC_VER //Microsoft nomenclature for packed structures is different... #pragma pack(2) typedef struct { char name[64]; //null-terminated int32_t vm; char vr[4]; // possibly nul-term string int32_t syngodt;// ?? int32_t nitems;// number of items in CSA int32_t xx;// maybe == 77 or 205 } TCSAtag; //Siemens csa tag structure typedef struct { int32_t xx1, xx2_Len, xx3_77, xx4; } TCSAitem; //Siemens csa item structure #pragma pack() #else typedef struct __attribute__((packed)) { char name[64]; //null-terminated int32_t vm; char vr[4]; // possibly nul-term string int32_t syngodt;// ?? int32_t nitems;// number of items in CSA int32_t xx;// maybe == 77 or 205 } TCSAtag; //Siemens csa tag structure typedef struct __attribute__((packed)) { int32_t xx1, xx2_Len, xx3_77, xx4; } TCSAitem; //Siemens csa item structure #endif struct TCSAdata { float sliceTiming[kMaxEPI3D], dtiV[4], sliceNormV[4], bandwidthPerPixelPhaseEncode, sliceMeasurementDuration; int numDti, SeriesHeader_offset, SeriesHeader_length, multiBandFactor, sliceOrder, slice_start, slice_end, mosaicSlices, protocolSliceNumber1, phaseEncodingDirectionPositive; bool isPhaseMap; }; struct TDICOMdata { long seriesNum; 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]; 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. 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]; 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; char phaseEncodingRC, patientSex; }; struct TDCMprefs { int isVerbose, compressFlag, isIgnoreTriggerTimes; }; size_t nii_ImgBytes(struct nifti_1_header hdr); void setDefaultPrefs (struct TDCMprefs *prefs); int isSameFloatGE (float a, float b); void getFileNameX( char *pathParent, const char *path, int maxLen); struct TDICOMdata readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D); struct TDICOMdata readDICOMx(char * fname, struct TDCMprefs* prefs, struct TDTI4D *dti4D); struct TDICOMdata readDICOM(char * fname); struct TDICOMdata clear_dicom_data(void); struct TDICOMdata nii_readParRec (char * parname, int isVerbose, struct TDTI4D *dti4D, bool isReadPhase); unsigned char * nii_flipY(unsigned char* bImg, struct nifti_1_header *h); unsigned char *nii_flipImgY(unsigned char *bImg, struct nifti_1_header *hdr); unsigned char * nii_flipZ(unsigned char* bImg, struct nifti_1_header *h); //*unsigned char * nii_reorderSlices(unsigned char* bImg, struct nifti_1_header *h, struct TDTI4D *dti4D); void changeExt (char *file_name, const char* ext); unsigned char * nii_planar2rgb(unsigned char* bImg, struct nifti_1_header *hdr, int isPlanar); int isDICOMfile(const char * fname); //0=not DICOM, 1=DICOM, 2=NOTSURE(not part 10 compliant) void setQSForm(struct nifti_1_header *h, mat44 Q44i, bool isVerbose); int headerDcm2Nii2(struct TDICOMdata d, struct TDICOMdata d2, struct nifti_1_header *h, int isVerbose); int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeSForm) ; unsigned char * nii_loadImgXL(char* imgname, struct nifti_1_header *hdr, struct TDICOMdata dcm, bool iVaries, int compressFlag, int isVerbose, struct TDTI4D *dti4D); #ifdef __cplusplus } #endif #endif dcm2niix-1.0.20211006/console/nii_dicom_batch.cpp000066400000000000000000012245511412761503300212360ustar00rootroot00000000000000//#define myNoSave //do not save images to disk #ifdef _MSC_VER #include #define getcwd _getcwd #define chdir _chrdir #include "io.h" //#include #define MiniZ #else #include #ifdef myDisableMiniZ #undef MiniZ #else #define MiniZ #endif #endif #if defined(__APPLE__) && defined(__MACH__) #endif #ifndef myDisableZLib #ifdef MiniZ #include "miniz.c" //single file clone of libz #else #include #endif #else #undef MiniZ #endif #include "tinydir.h" #include "print.h" #include "nifti1_io_core.h" #ifndef USING_R #include "nifti1.h" #endif #include "nii_dicom_batch.h" #ifndef USING_R #include "nii_foreign.h" #endif #include "nii_dicom.h" #include "nii_ortho.h" #include //toupper #include #include #include //requires VS 2015 or later #include #include #include #include #include #include #include // clock_t, clock, CLOCKS_PER_SEC #if defined(_WIN64) || defined(_WIN32) #include //write to registry #endif #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #if defined(_WIN64) || defined(_WIN32) const char kPathSeparator = '\\'; const char kFileSep[2] = "\\"; #else const char kPathSeparator = '/'; const char kFileSep[2] = "/"; #endif #ifdef USING_R #include "ImageList.h" #undef isnan #define isnan ISNAN #undef isfinite #define isfinite R_FINITE #endif #define newTilt #ifdef USING_R #ifndef max #define max(a, b) std::max(a, b) #endif #ifndef min #define min(a, b) std::min(a, b) #endif #else #ifndef max #define max(a, b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a > _b ? _a : _b; }) #endif #ifndef min #define min(a, b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a < _b ? _a : _b; }) #endif #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)))); } struct TDCMsort { uint64_t indx, img; uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; }; struct TSearchList { unsigned long numItems, maxItems; char **str; }; #ifndef PATH_MAX #define PATH_MAX 4096 #endif void dropFilenameFromPath(char *path) { const char *dirPath = strrchr(path, '/'); //UNIX if (dirPath == 0) dirPath = strrchr(path, '\\'); //Windows if (dirPath == NULL) { strcpy(path, ""); } else path[dirPath - path] = 0; // please make sure there is enough space in TargetDirectory if (strlen(path) == 0) { //file name did not specify path, assume relative path and return current working directory //strcat (path,"."); //relative path - use cwd <- not sure if this works on Windows! char cwd[PATH_MAX]; char *ok = getcwd(cwd, sizeof(cwd)); if (ok != NULL) strcat(path, cwd); } } void dropTrailingFileSep(char *path) { // size_t len = strlen(path) - 1; if (len <= 0) return; if (path[len] == '/') path[len] = '\0'; else if (path[len] == '\\') path[len] = '\0'; } bool is_fileexists(const char *filename) { FILE *fp = NULL; if ((fp = fopen(filename, "r"))) { fclose(fp); return true; } return false; } #ifndef S_ISDIR #define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR) #endif #ifndef S_ISREG #define S_ISREG(mode) (((mode)&S_IFMT) == S_IFREG) #endif bool is_fileNotDir(const char *path) { //returns false if path is a folder; requires #include struct stat buf; stat(path, &buf); return S_ISREG(buf.st_mode); } //is_file() bool is_exe(const char *path) { //requires #include struct stat buf; if (stat(path, &buf) != 0) return false; //file does not eist if (!S_ISREG(buf.st_mode)) return false; //not regular file, e.g. '..' return (buf.st_mode & 0111); //return (S_ISREG(buf.st_mode) && (buf.st_mode & 0111) ); } //is_exe() #if defined(_WIN64) || defined(_WIN32) //Windows does not support lstat int is_dir(const char *pathname, int follow_link) { struct stat s; if ((NULL == pathname) || (0 == strlen(pathname))) return 0; int err = stat(pathname, &s); if (-1 == err) return 0; // does not exist else { if (S_ISDIR(s.st_mode)) { return 1; // it's a dir } else { return 0; // exists but is no dir } } } // is_dir() #else //if windows else Unix int is_dir(const char *pathname, int follow_link) { struct stat s; int retval; if ((NULL == pathname) || (0 == strlen(pathname))) return 0; // does not exist retval = follow_link ? stat(pathname, &s) : lstat(pathname, &s); if ((-1 != retval) && (S_ISDIR(s.st_mode))) return 1; // it's a dir return 0; // exists but is no dir } // is_dir() #endif void opts2Prefs(struct TDCMopts *opts, struct TDCMprefs *prefs) { setDefaultPrefs(prefs); prefs->isVerbose = opts->isVerbose; prefs->compressFlag = opts->compressFlag; prefs->isIgnoreTriggerTimes = opts->isIgnoreTriggerTimes; } void geCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx, int isVerbose) { //0018,1312 phase encoding is either in row or column direction //0043,1039 (or 0043,a039). b value (as the first number in the string). //0019,10bb (or 0019,a0bb). phase diffusion direction //0019,10bc (or 0019,a0bc). frequency diffusion direction //0019,10bd (or 0019,a0bd). slice diffusion direction //These directions are relative to freq,phase,slice, so although no //transformations are required, you need to check the direction of the //phase encoding. This is in DICOM message 0018,1312. If this has value //COL then if swap the x and y value and reverse the sign on the z value. //If the phase encoding is not COL, then just reverse the sign on the x value. if ((d->manufacturer != kMANUFACTURER_GE) && (d->manufacturer != kMANUFACTURER_CANON)) return; if (d->isBVecWorldCoordinates) return; //Canon classic DICOMs use image space, enhanced use world space! if ((!d->isEPI) && (d->CSA.numDti == 1)) d->CSA.numDti = 0; //issue449 if (d->CSA.numDti < 1) return; if ((toupper(d->patientOrient[0]) == 'H') && (toupper(d->patientOrient[1]) == 'F') && (toupper(d->patientOrient[2]) == 'S')) ; //participant was head first supine else { printMessage("GE DTI directions require head first supine acquisition\n"); return; } bool col = false; if (d->phaseEncodingRC == 'C') col = true; else if (d->phaseEncodingRC != 'R') { printWarning("Unable to determine DTI gradients, 0018,1312 should be either R or C"); return; } if (abs(sliceDir) != 3) printWarning("Limited validation for non-Axial DTI: confirm gradient vector transformation.\n"); //GE vectors from Xiangrui Li' dicm2nii, validated with datasets from https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging ivec3 flp; if (abs(sliceDir) == 1) flp = setiVec3(1, 1, 0); //SAGITTAL else if (abs(sliceDir) == 2) flp = setiVec3(0, 1, 1); //CORONAL else if (abs(sliceDir) == 3) flp = setiVec3(0, 0, 1); //AXIAL else { printMessage("Impossible GE slice orientation!"); flp = setiVec3(0, 0, 1); //AXIAL??? } if (sliceDir < 0) flp.v[2] = 1 - flp.v[2]; if ((isVerbose) || (!col)) { printMessage("Saving %d DTI gradients. GE Reorienting %s : please validate. isCol=%d sliceDir=%d flp=%d %d %d\n", d->CSA.numDti, d->protocolName, col, sliceDir, flp.v[0], flp.v[1], flp.v[2]); if (!col) printWarning("Reorienting for ROW phase-encoding untested.\n"); } bool scaledBValWarning = false; for (int i = 0; i < d->CSA.numDti; i++) { float vLen = sqrt((vx[i].V[1] * vx[i].V[1]) + (vx[i].V[2] * vx[i].V[2]) + (vx[i].V[3] * vx[i].V[3])); if ((vx[i].V[0] <= FLT_EPSILON) || (vLen <= FLT_EPSILON)) { //bvalue=0 for (int v = 1; v < 4; v++) vx[i].V[v] = 0.0f; continue; //do not normalize or reorient 0 vectors } if ((vLen > 0.03) && (vLen < 0.97)) { //bVal scaled by norm(g)^2 issue163,245 float bValtemp = 0, bVal = 0, bVecScale = 0; // rounding by 5 with minimum of 5 if b-value > 0 bValtemp = vx[i].V[0] * (vLen * vLen); if (bValtemp > 0 && bValtemp < 5) { bVal = 5; } else { bVal = (int)((bValtemp + 2.5f) / 5) * 5; } if (bVal == 0) bVecScale = 0; else { bVecScale = sqrt((float)vx[i].V[0] / bVal); } if (!scaledBValWarning) { printMessage("GE BVal scaling (e.g. %g -> %g s/mm^2)\n", vx[i].V[0], bVal); scaledBValWarning = true; } vx[i].V[0] = bVal; vx[i].V[1] = vx[i].V[1] * bVecScale; vx[i].V[2] = vx[i].V[2] * bVecScale; vx[i].V[3] = vx[i].V[3] * bVecScale; } if (!col) { //rows need to be swizzled //see Stanford dataset Ax_DWI_Tetrahedral_7 unable to resolve between possible solutions // http://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging float swap = vx[i].V[1]; vx[i].V[1] = vx[i].V[2]; vx[i].V[2] = swap; vx[i].V[2] = -vx[i].V[2]; //because of transpose? } for (int v = 0; v < 3; v++) if (flp.v[v] == 1) vx[i].V[v + 1] = -vx[i].V[v + 1]; vx[i].V[2] = -vx[i].V[2]; //we read out Y-direction opposite order as dicm2nii, see also opts.isFlipY } //These next lines are only so files appear identical to old versions of dcm2niix: // dicm2nii and dcm2niix generate polar opposite gradient directions. // this does not matter, since intensity is the normal of the gradient vector. for (int i = 0; i < d->CSA.numDti; i++) for (int v = 1; v < 4; v++) vx[i].V[v] = -vx[i].V[v]; //These next lines convert any "-0" values to "0" for (int i = 0; i < d->CSA.numDti; i++) for (int v = 1; v < 4; v++) if (isSameFloat(vx[i].V[v], -0)) vx[i].V[v] = 0.0f; } // geCorrectBvecs() void siemensPhilipsCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx, int isVerbose) { //see Matthew Robson's http://users.fmrib.ox.ac.uk/~robson/internal/Dicom2Nifti111.m //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)) return; if (d->CSA.numDti < 1) return; if (d->manufacturer == kMANUFACTURER_UIH) { for (int i = 0; i < d->CSA.numDti; i++) { vx[i].V[2] = -vx[i].V[2]; for (int v = 0; v < 4; v++) 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); //return; //see https://github.com/rordenlab/dcm2niix/issues/238 } vec3 read_vector = setVec3(d->orient[1], d->orient[2], d->orient[3]); vec3 phase_vector = setVec3(d->orient[4], d->orient[5], d->orient[6]); vec3 slice_vector = crossProduct(read_vector, phase_vector); read_vector = nifti_vect33_norm(read_vector); phase_vector = nifti_vect33_norm(phase_vector); slice_vector = nifti_vect33_norm(slice_vector); for (int i = 0; i < d->CSA.numDti; i++) { float vLen = sqrt((vx[i].V[1] * vx[i].V[1]) + (vx[i].V[2] * vx[i].V[2]) + (vx[i].V[3] * vx[i].V[3])); if ((vx[i].V[0] <= FLT_EPSILON) || (vLen <= FLT_EPSILON)) { //bvalue=0 if (vx[i].V[0] > 5.0) //Philip stores n.b. UIH B=1.25126 Vec=0,0,0 while Philips stored isotropic images printWarning("Volume %d appears to be derived image ADC/Isotropic (non-zero b-value with zero vector length)\n", i); continue; //do not normalize or reorient b0 vectors } //if bvalue=0 vec3 bvecs_old = setVec3(vx[i].V[1], vx[i].V[2], vx[i].V[3]); vec3 bvecs_new = setVec3(dotProduct(bvecs_old, read_vector), dotProduct(bvecs_old, phase_vector), dotProduct(bvecs_old, slice_vector)); bvecs_new = nifti_vect33_norm(bvecs_new); vx[i].V[1] = bvecs_new.v[0]; vx[i].V[2] = -bvecs_new.v[1]; vx[i].V[3] = bvecs_new.v[2]; if (abs(sliceDir) == kSliceOrientMosaicNegativeDeterminant) vx[i].V[2] = -vx[i].V[2]; for (int v = 0; v < 4; v++) if (vx[i].V[v] == -0.0f) vx[i].V[v] = 0.0f; //remove sign from values that are virtually zero } //for each direction if (d->isVectorFromBMatrix) { printWarning("Saving %d DTI gradients. Eddy users: B-matrix does not encode b-vector polarity (issue 265).\n", d->CSA.numDti); } else if (abs(sliceDir) == kSliceOrientMosaicNegativeDeterminant) { printWarning("Saving %d DTI gradients. Validate vectors (matrix had a negative determinant).\n", d->CSA.numDti); //perhaps Siemens sagittal } else if ((d->sliceOrient == kSliceOrientTra) || (d->manufacturer != kMANUFACTURER_PHILIPS)) { if (isVerbose) printMessage("Saving %d DTI gradients. Validate vectors.\n", d->CSA.numDti); } else if (d->sliceOrient == kSliceOrientUnknown) printWarning("Saving %d DTI gradients. Validate vectors (image slice orientation not reported, e.g. 2001,100B).\n", d->CSA.numDti); if (d->manufacturer == kMANUFACTURER_BRUKER) printWarning("Bruker DTI support experimental (issue 265).\n"); } // siemensPhilipsCorrectBvecs() bool isNanPosition(struct TDICOMdata d) { //in 2007 some Siemens RGB DICOMs did not include the PatientPosition 0020,0032 tag if (isnan(d.patientPosition[1])) return true; if (isnan(d.patientPosition[2])) return true; if (isnan(d.patientPosition[3])) return true; return false; } // isNanPosition() bool isSamePosition(struct TDICOMdata d, struct TDICOMdata d2) { if (isNanPosition(d) || isNanPosition(d2)) return false; if (!isSameFloat(d.patientPosition[1], d2.patientPosition[1])) return false; if (!isSameFloat(d.patientPosition[2], d2.patientPosition[2])) return false; if (!isSameFloat(d.patientPosition[3], d2.patientPosition[3])) return false; return true; } // isSamePosition() void nii_saveText(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, char *dcmname) { if (!opts.isCreateText) return; char txtname[2048] = {""}; strcpy(txtname, pathoutname); strcat(txtname, ".txt"); //printMessage("Saving text %s\n",txtname); FILE *fp = fopen(txtname, "w"); fprintf(fp, "%s\tField Strength:\t%g\tProtocolName:\t%s\tScanningSequence00180020:\t%s\tTE:\t%g\tTR:\t%g\tSeriesNum:\t%ld\tAcquNum:\t%d\tImageNum:\t%d\tImageComments:\t%s\tDateTime:\t%f\tName:\t%s\tConvVers:\t%s\tDoB:\t%s\tGender:\t%c\tAge:\t%s\tDimXYZT:\t%d\t%d\t%d\t%d\tCoil:\t%d\tEchoNum:\t%d\tOrient(6)\t%g\t%g\t%g\t%g\t%g\t%g\tbitsAllocated\t%d\tInputName\t%s\n", pathoutname, d.fieldStrength, d.protocolName, d.scanningSequence, d.TE, d.TR, d.seriesNum, d.acquNum, d.imageNum, d.imageComments, d.dateTime, d.patientName, kDCMvers, d.patientBirthDate, d.patientSex, d.patientAge, h->dim[1], h->dim[2], h->dim[3], h->dim[4], d.coilCrc, d.echoNum, d.orient[1], d.orient[2], d.orient[3], d.orient[4], d.orient[5], d.orient[6], d.bitsAllocated, dcmname); fclose(fp); } // nii_saveText() #define myReadAsciiCsa #ifdef myReadAsciiCsa //read from the ASCII portion of the Siemens CSA series header // this is not recommended: poorly documented // it is better to stick to the binary portion of the Siemens CSA image header #if defined(_WIN64) || defined(_WIN32) || defined(__sun) || (defined(__APPLE__) && defined(__POWERPC__)) //https://opensource.apple.com/source/Libc/Libc-1044.1.2/string/FreeBSD/memmem.c /*- * Copyright (c) 2005 Pascal Gloor * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ const void *memmem(const char *l, size_t l_len, const char *s, size_t s_len) { char *cur, *last; const char *cl = (const char *)l; const char *cs = (const char *)s; /* we need something to compare */ if (l_len == 0 || s_len == 0) return NULL; /* "s" must be smaller or equal to "l" */ if (l_len < s_len) return NULL; /* special case where s_len == 1 */ if (s_len == 1) return memchr(l, (int)*cs, l_len); /* the last position where its possible to find "s" in "l" */ last = (char *)cl + l_len - s_len; for (cur = (char *)cl; cur <= last; cur++) if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) return cur; return NULL; } //n.b. memchr returns "const void *" not "void *" for Windows C++ https://msdn.microsoft.com/en-us/library/d7zdhf37.aspx #endif //for systems without memmem int readKeyN1(const char *key, char *buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); if (!keyPos) return -1; int ret = 0; int i = (int)strlen(key); while ((i < remLength) && (keyPos[i] != 0x0A)) { if (keyPos[i] >= '0' && keyPos[i] <= '9') ret = (10 * ret) + keyPos[i] - '0'; i++; } return ret; } //readKeyN1() //return -1 if key not found int readKey(const char *key, char *buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value int ret = 0; char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); if (!keyPos) return ret; int i = (int)strlen(key); while ((i < remLength) && (keyPos[i] != 0x0A)) { if (keyPos[i] >= '0' && keyPos[i] <= '9') ret = (10 * ret) + keyPos[i] - '0'; i++; } return ret; } //readKey() //return 0 if key not found float readKeyFloatNan(const char *key, char *buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); if (!keyPos) return NAN; char str[kDICOMStr]; strcpy(str, ""); char tmpstr[2]; tmpstr[1] = 0; int i = (int)strlen(key); while ((i < remLength) && (keyPos[i] != 0x0A)) { if ((keyPos[i] >= '0' && keyPos[i] <= '9') || (keyPos[i] == '.') || (keyPos[i] == '-')) { tmpstr[0] = keyPos[i]; strcat(str, tmpstr); } i++; } if (strlen(str) < 1) return NAN; return atof(str); } //readKeyFloatNan() float readKeyFloat(const char *key, char *buffer, int remLength) { //look for text key in binary data stream, return subsequent integer value char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); if (!keyPos) return 0.0; char str[kDICOMStr]; strcpy(str, ""); char tmpstr[2]; tmpstr[1] = 0; int i = (int)strlen(key); while ((i < remLength) && (keyPos[i] != 0x0A)) { if ((keyPos[i] >= '0' && keyPos[i] <= '9') || (keyPos[i] == '.') || (keyPos[i] == '-')) { tmpstr[0] = keyPos[i]; strcat(str, tmpstr); } i++; } if (strlen(str) < 1) return 0.0; return atof(str); } //readKeyFloat() void readKeyStr(const char *key, char *buffer, int remLength, char *outStr) { //if key is CoilElementID.tCoilID the string 'CoilElementID.tCoilID = ""Head_32""' returns 'Head32' strcpy(outStr, ""); char *keyPos = (char *)memmem(buffer, remLength, key, strlen(key)); if (!keyPos) return; int i = (int)strlen(key); int outLen = 0; char tmpstr[2]; tmpstr[1] = 0; bool isQuote = false; while ((i < remLength) && (keyPos[i] != 0x0A)) { if ((isQuote) && (keyPos[i] != '"') && (outLen < kDICOMStrLarge)) { tmpstr[0] = keyPos[i]; strcat(outStr, tmpstr); outLen++; } if (keyPos[i] == '"') { if (outLen > 0) break; isQuote = true; } i++; } } //readKeyStr() int phoenixOffsetCSASeriesHeader(unsigned char *buff, int lLength) { //returns offset to ASCII Phoenix data if (lLength < 36) return 0; if ((buff[0] != 'S') || (buff[1] != 'V') || (buff[2] != '1') || (buff[3] != '0')) return EXIT_FAILURE; int lPos = 8; //skip 8 bytes of data, 'SV10' plus 2 32-bit values unused1 and unused2 int lnTag = buff[lPos] + (buff[lPos + 1] << 8) + (buff[lPos + 2] << 16) + (buff[lPos + 3] << 24); if ((buff[lPos + 4] != 77) || (lnTag < 1)) return 0; lPos += 8; //skip 8 bytes of data, 32-bit lnTag plus 77 00 00 0 TCSAtag tagCSA; TCSAitem itemCSA; for (int lT = 1; lT <= lnTag; lT++) { memcpy(&tagCSA, &buff[lPos], sizeof(tagCSA)); //read tag if (!littleEndianPlatform()) nifti_swap_4bytes(1, &tagCSA.nitems); //printf("%d CSA of %s %d\n",lPos, tagCSA.name, tagCSA.nitems); lPos += sizeof(tagCSA); if (strcmp(tagCSA.name, "MrPhoenixProtocol") == 0) return lPos; for (int lI = 1; lI <= tagCSA.nitems; lI++) { memcpy(&itemCSA, &buff[lPos], sizeof(itemCSA)); lPos += sizeof(itemCSA); if (!littleEndianPlatform()) nifti_swap_4bytes(1, &itemCSA.xx2_Len); lPos += ((itemCSA.xx2_Len + 3) / 4) * 4; } } return 0; } //phoenixOffsetCSASeriesHeader() #define kMaxWipFree 64 typedef struct { float TE0, TE1, delayTimeInTR, phaseOversampling, phaseResolution, txRefAmp; int phaseEncodingLines, existUcImageNumb, ucMode, baseResolution, interp, partialFourier, echoSpacing, difBipolar, parallelReductionFactorInPlane, refLinesPE, combineMode, patMode, ucMTC, accelFact3D; float alFree[kMaxWipFree]; float adFree[kMaxWipFree]; float alTI[kMaxWipFree]; float 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->TE0 = 0.0; csaAscii->TE1 = 0.0; csaAscii->delayTimeInTR = -0.001; csaAscii->phaseOversampling = 0.0; csaAscii->phaseResolution = 0.0; csaAscii->txRefAmp = 0.0; csaAscii->phaseEncodingLines = 0; csaAscii->existUcImageNumb = 0; csaAscii->ucMode = -1; csaAscii->baseResolution = 0; csaAscii->interp = 0; csaAscii->partialFourier = 0; csaAscii->echoSpacing = 0; csaAscii->difBipolar = 0; //0=not assigned,1=bipolar,2=monopolar csaAscii->parallelReductionFactorInPlane = 0; csaAscii->accelFact3D = 0;//lAccelFact3D csaAscii->refLinesPE = 0; csaAscii->combineMode = 0; csaAscii->patMode = 0; csaAscii->ucMTC = 0; for (int i = 0; i < 8; i++) shimSetting[i] = 0.0; strcpy(coilID, ""); strcpy(consistencyInfo, ""); strcpy(coilElements, ""); strcpy(pulseSequenceDetails, ""); strcpy(fmriExternalInfo, ""); strcpy(wipMemBlock, ""); strcpy(protocolName, ""); if ((csaOffset < 0) || (csaLength < 8)) return; FILE *pFile = fopen(filename, "rb"); if (pFile == NULL) return; fseek(pFile, 0, SEEK_END); long lSize = ftell(pFile); if (lSize < (csaOffset + csaLength)) { fclose(pFile); return; } fseek(pFile, csaOffset, SEEK_SET); char *buffer = (char *)malloc(csaLength); if (buffer == NULL) return; size_t result = fread(buffer, 1, csaLength, pFile); if ((int)result != csaLength) return; //next bit complicated: restrict to ASCII portion to avoid buffer overflow errors in BINARY portion int startAscii = phoenixOffsetCSASeriesHeader((unsigned char *)buffer, csaLength); int csaLengthTrim = csaLength; char *bufferTrim = buffer; if ((startAscii > 0) && (startAscii < csaLengthTrim)) { //ignore binary data at start bufferTrim += startAscii; csaLengthTrim -= startAscii; } char keyStr[] = "### ASCCONV BEGIN"; //skip to start of ASCII often "### ASCCONV BEGIN ###" but also "### ASCCONV BEGIN object=MrProtDataImpl@MrProtocolData" char *keyPos = (char *)memmem(bufferTrim, csaLengthTrim, keyStr, strlen(keyStr)); if (keyPos) { //We could detect multi-echo MPRAGE here, e.g. "lContrasts = 4"- but ideally we want an earlier detection csaLengthTrim -= (keyPos - bufferTrim); //FmriExternalInfo listed AFTER AscConvEnd and uses different delimiter || // char keyStrExt[] = "FmriExternalInfo"; // readKeyStr(keyStrExt, keyPos, csaLengthTrim, fmriExternalInfo); #define myCropAtAscConvEnd #ifdef myCropAtAscConvEnd char keyStrEnd[] = "### ASCCONV END"; char *keyPosEnd = (char *)memmem(keyPos, csaLengthTrim, keyStrEnd, strlen(keyStrEnd)); if ((keyPosEnd) && ((keyPosEnd - keyPos) < csaLengthTrim)) //ignore binary data at end csaLengthTrim = (int)(keyPosEnd - keyPos); #endif char keyStrLns[] = "sKSpace.lPhaseEncodingLines"; csaAscii->phaseEncodingLines = readKey(keyStrLns, keyPos, csaLengthTrim); char keyStrUcImg[] = "sSliceArray.ucImageNumb"; csaAscii->existUcImageNumb = readKey(keyStrUcImg, keyPos, csaLengthTrim); char keyStrUcMode[] = "sSliceArray.ucMode"; csaAscii->ucMode = readKeyN1(keyStrUcMode, keyPos, csaLengthTrim); char keyStrBase[] = "sKSpace.lBaseResolution"; csaAscii->baseResolution = readKey(keyStrBase, keyPos, csaLengthTrim); char keyStrInterp[] = "sKSpace.uc2DInterpolation"; csaAscii->interp = readKey(keyStrInterp, keyPos, csaLengthTrim); char keyStrPF[] = "sKSpace.ucPhasePartialFourier"; csaAscii->partialFourier = readKey(keyStrPF, keyPos, csaLengthTrim); char keyStrES[] = "sFastImaging.lEchoSpacing"; csaAscii->echoSpacing = readKey(keyStrES, keyPos, csaLengthTrim); char keyStrDS[] = "sDiffusion.dsScheme"; csaAscii->difBipolar = readKey(keyStrDS, keyPos, csaLengthTrim); if (csaAscii->difBipolar == 0) { char keyStrROM[] = "ucReadOutMode"; csaAscii->difBipolar = readKey(keyStrROM, keyPos, csaLengthTrim); if ((csaAscii->difBipolar >= 1) && (csaAscii->difBipolar <= 2)) { //E11C Siemens/CMRR dsScheme: 1=bipolar, 2=unipolar, B17 CMRR ucReadOutMode 0x1=monopolar, 0x2=bipolar csaAscii->difBipolar = 3 - csaAscii->difBipolar; } //https://github.com/poldracklab/fmriprep/pull/1359#issuecomment-448379329 } char keyStrAF[] = "sPat.lAccelFactPE"; csaAscii->parallelReductionFactorInPlane = readKey(keyStrAF, keyPos, csaLengthTrim); char keyStrAF3D[] = "sPat.lAccelFact3D"; csaAscii->accelFact3D = readKey(keyStrAF3D, keyPos, csaLengthTrim); char keyStrRef[] = "sPat.lRefLinesPE"; csaAscii->refLinesPE = readKey(keyStrRef, keyPos, csaLengthTrim); char keyStrCombineMode[] = "ucCoilCombineMode"; csaAscii->combineMode = readKeyN1(keyStrCombineMode, keyPos, csaLengthTrim); //BIDS CoilCombinationMethod <- Siemens 'Coil Combine Mode' CSA ucCoilCombineMode 1 = Sum of Squares, 2 = Adaptive Combine, //printf("CoilCombineMode %d\n", csaAscii->combineMode); char keyStrPATMode[] = "sPat.ucPATMode"; //n.b. field set even if PAT not enabled, e.g. will list SENSE for a R-factor of 1 csaAscii->patMode = readKeyN1(keyStrPATMode, keyPos, csaLengthTrim); char keyStrucMTC[] = "sPrepPulses.ucMTC"; //n.b. field set even if PAT not enabled, e.g. will list SENSE for a R-factor of 1 csaAscii->ucMTC = readKeyN1(keyStrucMTC, keyPos, csaLengthTrim); //printf("PATMODE %d\n", csaAscii->patMode); //char keyStrETD[] = "sFastImaging.lEchoTrainDuration"; //*echoTrainDuration = readKey(keyStrETD, keyPos, csaLengthTrim); //char keyStrEF[] = "sFastImaging.lEPIFactor"; //ret = readKey(keyStrEF, keyPos, csaLengthTrim); char keyStrCoil[] = "sCoilElementID.tCoilID"; readKeyStr(keyStrCoil, keyPos, csaLengthTrim, coilID); char keyStrCI[] = "sProtConsistencyInfo.tMeasuredBaselineString"; readKeyStr(keyStrCI, keyPos, csaLengthTrim, consistencyInfo); char keyStrCS[] = "sCoilSelectMeas.sCoilStringForConversion"; readKeyStr(keyStrCS, keyPos, csaLengthTrim, coilElements); char keyStrSeq[] = "tSequenceFileName"; readKeyStr(keyStrSeq, keyPos, csaLengthTrim, pulseSequenceDetails); char keyStrWipMemBlock[] = "sWipMemBlock.tFree"; readKeyStr(keyStrWipMemBlock, keyPos, csaLengthTrim, wipMemBlock); char keyStrPn[] = "tProtocolName"; readKeyStr(keyStrPn, keyPos, csaLengthTrim, protocolName); char keyStrTE0[] = "alTE[0]"; csaAscii->TE0 = readKeyFloatNan(keyStrTE0, keyPos, csaLengthTrim); char keyStrTE1[] = "alTE[1]"; csaAscii->TE1 = readKeyFloatNan(keyStrTE1, keyPos, csaLengthTrim); //read ALL alTI[*] values for (int k = 0; k < kMaxWipFree; k++) csaAscii->alTI[k] = NAN; char keyStrTiFree[] = "alTI["; //check if ANY csaAscii.alFree tags exist char *keyPosTi = (char *)memmem(keyPos, csaLengthTrim, keyStrTiFree, strlen(keyStrTiFree)); if (keyPosTi) { for (int k = 0; k < kMaxWipFree; k++) { char txt[1024] = {""}; sprintf(txt, "%s%d]", keyStrTiFree, k); csaAscii->alTI[k] = readKeyFloatNan(txt, keyPos, csaLengthTrim); } } //read ALL csaAscii.alFree[*] values for (int k = 0; k < kMaxWipFree; k++) csaAscii->alFree[k] = 0.0; char keyStrAlFree[] = "sWipMemBlock.alFree["; //check if ANY csaAscii.alFree tags exist char *keyPosFree = (char *)memmem(keyPos, csaLengthTrim, keyStrAlFree, strlen(keyStrAlFree)); if (keyPosFree) { for (int k = 0; k < kMaxWipFree; k++) { char txt[1024] = {""}; sprintf(txt, "%s%d]", keyStrAlFree, k); csaAscii->alFree[k] = readKeyFloat(txt, keyPos, csaLengthTrim); } } //read ALL csaAscii.adFree[*] values for (int k = 0; k < kMaxWipFree; k++) csaAscii->adFree[k] = NAN; char keyStrAdFree[50]; strcpy(keyStrAdFree, "sWipMemBlock.adFree["); //char keyStrAdFree[] = "sWipMemBlock.adFree["; //check if ANY csaAscii.adFree tags exist keyPosFree = (char *)memmem(keyPos, csaLengthTrim, keyStrAdFree, strlen(keyStrAdFree)); if (!keyPosFree) { //"Wip" -> "WiP", modern -> old Siemens strcpy(keyStrAdFree, "sWiPMemBlock.adFree["); keyPosFree = (char *)memmem(keyPos, csaLengthTrim, keyStrAdFree, strlen(keyStrAdFree)); } if (keyPosFree) { for (int k = 0; k < kMaxWipFree; k++) { char txt[1024] = {""}; sprintf(txt, "%s%d]", keyStrAdFree, k); csaAscii->adFree[k] = readKeyFloatNan(txt, keyPos, csaLengthTrim); } } //read labelling plane char keyStrDThickness[] = "sRSatArray.asElm[1].dThickness"; csaAscii->dThickness = readKeyFloat(keyStrDThickness, keyPos, csaLengthTrim); if (csaAscii->dThickness > 0.0) { char keyStrUlShape[] = "sRSatArray.asElm[1].ulShape"; csaAscii->ulShape = readKeyFloat(keyStrUlShape, keyPos, csaLengthTrim); char keyStrSPositionDTra[] = "sRSatArray.asElm[1].sPosition.dTra"; csaAscii->sPositionDTra = readKeyFloat(keyStrSPositionDTra, keyPos, csaLengthTrim); char keyStrSNormalDTra[] = "sRSatArray.asElm[1].sNormal.dTra"; csaAscii->sNormalDTra = readKeyFloat(keyStrSNormalDTra, keyPos, csaLengthTrim); } //Read NEX number of averages char keyStrDAveragesDouble[] = "dAveragesDouble"; csaAscii->dAveragesDouble = readKeyFloat(keyStrDAveragesDouble, keyPos, csaLengthTrim); //read delay time char keyStrDelay[] = "lDelayTimeInTR"; csaAscii->delayTimeInTR = readKeyFloat(keyStrDelay, keyPos, csaLengthTrim); char keyStrOver[] = "sKSpace.dPhaseOversamplingForDialog"; csaAscii->phaseOversampling = readKeyFloat(keyStrOver, keyPos, csaLengthTrim); char keyStrPhase[] = "sKSpace.dPhaseResolution"; csaAscii->phaseResolution = readKeyFloat(keyStrPhase, keyPos, csaLengthTrim); char keyStrAmp[] = "sTXSPEC.asNucleusInfo[0].flReferenceAmplitude"; csaAscii->txRefAmp = readKeyFloat(keyStrAmp, keyPos, csaLengthTrim); //lower order shims: newer sequences char keyStrSh0[] = "sGRADSPEC.asGPAData[0].lOffsetX"; shimSetting[0] = readKeyFloat(keyStrSh0, keyPos, csaLengthTrim); char keyStrSh1[] = "sGRADSPEC.asGPAData[0].lOffsetY"; shimSetting[1] = readKeyFloat(keyStrSh1, keyPos, csaLengthTrim); char keyStrSh2[] = "sGRADSPEC.asGPAData[0].lOffsetZ"; shimSetting[2] = readKeyFloat(keyStrSh2, keyPos, csaLengthTrim); //lower order shims: older sequences char keyStrSh0s[] = "sGRADSPEC.lOffsetX"; if (shimSetting[0] == 0.0) shimSetting[0] = readKeyFloat(keyStrSh0s, keyPos, csaLengthTrim); char keyStrSh1s[] = "sGRADSPEC.lOffsetY"; if (shimSetting[1] == 0.0) shimSetting[1] = readKeyFloat(keyStrSh1s, keyPos, csaLengthTrim); char keyStrSh2s[] = "sGRADSPEC.lOffsetZ"; if (shimSetting[2] == 0.0) shimSetting[2] = readKeyFloat(keyStrSh2s, keyPos, csaLengthTrim); //higher order shims: older sequences char keyStrSh3[] = "sGRADSPEC.alShimCurrent[0]"; shimSetting[3] = readKeyFloat(keyStrSh3, keyPos, csaLengthTrim); char keyStrSh4[] = "sGRADSPEC.alShimCurrent[1]"; shimSetting[4] = readKeyFloat(keyStrSh4, keyPos, csaLengthTrim); char keyStrSh5[] = "sGRADSPEC.alShimCurrent[2]"; shimSetting[5] = readKeyFloat(keyStrSh5, keyPos, csaLengthTrim); char keyStrSh6[] = "sGRADSPEC.alShimCurrent[3]"; shimSetting[6] = readKeyFloat(keyStrSh6, keyPos, csaLengthTrim); char keyStrSh7[] = "sGRADSPEC.alShimCurrent[4]"; shimSetting[7] = readKeyFloat(keyStrSh7, keyPos, csaLengthTrim); } fclose(pFile); free(buffer); return; } // siemensCsaAscii() #endif //myReadAsciiCsa() #ifndef myDisableZLib //Uncomment next line to decode GE Protocol Data Block, for caveats see https://github.com/rordenlab/dcm2niix/issues/163 #define myReadGeProtocolBlock #endif #ifdef myReadGeProtocolBlock int geProtocolBlock(const char *filename, int geOffset, int geLength, int isVerbose, int *sliceOrder, int *viewOrder, int *mbAccel, int *nSlices, float *groupDelay, char ioptGE[]) { *sliceOrder = -1; *viewOrder = 0; *mbAccel = 0; *nSlices = 0; *groupDelay = 0.0; int ret = EXIT_FAILURE; if ((geOffset < 0) || (geLength < 20)) return ret; FILE *pFile = fopen(filename, "rb"); if (pFile == NULL) return ret; fseek(pFile, 0, SEEK_END); long lSize = ftell(pFile); if (lSize < (geOffset + geLength)) { fclose(pFile); return ret; } fseek(pFile, geOffset, SEEK_SET); uint8_t *pCmp = (uint8_t *)malloc(geLength); //uint8_t -> mz_uint8 if (pCmp == NULL) return ret; size_t result = fread(pCmp, 1, geLength, pFile); if ((int)result != geLength) return ret; int cmpSz = geLength; //http://www.forensicswiki.org/wiki/Gzip // always little endia! http://www.onicos.com/staff/iz/formats/gzip.html if (cmpSz < 20) return ret; if ((pCmp[0] != 31) || (pCmp[1] != 139) || (pCmp[2] != 8)) return ret; //check signature and deflate algorithm uint8_t flags = pCmp[3]; bool isFNAME = ((flags & 0x08) == 0x08); bool isFCOMMENT = ((flags & 0x10) == 0x10); uint32_t hdrSz = 10; if (isFNAME) { //skip null-terminated string FNAME for (; hdrSz < cmpSz; hdrSz++) if (pCmp[hdrSz] == 0) break; hdrSz++; } if (isFCOMMENT) { //skip null-terminated string COMMENT for (; hdrSz < cmpSz; hdrSz++) if (pCmp[hdrSz] == 0) break; hdrSz++; } uint32_t unCmpSz = ((uint32_t)pCmp[cmpSz - 4]) + ((uint32_t)pCmp[cmpSz - 3] << 8) + ((uint32_t)pCmp[cmpSz - 2] << 16) + ((uint32_t)pCmp[cmpSz - 1] << 24); //printf(">> %d %d %zu %zu %zu\n", isFNAME, isFCOMMENT, cmpSz, unCmpSz, hdrSz); z_stream s; memset(&s, 0, sizeof(z_stream)); #ifdef myDisableMiniZ #define MZ_DEFAULT_WINDOW_BITS 15 // Window bits #endif inflateInit2(&s, -MZ_DEFAULT_WINDOW_BITS); uint8_t *pUnCmp = (uint8_t *)malloc((size_t)unCmpSz); s.avail_out = unCmpSz; s.next_in = pCmp + hdrSz; s.avail_in = cmpSz - hdrSz - 8; s.next_out = (uint8_t *)pUnCmp; #ifdef myDisableMiniZ ret = inflate(&s, Z_SYNC_FLUSH); if (ret != Z_STREAM_END) { free(pUnCmp); return EXIT_FAILURE; } #else ret = mz_inflate(&s, MZ_SYNC_FLUSH); if (ret != MZ_STREAM_END) { free(pUnCmp); return EXIT_FAILURE; } #endif //https://groups.google.com/forum/#!msg/comp.protocols.dicom/mxnCkv8A-i4/W_uc6SxLwHQJ // DISCOVERY MR750 / 24\MX\MR Software release:DV24.0_R01_1344.a) are now storing an XML file // if ((pUnCmp[0] == '<') && (pUnCmp[1] == '?')) printWarning("New XML-based GE Protocol Block is not yet supported: please report issue on dcm2niix Github page\n"); char keyStrSO[] = "SLICEORDER"; *sliceOrder = readKeyN1(keyStrSO, (char *)pUnCmp, unCmpSz); char keyStrVO[] = "VIEWORDER"; *viewOrder = readKey(keyStrVO, (char *)pUnCmp, unCmpSz); char keyStrMB[] = "MBACCEL"; *mbAccel = readKey(keyStrMB, (char *)pUnCmp, unCmpSz); char keyStrNS[] = "NOSLC"; *nSlices = readKey(keyStrNS, (char *)pUnCmp, unCmpSz); char keyStrDELACQ[] = "DELACQ"; char DELACQ[100]; readKeyStr(keyStrDELACQ, (char *)pUnCmp, unCmpSz, DELACQ); char keyStrGD[] = "DELACQNOAV"; *groupDelay = readKeyFloat(keyStrGD, (char *)pUnCmp, unCmpSz); char keyStrIOPT[] = "IOPT"; readKeyStr(keyStrIOPT, (char *)pUnCmp, unCmpSz, ioptGE); char PHASEDELAYS1[10000]; char keyStrPHASEDELAYS1[] = "PHASEDELAYS1"; readKeyStr(keyStrPHASEDELAYS1, (char *)pUnCmp, unCmpSz, PHASEDELAYS1); if (strstr(ioptGE, "MPh") != NULL) { if (strcmp(DELACQ, "Minimum") == 0) { *groupDelay = 0; } if (strstr(ioptGE, "MPhVar") != NULL) { *groupDelay = -1; // Multiphase EPI with Variable Delays // TO-DO // NEED TO rescue ALL_PHASES case (=Group delay) // IF values in PHASEDELAYS1 are all same except 1st value (0), this case should be same as Group Delay } } if (isVerbose > 1) { printMessage("GE Protocol Block %s bytes %d compressed, %d uncompressed @ %d\n", filename, geLength, unCmpSz, geOffset); printMessage(" ViewOrder %d SliceOrder %d\n", *viewOrder, *sliceOrder); printMessage("%s\n", pUnCmp); } free(pUnCmp); return EXIT_SUCCESS; } #endif //myReadGeProtocolBlock() void json_Str(FILE *fp, const char *sLabel, char *sVal) { // issue131,425 if (strlen(sVal) < 1) return; unsigned char sValEsc[2048] = {""}; unsigned char *iVal = (unsigned char *)sVal; int o = 0; for (int i = 0; i < strlen(sVal); i++) { //escape double quote (") and Backslash if ((sVal[i] == '"') || (sVal[i] == '\\')) { //escape double quotes and back slash sValEsc[o] = '\\'; o++; } if ((sVal[i] >= 0x01) && (sVal[i] <= 0x07)) continue; //control characters like "bell" //http://dicom.nema.org/medical/dicom/current/output/html/part05.html //0x08 Backspace is replaced with \b //0x09 Tab is replaced with \t //0x0A Newline is replaced with \n //0x0B Escape ?? //0x0C Form feed is replaced with \f //0x0D Carriage return is replaced with \r if ((sVal[i] >= 0x08) && (sVal[i] <= 0x0D)) { sValEsc[o] = '\\'; o++; if (sVal[i] == 0x08) sValEsc[o] = 'b'; if (sVal[i] == 0x09) sValEsc[o] = '9'; if (sVal[i] == 0x0A) sValEsc[o] = 'n'; if (sVal[i] == 0x0B) sValEsc[o] = '\\'; if (sVal[i] == 0x0C) sValEsc[o] = 'f'; if (sVal[i] == 0x0D) sValEsc[o] = 'r'; o++; continue; } //https://stackoverflow.com/questions/4059775/convert-iso-8859-1-strings-to-utf-8-in-c-c if (iVal[i] >= 128) { sValEsc[o] = 0xc2 + (iVal[i] > 0xbf); o++; sValEsc[o] = (iVal[i] & 0x3f) + 0x80; } else { sValEsc[o] = sVal[i]; } o++; } sValEsc[o] = '\0'; fprintf(fp, sLabel, sValEsc); } //json_Str void json_FloatNotNan(FILE *fp, const char *sLabel, float sVal) { if (isnan(sVal)) return; fprintf(fp, sLabel, sVal); } //json_Float void print_FloatNotNan(const char *sLabel, int iVal, float sVal) { if (isnan(sVal)) return; printMessage(sLabel, iVal, sVal); } //json_Float void json_Float(FILE *fp, const char *sLabel, float sVal) { if (!isfinite(sVal)) { //isfinite() defined in C99 // https://github.com/bids-standard/bids-2-devel/issues/12 printWarning(sLabel, sVal); return; } if (sVal <= 0.0) return; fprintf(fp, sLabel, sVal); } //json_Float void json_Bool(FILE *fp, const char *sLabel, int sVal) { // json_Str(fp, "\t\"MTState\"", d.mtState); //n.b. in JSON, true and false are lower case, whereas in Python they are capitalized // only print 0 and >=1 for false and true, ignore negative values if (sVal == 0) fprintf(fp, sLabel, "false"); if (sVal > 0) fprintf(fp, sLabel, "true"); } //json_Bool void rescueProtocolName(struct TDICOMdata *d, const char *filename) { //tools like gdcmanon strip protocol name (0018,1030) but for Siemens we can recover it from CSASeriesHeaderInfo (0029,1020) if ((d->manufacturer != kMANUFACTURER_SIEMENS) || (d->CSA.SeriesHeader_offset < 1) || (d->CSA.SeriesHeader_length < 1)) return; if (strlen(d->protocolName) > 0) return; #ifdef myReadAsciiCsa float shimSetting[8]; char protocolName[kDICOMStrLarge], fmriExternalInfo[kDICOMStrLarge], coilID[kDICOMStrLarge], consistencyInfo[kDICOMStrLarge], coilElements[kDICOMStrLarge], pulseSequenceDetails[kDICOMStrLarge], wipMemBlock[kDICOMStrLarge]; TCsaAscii csaAscii; siemensCsaAscii(filename, &csaAscii, d->CSA.SeriesHeader_offset, d->CSA.SeriesHeader_length, shimSetting, coilID, consistencyInfo, coilElements, pulseSequenceDetails, fmriExternalInfo, protocolName, wipMemBlock); if (strlen(protocolName) >= kDICOMStr) protocolName[kDICOMStr - 1] = 0; strcpy(d->protocolName, protocolName); #endif } void nii_SaveBIDSX(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char *filename, struct TDTI4D *dti4D) { //https://docs.google.com/document/d/1HFUkAEE-pB-angVcYe6pf_-fVf4sCpOHKesUvfb8Grc/edit# // Generate Brain Imaging Data Structure (BIDS) info // sidecar JSON file (with the same filename as the .nii.gz file, but with .json extension). // we will use %g for floats since exponents are allowed // we will not set the locale, so decimal separator is always a period, as required // https://www.ietf.org/rfc/rfc4627.txt if ((!opts.isCreateBIDS) && (opts.isOnlyBIDS)) printMessage("Input-only mode: no BIDS/NIfTI output generated for '%s'\n", pathoutname); if (!opts.isCreateBIDS) return; char txtname[2048] = {""}; strcpy(txtname, pathoutname); strcat(txtname, ".json"); FILE *fp = fopen(txtname, "w"); fprintf(fp, "{\n"); switch (d.modality) { case kMODALITY_CR: fprintf(fp, "\t\"Modality\": \"CR\",\n"); break; case kMODALITY_CT: fprintf(fp, "\t\"Modality\": \"CT\",\n"); break; case kMODALITY_MR: fprintf(fp, "\t\"Modality\": \"MR\",\n"); break; case kMODALITY_PT: fprintf(fp, "\t\"Modality\": \"PT\",\n"); break; case kMODALITY_US: fprintf(fp, "\t\"Modality\": \"US\",\n"); break; }; //attempt to determine BIDS sequence type /*(0018,0024) SequenceName ep_b: dwi epfid2d: perf epfid2d: bold epfid3d1_15: swi epse2d: dwi (when b-vals specified) epse2d: fmap (spin echo, e.g. TOPUP, nb could also be extra B=0 for DWI sequence) fl2d: localizer fl3d1r_t: angio fl3d1r_tm: angio fl3d1r: angio fl3d1r: swi fl3d1r: ToF fm2d: fmap (gradient echo, e.g. FUGUE) spc3d: T2 spcir: flair (dark fluid) spcR: PD tfl3d: T1 tfl_me3d5_16ns: T1 (ME-MPRAGE) tir2d: flair tse2d: PD tse2d: T2 tse3d: T2*/ /* if (d.manufacturer == kMANUFACTURER_SIEMENS) { #define kLabel_UNKNOWN 0 #define kLabel_T1w 1 #define kLabel_T2w 2 #define kLabel_bold 3 #define kLabel_perf 4 #define kLabel_dwi 5 #define kLabel_fieldmap 6 int iLabel = kLabel_UNKNOWN; if (d.CSA.numDti > 1) iLabel = kLabel_dwi; //if ((iLabel == kLabel_UNKNOWN) && (d.is2DAcq)) //if ((iLabel == kLabel_UNKNOWN) && (d.is3DAcq)) if (iLabel != kLabel_UNKNOWN) { char tLabel[20] = {""}; if (iLabel == kLabel_dwi) strcat (tLabel,"dwi"); json_Str(fp, "\t\"ModalityLabel\": \"%s\",\n", tLabel); } }*/ //report vendor if (d.fieldStrength > 0.0) fprintf(fp, "\t\"MagneticFieldStrength\": %g,\n", d.fieldStrength); //Imaging Frequency (0018,0084) can be useful https://support.brainvoyager.com/brainvoyager/functional-analysis-preparation/29-pre-processing/78-epi-distortion-correction-echo-spacing-and-bandwidth // however, UIH stores 128176031 not 128.176031 https://github.com/rordenlab/dcm2niix/issues/225 if (d.imagingFrequency < 9000000) json_Float(fp, "\t\"ImagingFrequency\": %g,\n", d.imagingFrequency); switch (d.manufacturer) { case kMANUFACTURER_BRUKER: fprintf(fp, "\t\"Manufacturer\": \"Bruker\",\n"); break; case kMANUFACTURER_SIEMENS: fprintf(fp, "\t\"Manufacturer\": \"Siemens\",\n"); break; case kMANUFACTURER_GE: fprintf(fp, "\t\"Manufacturer\": \"GE\",\n"); break; case kMANUFACTURER_PHILIPS: fprintf(fp, "\t\"Manufacturer\": \"Philips\",\n"); break; case kMANUFACTURER_TOSHIBA: fprintf(fp, "\t\"Manufacturer\": \"Toshiba\",\n"); break; case kMANUFACTURER_CANON: fprintf(fp, "\t\"Manufacturer\": \"Canon\",\n"); break; case kMANUFACTURER_MEDISO: fprintf(fp, "\t\"Manufacturer\": \"Mediso\",\n"); break; case kMANUFACTURER_HITACHI: fprintf(fp, "\t\"Manufacturer\": \"Hitachi\",\n"); break; case kMANUFACTURER_UIH: 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.internalepiVersionGE == 1) fprintf(fp, "\t\"InternalPulseSequenceName\": \"EPI\",\n"); if (d.internalepiVersionGE == 2) fprintf(fp, "\t\"InternalPulseSequenceName\": \"EPI2\",\n"); //GE pepolar with phase encoding direction reversed within a series //if (d.epiVersionGE >= kGE_EPI_PEPOLAR_FWD) // printWarning("Validate results for custom ABCD GE pepolar sequence\n"); json_Str(fp, "\t\"ManufacturersModelName\": \"%s\",\n", d.manufacturersModelName); json_Str(fp, "\t\"InstitutionName\": \"%s\",\n", d.institutionName); json_Str(fp, "\t\"InstitutionalDepartmentName\": \"%s\",\n", d.institutionalDepartmentName); json_Str(fp, "\t\"InstitutionAddress\": \"%s\",\n", d.institutionAddress); json_Str(fp, "\t\"DeviceSerialNumber\": \"%s\",\n", d.deviceSerialNumber); json_Str(fp, "\t\"StationName\": \"%s\",\n", d.stationName); if (!opts.isAnonymizeBIDS) { json_Str(fp, "\t\"SeriesInstanceUID\": \"%s\",\n", d.seriesInstanceUID); json_Str(fp, "\t\"StudyInstanceUID\": \"%s\",\n", d.studyInstanceUID); json_Str(fp, "\t\"ReferringPhysicianName\": \"%s\",\n", d.referringPhysicianName); json_Str(fp, "\t\"StudyID\": \"%s\",\n", d.studyID); //Next lines directly reveal patient identity json_Str(fp, "\t\"PatientName\": \"%s\",\n", d.patientName); json_Str(fp, "\t\"PatientID\": \"%s\",\n", d.patientID); json_Str(fp, "\t\"AccessionNumber\": \"%s\",\n", d.accessionNumber); if (strlen(d.patientBirthDate) == 8) { //DICOM DA YYYYMMDD -> ISO 8601 "YYYY-MM-DD" int ayear, amonth, aday; sscanf(d.patientBirthDate, "%4d%2d%2d", &ayear, &amonth, &aday); fprintf(fp, "\t\"PatientBirthDate\": "); fprintf(fp, (ayear >= 0 && ayear <= 9999) ? "\"%4d" : "\"%+4d", ayear); fprintf(fp, "-%02d-%02d\",\n", amonth, aday); } if (d.patientSex != '?') fprintf(fp, "\t\"PatientSex\": \"%c\",\n", d.patientSex); json_Float(fp, "\t\"PatientWeight\": %g,\n", d.patientWeight); //d.patientBirthDate //convert from DICOM YYYYMMDD to JSON //d.patientAge //4-digit Age String: nnnD, nnnW, nnnM, nnnY; } json_Str(fp, "\t\"BodyPartExamined\": \"%s\",\n", d.bodyPartExamined); json_Str(fp, "\t\"PatientPosition\": \"%s\",\n", d.patientOrient); // 0018,5100 = PatientPosition in DICOM json_Str(fp, "\t\"ProcedureStepDescription\": \"%s\",\n", d.procedureStepDescription); json_Str(fp, "\t\"SoftwareVersions\": \"%s\",\n", d.softwareVersions); //json_Str(fp, "\t\"MRAcquisitionType\": \"%s\",\n", d.mrAcquisitionType); if (d.is2DAcq) fprintf(fp, "\t\"MRAcquisitionType\": \"2D\",\n"); if (d.is3DAcq) fprintf(fp, "\t\"MRAcquisitionType\": \"3D\",\n"); json_Str(fp, "\t\"SeriesDescription\": \"%s\",\n", d.seriesDescription); json_Str(fp, "\t\"ProtocolName\": \"%s\",\n", d.protocolName); json_Str(fp, "\t\"ScanningSequence\": \"%s\",\n", d.scanningSequence); 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); if (strlen(d.imageType) > 0) { fprintf(fp, "\t\"ImageType\": [\""); bool isSep = false; for (size_t i = 0; i < strlen(d.imageType); i++) { if (d.imageType[i] != '_') { if (isSep) fprintf(fp, "\", \""); isSep = false; fprintf(fp, "%c", d.imageType[i]); } else isSep = true; } if ((d.isHasPhase) && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_PHASE_") == NULL))) fprintf(fp, "\", \"PHASE"); //"_IMAGINARY_" if ((d.isHasReal) && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_REAL_") == NULL))) fprintf(fp, "\", \"REAL"); if ((d.isHasImaginary) && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_IMAGINARY_") == NULL))) fprintf(fp, "\", \"IMAGINARY"); if ((d.isRealIsPhaseMapHz)) // && ((d.manufacturer == kMANUFACTURER_GE) || (strstr(d.imageType, "_IMAGINARY_") == NULL)) ) fprintf(fp, "\", \"FIELDMAPHZ"); fprintf(fp, "\"],\n"); } if (d.isDerived) //DICOM is derived image or non-spatial file (sounds, etc) fprintf(fp, "\t\"RawImage\": false,\n"); if (d.seriesNum > 0) fprintf(fp, "\t\"SeriesNumber\": %ld,\n", d.seriesNum); //Chris Gorgolewski: BIDS standard specifies ISO8601 date-time format (Example: 2016-07-06T12:49:15.679688) //Lines below directly save DICOM values if (d.acquisitionTime > 0.0 && d.acquisitionDate > 0.0) { long acquisitionDate = d.acquisitionDate; double acquisitionTime = d.acquisitionTime; char acqDateTimeBuf[64]; //snprintf(acqDateTimeBuf, sizeof acqDateTimeBuf, "%+08ld%+08f", acquisitionDate, acquisitionTime); snprintf(acqDateTimeBuf, sizeof acqDateTimeBuf, "%+08ld%+013.5f", acquisitionDate, acquisitionTime); //CR 20170404 add zero pad so 1:23am appears as +012300.00000 not +12300.00000 //printMessage("acquisitionDateTime %s\n",acqDateTimeBuf); int ayear, amonth, aday, ahour, amin; double asec; int count = 0; sscanf(acqDateTimeBuf, "%5d%2d%2d%3d%2d%lf%n", &ayear, &amonth, &aday, &ahour, &amin, &asec, &count); //CR 20170404 %lf not %f for double precision //printf("-%02d-%02dT%02d:%02d:%02.6f\",\n", amonth, aday, ahour, amin, asec); if (count) { // ISO 8601 specifies a sign must exist for distant years. //report time of the day only format, https://www.cs.tut.fi/~jkorpela/iso8601.html fprintf(fp, "\t\"AcquisitionTime\": \"%02d:%02d:%02.6f\",\n", ahour, amin, asec); //report date and time together if (!opts.isAnonymizeBIDS) { fprintf(fp, "\t\"AcquisitionDateTime\": "); fprintf(fp, (ayear >= 0 && ayear <= 9999) ? "\"%4d" : "\"%+4d", ayear); fprintf(fp, "-%02d-%02dT%02d:%02d:%02.6f\",\n", amonth, aday, ahour, amin, asec); } } //if (count) } //if acquisitionTime and acquisitionDate recorded // if (d.acquisitionTime > 0.0) fprintf(fp, "\t\"AcquisitionTime\": %f,\n", d.acquisitionTime ); // if (d.acquisitionDate > 0.0) fprintf(fp, "\t\"AcquisitionDate\": %8.0f,\n", d.acquisitionDate ); if (d.acquNum > 0) fprintf(fp, "\t\"AcquisitionNumber\": %d,\n", d.acquNum); json_Str(fp, "\t\"ImageComments\": \"%s\",\n", d.imageComments); json_Str(fp, "\t\"ConversionComments\": \"%s\",\n", opts.imageComments); //if conditionals: the following values are required for DICOM MRI, but not available for CT json_Float(fp, "\t\"TriggerDelayTime\": %g,\n", d.triggerDelayTime); if (d.RWVScale != 0) { fprintf(fp, "\t\"PhilipsRWVSlope\": %g,\n", d.RWVScale); fprintf(fp, "\t\"PhilipsRWVIntercept\": %g,\n", d.RWVIntercept); } if ((!d.isScaleVariesEnh) && (d.intenScalePhilips != 0) && (d.manufacturer == kMANUFACTURER_PHILIPS)) { //for details, see PhilipsPrecise() fprintf(fp, "\t\"PhilipsRescaleSlope\": %g,\n", d.intenScale); fprintf(fp, "\t\"PhilipsRescaleIntercept\": %g,\n", d.intenIntercept); fprintf(fp, "\t\"PhilipsScaleSlope\": %g,\n", d.intenScalePhilips); fprintf(fp, "\t\"UsePhilipsFloatNotDisplayScaling\": %d,\n", opts.isPhilipsFloatNotDisplayScaling); } //https://bids-specification--622.org.readthedocs.build/en/622/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#case-3-direct-field-mapping if ((d.isRealIsPhaseMapHz) && (d.isHasReal)) fprintf(fp, "\t\"Units\": \"Hz\",\n"); // //PET ISOTOPE MODULE ATTRIBUTES json_Str(fp, "\t\"Radiopharmaceutical\": \"%s\",\n", d.radiopharmaceutical); json_Float(fp, "\t\"RadionuclidePositronFraction\": %g,\n", d.radionuclidePositronFraction); json_Float(fp, "\t\"RadionuclideTotalDose\": %g,\n", d.radionuclideTotalDose); json_Float(fp, "\t\"RadionuclideHalfLife\": %g,\n", d.radionuclideHalfLife); json_Float(fp, "\t\"DoseCalibrationFactor\": %g,\n", d.doseCalibrationFactor); json_Float(fp, "\t\"IsotopeHalfLife\": %g,\n", d.ecat_isotope_halflife); json_Float(fp, "\t\"Dosage\": %g,\n", d.ecat_dosage); json_Str(fp, "\t\"ConvolutionKernel\": \"%s\",\n", d.convolutionKernel); json_Str(fp, "\t\"Units\": \"%s\",\n", d.unitsPT); //https://github.com/bids-standard/bids-specification/pull/773 json_Str(fp, "\t\"DecayCorrection\": \"%s\",\n", d.decayCorrection); json_Str(fp, "\t\"AttenuationCorrectionMethod\": \"%s\",\n", d.attenuationCorrectionMethod); json_Str(fp, "\t\"ReconstructionMethod\": \"%s\",\n", d.reconstructionMethod); //json_Float(fp, "\t\"DecayFactor\": %g,\n", d.decayFactor); if (dti4D->decayFactor[0] >= 0.0) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 fprintf(fp, "\t\"DecayFactor\": [\n"); for (int i = 0; i < h->dim[4]; i++) { if (i != 0) fprintf(fp, ",\n"); if (dti4D->decayFactor[i] < 0) break; fprintf(fp, "\t\t%g", dti4D->decayFactor[i]); } fprintf(fp, "\t],\n"); } if (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) fprintf(fp, ",\n"); if (dti4D->volumeOnsetTime[i] < 0) break; fprintf(fp, "\t\t%g", dti4D->volumeOnsetTime[i]); } fprintf(fp, "\t],\n"); } if (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) fprintf(fp, ",\n"); if (dti4D->frameDuration[i] < 0) break; if ((isSameFloatGE(dti4D->frameDuration[i], 0.0)) && (d.TR > 0.0)) fprintf(fp, "\t\t%g", d.TR / 1000.0); // from 0018,1242 ms -> sec else fprintf(fp, "\t\t%g", dti4D->frameDuration[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); if ((d.TE > 0.0) && (d.isXRay)) fprintf(fp, "\t\"XRayExposure\": %g,\n", d.TE); //MRI parameters if (!d.isXRay) { //with CT scans, slice thickness often varies //beware, not used correctly by all vendors https://public.kitware.com/pipermail/insight-users/2005-September/014711.html json_Float(fp, "\t\"SliceThickness\": %g,\n", d.zThick); json_Float(fp, "\t\"SpacingBetweenSlices\": %g,\n", d.zSpacing); } json_Float(fp, "\t\"SAR\": %g,\n", d.SAR); if (d.numberOfAverages > 1.0) json_Float(fp, "\t\"NumberOfAverages\": %g,\n", d.numberOfAverages); if ((d.echoNum > 1) || (d.isMultiEcho)) 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.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); json_Float(fp, "\t\"RepetitionTimeExcitation\": %g,\n", dti4D->repetitionTimeExcitation); json_Float(fp, "\t\"RepetitionTimeInversion\": %g,\n", dti4D->repetitionTimeInversion); json_Bool(fp, "\t\"MTState\": %s,\n", d.mtState); // BIDS suggests 0018,9020 but Siemens V-series do not populate this, alternatives are CSA or (0018,0021) CS [SK\MTC\SP] //SpoilingState bool isSpoiled = (d.spoiling > kSPOILING_NONE); if ((d.spoiling == kSPOILING_UNKOWN) && (strstr(d.sequenceVariant, "\\SP") != NULL)) //BIDS suggests 0018,9016 Siemens V-series do not populate this, (0018,0021) CS [SK\MTC\SP] isSpoiled = true; if (isSpoiled) json_Bool(fp, "\t\"SpoilingState\": %s,\n", true); //Siemens reports SpoilingState but not SpoilingType if (d.spoiling == kSPOILING_RF) fprintf(fp, "\t\"SpoilingType\": \"RF\",\n"); if (d.spoiling == kSPOILING_GRADIENT) fprintf(fp, "\t\"SpoilingType\": \"GRADIENT\",\n"); if (d.spoiling == kSPOILING_RF_AND_GRADIENT) fprintf(fp, "\t\"SpoilingType\": \"COMBINED\",\n"); json_Float(fp, "\t\"InversionTime\": %g,\n", d.TI / 1000.0); json_Float(fp, "\t\"FlipAngle\": %g,\n", d.flipAngle); bool interp = false; //2D interpolation float phaseOversampling = 0.0; //n.b. https://neurostars.org/t/getting-missing-ge-information-required-by-bids-for-common-preprocessing/1357/7 json_Str(fp, "\t\"PhaseEncodingDirectionDisplayed\": \"%s\",\n", d.phaseEncodingDirectionDisplayedUIH); if ((d.manufacturer == kMANUFACTURER_GE) && (d.phaseEncodingGE != kGE_PHASE_ENCODING_POLARITY_UNKNOWN)) { //only set for GE if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) fprintf(fp, "\t\"PhaseEncodingPolarityGE\": \"Unflipped\",\n"); if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED) fprintf(fp, "\t\"PhaseEncodingPolarityGE\": \"Flipped\",\n"); } float delayTimeInTR = -0.01; float repetitionTimePreparation = 0.0; #ifdef myReadAsciiCsa if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (d.CSA.SeriesHeader_offset > 0) && (d.CSA.SeriesHeader_length > 0)) { float pf = 1.0f; //partial fourier float shimSetting[8]; char protocolName[kDICOMStrLarge], fmriExternalInfo[kDICOMStrLarge], coilID[kDICOMStrLarge], consistencyInfo[kDICOMStrLarge], coilElements[kDICOMStrLarge], pulseSequenceDetails[kDICOMStrLarge], wipMemBlock[kDICOMStrLarge]; TCsaAscii csaAscii; siemensCsaAscii(filename, &csaAscii, d.CSA.SeriesHeader_offset, d.CSA.SeriesHeader_length, shimSetting, coilID, consistencyInfo, coilElements, pulseSequenceDetails, fmriExternalInfo, protocolName, wipMemBlock); if ((d.phaseEncodingLines < 1) && (csaAscii.phaseEncodingLines > 0)) d.phaseEncodingLines = csaAscii.phaseEncodingLines; //if (d.phaseEncodingLines != csaAscii.phaseEncodingLines) //e.g. phaseOversampling // printWarning("PhaseEncodingLines reported in DICOM (%d) header does not match value CSA-ASCII (%d) %s\n", d.phaseEncodingLines, csaAscii.phaseEncodingLines, pathoutname); delayTimeInTR = csaAscii.delayTimeInTR; if ((d.isHasPhase) && (csaAscii.TE0 > 0.0) && (csaAscii.TE1 > 0.0)) { //issue400 //https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html json_Float(fp, "\t\"EchoTime1\": %g,\n", csaAscii.TE0 / 1000000.0); json_Float(fp, "\t\"EchoTime2\": %g,\n", csaAscii.TE1 / 1000000.0); } if (csaAscii.dAveragesDouble > 1.0) //*spcR_44ns fractional and independent of (0018,0083) DS NumberOfAverages, e.g. 0018,0083=2, dAveragesDouble = 1.4? json_Float(fp, "\t\"AveragesDouble\": %g,\n", csaAscii.dAveragesDouble); phaseOversampling = csaAscii.phaseOversampling; if (csaAscii.existUcImageNumb > 0) { if (d.CSA.protocolSliceNumber1 < 2) { printWarning("Assuming mosaics saved in reverse order due to 'sSliceArray.ucImageNumb'\n"); //never seen such an image in the wild.... sliceDir may need to be reversed } d.CSA.protocolSliceNumber1 = 2; } //ultra-verbose output for deciphering adFree/alFree/alTI values: /* if (opts.isVerbose > 1) { for (int i = 0; i < kMaxWipFree; i++) print_FloatNotNan("adFree[%d]=\t%g\n",i, csaAscii.adFree[i]); for (int i = 0; i < kMaxWipFree; i++) print_FloatNotNan("alFree[%d]=\t%g\n",i, csaAscii.alFree[i]); for (int i = 0; i < kMaxWipFree; i++) print_FloatNotNan("alTI[%d]=\t%g\n",i, csaAscii.alTI[i]); } //verbose */ bool isPCASL = false; bool isPASL = false; //ASL specific tags - 2D pCASL Danny J.J. Wang http://www.loft-lab.org if ((strstr(pulseSequenceDetails, "ep2d_pcasl")) || (strstr(pulseSequenceDetails, "ep2d_pcasl_UI_PHC"))) { isPCASL = true; repetitionTimePreparation = d.TR; json_FloatNotNan(fp, "\t\"LabelOffset\": %g,\n", csaAscii.adFree[1]); //mm json_FloatNotNan(fp, "\t\"PostLabelDelay\": %g,\n", csaAscii.adFree[2] * (1.0 / 1000000.0)); //usec -> sec json_FloatNotNan(fp, "\t\"NumRFBlocks\": %g,\n", csaAscii.adFree[3]); json_FloatNotNan(fp, "\t\"RFGap\": %g,\n", csaAscii.adFree[4] * (1.0 / 1000000.0)); //usec -> sec json_FloatNotNan(fp, "\t\"MeanGzx10\": %g,\n", csaAscii.adFree[10]); // mT/m json_FloatNotNan(fp, "\t\"PhiAdjust\": %g,\n", csaAscii.adFree[11]); // percent } //ASL specific tags - 3D pCASL Danny J.J. Wang http://www.loft-lab.org if (strstr(pulseSequenceDetails, "tgse_pcasl")) { isPCASL = true; repetitionTimePreparation = d.TR; json_FloatNotNan(fp, "\t\"RFGap\": %g,\n", csaAscii.adFree[4] * (1.0 / 1000000.0)); //usec -> sec json_FloatNotNan(fp, "\t\"MeanGzx10\": %g,\n", csaAscii.adFree[10]); // mT/m json_FloatNotNan(fp, "\t\"T1\": %g,\n", csaAscii.adFree[12] * (1.0 / 1000000.0)); //usec -> sec } //ASL specific tags - 2D PASL Siemens Product if (strstr(pulseSequenceDetails, "ep2d_pasl")) { isPASL = true; json_FloatNotNan(fp, "\t\"BolusDuration\": %g,\n", csaAscii.alTI[0] * (1.0 / 1000000.0)); //usec->sec json_FloatNotNan(fp, "\t\"InversionTime\": %g,\n", csaAscii.alTI[2] * (1.0 / 1000000.0)); //usec -> sec } //ASL specific tags - 3D PASL Siemens Product http://adni.loni.usc.edu/wp-content/uploads/2010/05/ADNI3_Basic_Siemens_Skyra_E11.pdf if (strstr(pulseSequenceDetails, "tgse_pasl")) { isPASL = true; json_FloatNotNan(fp, "\t\"BolusDuration\": %g,\n", csaAscii.alTI[0] * (1.0 / 1000000.0)); //usec->sec json_FloatNotNan(fp, "\t\"InversionTime\": %g,\n", csaAscii.alTI[2] * (1.0 / 1000000.0)); //usec -> sec //json_FloatNotNan(fp, "\t\"SaturationStopTime\": %g,\n", csaAscii.alTI[2] * (1.0/1000.0)); } //PASL http://www.pubmed.com/11746944 http://www.pubmed.com/21606572 if (strstr(pulseSequenceDetails, "ep2d_fairest")) { isPASL = true; json_FloatNotNan(fp, "\t\"PostInversionDelay\": %g,\n", csaAscii.adFree[2] * (1.0 / 1000.0)); //usec->sec json_FloatNotNan(fp, "\t\"PostLabelDelay\": %g,\n", csaAscii.adFree[4] * (1.0 / 1000.0)); //usec -> sec } //ASL specific tags - Oxford (Thomas OKell) bool isOxfordASL = false; if (strstr(pulseSequenceDetails, "to_ep2d_VEPCASL")) { //Oxford 2D pCASL isOxfordASL = true; isPCASL = true; repetitionTimePreparation = d.TR; json_FloatNotNan(fp, "\t\"InversionTime\": %g,\n", csaAscii.alTI[2] * (1.0 / 1000000.0)); //ms->sec json_FloatNotNan(fp, "\t\"BolusDuration\": %g,\n", csaAscii.alTI[0] * (1.0 / 1000000.0)); //usec -> sec json_Float(fp, "\t\"TagRFFlipAngle\": %g,\n", csaAscii.alFree[4]); json_Float(fp, "\t\"TagRFDuration\": %g,\n", csaAscii.alFree[5] / 1000000.0); //usec -> sec json_Float(fp, "\t\"TagRFSeparation\": %g,\n", csaAscii.alFree[6] / 1000000.0); //usec -> sec json_FloatNotNan(fp, "\t\"MeanTagGradient\": %g,\n", csaAscii.adFree[0]); //mTm json_FloatNotNan(fp, "\t\"TagGradientAmplitude\": %g,\n", csaAscii.adFree[1]); //mTm json_Float(fp, "\t\"TagDuration\": %g,\n", csaAscii.alFree[9] / 1000.0); //ms -> sec json_Float(fp, "\t\"MaximumT1Opt\": %g,\n", csaAscii.alFree[10] / 1000.0); //ms -> sec //report post label delay int nPLD = 0; bool isValid = true; //detect gaps in PLD array: If user sets PLD1=250, PLD2=0 PLD3=375 only PLD1 was acquired for (int k = 11; k < 31; k++) { if ((isnan(csaAscii.alFree[k])) || (csaAscii.alFree[k] <= 0.0)) isValid = false; if (isValid) nPLD++; } //for k if (nPLD > 0) { // record PostLabelDelays, these are listed as "PLD0","PLD1",etc in PDF fprintf(fp, "\t\"InitialPostLabelDelay\": [\n"); //https://docs.google.com/document/d/15tnn5F10KpgHypaQJNNGiNKsni9035GtDqJzWqkkP6c/edit# for (int i = 0; i < nPLD; i++) { if (i != 0) fprintf(fp, ",\n"); fprintf(fp, "\t\t%g", csaAscii.alFree[i + 11] / 1000.0); //ms -> sec } fprintf(fp, "\t],\n"); } /*isValid = true; //detect gaps in PLD array: If user sets PLD1=250, PLD2=0 PLD3=375 only PLD1 was acquired for (int k = 11; k < 31; k++) { if (isValid) { char newstr[256]; sprintf(newstr, "\t\"PLD%d\": %%g,\n", k-11); json_Float(fp, newstr, csaAscii.alFree[k]/ 1000.0); //ms -> sec if (csaAscii.alFree[k] <= 0.0) isValid = false; }//isValid } //for k */ for (int k = 3; k < 11; k++) { //vessel locations char newstr[256]; sprintf(newstr, "\t\"sWipMemBlockAdFree%d\": %%g,\n", k); //issue483: sWipMemBlock.AdFree -> sWipMemBlockAdFree json_FloatNotNan(fp, newstr, csaAscii.adFree[k]); } } if (strstr(pulseSequenceDetails, "jw_tgse_VEPCASL")) { //Oxford 3D pCASL isPCASL = true; isOxfordASL = true; json_Float(fp, "\t\"TagRFFlipAngle\": %g,\n", csaAscii.alFree[6]); json_Float(fp, "\t\"TagRFDuration\": %g,\n", csaAscii.alFree[7] / 1000000.0); //usec -> sec json_Float(fp, "\t\"TagRFSeparation\": %g,\n", csaAscii.alFree[8] / 1000000.0); //usec -> sec json_Float(fp, "\t\"MaximumT1Opt\": %g,\n", csaAscii.alFree[9] / 1000.0); //ms -> sec json_Float(fp, "\t\"Tag0\": %g,\n", csaAscii.alFree[10] / 1000.0); //DelayTimeInTR usec -> sec json_Float(fp, "\t\"Tag1\": %g,\n", csaAscii.alFree[11] / 1000.0); //DelayTimeInTR usec -> sec json_Float(fp, "\t\"Tag2\": %g,\n", csaAscii.alFree[12] / 1000.0); //DelayTimeInTR usec -> sec json_Float(fp, "\t\"Tag3\": %g,\n", csaAscii.alFree[13] / 1000.0); //DelayTimeInTR usec -> sec int nPLD = 0; bool isValid = true; //detect gaps in PLD array: If user sets PLD1=250, PLD2=0 PLD3=375 only PLD1 was acquired for (int k = 30; k < 38; k++) { if ((isnan(csaAscii.alFree[k])) || (csaAscii.alFree[k] <= 0.0)) isValid = false; if (isValid) nPLD++; } //for k if (nPLD > 0) { // record PostLabelDelays, these are listed as "PLD0","PLD1",etc in PDF fprintf(fp, "\t\"InitialPostLabelDelay\": [\n"); //https://docs.google.com/document/d/15tnn5F10KpgHypaQJNNGiNKsni9035GtDqJzWqkkP6c/edit# for (int i = 0; i < nPLD; i++) { if (i != 0) fprintf(fp, ",\n"); fprintf(fp, "\t\t%g", csaAscii.alFree[i + 30] / 1000.0); //ms -> sec } fprintf(fp, "\t],\n"); } /* json_Float(fp, "\t\"PLD0\": %g,\n", csaAscii.alFree[30]/1000.0); json_Float(fp, "\t\"PLD1\": %g,\n", csaAscii.alFree[31]/1000.0); json_Float(fp, "\t\"PLD2\": %g,\n", csaAscii.alFree[32]/1000.0); json_Float(fp, "\t\"PLD3\": %g,\n", csaAscii.alFree[33]/1000.0); json_Float(fp, "\t\"PLD4\": %g,\n", csaAscii.alFree[34]/1000.0); json_Float(fp, "\t\"PLD5\": %g,\n", csaAscii.alFree[35]/1000.0); */ } if (isOxfordASL) { //properties common to 2D and 3D ASL //labelling plane fprintf(fp, "\t\"TagPlaneDThickness\": %g,\n", csaAscii.dThickness); fprintf(fp, "\t\"TagPlaneUlShape\": %g,\n", csaAscii.ulShape); fprintf(fp, "\t\"TagPlaneSPositionDTra\": %g,\n", csaAscii.sPositionDTra); fprintf(fp, "\t\"TagPlaneSNormalDTra\": %g,\n", csaAscii.sNormalDTra); } if (isPCASL) fprintf(fp, "\t\"ArterialSpinLabelingType\": \"PCASL\",\n"); if (isPASL) fprintf(fp, "\t\"ArterialSpinLabelingType\": \"PASL\",\n"); //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 if (csaAscii.partialFourier == 1) pf = 0.5; // 4/8 if (csaAscii.partialFourier == 2) pf = 0.625; // 5/8 if (csaAscii.partialFourier == 4) pf = 0.75; if (csaAscii.partialFourier == 8) pf = 0.875; fprintf(fp, "\t\"PartialFourier\": %g,\n", pf); } if (csaAscii.interp > 0) { //in-plane interpolation 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) { fprintf(fp, "\t\"ShimSetting\": [\n"); for (int i = 0; i < 8; i++) { if (i != 0) fprintf(fp, ",\n"); fprintf(fp, "\t\t%g", shimSetting[i]); } fprintf(fp, "\t],\n"); } if (d.CSA.numDti > 0) { // if (csaAscii.difBipolar == 1) fprintf(fp, "\t\"DiffusionScheme\": \"Bipolar\",\n"); if (csaAscii.difBipolar == 2) fprintf(fp, "\t\"DiffusionScheme\": \"Monopolar\",\n"); } //DelayTimeInTR // https://groups.google.com/forum/#!topic/bids-discussion/nmg1BOVH1SU // https://groups.google.com/forum/#!topic/bids-discussion/seD7AtJfaFE json_Float(fp, "\t\"DelayTime\": %g,\n", delayTimeInTR / 1000000.0); //DelayTimeInTR usec -> sec if (d.modality == kMODALITY_MR) json_Float(fp, "\t\"TxRefAmp\": %g,\n", csaAscii.txRefAmp); if (d.modality == kMODALITY_MR) json_Float(fp, "\t\"PhaseResolution\": %g,\n", csaAscii.phaseResolution); json_Float(fp, "\t\"PhaseOversampling\": %g,\n", phaseOversampling); json_Float(fp, "\t\"VendorReportedEchoSpacing\": %g,\n", csaAscii.echoSpacing / 1000000.0); //usec -> sec //ETD and epiFactor not useful/reliable https://github.com/rordenlab/dcm2niix/issues/127 //if (echoTrainDuration > 0) fprintf(fp, "\t\"EchoTrainDuration\": %g,\n", echoTrainDuration / 1000000.0); //usec -> sec //if (epiFactor > 0) fprintf(fp, "\t\"EPIFactor\": %d,\n", epiFactor); json_Str(fp, "\t\"ReceiveCoilName\": \"%s\",\n", coilID); if (d.modality == kMODALITY_MR) json_Str(fp, "\t\"ReceiveCoilActiveElements\": \"%s\",\n", coilElements); if (strcmp(coilElements, d.coilName) != 0) json_Str(fp, "\t\"CoilString\": \"%s\",\n", d.coilName); strcpy(d.coilName, ""); json_Str(fp, "\t\"PulseSequenceDetails\": \"%s\",\n", pulseSequenceDetails); json_Str(fp, "\t\"FmriExternalInfo\": \"%s\",\n", fmriExternalInfo); json_Str(fp, "\t\"WipMemBlock\": \"%s\",\n", wipMemBlock); if (strlen(d.protocolName) < 1) //insert protocol name if it exists in CSA but not DICOM header: https://github.com/nipy/heudiconv/issues/80 json_Str(fp, "\t\"ProtocolName\": \"%s\",\n", protocolName); if (csaAscii.refLinesPE > 0) fprintf(fp, "\t\"RefLinesPE\": %d,\n", csaAscii.refLinesPE); //https://github.com/bids-standard/bids-specification/pull/681#issuecomment-861767213 if (csaAscii.combineMode == 1) fprintf(fp, "\t\"CoilCombinationMethod\": \"Sum of Squares\",\n"); if (csaAscii.combineMode == 2) fprintf(fp, "\t\"CoilCombinationMethod\": \"Adaptive Combine\",\n"); if ((csaAscii.ucMTC == 1) && (d.mtState < 0)) //precedence for 0018,9020 over CSA json_Bool(fp, "\t\"MTState\": %s,\n", 1); json_Str(fp, "\t\"ConsistencyInfo\": \"%s\",\n", consistencyInfo); if (csaAscii.accelFact3D > 1.01) json_Float(fp, "\t\"AccelFact3D\": %g,\n", csaAscii.accelFact3D); //see *spcR_44ns where "sPat.lAccelFactPE = 1", "sPat.lAccelFact3D = 2" (0051,1011) LO [p2], perhaps ParallelReductionFactorInPlane should be 1? if (csaAscii.parallelReductionFactorInPlane > 0) { //AccelFactorPE -> phase encoding if (csaAscii.patMode == 1) fprintf(fp, "\t\"MatrixCoilMode\": \"SENSE\",\n"); if (csaAscii.patMode == 2) fprintf(fp, "\t\"MatrixCoilMode\": \"GRAPPA\",\n"); if (d.accelFactPE < 1.0) { //value not found in DICOM header, but WAS found in CSA ascii d.accelFactPE = csaAscii.parallelReductionFactorInPlane; //value found in ASCII but not in DICOM (0051,1011) } if ((csaAscii.accelFact3D < 1.01) && (csaAscii.parallelReductionFactorInPlane != (int)(d.accelFactPE))) printWarning("ParallelReductionFactorInPlane reported in DICOM [0051,1011] (%d) does not match CSA series value %d\n", (int)(d.accelFactPE), csaAscii.parallelReductionFactorInPlane); } } else { //e.g. Siemens Vida does not have CSA header, but has many attributes json_Str(fp, "\t\"ReceiveCoilActiveElements\": \"%s\",\n", d.coilElements); if (strcmp(d.coilElements, d.coilName) != 0) json_Str(fp, "\t\"CoilString\": \"%s\",\n", d.coilName); if ((d.manufacturer == kMANUFACTURER_SIEMENS) && (!d.is3DAcq) && (d.phaseEncodingLines > d.echoTrainLength) && (d.echoTrainLength > 1)) { //ETL is > 1, as some GE files list 1, as an example see series mr_0005 in dcm_qa_nih float pf = (float)d.phaseEncodingLines; if (d.accelFactPE > 1) pf = (float)pf / (float)d.accelFactPE; //estimate: not sure if we round up or down pf = (float)d.echoTrainLength / (float)pf; if (pf < 1.0) //e.g. if difference between lines and echo length not all explained by iPAT (SENSE/GRAPPA) fprintf(fp, "\t\"PartialFourier\": %g,\n", pf); } //compute partial Fourier: not reported in XA10, so infer //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 // 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 //Philips ASL issue 533 /* //see dcm_qa_philips_asl: this works for the mulit-PLD sequence, but not for other sequences. Also beware that value varies per slice, so incorrect values for descending if ( (d.aslFlags != kASL_FLAG_NONE) && (dti4D->triggerDelayTime[0] >= 0.0)) { //see BEP009 PET https://docs.google.com/document/d/1mqMLnxVdLwZjDd4ZiWFqjEAmOmfcModA_R535v3eQs0 bool isMultiPLD = false; for (int i = 0; i < h->dim[4]; i++) if (dti4D->triggerDelayTime[i] != dti4D->triggerDelayTime[0]) isMultiPLD = true; if (isMultiPLD) { fprintf(fp, "\t\"InitialPostLabelDelay\": [\n"); for (int i = 0; i < h->dim[4]; i++) { if (i != 0) fprintf(fp, ",\n"); if (dti4D->triggerDelayTime[i] < 0) break; fprintf(fp, "\t\t%g", dti4D->triggerDelayTime[i] / 1000.0); } fprintf(fp, "\t],\n"); } else //if all delays are the same, write scalar 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"); 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\"NumberOfPointsPerArm\": %g,\n", d.numberOfPointsPerArm); json_Float(fp, "\t\"NumberOfArms\": %g,\n", d.numberOfArms); } if (d.numberOfExcitations > 1) json_Float(fp, "\t\"NumberOfExcitations\": %g,\n", d.numberOfExcitations); if ((d.CSA.multiBandFactor > 1) && (d.modality == kMODALITY_MR)) //AccelFactorSlice fprintf(fp, "\t\"MultibandAccelerationFactor\": %d,\n", d.CSA.multiBandFactor); json_Float(fp, "\t\"PercentPhaseFOV\": %g,\n", d.phaseFieldofView); json_Float(fp, "\t\"PercentSampling\": %g,\n", d.percentSampling); if (d.echoTrainLength > 1) //>1 as for Siemens EPI this is 1, Siemens uses EPI factor http://mriquestions.com/echo-planar-imaging.html fprintf(fp, "\t\"EchoTrainLength\": %d,\n", d.echoTrainLength); //0018,0091 Combination of partial fourier and in-plane parallel imaging if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_PHASE) fprintf(fp, "\t\"PartialFourierDirection\": \"PHASE\",\n"); if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_FREQUENCY) fprintf(fp, "\t\"PartialFourierDirection\": \"FREQUENCY\",\n"); if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_SLICE_SELECT) fprintf(fp, "\t\"PartialFourierDirection\": \"SLICE_SELECT\",\n"); if (d.partialFourierDirection == kPARTIAL_FOURIER_DIRECTION_COMBINATION) fprintf(fp, "\t\"PartialFourierDirection\": \"COMBINATION\",\n"); if ((d.phaseEncodingSteps > 0) && (d.isPartialFourier) && (d.manufacturer == kMANUFACTURER_PHILIPS)) { //issue 377 fprintf(fp, "\t\"PartialFourierEnabled\": \"YES\",\n"); fprintf(fp, "\t\"PhaseEncodingStepsNoPartialFourier\": %d,\n", d.phaseEncodingSteps); } else if (d.phaseEncodingSteps > 0) fprintf(fp, "\t\"PhaseEncodingSteps\": %d,\n", d.phaseEncodingSteps); if ((d.phaseEncodingLines > 0) && (d.modality == kMODALITY_MR)) fprintf(fp, "\t\"AcquisitionMatrixPE\": %d,\n", d.phaseEncodingLines); //Compute ReconMatrixPE // Actual size of the *reconstructed* data in the PE dimension, which does NOT match // phaseEncodingLines in the case of interpolation or phaseResolution < 100% // We'll need this for generating a value for effectiveEchoSpacing that is consistent // with the *reconstructed* data. int reconMatrixPE = d.phaseEncodingLines; if ((h->dim[2] > 0) && (h->dim[1] > 0)) { if (h->dim[1] == h->dim[2]) //phase encoding does not matter reconMatrixPE = h->dim[2]; else if (d.phaseEncodingRC == 'C') reconMatrixPE = h->dim[2]; //see dcm_qa: NOPF_NOPAT_NOPOS_PERES100_ES0P59_BW2222_200PFOV_AP_0034 else if (d.phaseEncodingRC == 'R') reconMatrixPE = h->dim[1]; } if ((d.modality == kMODALITY_MR) && (reconMatrixPE > 0)) fprintf(fp, "\t\"ReconMatrixPE\": %d,\n", reconMatrixPE); double bandwidthPerPixelPhaseEncode = d.bandwidthPerPixelPhaseEncode; if (bandwidthPerPixelPhaseEncode == 0.0) bandwidthPerPixelPhaseEncode = d.CSA.bandwidthPerPixelPhaseEncode; json_Float(fp, "\t\"BandwidthPerPixelPhaseEncode\": %g,\n", bandwidthPerPixelPhaseEncode); //if ((!d.is3DAcq) && (d.accelFactPE > 1.0)) fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); if (d.accelFactPE > 1.0) fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); json_Str(fp, "\t\"ParallelAcquisitionTechnique\": \"%s\",\n", d.parallelAcquisitionTechnique); //https://github.com/rordenlab/dcm2niix/issues/314 if (d.accelFactOOP > 1.0) fprintf(fp, "\t\"ParallelReductionOutOfPlane\": %g,\n", d.accelFactOOP); //EffectiveEchoSpacing // Siemens bandwidthPerPixelPhaseEncode already accounts for the effects of parallel imaging, // interpolation, phaseOversampling, and phaseResolution, in the context of the size of the // *reconstructed* data in the PE dimension double effectiveEchoSpacing = 0.0; //next: dicm2nii's method for determining effectiveEchoSpacing if bandwidthPerPixelPhaseEncode is unknown, see issue 315 //if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode <= 0.0) && (d.CSA.sliceMeasurementDuration >= 0)) // effectiveEchoSpacing = d.CSA.sliceMeasurementDuration / (reconMatrixPE * 1000.0); if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode > 0.0)) effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * reconMatrixPE); json_Float(fp, "\t\"WaterFatShift\": %g,\n", d.waterFatShift); if ((effectiveEchoSpacing == 0.0) && (d.imagingFrequency > 0.0) && (d.waterFatShift != 0.0) && (d.echoTrainLength > 0) && (reconMatrixPE > 1)) { //in theory we could use either fieldStrength or imagingFrequency, but the former is typically provided with low precision //https://github.com/rordenlab/dcm2niix/issues/377 // EchoSpacing 1/BW/EPI_factor https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=ind1308&L=FSL&D=0&P=113520 // this formula from https://support.brainvoyager.com/brainvoyager/functional-analysis-preparation/29-pre-processing/78-epi-distortion-correction-echo-spacing-and-bandwidth // https://neurostars.org/t/consolidating-epi-echo-spacing-and-readout-time-for-philips-scanner/4406 /* ActualEchoSpacing = WaterFatShift / (ImagingFrequency * 3.4 * (EPI_Factor + 1)) TotalReadoutTIme = ActualEchoSpacing * EPI_Factor EffectiveEchoSpacing = TotalReadoutTime / (ReconMatrixPE - 1) WaterFatShift = 2001,1022 ImagingFrequency = 0018,0084 EPI_Factor = 0018,0091 or 2001,1013 ReconMatrixPE = 0028,0010 or 0028,0011 depending on 0018,1312 */ float actualEchoSpacing = d.waterFatShift / (d.imagingFrequency * 3.4 * (d.echoTrainLength + 1)); float totalReadoutTime = actualEchoSpacing * d.echoTrainLength; float effectiveEchoSpacingPhil = totalReadoutTime / (reconMatrixPE - 1); json_Float(fp, "\t\"EstimatedEffectiveEchoSpacing\": %g,\n", effectiveEchoSpacingPhil); fprintf(fp, "\t\"EstimatedTotalReadoutTime\": %g,\n", totalReadoutTime); } if (d.effectiveEchoSpacingGE > 0.0) { //TotalReadoutTime = [ ceil (PE_AcquisitionMatrix / Asset_R_factor) - 1] * ESP float roundFactor = 2.0; if (d.isPartialFourier) roundFactor = 4.0; float totalReadoutTime = ((ceil(1 / roundFactor * d.phaseEncodingLines / d.accelFactPE) * roundFactor) - 1.0) * d.effectiveEchoSpacingGE * 0.000001; //printf("ASSET= %g PE_AcquisitionMatrix= %d ESP= %d TotalReadoutTime= %g\n", d.accelFactPE, d.phaseEncodingLines, d.effectiveEchoSpacingGE, totalReadoutTime); //json_Float(fp, "\t\"TotalReadoutTime\": %g,\n", totalReadoutTime); effectiveEchoSpacing = totalReadoutTime / (reconMatrixPE - 1); } json_Float(fp, "\t\"EffectiveEchoSpacing\": %g,\n", effectiveEchoSpacing); // Calculate true echo spacing (should match what Siemens reports on the console) // i.e., should match "echoSpacing" extracted from the ASCII CSA header, when that exists double trueESfactor = 1.0; if (d.accelFactPE > 1.0) trueESfactor /= d.accelFactPE; if (phaseOversampling > 0.0) trueESfactor *= (1.0 + phaseOversampling); float derivedEchoSpacing = 0.0; derivedEchoSpacing = bandwidthPerPixelPhaseEncode * trueESfactor * reconMatrixPE; if (derivedEchoSpacing != 0) derivedEchoSpacing = 1 / derivedEchoSpacing; json_Float(fp, "\t\"DerivedVendorReportedEchoSpacing\": %g,\n", derivedEchoSpacing); //TotalReadOutTime: Really should be called "EffectiveReadOutTime", by analogy with "EffectiveEchoSpacing". // But BIDS spec calls it "TotalReadOutTime". // So, we DO NOT USE EchoTrainLength, because not trying to compute the actual (physical) readout time. // Rather, the point of computing "EffectiveEchoSpacing" properly is so that this // "Total(Effective)ReadOutTime" can be computed straightforwardly as the product of the // EffectiveEchoSpacing and the size of the *reconstructed* matrix in the PE direction. // see https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup/TopupUsersGuide#A--datain // 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) ) 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)) fprintf(fp, "\t\"DwellTime\": %g,\n", d.dwellTime * 1E-9); // Phase encoding polarity int phPos = d.CSA.phaseEncodingDirectionPositive; //next two conditionals updated: make GE match Siemens if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) phPos = 1; if (d.phaseEncodingGE == kGE_PHASE_ENCODING_POLARITY_FLIPPED) phPos = 0; //if ((phPos >= 0) && (d.phaseEncodingRC == 'R') && (d.manufacturer == kMANUFACTURER_UIH)) phPos = 1 - phPos; //issue410 bool isSkipPhaseEncodingAxis = d.is3DAcq; if (d.echoTrainLength > 1) isSkipPhaseEncodingAxis = false; //issue 371: ignore phaseEncoding for 3D MP-RAGE/SPACE, but report for 3D EPI if (((d.phaseEncodingRC == 'R') || (d.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && (phPos < 0)) { //when phase encoding axis is known but we do not know phase encoding polarity // https://github.com/rordenlab/dcm2niix/issues/163 // This will typically correspond with InPlanePhaseEncodingDirectionDICOM if (d.phaseEncodingRC == 'C') //Values should be "R"ow, "C"olumn or "?"Unknown fprintf(fp, "\t\"PhaseEncodingAxis\": \"j\",\n"); else if (d.phaseEncodingRC == 'R') fprintf(fp, "\t\"PhaseEncodingAxis\": \"i\",\n"); } if (((d.phaseEncodingRC == 'R') || (d.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && (phPos >= 0)) { //printf("%ld %d %d %c %d\n", d.seriesNum, d.echoTrainLength, isSkipPhaseEncodingAxis, d.phaseEncodingRC, phPos); //test issue 371 if (d.phaseEncodingRC == 'C') //Values should be "R"ow, "C"olumn or "?"Unknown fprintf(fp, "\t\"PhaseEncodingDirection\": \"j"); else if (d.phaseEncodingRC == 'R') fprintf(fp, "\t\"PhaseEncodingDirection\": \"i"); else fprintf(fp, "\t\"PhaseEncodingDirection\": \"?"); //phaseEncodingDirectionPositive has one of three values: UNKNOWN (-1), NEGATIVE (0), POSITIVE (1) //However, DICOM and NIfTI are reversed in the j (ROW) direction //Equivalent to dicm2nii's "if flp(iPhase), phPos = ~phPos; end" //for samples see https://github.com/rordenlab/dcm2niix/issues/125 if (phPos < 0) fprintf(fp, "?"); //unknown else if ((phPos == 0) && (d.phaseEncodingRC != 'C')) fprintf(fp, "-"); else if ((d.phaseEncodingRC == 'C') && (phPos == 1) && (opts.isFlipY)) fprintf(fp, "-"); else if ((d.phaseEncodingRC == 'C') && (phPos == 0) && (!opts.isFlipY)) fprintf(fp, "-"); fprintf(fp, "\",\n"); } //only save PhaseEncodingDirection if BOTH direction and POLARITY are known //Slice Timing UIH or GE >>>> //in theory, we should also report XA10 slice times here, but see series 24 of https://github.com/rordenlab/dcm2niix/issues/236 if ((d.modality != kMODALITY_CT) && (d.modality != kMODALITY_PT) && (!d.is3DAcq) && (d.CSA.sliceTiming[0] >= 0.0)) { fprintf(fp, "\t\"SliceTiming\": [\n"); for (int i = 0; i < h->dim[3]; i++) { if (i != 0) fprintf(fp, ",\n"); fprintf(fp, "\t\t%g", d.CSA.sliceTiming[i] / 1000.0); } fprintf(fp, "\t],\n"); } //DICOM orientation and phase encoding: useful for 3D undistortion. Original DICOM values: DICOM not NIfTI space, ignores if 3D image re-oriented float mxOrient = 0.0; for (int i = 1; i < 7; i++) mxOrient = max(mxOrient, fabs(d.orient[i])); if (! isSameFloatGE(mxOrient, 0.0)) { //if set fprintf(fp, "\t\"ImageOrientationPatientDICOM\": [\n"); for (int i = 1; i < 7; i++) { if (i != 1) fprintf(fp, ",\n"); fprintf(fp, "\t\t%g", d.orient[i]); } fprintf(fp, "\t],\n"); } json_Str(fp, "\t\"ImageOrientationText\": \"%s\",\n", d.imageOrientationText); if (d.phaseEncodingRC == 'C') fprintf(fp, "\t\"InPlanePhaseEncodingDirectionDICOM\": \"COL\",\n"); if (d.phaseEncodingRC == 'R') fprintf(fp, "\t\"InPlanePhaseEncodingDirectionDICOM\": \"ROW\",\n"); // Finish up with info on the conversion tool fprintf(fp, "\t\"ConversionSoftware\": \"dcm2niix\",\n"); fprintf(fp, "\t\"ConversionSoftwareVersion\": \"%s\"\n", kDCMdate); //fprintf(fp, "\t\"ConversionSoftwareVersion\": \"%s\"\n", kDCMvers );kDCMdate fprintf(fp, "}\n"); fclose(fp); } // nii_SaveBIDSX() #ifndef USING_R void swapEndian(struct nifti_1_header *hdr, unsigned char *im, bool isNative) { //swap endian from big->little or little->big // must be told which is native to detect datatype and number of voxels // one could also auto-detect: hdr->sizeof_hdr==348 if (!isNative) swap_nifti_header(hdr); int nVox = 1; for (int i = 1; i < 8; i++) if (hdr->dim[i] > 1) nVox = nVox * hdr->dim[i]; int bitpix = hdr->bitpix; int datatype = hdr->datatype; if (isNative) swap_nifti_header(hdr); if (datatype == DT_RGBA32) return; //n.b. do not swap 8-bit, 24-bit RGB, and 32-bit RGBA if (bitpix == 16) nifti_swap_2bytes(nVox, im); if (bitpix == 32) nifti_swap_4bytes(nVox, im); if (bitpix == 64) nifti_swap_8bytes(nVox, im); } void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char *filename) { struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); dti4D->sliceOrder[0] = -1; dti4D->volumeOnsetTime[0] = -1; dti4D->decayFactor[0] = -1; dti4D->triggerDelayTime[0] = -1.0; dti4D->intenScale[0] = 0.0; dti4D->repetitionTimeExcitation = 0.0; dti4D->repetitionTimeInversion = 0.0; nii_SaveBIDSX(pathoutname, d, opts, h, filename, dti4D); free(dti4D); } // nii_SaveBIDSX() #endif unsigned char *removeADC(struct nifti_1_header *hdr, unsigned char *inImg, int numADC) { //for speed we just clip the number of volumes, the realloc routine would be nice // we do not want to copy input to a new smaller array since 4D DTI datasets can be huge // and that would require almost twice as much RAM if (numADC < 1) return inImg; hdr->dim[4] = hdr->dim[4] - numADC; if (hdr->dim[4] < 2) hdr->dim[0] = 3; //e.g. 4D 2-volume DWI+ADC becomes 3D DWI if ADC is removed return inImg; } //removeADC() //#define naive_reorder_vols //for simple, fast re-ordering that consumes a lot of RAM #ifdef naive_reorder_vols unsigned char *reorderVolumes(struct nifti_1_header *hdr, unsigned char *inImg, int *volOrderIndex) { //reorder volumes to place ADC at end and (optionally) B=0 at start // volOrderIndex[0] reports location of desired first volume // naive solution creates an output buffer that doubles RAM usage (2 *numVol) int numVol = hdr->dim[4]; int numVolBytes = hdr->dim[1] * hdr->dim[2] * hdr->dim[3] * (hdr->bitpix / 8); if ((!volOrderIndex) || (numVol < 1) || (numVolBytes < 1)) return inImg; unsigned char *outImg = (unsigned char *)malloc(numVolBytes * numVol); int outPos = 0; for (int i = 0; i < numVol; i++) { memcpy(&outImg[outPos], &inImg[volOrderIndex[i] * numVolBytes], numVolBytes); // dest, src, bytes outPos += numVolBytes; } //for each volume free(volOrderIndex); free(inImg); return outImg; } //reorderVolumes() #else // naive_reorder_vols unsigned char *reorderVolumes(struct nifti_1_header *hdr, unsigned char *inImg, int *volOrderIndex) { //reorder volumes to place ADC at end and (optionally) B=0 at start // volOrderIndex[0] reports location of desired first volume // complicated by fact that 4D DTI data is often huge // simple solutions would create an output buffer that would double RAM usage (2 *numVol) // here we bubble-sort volumes in place to use numVols+1 memory int numVol = hdr->dim[4]; int numVolBytes = hdr->dim[1] * hdr->dim[2] * hdr->dim[3] * (hdr->bitpix / 8); int *inPos = (int *)malloc(numVol * sizeof(int)); for (int i = 0; i < numVol; i++) inPos[i] = i; unsigned char *tempVol = (unsigned char *)malloc(numVolBytes); int outPos = 0; for (int o = 0; o < numVol; o++) { int i = inPos[volOrderIndex[o]]; //input volume if (i == o) continue; //volume in correct order memcpy(&tempVol[0], &inImg[o * numVolBytes], numVolBytes); //make temp memcpy(&inImg[o * numVolBytes], &inImg[i * numVolBytes], numVolBytes); //copy volume to desire location dest, src, bytes memcpy(&inImg[i * numVolBytes], &tempVol[0], numVolBytes); //copy unsorted volume inPos[o] = i; outPos += numVolBytes; } //for each volume free(inPos); free(volOrderIndex); free(tempVol); return inImg; } //reorderVolumes() #endif // naive_reorder_vols float *bvals; //global variable for cmp_bvals int cmp_bvals(const void *a, const void *b) { int ia = *(int *)a; int ib = *(int *)b; //return bvals[ia] > bvals[ib] ? -1 : bvals[ia] < bvals[ib]; return bvals[ia] < bvals[ib] ? -1 : bvals[ia] > bvals[ib]; } // cmp_bvals() bool isAllZeroFloat(float v1, float v2, float v3) { if (!isSameFloatGE(v1, 0.0)) return false; if (!isSameFloatGE(v2, 0.0)) return false; if (!isSameFloatGE(v3, 0.0)) return false; return true; } int *nii_saveDTI(char pathoutname[], int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TDCMopts opts, int sliceDir, struct TDTI4D *dti4D, int *numADC, int numVol) { //reports non-zero if any volumes should be excluded (e.g. philip stores an ADC maps) *numADC = 0; if (opts.isOnlyBIDS) return NULL; uint64_t indx0 = dcmSort[0].indx; //first volume int numDti = dcmList[indx0].CSA.numDti; #ifdef USING_R ImageList *images = (ImageList *)opts.imageList; #endif //https://github.com/rordenlab/dcm2niix/issues/352 bool allB0 = dcmList[indx0].isDiffusion; if (dcmList[indx0].isDerived) allB0 = false; //e.g. FA map if ((numDti == numVol) && (numDti > 1)) allB0 = false; if (numDti > 1) allB0 = false; if (nConvert > 1) allB0 = false; if ((numDti == 1) && (dti4D->S[0].V[0] > 50.0)) allB0 = false; if (allB0) { if (opts.isVerbose) printMessage("Diffusion image without gradients: assuming %d volume B=0 series\n", numVol); #ifdef USING_R // The image hasn't been created yet, so the attributes must be deferred images->addDeferredAttribute("bValues", std::vector(numVol, 0.0)); images->addDeferredAttribute("bVectors", std::vector(numVol * 3, 0.0), numVol, 3); #else char sep = '\t'; if (opts.isCreateBIDS) sep = ' '; //save bval char txtname[2048] = {""}; strcpy(txtname, pathoutname); strcat(txtname, ".bval"); FILE *fp = fopen(txtname, "w"); for (int i = 0; i < (numVol); i++) fprintf(fp, "%d%c", 0, sep); fprintf(fp, "\n"); fclose(fp); //save bvec strcpy(txtname, pathoutname); strcat(txtname, ".bvec"); fp = fopen(txtname, "w"); for (int v = 0; v < (3); v++) { for (int i = 0; i < (numVol); i++) fprintf(fp, "%d%c", 0, sep); fprintf(fp, "\n"); } fclose(fp); #endif } if (numDti < 1) return NULL; if ((numDti < 3) && (nConvert < 3)) return NULL; TDTI *vx = NULL; if (numDti > 2) { vx = (TDTI *)malloc(numDti * sizeof(TDTI)); for (int i = 0; i < numDti; i++) //for each direction for (int v = 0; v < 4; v++) //for each vector+B-value vx[i].V[v] = dti4D->S[i].V[v]; } else { //if (numDti == 1) {//extract DTI from different slices vx = (TDTI *)malloc(nConvert * sizeof(TDTI)); numDti = 0; for (int i = 0; i < nConvert; i++) { //for each image if ((dcmList[indx0].CSA.mosaicSlices > 1) || (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx]))) { //if (numDti < kMaxDTIv) for (int v = 0; v < 4; v++) //for each vector+B-value vx[numDti].V[v] = dcmList[dcmSort[i].indx].CSA.dtiV[v]; //dcmList[indx0].CSA.dtiV[numDti][v] = dcmList[dcmSort[i].indx].CSA.dtiV[0][v]; numDti++; } //for slices with repeats } //for each file dcmList[indx0].CSA.numDti = numDti; //warning structure not changed outside scope! } bool bValueVaries = false; for (int i = 1; i < numDti; i++) //check if all bvalues match first volume if (vx[i].V[0] != vx[0].V[0]) bValueVaries = true; //optional: record b-values even without variability float minBval = vx[0].V[0]; for (int i = 1; i < numDti; i++) //check if all bvalues match first volume if (vx[i].V[0] < minBval) minBval = vx[i].V[0]; if (minBval > 50.0) bValueVaries = true; //start issue394: experimental, single volume per series, Siemens XA if (!isAllZeroFloat(vx[0].V[1], vx[0].V[2], vx[0].V[3])) bValueVaries = true; //end issue394 if (!bValueVaries) { bool bVecVaries = false; for (int i = 1; i < numDti; i++) { //check if all bvalues match first volume if (vx[i].V[1] != vx[0].V[1]) bVecVaries = true; if (vx[i].V[2] != vx[0].V[2]) bVecVaries = true; if (vx[i].V[3] != vx[0].V[3]) bVecVaries = true; } if (!bVecVaries) { free(vx); return NULL; } if (opts.isVerbose) { for (int i = 0; i < numDti; i++) printMessage("bxyz %g %g %g %g\n", vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); } //Stutters XINAPSE7 seem to save B=0 as B=2000, but these are not derived? https://github.com/rordenlab/dcm2niix/issues/182 bool bZeroBvec = false; for (int i = 0; i < numDti; i++) { //check if all bvalues match first volume if (isAllZeroFloat(vx[i].V[1], vx[i].V[2], vx[i].V[3])) { vx[i].V[0] = 0; //printWarning("volume %d might be B=0\n", i); bZeroBvec = true; } } if (bZeroBvec) printWarning("Assuming volumes without gradients are actually B=0\n"); else { printWarning("No bvec/bval files created. Only one B-value reported for all volumes: %g\n", vx[0].V[0]); free(vx); return NULL; } } //report values: //for (int i = 1; i < numDti; i++) //check if all bvalues match first volume // printMessage("%d bval= %g bvec= %g %g %g\n",i, vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); int minB0idx = 0; float minB0 = vx[0].V[0]; for (int i = 0; i < numDti; i++) if (vx[i].V[0] < minB0) { minB0 = vx[i].V[0]; minB0idx = i; } float maxB0 = vx[0].V[0]; for (int i = 0; i < numDti; i++) if (vx[i].V[0] > maxB0) maxB0 = vx[i].V[0]; //for CMRR sequences unweighted volumes are not actually B=0 but they have B near zero if (minB0 > 50) printWarning("This diffusion series does not have a B0 (reference) volume\n"); if ((!opts.isSortDTIbyBVal) && (minB0idx > 0)) printMessage("Note: B0 not the first volume in the series (FSL eddy reference volume is %d)\n", minB0idx); float kADCval = maxB0 + 1; //mark as unusual *numADC = 0; bvals = (float *)malloc(numDti * sizeof(float)); int numGEwarn = 0; bool isGEADC = (dcmList[indx0].numberOfDiffusionDirectionGE == 0); for (int i = 0; i < numDti; i++) { bvals[i] = vx[i].V[0]; //printMessage("---bxyz %g %g %g %g\n",vx[i].V[0],vx[i].V[1],vx[i].V[2],vx[i].V[3]); //Philips includes derived isotropic images //if (((dcmList[indx0].manufacturer == kMANUFACTURER_GE) || (dcmList[indx0].manufacturer == kMANUFACTURER_PHILIPS)) && (isADCnotDTI(vx[i]))) { if (((dcmList[indx0].manufacturer == kMANUFACTURER_GE)) && (isADCnotDTI(vx[i]))) { numGEwarn += 1; if (isGEADC) { //e.g. GE Trace where bval=900, bvec=0,0,0 *numADC = *numADC + 1; //printWarning("GE ADC volume %d\n", i+1); bvals[i] = kADCval; } else vx[i].V[0] = 0; //e.g. GE raw B=0 where bval=900, bvec=0,0,0 } //see issue 245, however this does impact some anonymized files where bvec but not bval removed https://neurostars.org/t/dcm2bids-after-conversion-to-bids-bvals-are-zeros/20198/11 if (((dcmList[indx0].manufacturer == kMANUFACTURER_PHILIPS)) && (isADCnotDTI(vx[i]))) { *numADC = *numADC + 1; bvals[i] = kADCval; //printMessage("+++bxyz %d\n",i); } bvals[i] = bvals[i] + (0.5 * i / numDti); //add a small bias so ties are kept in sequential order } if (numGEwarn > 0) printWarning("Some images had bval>0 but bvec=0 (either Trace or b=0, see issue 245)\n"); /*if ((*numADC == numDti) || (numGEwarn == numDti)) { //issue 405: we now save bvals file for isotropic series //all isotropic/ADC images - no valid bvecs *numADC = 0; free(bvals); free(vx); return NULL; }*/ bool isIsotropic = false; if ((*numADC == numDti) || (numGEwarn == numDti)) //issue 405: we now save bvals file for isotropic series isIsotropic = true; if (*numADC > 0) { // DWIs (i.e. short diffusion scans with too few directions to // calculate tensors...they typically acquire b=0 + 3 b > 0 so // the isotropic trace or MD can be calculated) often come as // b=0 and trace pairs, with the b=0 and trace in either order, // and often as "ORIGINAL", even though the trace is not. // The bval file is needed for downstream processing to know // * which is the b=0 and which is the trace, and // * what b is for the trace, // so dcm2niix should *always* write the bval and bvec files, // AND include the b for the trace for DWIs. // One hackish way to accomplish that is to set *numADC = 0 // when *numADC == 1 && numDti == 2. // - Rob Reid, 2017-11-29. if ((*numADC == 1) && ((numDti - *numADC) < 2)) { *numADC = 0; printMessage("Note: this appears to be a b=0+trace DWI; ADC/trace removal has been disabled.\n"); } else { if ((numDti - *numADC) < 2) { /*if (!dcmList[indx0].isDerived) //no need to warn if images are derived Trace/ND pair printWarning("No bvec/bval files created: only single value after ADC excluded\n"); *numADC = 0; free(bvals); free(vx); return NULL;*/ printMessage("Warning: Isotropic DWI series, all bvecs are zero (issue 405)\n"); *numADC = 0; } else printMessage("Note: %d volumes appear to be ADC or trace images that will be removed to allow processing\n", *numADC); } } //sort ALL including ADC int *volOrderIndex = (int *)malloc(numDti * sizeof(int)); for (int i = 0; i < numDti; i++) volOrderIndex[i] = i; if (opts.isSortDTIbyBVal) qsort(volOrderIndex, numDti, sizeof(*volOrderIndex), cmp_bvals); else if (*numADC > 0) { int o = 0; for (int i = 0; i < numDti; i++) { if (bvals[i] < kADCval) { volOrderIndex[o] = i; o++; } //if not ADC } //for each volume } //if sort else if has ADC free(bvals); //save VX as sorted TDTI *vxOrig = (TDTI *)malloc(numDti * sizeof(TDTI)); for (int i = 0; i < numDti; i++) vxOrig[i] = vx[i]; //remove ADC numDti = numDti - *numADC; free(vx); vx = (TDTI *)malloc(numDti * sizeof(TDTI)); for (int i = 0; i < numDti; i++) vx[i] = vxOrig[volOrderIndex[i]]; free(vxOrig); //if no ADC or sequential, the is no need to re-order volumes bool isSequential = true; for (int i = 1; i < (numDti + *numADC); i++) if (volOrderIndex[i] <= volOrderIndex[i - 1]) isSequential = false; if (isSequential) { free(volOrderIndex); volOrderIndex = NULL; } if (!isSequential) printMessage("DTI volumes re-ordered by ascending b-value\n"); #ifdef RAW_BVEC //save raw bvecs as reported by DICOM, not rotated to image space char txtname[2048] = {""}; strcpy(txtname, pathoutname); strcat(txtname, ".rvec"); FILE *fp = fopen(txtname, "w"); for (int i = 0; i < numDti; i++) fprintf(fp, "%g\t", vx[i].V[1]); fprintf(fp, "\n"); for (int i = 0; i < numDti; i++) fprintf(fp, "%g\t", vx[i].V[2]); fprintf(fp, "\n"); for (int i = 0; i < numDti; i++) fprintf(fp, "%g\t", vx[i].V[3]); fprintf(fp, "\n"); fclose(fp); #endif dcmList[indx0].CSA.numDti = numDti; //warning structure not changed outside scope! geCorrectBvecs(&dcmList[indx0], sliceDir, vx, opts.isVerbose); siemensPhilipsCorrectBvecs(&dcmList[indx0], sliceDir, vx, opts.isVerbose); if (dcmList[indx0].CSA.numDti < 1) { //issue449 free(vx); return NULL; } if (!opts.isFlipY) { //!FLIP_Y&& (dcmList[indx0].CSA.mosaicSlices < 2) mosaics are always flipped in the Y direction 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 } //if not a mosaic if (opts.isVerbose) { for (int i = 0; i < (numDti); i++) { printMessage("%d\tB=\t%g\tVec=\t%g\t%g\t%g\n", i, vx[i].V[0], vx[i].V[1], vx[i].V[2], vx[i].V[3]); } //for each direction } //printMessage("%f\t%f\t%f",dcmList[indx0].CSA.dtiV[1][1],dcmList[indx0].CSA.dtiV[1][2],dcmList[indx0].CSA.dtiV[1][3]); #ifdef USING_R std::vector bValues(numDti); std::vector bVectors(numDti * 3); for (int i = 0; i < numDti; i++) { bValues[i] = vx[i].V[0]; for (int j = 0; j < 3; j++) bVectors[i + j * numDti] = vx[i].V[j + 1]; } // The image hasn't been created yet, so the attributes must be deferred images->addDeferredAttribute("bValues", bValues); images->addDeferredAttribute("bVectors", bVectors, numDti, 3); #else if (opts.saveFormat != kSaveFormatNIfTI) { if (numDti < kMaxDTI4D) { dcmList[indx0].CSA.numDti = numDti; for (int i = 0; i < numDti; i++) //for each direction for (int v = 0; v < 4; v++) //for each vector+B-value dti4D->S[i].V[v] = vx[i].V[v]; } free(vx); return volOrderIndex; } char txtname[2048] = {""}; strcpy(txtname, pathoutname); strcat(txtname, ".bval"); //printMessage("Saving DTI %s\n",txtname); FILE *fp = fopen(txtname, "w"); if (fp == NULL) { free(vx); return volOrderIndex; } for (int i = 0; i < (numDti - 1); i++) { if (opts.isCreateBIDS) { fprintf(fp, "%g ", vx[i].V[0]); } else { fprintf(fp, "%g\t", vx[i].V[0]); } } fprintf(fp, "%g\n", vx[numDti - 1].V[0]); fclose(fp); if (isIsotropic) { //issue 405: ISOTROPIC images have bval but not bvec free(vx); return volOrderIndex; } strcpy(txtname, pathoutname); if (dcmList[indx0].isVectorFromBMatrix) strcat(txtname, ".mvec"); else strcat(txtname, ".bvec"); //printMessage("Saving DTI %s\n",txtname); fp = fopen(txtname, "w"); if (fp == NULL) { free(vx); return volOrderIndex; } for (int v = 1; v < 4; v++) { for (int i = 0; i < (numDti - 1); i++) { if (opts.isCreateBIDS) { fprintf(fp, "%g ", vx[i].V[v]); } else { fprintf(fp, "%g\t", vx[i].V[v]); } } fprintf(fp, "%g\n", vx[numDti - 1].V[v]); } fclose(fp); #endif free(vx); return volOrderIndex; } // nii_saveDTI() float sqr(float v) { return v * v; } // sqr() #ifdef newTilt //see issue 254 float vec3Length(vec3 v) { //normalize vector length return sqrt((v.v[0] * v.v[0]) + (v.v[1] * v.v[1]) + (v.v[2] * v.v[2])); } float vec3maxMag(vec3 v) { //return signed vector with maximum magnitude float mx = v.v[0]; if (fabs(v.v[1]) > fabs(mx)) mx = v.v[1]; if (fabs(v.v[2]) > fabs(mx)) mx = v.v[2]; return mx; } vec3 makePositive(vec3 v) { //we do not no order of cross product or order of instance number (e.g. head->foot, foot->head) // this function matches the polarity of slice direction inferred from patient position and image orient vec3 ret = v; if (vec3maxMag(v) >= 0.0) return ret; ret.v[0] = -ret.v[0]; ret.v[1] = -ret.v[1]; ret.v[2] = -ret.v[2]; return ret; } void vecRep(vec3 v) { //normalize vector length printMessage("[%g %g %g]\n", v.v[0], v.v[1], v.v[2]); } //Precise method for determining gantry tilt // rationale: // gantry tilt (0018,1120) is optional // some tools may correct gantry tilt but not reset 0018,1120 // 0018,1120 might be saved at low precision (though patientPosition, orient might be as well) //https://github.com/rordenlab/dcm2niix/issues/253 float computeGantryTiltPrecise(struct TDICOMdata d1, struct TDICOMdata d2, int isVerbose) { float ret = 0.0; if (isNanPosition(d1)) return ret; vec3 slice_vector = setVec3(d2.patientPosition[1] - d1.patientPosition[1], d2.patientPosition[2] - d1.patientPosition[2], d2.patientPosition[3] - d1.patientPosition[3]); float len = vec3Length(slice_vector); if (isSameFloat(len, 0.0)) { slice_vector = setVec3(d1.patientPositionLast[1] - d1.patientPosition[1], d1.patientPositionLast[2] - d1.patientPosition[2], d1.patientPositionLast[3] - d1.patientPosition[3]); len = vec3Length(slice_vector); if (isSameFloat(len, 0.0)) return ret; } if (isnan(slice_vector.v[0])) return ret; slice_vector = makePositive(slice_vector); vec3 read_vector = setVec3(d1.orient[1], d1.orient[2], d1.orient[3]); vec3 phase_vector = setVec3(d1.orient[4], d1.orient[5], d1.orient[6]); vec3 slice_vector90 = crossProduct(read_vector, phase_vector); //perpendicular slice_vector90 = makePositive(slice_vector90); float len90 = vec3Length(slice_vector90); if (isSameFloat(len90, 0.0)) return ret; float dotX = dotProduct(slice_vector90, slice_vector); float cosX = dotX / (len * len90); float degX = acos(cosX) * (180.0 / M_PI); //arccos, radian -> degrees if (!isSameFloat(cosX, 1.0)) ret = degX; if ((isSameFloat(ret, 0.0)) && (isSameFloat(ret, d1.gantryTilt))) return 0.0; //determine if gantry tilt is positive or negative vec3 signv = crossProduct(slice_vector, slice_vector90); float sign = vec3maxMag(signv); if (isSameFloatGE(ret, 0.0)) return 0.0; //parallel vectors if (sign > 0.0) ret = -ret; //the length of len90 was negative, negative gantry tilt //while (ret >= 89.99) ret -= 90; //while (ret <= -89.99) ret += 90; if (isSameFloatGE(ret, 0.0)) return 0.0; if ((isVerbose) || (isnan(ret))) { printMessage("Gantry Tilt Parameters (see issue 253)\n"); printMessage(" Read ="); vecRep(read_vector); printMessage(" Phase ="); vecRep(phase_vector); printMessage(" CrossReadPhase ="); vecRep(slice_vector90); printMessage(" Slice ="); vecRep(slice_vector); } printMessage("Gantry Tilt based on 0018,1120 %g, estimated from slice vector %g\n", d1.gantryTilt, ret); return ret; } #endif //newTilt //see issue 254 float intersliceDistance(struct TDICOMdata d1, struct TDICOMdata d2) { //some MRI scans have gaps between slices, some CT have overlapping slices. Comparing adjacent slices provides measure for dx between slices if (isNanPosition(d1) || isNanPosition(d2)) return d1.xyzMM[3]; float tilt = 1.0; //printMessage("0020,0032 %g %g %g -> %g %g %g\n",d1.patientPosition[1],d1.patientPosition[2],d1.patientPosition[3],d2.patientPosition[1],d2.patientPosition[2],d2.patientPosition[3]); if (d1.gantryTilt != 0) tilt = (float)cos(d1.gantryTilt * M_PI / 180); //for CT scans with gantry tilt, we need to compute distance between slices, not distance along bed return tilt * sqrt(sqr(d1.patientPosition[1] - d2.patientPosition[1]) + sqr(d1.patientPosition[2] - d2.patientPosition[2]) + sqr(d1.patientPosition[3] - d2.patientPosition[3])); } //intersliceDistance() //#define myInstanceNumberOrderIsNotSpatial //instance number is virtually always ordered based on spatial position. // interleaved/multi-band conversion will be disrupted if instance number refers to temporal order // these functions reorder images based on spatial position // this situation is exceptionally rare, and there is a performance penalty // further, there may be unintended consequences. // Therefore, use of myInstanceNumberOrderIsNotSpatial is NOT recommended // a better solution is to fix the sequences that generated those files // as such images will probably disrupt most tools. // This option is only to salvage borked data. // This code has also not been tested on data stored in TXYZ rather than XYZT order //#ifdef myInstanceNumberOrderIsNotSpatial float intersliceDistanceSigned(struct TDICOMdata d1, struct TDICOMdata d2) { // Compute the signed slice position on the through-slice axis // https://nipy.org/nibabel/dicom/dicom_orientation.html#working-out-the-z-coordinates-for-a-set-of-slices // https://itk.org/pipermail/insight-users/2003-September/004762.html //reports distance between two slices, signed as 2nd slice can be in front or behind 1st vec3 slice_vector = setVec3(d2.patientPosition[1] - d1.patientPosition[1], d2.patientPosition[2] - d1.patientPosition[2], d2.patientPosition[3] - d1.patientPosition[3]); float len = vec3Length(slice_vector); if (isSameFloat(len, 0.0)) return len; if (d1.gantryTilt != 0) len = len * cos(d1.gantryTilt * M_PI / 180); vec3 read_vector = setVec3(d1.orient[1], d1.orient[2], d1.orient[3]); vec3 phase_vector = setVec3(d1.orient[4], d1.orient[5], d1.orient[6]); vec3 slice_vector90 = crossProduct(read_vector, phase_vector); //perpendicular float dot = dotProduct(slice_vector90, slice_vector); if (dot < 0.0) return -len; return len; } //https://stackoverflow.com/questions/36714030/c-sort-float-array-while-keeping-track-of-indices/36714204 struct TFloatSort { float position; int volume, index; }; int compareTFloatSort(const void *a, const void *b) { struct TFloatSort *a1 = (struct TFloatSort *)a; struct TFloatSort *a2 = (struct TFloatSort *)b; if ((*a1).volume > (*a2).volume) return 1; if ((*a1).volume < (*a2).volume) return -1; if ((*a1).position > (*a2).position) return 1; if ((*a1).position < (*a2).position) return -1; //if value is tied, retain index order (useful for TXYZ images?) if ((*a1).index > (*a2).index) return 1; if ((*a1).index < (*a2).index) return -1; return 0; } // compareTFloatSort() bool ensureSequentialSlicePositions(int d3, int d4, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], int verbose) { //ensure slice position is sequential: either ascending [1 2 3] or descending [3 2 1], not [1 3 2], [3 1 2] etc. //n.b. as currently designed, this will force swapDim3Dim4() for 4D data 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; int maxVolNotADC = -1; int minInstance = dcmList[dcmSort[0].indx].imageNum; int maxInstance = minInstance; int maxPhase = 1; //Philips Multi-Phase for (int i = 0; i < nConvert; i++) { int vol = dcmList[dcmSort[i].indx].rawDataRunNumber; minVol = min(minVol, vol); maxVol = max(maxVol, vol); if (vol < kMaxDTI4D) maxVolNotADC = max(maxVolNotADC, vol); int instance = dcmList[dcmSort[i].indx].imageNum; minInstance = min(minInstance, instance); maxInstance = max(maxInstance, instance); maxPhase = max(maxPhase, dcmList[dcmSort[i].indx].phaseNumber); } bool isUseInstanceNumberForVolume = false; if ((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; } bool isVerbose = (verbose > 1); //issue533 if (isVerbose) printMessage("Ranges volume %d..%d instance %d..%d\n", minVol, maxVolNotADC, minInstance, maxInstance); //TODO bool isASL = (dcmList[dcmSort[0].indx].aslFlags != kASL_FLAG_NONE); //we will renumber volumes for Philips ASL (Contrast/Label, phase) and DWI (derived trace) int minVolOut = kMaxDTI4D + 1; int maxVolOut = -1; bool isUsePhaseForVol = false; if ((!isASL) && (minVol == maxVol) && (maxPhase > 1)) isUsePhaseForVol = true;//e.g. TurboQUASAR bool isPhaseIsBValNumber =false; if ((!isASL) && (minVol < maxVol) && (maxPhase > 1)) isPhaseIsBValNumber = true;//DWI track both gradient number and vector number issue 546 if (isVerbose) printMessage("InstanceNumber\tPosition\tVolume\tRepeat\tASLlabel\tPhase\tTriggerTime\n"); for (int i = 0; i < nConvert; i++) { int vol = dcmList[dcmSort[i].indx].rawDataRunNumber; int rawvol = vol; int instance = dcmList[dcmSort[i].indx].imageNum; int phase = max(1, dcmList[dcmSort[i].indx].phaseNumber); 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]); if (isASL) { #ifdef myMatchEnhanced00209157 //issue533: make classic DICOMs match enhanced DICOM volume order //disk order: slice < repeat < phase < label/control vol += (phase - 1) * maxVol; if (isAslLabel) vol += maxPhase * maxVol; #else //"temporal" disk order: slice < phase < label/control < repeat : should match instance number vol = phase; if (isAslLabel) vol += maxPhase; vol += (rawvol - 1) * (2 * maxPhase); #endif } if (isUseInstanceNumberForVolume) vol = instance; if (isVerbose) //only report slice data for logorrheic verbosity 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; minVolOut = min(minVolOut, vol); maxVolOut = max(maxVolOut, vol); floatSort[i].volume = vol; floatSort[i].position = dx; 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)) 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]; qsort(floatSort, nConvert, sizeof(struct TFloatSort), compareTFloatSort); //sort based on series and image numbers.... for (int i = 0; i < nConvert; i++) 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; TDCMsort *dcmSortIn = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); for (int i = 0; i < nConvert; i++) dcmSortIn[i] = dcmSort[i]; int i = 0; for (int b = 0; b < d3; b++) for (int a = 0; a < d4; a++) { int k = (a * d3) + b; //printMessage("%d -> %d %d ->%d\n",i,a, b, k); dcmSort[k] = dcmSortIn[i]; i++; } free(dcmSortIn); } //swapDim3Dim4() bool intensityScaleVaries(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[]) { //detect whether some DICOM images report different intensity scaling //some Siemens PET scanners generate 16-bit images where slice has its own scaling factor. // since NIfTI provides a single scaling factor for each file, these images require special consideration if (nConvert < 2) return false; int dt = dcmList[dcmSort[0].indx].bitsAllocated; float iScale = dcmList[dcmSort[0].indx].intenScale; float iInter = dcmList[dcmSort[0].indx].intenIntercept; for (int i = 1; i < nConvert; i++) { //stack additional images uint64_t indx = dcmSort[i].indx; if (dcmList[indx].bitsAllocated != dt) return true; if (fabs(dcmList[indx].intenScale - iScale) > FLT_EPSILON) return true; if (fabs(dcmList[indx].intenIntercept - iInter) > FLT_EPSILON) return true; } return false; } //intensityScaleVaries() /*unsigned char * nii_bgr2rgb(unsigned char* bImg, struct nifti_1_header *hdr) { //DICOM planarappears to be BBB..B,GGG..G,RRR..R, NIfTI RGB saved in planes RRR..RGGG..GBBBB..B // see http://www.barre.nom.fr/medical/samples/index.html US-RGB-8-epicard if (hdr->datatype != DT_RGB24) return bImg; int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int sliceBytes24 = hdr->dim[1]*hdr->dim[2] * hdr->bitpix/8; int sliceBytes8 = hdr->dim[1]*hdr->dim[2]; //Byte bImg[ bSz ]; //[img getBytes:&bImg length:bSz]; unsigned char slice24[sliceBytes24]; int sliceOffsetR = 0; for (int sl = 0; sl < dim3to7; sl++) { //for each 2D slice memcpy(&slice24, &bImg[sliceOffsetR], sliceBytes24); memcpy( &bImg[sliceOffsetR], &slice24[sliceBytes8*2], sliceBytes8); sliceOffsetR += sliceBytes8; memcpy( &bImg[sliceOffsetR], &slice24[sliceBytes8], sliceBytes8); sliceOffsetR += sliceBytes8; memcpy( &bImg[sliceOffsetR], &slice24[0], sliceBytes8); sliceOffsetR += sliceBytes8; } //for each slice return bImg; } */ void niiDeleteFnm(const char *outname, const char *ext) { char niiname[2048] = {""}; strcat(niiname, outname); strcat(niiname, ext); if (is_fileexists(niiname)) remove(niiname); } void niiDelete(const char *niiname) { //for niiname "~/d/img" delete img.nii, img.bvec, img.bval, img.json niiDeleteFnm(niiname, ".nii"); niiDeleteFnm(niiname, ".nii.gz"); niiDeleteFnm(niiname, ".nrrd"); niiDeleteFnm(niiname, ".nhdr"); niiDeleteFnm(niiname, ".raw.gz"); niiDeleteFnm(niiname, ".json"); niiDeleteFnm(niiname, ".bval"); niiDeleteFnm(niiname, ".bvec"); } bool niiExists(const char *pathoutname) { char niiname[2048] = {""}; strcat(niiname, pathoutname); strcat(niiname, ".nii"); if (is_fileexists(niiname)) return true; char gzname[2048] = {""}; strcat(gzname, pathoutname); strcat(gzname, ".nii.gz"); if (is_fileexists(gzname)) return true; strcpy(niiname, pathoutname); strcat(niiname, ".nrrd"); if (is_fileexists(niiname)) return true; strcpy(niiname, pathoutname); strcat(niiname, ".nhdr"); if (is_fileexists(niiname)) return true; return false; } //niiExists() #ifndef W_OK #define W_OK 2 /* write mode check */ #endif int strcicmp(char const *a, char const *b) //case insensitive compare { for (;; a++, b++) { int d = tolower(*a) - tolower(*b); if (d != 0 || !*a) return d; } } // strcicmp() bool isExt(char *file_name, const char *ext) { char *p_extension; if ((p_extension = strrchr(file_name, '.')) != NULL) if (strcicmp(p_extension, ext) == 0) return true; //if(strcmp(p_extension,ext) == 0) return true; return false; } // isExt() void cleanISO8859(char *cString) { int len = strlen(cString); if (len < 1) return; for (int i = 0; i < len; 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]; if ((c >= 192) && (c <= 198)) cString[i] = 'A'; if (c == 199) cString[i] = 'C'; if ((c >= 200) && (c <= 203)) cString[i] = 'E'; if ((c >= 204) && (c <= 207)) cString[i] = 'I'; if (c == 208) cString[i] = 'D'; if (c == 209) cString[i] = 'N'; if ((c >= 210) && (c <= 214)) cString[i] = 'O'; if (c == 215) cString[i] = 'x'; if (c == 216) cString[i] = 'O'; if ((c >= 217) && (c <= 220)) cString[i] = 'O'; if (c == 221) cString[i] = 'Y'; if ((c >= 224) && (c <= 230)) cString[i] = 'a'; if (c == 231) cString[i] = 'c'; if ((c >= 232) && (c <= 235)) cString[i] = 'e'; if ((c >= 236) && (c <= 239)) cString[i] = 'i'; if (c == 240) cString[i] = 'o'; if (c == 241) cString[i] = 'n'; if ((c >= 242) && (c <= 246)) cString[i] = 'o'; if (c == 248) cString[i] = 'o'; if ((c >= 249) && (c <= 252)) cString[i] = 'u'; if (c == 253) cString[i] = 'y'; if (c == 255) cString[i] = 'y'; } } int nii_createFilename(struct TDICOMdata dcm, char *niiFilename, struct TDCMopts opts) { char pth[PATH_MAX] = {""}; if (strlen(opts.outdir) > 0) { strcpy(pth, opts.outdir); int w = access(pth, W_OK); if (w != 0) { //should never happen except with "-b i": see kEXIT_OUTPUT_FOLDER_READ_ONLY for early termination // with "-b i" the code below generates a warning but no files are created if (getcwd(pth, sizeof(pth)) != NULL) { #ifdef USE_CWD_IF_OUTDIR_NO_WRITE //optional: fall back to current working directory w = access(pth, W_OK); if (w != 0) { printError("You do not have write permissions for the directory %s\n", opts.outdir); return EXIT_FAILURE; } printWarning("%s write permission denied. Saving to working directory %s \n", opts.outdir, pth); #else printError("You do not have write permissions for the directory %s\n", opts.outdir); return EXIT_FAILURE; #endif } } } char inname[PATH_MAX] = {""}; //{"test%t_%av"}; //% a = acquisition, %n patient name, %t time strcpy(inname, opts.filename); bool isDcmExt = isExt(inname, ".dcm"); // "%r.dcm" with multi-echo should generate "1.dcm", "1e2.dcm" if (isDcmExt) { inname[strlen(inname) - 4] = '\0'; } char outname[PATH_MAX] = {""}; char newstr[256]; if (strlen(inname) < 1) { strcpy(inname, "T%t_N%n_S%s"); } const char kTempPathSeparator = '\a'; for (size_t pos = 0; pos < strlen(inname); pos++) if ((inname[pos] == '\\') || (inname[pos] == '/')) inname[pos] = kTempPathSeparator; size_t start = 0; size_t pos = 0; bool isAddNamePostFixes = opts.isAddNamePostFixes; bool isCoilReported = false; bool isEchoReported = false; bool isSeriesReported = false; //bool isAcquisitionReported = false; bool isImageNumReported = false; while (pos < strlen(inname)) { if (inname[pos] == '%') { if (pos > start) { strncpy(&newstr[0], &inname[0] + start, pos - start); newstr[pos - start] = '\0'; strcat(outname, newstr); } pos++; //extra increment: skip both % and following character char f = 'P'; if (pos < strlen(inname)) f = toupper(inname[pos]); if (f == 'A') { isCoilReported = true; strcat(outname, dcm.coilName); } if (f == 'B') strcat(outname, dcm.imageBaseName); if (f == 'C') strcat(outname, dcm.imageComments); if (f == 'D') strcat(outname, dcm.seriesDescription); if (f == 'E') { isEchoReported = true; sprintf(newstr, "%d", dcm.echoNum); strcat(outname, newstr); } if (f == 'F') strcat(outname, opts.indirParent); if (f == 'G') strcat(outname, dcm.accessionNumber); if (f == 'I') strcat(outname, dcm.patientID); if (f == 'J') strcat(outname, dcm.seriesInstanceUID); if (f == 'K') strcat(outname, dcm.studyInstanceUID); if (f == 'L') //"L"ocal Institution-generated description or classification of the Procedure Step that was performed. strcat(outname, dcm.procedureStepDescription); if (f == 'M') { if (dcm.manufacturer == kMANUFACTURER_BRUKER) strcat(outname, "Br"); else if (dcm.manufacturer == kMANUFACTURER_GE) strcat(outname, "GE"); else if (dcm.manufacturer == kMANUFACTURER_TOSHIBA) strcat(outname, "To"); else if (dcm.manufacturer == kMANUFACTURER_CANON) strcat(outname, "Ca"); else if (dcm.manufacturer == kMANUFACTURER_UIH) strcat(outname, "UI"); else if (dcm.manufacturer == kMANUFACTURER_PHILIPS) strcat(outname, "Ph"); else if (dcm.manufacturer == kMANUFACTURER_SIEMENS) strcat(outname, "Si"); else strcat(outname, "NA"); //manufacturer name not available } if (f == 'N') strcat(outname, dcm.patientName); if (f == 'O') { strcat(outname, dcm.instanceUID); if (strlen(dcm.instanceUID) > 0) isAddNamePostFixes = false; //should be unique, so no need to post-fix } if (f == 'P') { strcat(outname, dcm.protocolName); if (strlen(dcm.protocolName) < 1) printWarning("Unable to append protocol name (0018,1030) to filename (it is empty).\n"); } if (f == 'R') { sprintf(newstr, "%d", dcm.imageNum); strcat(outname, newstr); isImageNumReported = true; } if (f == 'Q') strcat(outname, dcm.scanningSequence); if (f == 'S') { sprintf(newstr, "%ld", dcm.seriesNum); strcat(outname, newstr); isSeriesReported = true; } if (f == 'T') { sprintf(newstr, "%0.0f", dcm.dateTime); strcat(outname, newstr); } if (f == 'U') { if (opts.isRenameNotConvert) { sprintf(newstr, "%d", dcm.acquNum); strcat(outname, newstr); //isAcquisitionReported = true; } else { sprintf(newstr, "%d", dcm.acquNum); strcat(outname, newstr); #ifdef mySegmentByAcq //isAcquisitionReported = true; #else printWarning("'%%u' in output filename can be misleading (issue 526)\n"); #endif } } if (f == 'V') { if (dcm.manufacturer == kMANUFACTURER_BRUKER) strcat(outname, "Bruker"); else if (dcm.manufacturer == kMANUFACTURER_GE) strcat(outname, "GE"); else if (dcm.manufacturer == kMANUFACTURER_PHILIPS) strcat(outname, "Philips"); else if (dcm.manufacturer == kMANUFACTURER_SIEMENS) strcat(outname, "Siemens"); else if (dcm.manufacturer == kMANUFACTURER_TOSHIBA) strcat(outname, "Toshiba"); else if (dcm.manufacturer == kMANUFACTURER_CANON) strcat(outname, "Canon"); else if (dcm.manufacturer == kMANUFACTURER_UIH) strcat(outname, "UIH"); else strcat(outname, "NA"); } if (f == 'X') strcat(outname, dcm.studyID); if ((f == 'Y') && (dcm.rawDataRunNumber >= 0)) { sprintf(newstr, "%d", dcm.rawDataRunNumber); //GE (0019,10A2) else (0020,0100) strcat(outname, newstr); } if (f == 'Z') strcat(outname, dcm.sequenceName); if ((f >= '0') && (f <= '9')) { if ((pos < strlen(inname)) && (toupper(inname[pos + 1]) == 'S')) { char zeroPad[12] = {""}; sprintf(zeroPad, "%%0%dd", f - '0'); sprintf(newstr, zeroPad, dcm.seriesNum); strcat(outname, newstr); pos++; // e.g. %3f requires extra increment: skip both number and following character } if ((pos < strlen(inname)) && (toupper(inname[pos + 1]) == 'R')) { char zeroPad[12] = {""}; sprintf(zeroPad, "%%0%dd", f - '0'); sprintf(newstr, zeroPad, dcm.imageNum); isImageNumReported = true; strcat(outname, newstr); pos++; // e.g. %3f requires extra increment: skip both number and following character } if ((pos < strlen(inname)) && (toupper(inname[pos + 1]) == 'Y') && (dcm.rawDataRunNumber >= 0)) { char zeroPad[12] = {""}; sprintf(zeroPad, "%%0%dd", f - '0'); sprintf(newstr, zeroPad, dcm.rawDataRunNumber); //GE (0019,10A2) else (0020,0100) strcat(outname, newstr); pos++; // e.g. %3f requires extra increment: skip both number and following character } } start = pos + 1; } //found a % character pos++; } //for each character in input if (pos > start) { //append any trailing characters strncpy(&newstr[0], &inname[0] + start, pos - start); newstr[pos - start] = '\0'; strcat(outname, newstr); } if ((isAddNamePostFixes) && (!isCoilReported) && (dcm.isCoilVaries)) { //sprintf(newstr, "_c%d", dcm.coilNum); //strcat (outname,newstr); strcat(outname, "_c"); strcat(outname, dcm.coilName); } // myMultiEchoFilenameSkipEcho1 https://github.com/rordenlab/dcm2niix/issues/237 #ifdef myMultiEchoFilenameSkipEcho1 if ((isAddNamePostFixes) && (!isEchoReported) && (dcm.isMultiEcho) && (dcm.echoNum >= 1)) { //multiple echoes saved as same series #else if ((isAddNamePostFixes) && (!isEchoReported) && ((dcm.isMultiEcho) || (dcm.echoNum > 1))) { //multiple echoes saved as same series #endif sprintf(newstr, "_e%d", dcm.echoNum); strcat(outname, newstr); isEchoReported = true; } if ((isAddNamePostFixes) && (!isSeriesReported) && (!isEchoReported) && (dcm.echoNum > 1)) { //last resort: user provided no method to disambiguate echo number in filename sprintf(newstr, "_e%d", dcm.echoNum); strcat(outname, newstr); isEchoReported = true; } if ((dcm.isNonParallelSlices) && (!isImageNumReported)) { sprintf(newstr, "_i%05d", dcm.imageNum); strcat(outname, newstr); } /*if (dcm.maxGradDynVol > 0) { //Philips segmented sprintf(newstr, "_v%04d", dcm.gradDynVol+1); //+1 as indexed from zero strcat (outname,newstr); }*/ if ((isAddNamePostFixes) && (dcm.isHasImaginary)) { strcat(outname, "_imaginary"); //has phase map } if ((isAddNamePostFixes) && (dcm.isHasReal) && (dcm.isRealIsPhaseMapHz)) { strcat(outname, "_fieldmaphz"); //has field map } if ((isAddNamePostFixes) && (dcm.isHasReal) && (!dcm.isRealIsPhaseMapHz)) { strcat(outname, "_real"); //has phase map } if ((isAddNamePostFixes) && (dcm.isHasPhase)) { strcat(outname, "_ph"); //has phase map if (dcm.isHasMagnitude) strcat(outname, "Mag"); //Philips enhanced with BOTH phase and Magnitude in single file } if ((isAddNamePostFixes) && (dcm.aslFlags == kASL_FLAG_NONE) && (dcm.triggerDelayTime >= 1) && (dcm.manufacturer != kMANUFACTURER_GE)) { //issue 336 GE uses this for slice timing sprintf(newstr, "_t%d", (int)roundf(dcm.triggerDelayTime)); strcat(outname, newstr); } //could add (isAddNamePostFixes) to these next two, but consequences could be catastrophic if (dcm.isRawDataStorage) //avoid name clash for Philips XX_ files strcat(outname, "_Raw"); if (dcm.isGrayscaleSoftcopyPresentationState) //avoid name clash for Philips PS_ files strcat(outname, "_PS"); if (isDcmExt) strcat(outname, ".dcm"); if (strlen(outname) < 1) strcpy(outname, "dcm2nii_invalidName"); if (outname[0] == '.') outname[0] = '_'; //make sure not a hidden file //eliminate illegal characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx // https://github.com/rordenlab/dcm2niix/issues/237 #ifdef myOsSpecificFilenameMask #define kMASK_WINDOWS_SPECIAL_CHARACTERS 0 #else #define kMASK_WINDOWS_SPECIAL_CHARACTERS 1 #endif #if defined(_WIN64) || defined(_WIN32) || defined(kMASK_WINDOWS_SPECIAL_CHARACTERS) //https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names for (size_t pos = 0; pos < strlen(outname); pos++) if ((outname[pos] == '\\') || (outname[pos] == '/') || (outname[pos] == ' ') || (outname[pos] == '<') || (outname[pos] == '>') || (outname[pos] == ':') || (outname[pos] == ';') || (outname[pos] == '"') // || (outname[pos] == '/') || (outname[pos] == '\\') //|| (outname[pos] == '^') issue398 || (outname[pos] == '*') || (outname[pos] == '|') || (outname[pos] == '?')) outname[pos] = '_'; #else for (size_t pos = 0; pos < strlen(outname); pos++) if (outname[pos] == ':') //not allowed by MacOS outname[pos] = '_'; #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++) { 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 outname[pos] = '_'; } char baseoutname[2048] = {""}; strcat(baseoutname, pth); char appendChar[2] = {"a"}; appendChar[0] = kPathSeparator; if ((strlen(pth) > 0) && (pth[strlen(pth) - 1] != kPathSeparator) && (outname[0] != kPathSeparator)) strcat(baseoutname, appendChar); //remove redundant underscores int len = strlen(outname); int outpos = 0; for (int inpos = 0; inpos < len; inpos++) { if ((outpos > 0) && (outname[inpos] == '_') && (outname[outpos - 1] == '_')) continue; outname[outpos] = outname[inpos]; outpos++; } outname[outpos] = 0; //Allow user to specify new folders, e.g. "-f dir/%p" or "-f %s/%p/%m" // These folders are created if they do not exist char *sep = strchr(outname, kPathSeparator); #if defined(USING_R) && (defined(_WIN64) || defined(_WIN32)) // R also uses forward slash on Windows, so allow it here if (!sep) sep = strchr(outname, '/'); #endif if (sep) { char newdir[2048] = {""}; strcat(newdir, baseoutname); //struct stat st = {0}; for (size_t pos = 0; pos < strlen(outname); pos++) { if (outname[pos] == kPathSeparator) { //if (stat(newdir, &st) == -1) if (!is_dir(newdir, true)) #if defined(_WIN64) || defined(_WIN32) mkdir(newdir); #else mkdir(newdir, 0700); #endif } char ch[12] = {""}; sprintf(ch, "%c", outname[pos]); strcat(newdir, ch); } } //printMessage("path='%s' name='%s'\n", pathoutname, outname); //make sure outname is unique strcat(baseoutname, outname); char pathoutname[2048] = {""}; strcat(pathoutname, baseoutname); if ((niiExists(pathoutname)) && (opts.nameConflictBehavior == kNAME_CONFLICT_SKIP)) { printWarning("Skipping existing file named %s\n", pathoutname); return EXIT_FAILURE; } if ((niiExists(pathoutname)) && (opts.nameConflictBehavior == kNAME_CONFLICT_OVERWRITE)) { printWarning("Overwriting existing file with the name %s\n", pathoutname); niiDelete(pathoutname); strcpy(niiFilename, pathoutname); return EXIT_SUCCESS; } int i = 0; while (niiExists(pathoutname) && (i < 26)) { strcpy(pathoutname, baseoutname); appendChar[0] = 'a' + i; strcat(pathoutname, appendChar); i++; } if (i >= 26) { printError("Too many NIFTI images with the name %s\n", baseoutname); return EXIT_FAILURE; } //printMessage("-->%s\n",pathoutname); return EXIT_SUCCESS; //printMessage("outname=%s\n", pathoutname); strcpy(niiFilename, pathoutname); return EXIT_SUCCESS; } //nii_createFilename() void nii_createDummyFilename(char *niiFilename, struct TDCMopts opts) { //generate string that illustrating sample of filename struct TDICOMdata d = clear_dicom_data(); strcpy(d.patientName, "John_Doe"); strcpy(d.patientID, "ID123"); strcpy(d.accessionNumber, "ID123"); strcpy(d.imageType, "ORIGINAL"); strcpy(d.imageComments, "imgComments"); strcpy(d.studyDate, "1/1/1977"); strcpy(d.studyTime, "11:11:11"); strcpy(d.protocolName, "MPRAGE"); strcpy(d.seriesDescription, "T1_mprage"); strcpy(d.sequenceName, "T1"); strcpy(d.scanningSequence, "tfl3d1_ns"); strcpy(d.sequenceVariant, "tfl3d1_ns"); strcpy(d.manufacturersModelName, "N/A"); strcpy(d.procedureStepDescription, ""); strcpy(d.seriesInstanceUID, ""); strcpy(d.studyInstanceUID, ""); strcpy(d.bodyPartExamined, ""); strcpy(opts.indirParent, "myFolder"); char niiFilenameBase[PATH_MAX] = {"/usr/myFolder/dicom.dcm"}; nii_createFilename(d, niiFilenameBase, opts); strcpy(niiFilename, "Example output filename: '"); strcat(niiFilename, niiFilenameBase); if (opts.saveFormat != kSaveFormatNIfTI) { if (opts.isGz) strcat(niiFilename, ".nhdr'"); else strcat(niiFilename, ".nrrd'"); } else { if (opts.isGz) strcat(niiFilename, ".nii.gz'"); else strcat(niiFilename, ".nii'"); } } // nii_createDummyFilename() #ifndef myDisableZLib #ifndef MiniZ unsigned long mz_compressBound(unsigned long source_len) { return compressBound(source_len); } unsigned long mz_crc32(unsigned long crc, const unsigned char *ptr, size_t buf_len) { return crc32(crc, ptr, (uInt)buf_len); } #endif #ifndef MZ_UBER_COMPRESSION //defined in miniz, not defined in zlib #define MZ_UBER_COMPRESSION 9 #endif #ifndef MZ_DEFAULT_LEVEL #define MZ_DEFAULT_LEVEL 6 #endif void writeNiiGz(char *baseName, struct nifti_1_header hdr, unsigned char *src_buffer, unsigned long src_len, int gzLevel, bool isSkipHeader) { //create gz file in RAM, save to disk http://www.zlib.net/zlib_how.html // in general this single-threaded approach is slower than PIGZ but is useful for slow (network attached) disk drives char fname[2048] = {""}; strcpy(fname, baseName); if (!isSkipHeader) strcat(fname, ".nii.gz"); unsigned long hdrPadBytes = sizeof(hdr) + 4; //348 byte header + 4 byte pad if (isSkipHeader) hdrPadBytes = 0; unsigned long cmp_len = mz_compressBound(src_len + hdrPadBytes); unsigned char *pCmp = (unsigned char *)malloc(cmp_len); z_stream strm; strm.total_in = 0; strm.total_out = 0; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.next_out = pCmp; // output char array strm.avail_out = (unsigned int)cmp_len; // size of output int zLevel = MZ_DEFAULT_LEVEL; //Z_DEFAULT_COMPRESSION; if ((gzLevel > 0) && (gzLevel < 11)) zLevel = gzLevel; if (zLevel > MZ_UBER_COMPRESSION) zLevel = MZ_UBER_COMPRESSION; if (deflateInit(&strm, zLevel) != Z_OK) { free(pCmp); return; } unsigned char *pHdr; if (!isSkipHeader) { //add header pHdr = (unsigned char *)malloc(hdrPadBytes); pHdr[hdrPadBytes - 1] = 0; pHdr[hdrPadBytes - 2] = 0; pHdr[hdrPadBytes - 3] = 0; pHdr[hdrPadBytes - 4] = 0; memcpy(pHdr, &hdr, sizeof(hdr)); strm.avail_in = (unsigned int)hdrPadBytes; // size of input strm.next_in = (uint8_t *)pHdr; // input header -- TPX strm.next_in = (Bytef *)pHdr; uint32_t deflate(&strm, Z_NO_FLUSH); } //add image strm.avail_in = (unsigned int)src_len; // size of input strm.next_in = (uint8_t *)src_buffer; // input image -- TPX strm.next_in = (Bytef *)src_buffer; deflate(&strm, Z_FINISH); //Z_NO_FLUSH; //finish up deflateEnd(&strm); unsigned long file_crc32 = mz_crc32(0L, Z_NULL, 0); if (!isSkipHeader) file_crc32 = mz_crc32(file_crc32, pHdr, (unsigned int)hdrPadBytes); file_crc32 = mz_crc32(file_crc32, src_buffer, (unsigned int)src_len); cmp_len = strm.total_out; if (cmp_len <= 0) { free(pCmp); free(src_buffer); return; } FILE *fileGz = fopen(fname, "wb"); if (!fileGz) { free(pCmp); free(src_buffer); return; } //write header http://www.gzip.org/zlib/rfc-gzip.html fputc((char)0x1f, fileGz); //ID1 fputc((char)0x8b, fileGz); //ID2 fputc((char)0x08, fileGz); //CM - use deflate compression method fputc((char)0x00, fileGz); //FLG - no addition fields fputc((char)0x00, fileGz); //MTIME0 fputc((char)0x00, fileGz); //MTIME1 fputc((char)0x00, fileGz); //MTIME2 fputc((char)0x00, fileGz); //MTIME2 fputc((char)0x00, fileGz); //XFL fputc((char)0xff, fileGz); //OS //write Z-compressed data fwrite(&pCmp[2], sizeof(char), cmp_len - 6, fileGz); //-6 as LZ78 format has 2 bytes header (typically 0x789C) and 4 bytes tail (ADLER 32) //write tail: write redundancy check and uncompressed size as bytes to ensure LITTLE-ENDIAN order fputc((unsigned char)(file_crc32), fileGz); fputc((unsigned char)(file_crc32 >> 8), fileGz); fputc((unsigned char)(file_crc32 >> 16), fileGz); fputc((unsigned char)(file_crc32 >> 24), fileGz); fputc((unsigned char)(strm.total_in), fileGz); fputc((unsigned char)(strm.total_in >> 8), fileGz); fputc((unsigned char)(strm.total_in >> 16), fileGz); fputc((unsigned char)(strm.total_in >> 24), fileGz); fclose(fileGz); free(pCmp); if (!isSkipHeader) free(pHdr); } //writeNiiGz() #endif #ifdef USING_R // Version of nii_saveNII() for R/divest: create nifti_image pointer and push onto stack int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) { hdr.vox_offset = 352; // Extract the basename from the full file path char *start = niiFilename + strlen(niiFilename); while (start >= niiFilename && *start != '/' && *start != kPathSeparator) start--; std::string name(++start); nifti_image *image = nifti_convert_nhdr2nim(hdr, niiFilename); if (image == NULL) return EXIT_FAILURE; image->data = (void *)im; ImageList *images = (ImageList *)opts.imageList; images->append(image, name); free(image); return EXIT_SUCCESS; } void nii_saveAttributes(struct TDICOMdata &data, struct nifti_1_header &header, struct TDCMopts &opts, const char *filename) { ImageList *images = (ImageList *)opts.imageList; switch (data.modality) { case kMODALITY_CR: images->addAttribute("modality", "CR"); break; case kMODALITY_CT: images->addAttribute("modality", "CT"); break; case kMODALITY_MR: images->addAttribute("modality", "MR"); break; case kMODALITY_PT: images->addAttribute("modality", "PT"); break; case kMODALITY_US: images->addAttribute("modality", "US"); break; } switch (data.manufacturer) { case kMANUFACTURER_SIEMENS: images->addAttribute("manufacturer", "Siemens"); break; case kMANUFACTURER_GE: images->addAttribute("manufacturer", "GE"); break; case kMANUFACTURER_MEDISO: images->addAttribute("manufacturer", "Mediso"); break; case kMANUFACTURER_PHILIPS: images->addAttribute("manufacturer", "Philips"); break; case kMANUFACTURER_TOSHIBA: images->addAttribute("manufacturer", "Toshiba"); break; case kMANUFACTURER_UIH: images->addAttribute("manufacturer", "UIH"); break; case kMANUFACTURER_BRUKER: images->addAttribute("manufacturer", "Bruker"); break; case kMANUFACTURER_HITACHI: images->addAttribute("manufacturer", "Hitachi"); break; case kMANUFACTURER_CANON: images->addAttribute("manufacturer", "Canon"); break; } images->addAttribute("scannerModelName", data.manufacturersModelName); images->addAttribute("imageType", data.imageType); if (data.seriesNum > 0) images->addAttribute("seriesNumber", int(data.seriesNum)); images->addAttribute("seriesDescription", data.seriesDescription); images->addAttribute("sequenceName", data.sequenceName); images->addAttribute("protocolName", data.protocolName); images->addDateAttribute("studyDate", data.studyDate); images->addTimeAttribute("studyTime", data.studyTime); images->addAttribute("fieldStrength", data.fieldStrength); images->addAttribute("flipAngle", data.flipAngle); images->addAttribute("echoTime", data.TE); images->addAttribute("repetitionTime", data.TR); images->addAttribute("inversionTime", data.TI); if (!data.isXRay) { images->addAttribute("sliceThickness", data.zThick); images->addAttribute("sliceSpacing", data.zSpacing); } if (data.CSA.multiBandFactor > 1) images->addAttribute("multibandFactor", data.CSA.multiBandFactor); if (data.phaseEncodingSteps > 0) images->addAttribute("phaseEncodingSteps", data.phaseEncodingSteps); if (data.phaseEncodingLines > 0) images->addAttribute("phaseEncodingLines", data.phaseEncodingLines); // Calculations relating to the reconstruction in the phase encode direction, // which are needed to derive effective echo spacing and readout time below. // See the nii_SaveBIDS() function for details int reconMatrixPE = data.phaseEncodingLines; if ((header.dim[2] > 0) && (header.dim[1] > 0)) { if (header.dim[1] == header.dim[2]) //phase encoding does not matter reconMatrixPE = header.dim[2]; else if (data.phaseEncodingRC == 'C') reconMatrixPE = header.dim[2]; else if (data.phaseEncodingRC == 'R') reconMatrixPE = header.dim[1]; } double bandwidthPerPixelPhaseEncode = data.bandwidthPerPixelPhaseEncode; if (bandwidthPerPixelPhaseEncode == 0.0) bandwidthPerPixelPhaseEncode = data.CSA.bandwidthPerPixelPhaseEncode; double effectiveEchoSpacing = 0.0; if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode > 0.0)) effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * reconMatrixPE); if (data.effectiveEchoSpacingGE > 0.0) { double roundFactor = data.isPartialFourier ? 4.0 : 2.0; double totalReadoutTime = ((ceil(1.0 / roundFactor * data.phaseEncodingLines / data.accelFactPE) * roundFactor) - 1.0) * data.effectiveEchoSpacingGE * 0.000001; effectiveEchoSpacing = totalReadoutTime / (reconMatrixPE - 1); } images->addAttribute("effectiveEchoSpacing", effectiveEchoSpacing); if (data.manufacturer == kMANUFACTURER_UIH) images->addAttribute("effectiveReadoutTime", data.acquisitionDuration / 1000.0); else if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0)) images->addAttribute("effectiveReadoutTime", effectiveEchoSpacing * (reconMatrixPE - 1.0)); images->addAttribute("pixelBandwidth", data.pixelBandwidth); if ((data.manufacturer == kMANUFACTURER_SIEMENS) && (data.dwellTime > 0)) images->addAttribute("dwellTime", data.dwellTime * 1e-9); // Phase encoding polarity // We only save these attributes if both direction and polarity are known bool isSkipPhaseEncodingAxis = data.is3DAcq; if (data.echoTrainLength > 1) isSkipPhaseEncodingAxis = false; //issue 371: ignore phaseEncoding for 3D MP-RAGE/SPACE, but report for 3D EPI if (((data.phaseEncodingRC == 'R') || (data.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && ((data.CSA.phaseEncodingDirectionPositive == 1) || (data.CSA.phaseEncodingDirectionPositive == 0))) { if (data.phaseEncodingRC == 'C') { images->addAttribute("phaseEncodingDirection", "j"); // Notice the XOR (^): the sense of phaseEncodingDirectionPositive // is reversed if we are flipping the y-axis images->addAttribute("phaseEncodingSign", ((data.CSA.phaseEncodingDirectionPositive == 0) ^ opts.isFlipY) ? -1 : 1); } else if (data.phaseEncodingRC == 'R') { images->addAttribute("phaseEncodingDirection", "i"); images->addAttribute("phaseEncodingSign", data.CSA.phaseEncodingDirectionPositive == 0 ? -1 : 1); } } // Slice timing (stored in seconds) if (data.CSA.sliceTiming[0] >= 0.0 && (data.manufacturer == kMANUFACTURER_UIH || data.manufacturer == kMANUFACTURER_GE || (data.manufacturer == kMANUFACTURER_SIEMENS && !data.isXA10A))) { std::vector sliceTimes; for (int i = 0; i < header.dim[3]; i++) { if (data.CSA.sliceTiming[i] < 0.0) break; sliceTimes.push_back(data.CSA.sliceTiming[i] / 1000.0); } images->addAttribute("sliceTiming", sliceTimes); } images->addAttribute("patientIdentifier", data.patientID); images->addAttribute("patientName", data.patientName); images->addDateAttribute("patientBirthDate", data.patientBirthDate); if (strlen(data.patientAge) > 0 && strcmp(data.patientAge, "000Y") != 0) images->addAttribute("patientAge", data.patientAge); if (data.patientSex == 'F') images->addAttribute("patientSex", "F"); else if (data.patientSex == 'M') images->addAttribute("patientSex", "M"); images->addAttribute("patientWeight", data.patientWeight); images->addAttribute("comments", data.imageComments); } #else int pigz_File(char *fname, struct TDCMopts opts, size_t imgsz) { //given "/dir/file.nii" creates "/dir/file.nii.gz" char blockSize[768]; strcpy(blockSize, ""); //-b 960 increases block size from 128 to 960: each block has 32kb lead in... so less redundancy if (imgsz > 1000000) strcpy(blockSize, " -b 960"); char command[768]; strcpy(command, "\""); strcat(command, opts.pigzname); if ((opts.gzLevel > 0) && (opts.gzLevel < 12)) { char newstr[256]; sprintf(newstr, "\"%s -n -f -%d \"", blockSize, opts.gzLevel); strcat(command, newstr); } else { char newstr[256]; sprintf(newstr, "\"%s -n \"", blockSize); strcat(command, newstr); } strcat(command, fname); strcat(command, "\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' #if defined(_WIN64) || defined(_WIN32) //using CreateProcess instead of system to run in background (avoids screen flicker) DWORD exitCode; PROCESS_INFORMATION ProcessInfo = {0}; STARTUPINFO startupInfo = {0}; startupInfo.cb = sizeof(startupInfo); //StartupInfo.cb = sizeof StartupInfo ; //Only compulsory field if (CreateProcess(NULL, command, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, NULL, &startupInfo, &ProcessInfo)) { //printMessage("compression --- %s\n",command); WaitForSingleObject(ProcessInfo.hProcess, INFINITE); CloseHandle(ProcessInfo.hThread); CloseHandle(ProcessInfo.hProcess); } else printMessage("Compression failed %s\n", command); #else //if win else linux int ret = system(command); if (ret == -1) printWarning("Failed to execute: %s\n", command); #endif //else linux printMessage("Compress: %s\n", command); return EXIT_SUCCESS; } // pigz_File() #define kMGHpad 97 #ifdef __GNUC__ #define PACKD(...) __VA_ARGS__ __attribute__((__packed__)) #else #define PACKD(...) __pragma(pack(push, 1)) __VA_ARGS__ __pragma(pack(pop)) #endif PACKD(typedef struct { int32_t version, width,height,depth,nframes,type,dof; int16_t goodRASFlag; float spacingX,spacingY,spacingZ,xr,xa,xs,yr,ya,ys,zr,za,zs,cr,ca,cs; int16_t pad[kMGHpad]; }) Tmgh; PACKD(typedef struct { float TR, FlipAngle, TE, TI; }) TmghFooter; void writeMghGz(char *baseName, Tmgh hdr, TmghFooter footer, unsigned char *src_buffer, unsigned long src_len, int gzLevel) { //create gz file in RAM, save to disk http://www.zlib.net/zlib_how.html // in general this single-threaded approach is slower than PIGZ but is useful for slow (network attached) disk drives char fname[2048] = {""}; strcpy(fname, baseName); unsigned long hdrPadBytes = sizeof(hdr); //348 byte header + 4 byte pad unsigned long cmp_len = mz_compressBound(src_len + hdrPadBytes); unsigned char *pCmp = (unsigned char *)malloc(cmp_len); z_stream strm; strm.total_in = 0; strm.total_out = 0; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.next_out = pCmp; // output char array strm.avail_out = (unsigned int)cmp_len; // size of output int zLevel = MZ_DEFAULT_LEVEL; //Z_DEFAULT_COMPRESSION; if ((gzLevel > 0) && (gzLevel < 11)) zLevel = gzLevel; if (zLevel > MZ_UBER_COMPRESSION) zLevel = MZ_UBER_COMPRESSION; if (deflateInit(&strm, zLevel) != Z_OK) { 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; deflate(&strm, Z_NO_FLUSH); //add image strm.avail_in = (unsigned int)src_len; // size of input strm.next_in = (uint8_t *)src_buffer; // input image -- TPX strm.next_in = (Bytef *)src_buffer; deflate(&strm, Z_FINISH); //add footer strm.avail_in = (unsigned int)sizeof(footer); // size of input strm.next_in = (uint8_t *) &footer.TR; deflate(&strm, Z_NO_FLUSH); //finish up deflateEnd(&strm); unsigned long file_crc32 = mz_crc32(0L, Z_NULL, 0); file_crc32 = mz_crc32(file_crc32, (uint8_t *) &hdr.version, (unsigned int)sizeof(hdr)); file_crc32 = mz_crc32(file_crc32, src_buffer, (unsigned int)src_len); file_crc32 = mz_crc32(file_crc32, (uint8_t *) &footer.TR, (unsigned int)sizeof(footer)); cmp_len = strm.total_out; if (cmp_len <= 0) { free(pCmp); free(src_buffer); return; } FILE *fileGz = fopen(fname, "wb"); if (!fileGz) { free(pCmp); free(src_buffer); return; } //write header http://www.gzip.org/zlib/rfc-gzip.html fputc((char)0x1f, fileGz); //ID1 fputc((char)0x8b, fileGz); //ID2 fputc((char)0x08, fileGz); //CM - use deflate compression method fputc((char)0x00, fileGz); //FLG - no addition fields fputc((char)0x00, fileGz); //MTIME0 fputc((char)0x00, fileGz); //MTIME1 fputc((char)0x00, fileGz); //MTIME2 fputc((char)0x00, fileGz); //MTIME2 fputc((char)0x00, fileGz); //XFL fputc((char)0xff, fileGz); //OS //write Z-compressed data fwrite(&pCmp[2], sizeof(char), cmp_len - 6, fileGz); //-6 as LZ78 format has 2 bytes header (typically 0x789C) and 4 bytes tail (ADLER 32) //write tail: write redundancy check and uncompressed size as bytes to ensure LITTLE-ENDIAN order fputc((unsigned char)(file_crc32), fileGz); fputc((unsigned char)(file_crc32 >> 8), fileGz); fputc((unsigned char)(file_crc32 >> 16), fileGz); fputc((unsigned char)(file_crc32 >> 24), fileGz); fputc((unsigned char)(strm.total_in), fileGz); fputc((unsigned char)(strm.total_in >> 8), fileGz); fputc((unsigned char)(strm.total_in >> 16), fileGz); fputc((unsigned char)(strm.total_in >> 24), fileGz); fclose(fileGz); free(pCmp); } //writeMghGz() 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]; //printMessage("NRRD writer is experimental\n"); if (nDim < 1) return EXIT_FAILURE; bool isGz = opts.isGz; size_t imgsz = nii_ImgBytes(hdr); if ((isGz) && (imgsz >= 2147483647)) { printWarning("Saving huge image uncompressed (many GZip tools have 2 Gb limit).\n"); isGz = false; } //fill the footer TmghFooter footer; footer.TR = d.TR; footer.FlipAngle = d.flipAngle; footer.TE = d.TE; footer.TI = d.TI; #ifdef __LITTLE_ENDIAN__ //mgh data ALWAYS big endian! nifti_swap_4bytes(4, &footer.TR); #endif //fill the header Tmgh mgh; mgh.version = 1; mgh.width = hdr.dim[1]; mgh.height = hdr.dim[2]; mgh.depth = hdr.dim[3]; mgh.nframes = max(hdr.dim[4],1); if (hdr.datatype == DT_UINT8) mgh.type = 0; else if (hdr.datatype == DT_INT16) mgh.type = 4; else if (hdr.datatype == DT_INT32) mgh.type = 1; else if (hdr.datatype == DT_FLOAT32) mgh.type = 3; else { printError("MGH format does not support NIfTI datatype %d\n", hdr.datatype); return EXIT_FAILURE; } mgh.dof = 0; mgh.goodRASFlag = 1; float xmm = hdr.pixdim[1]; float ymm = hdr.pixdim[2]; float zmm = hdr.pixdim[3]; //avoid divide by zero errors: if (xmm <= 0.0) xmm = 1.0; if (ymm <= 0.0) ymm = 1.0; if (zmm <= 0.0) zmm = 1.0; mgh.spacingX = xmm; mgh.spacingY = ymm; mgh.spacingZ = zmm; mgh.xr = hdr.srow_x[0] / xmm; mgh.xa = hdr.srow_y[0] / xmm; mgh.xs = hdr.srow_z[0] / xmm; mgh.yr = hdr.srow_x[1] / ymm; mgh.ya = hdr.srow_y[1] / ymm; mgh.ys = hdr.srow_z[1] / ymm; mgh.zr = hdr.srow_x[2] / zmm; mgh.za = hdr.srow_y[2] / zmm; mgh.zs = hdr.srow_z[2] / zmm; float vec[3]; vec[0] = hdr.dim[1] * 0.5; vec[1] = hdr.dim[2] * 0.5; vec[2] = hdr.dim[3] * 0.5; mgh.cr = hdr.srow_x[0]*vec[0] + hdr.srow_x[1]*vec[1] + hdr.srow_x[2]*vec[2] + hdr.srow_x[3]; mgh.ca = hdr.srow_y[0]*vec[0] + hdr.srow_y[1]*vec[1] + hdr.srow_y[2]*vec[2] + hdr.srow_y[3]; mgh.cs = hdr.srow_z[0]*vec[0] + hdr.srow_z[1]*vec[1] + hdr.srow_z[2]*vec[2] + hdr.srow_z[3]; #ifdef __LITTLE_ENDIAN__ //mgh data ALWAYS big endian! nifti_swap_4bytes(7, &mgh.version); nifti_swap_2bytes(1, &mgh.goodRASFlag); nifti_swap_4bytes(15, &mgh.spacingX); #endif for (int i = 0; i < kMGHpad; i++) mgh.pad[i] = 0; //write the data char fname[2048] = {""}; strcpy(fname, niiFilename); #ifdef __LITTLE_ENDIAN__ //mgh data ALWAYS big endian! swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) #endif if (isGz) { strcat(fname, ".mgz"); writeMghGz(fname, mgh, footer, im, imgsz, opts.gzLevel); } else { strcat(fname, ".mgh"); FILE *fp = fopen(fname, "wb"); if (!fp) return EXIT_FAILURE; fwrite(&mgh, sizeof(Tmgh), 1, fp); fwrite(&im[0], imgsz, 1, fp); fwrite(&footer, sizeof(TmghFooter), 1, fp); fclose(fp); } #ifdef __LITTLE_ENDIAN__ //mgh data ALWAYS big endian! swapEndian(&hdr, im, false); //byte-swap endian (e.g. little->big) #endif return EXIT_SUCCESS; } // nii_saveMGH() int nii_saveNRRD(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, struct TDTI4D *dti4D, int numDTI) { int n, nDim = hdr.dim[0]; //printMessage("NRRD writer is experimental\n"); if (nDim < 1) return EXIT_FAILURE; bool isGz = opts.isGz; size_t imgsz = nii_ImgBytes(hdr); if ((isGz) && (imgsz >= 2147483647)) { printWarning("Saving huge image uncompressed (many GZip tools have 2 Gb limit).\n"); isGz = false; } char fname[2048] = {""}; strcpy(fname, niiFilename); if (isGz) strcat(fname, ".nhdr"); //nrrd or nhdr else strcat(fname, ".nrrd"); //nrrd or nhdr FILE *fp = fopen(fname, "w"); fprintf(fp, "NRRD0005\n"); fprintf(fp, "# Complete NRRD file format specification at:\n"); fprintf(fp, "# http://teem.sourceforge.net/nrrd/format.html\n"); fprintf(fp, "# dcm2niix %s NRRD export transforms by Tashrif Billah\n", kDCMdate); char rgbNoneStr[10] = {""}; //type tag switch (hdr.datatype) { case DT_RGB24: fprintf(fp, "type: uint8\n"); strcpy(rgbNoneStr, " none"); break; case DT_UINT8: fprintf(fp, "type: uint8\n"); break; case DT_INT16: fprintf(fp, "type: int16\n"); break; case DT_UINT16: fprintf(fp, "type: uint16\n"); break; case DT_FLOAT32: fprintf(fp, "type: float\n"); break; case DT_INT32: fprintf(fp, "type: int32\n"); break; case DT_FLOAT64: fprintf(fp, "type: double\n"); break; default: printError("Unknown NRRD datatype %d\n", hdr.datatype); fclose(fp); return EXIT_FAILURE; } //dimension tag if (hdr.datatype == DT_RGB24) fprintf(fp, "dimension: %d\n", nDim + 1); //RGB is first dimension else fprintf(fp, "dimension: %d\n", nDim); //space tag fprintf(fp, "space: right-anterior-superior\n"); //sizes tag fprintf(fp, "sizes:"); if (hdr.datatype == DT_RGB24) fprintf(fp, " 3"); for (int i = 1; i <= hdr.dim[0]; i++) fprintf(fp, " %d", hdr.dim[i]); fprintf(fp, "\n"); //thicknesses if ((d.zThick > 0.0) && (nDim >= 3)) { fprintf(fp, "thicknesses: NaN NaN %g", d.zThick); int n = 3; while (n < nDim) { fprintf(fp, " NaN"); n++; } fprintf(fp, "\n"); } //byteskip only for .nhdr, not .nrrd if (littleEndianPlatform()) //raw data in native format fprintf(fp, "endian: little\n"); else fprintf(fp, "endian: big\n"); if (isGz) { fprintf(fp, "encoding: gzip\n"); strcpy(fname, niiFilename); strcat(fname, ".raw.gz"); char basefname[2048] = {""}; getFileNameX(basefname, fname, 2048); fprintf(fp, "data file: %s\n", basefname); } else fprintf(fp, "encoding: raw\n"); fprintf(fp, "space units: \"mm\" \"mm\" \"mm\"\n"); //origin fprintf(fp, "space origin: (%g,%g,%g)\n", hdr.srow_x[3], hdr.srow_y[3], hdr.srow_z[3]); //space directions: fprintf(fp, "space directions:%s (%g,%g,%g) (%g,%g,%g) (%g,%g,%g)", rgbNoneStr, hdr.srow_x[0], hdr.srow_y[0], hdr.srow_z[0], hdr.srow_x[1], hdr.srow_y[1], hdr.srow_z[1], hdr.srow_x[2], hdr.srow_y[2], hdr.srow_z[2]); n = 3; while (n < nDim) { fprintf(fp, " none"); n++; } fprintf(fp, "\n"); //centerings tag if (hdr.dim[0] < 4) //*check RGB, more dims fprintf(fp, "centerings:%s cell cell cell\n", rgbNoneStr); else fprintf(fp, "centerings:%s cell cell cell ???\n", rgbNoneStr); //kinds tag fprintf(fp, "kinds:"); if (hdr.datatype == DT_RGB24) fprintf(fp, " RGB-color"); n = 0; while ((n < nDim) && (n < 3)) { fprintf(fp, " space"); //dims 1..3 n++; } while (n < nDim) { fprintf(fp, " list"); //dims 4..7 n++; } fprintf(fp, "\n"); //http://teem.sourceforge.net/nrrd/format.html bool isFloat = (hdr.datatype == DT_FLOAT64) || (hdr.datatype == DT_FLOAT32); if (((!isSameFloat(hdr.scl_inter, 0.0)) || (!isSameFloat(hdr.scl_slope, 1.0))) && (!isFloat)) { //http://teem.sourceforge.net/nrrd/format.html double dtMin = 0.0; //DT_UINT8, DT_RGB24, DT_UINT16 if (hdr.datatype == DT_INT16) dtMin = -32768.0; if (hdr.datatype == DT_INT32) dtMin = -2147483648.0; fprintf(fp, "oldmin: %8.8f\n", (dtMin * hdr.scl_slope) + hdr.scl_inter); double dtMax = 255.00; //DT_UINT8, DT_RGB24 if (hdr.datatype == DT_INT16) dtMax = 32767.0; if (hdr.datatype == DT_UINT16) dtMax = 65535.0; if (hdr.datatype == DT_INT32) dtMax = 2147483647.0; fprintf(fp, "oldmax: %8.8f\n", (dtMax * hdr.scl_slope) + hdr.scl_inter); } //Slicer DWIconvert values if (d.modality == kMODALITY_MR) fprintf(fp, "DICOM_0008_0060_Modality:=MR\n"); if (d.modality == kMODALITY_CT) fprintf(fp, "DICOM_0008_0060_Modality:=CT\n"); if (d.manufacturer == kMANUFACTURER_SIEMENS) fprintf(fp, "DICOM_0008_0070_Manufacturer:=SIEMENS\n"); if (d.manufacturer == kMANUFACTURER_PHILIPS) fprintf(fp, "DICOM_0008_0070_Manufacturer:=Philips Medical Systems\n"); if (d.manufacturer == kMANUFACTURER_GE) fprintf(fp, "DICOM_0008_0070_Manufacturer:=GE MEDICAL SYSTEMS\n"); if (strlen(d.manufacturersModelName) > 0) fprintf(fp, "DICOM_0008_1090_ManufacturerModelName:=%s\n", d.manufacturersModelName); if (strlen(d.scanOptions) > 0) fprintf(fp, "DICOM_0018_0022_ScanOptions:=%s\n", d.scanOptions); if (d.is2DAcq) fprintf(fp, "DICOM_0018_0023_MRAcquisitionType:=2D\n"); if (d.is3DAcq) fprintf(fp, "DICOM_0018_0023_MRAcquisitionType:=3D\n"); //if (strlen(d.mrAcquisitionType) > 0) fprintf(fp,"DICOM_0018_0023_MRAcquisitionType:=%s\n",d.mrAcquisitionType); if (d.TR > 0.0) fprintf(fp, "DICOM_0018_0080_RepetitionTime:=%g\n", d.TR); if ((d.TE > 0.0) && (!d.isXRay)) fprintf(fp, "DICOM_0018_0081_EchoTime:=%g\n", d.TE); if ((d.TE > 0.0) && (d.isXRay)) fprintf(fp, "DICOM_0018_1152_XRayExposure:=%g\n", d.TE); if (d.numberOfAverages > 0.0) fprintf(fp, "DICOM_0018_0083_NumberOfAverages:=%g\n", d.numberOfAverages); if (d.fieldStrength > 0.0) fprintf(fp, "DICOM_0018_0087_MagneticFieldStrength:=%g\n", d.fieldStrength); if (strlen(d.softwareVersions) > 0) fprintf(fp, "DICOM_0018_1020_SoftwareVersions:=%s\n", d.softwareVersions); if (d.flipAngle > 0.0) fprintf(fp, "DICOM_0018_1314_FlipAngle:=%g\n", d.flipAngle); //multivolume but NOT DTI, e.g. fMRI/DCE see https://www.slicer.org/wiki/Documentation/4.4/Modules/MultiVolumeExplorer // https://github.com/QIICR/PkModeling/blob/master/PkSolver/IO/MultiVolumeMetaDictReader.cxx#L34-L58 // for "MultiVolume.FrameLabels:=" // https://www.slicer.org/wiki/Documentation/4.4/Modules/MultiVolumeExplorer // for "axis 0 index values:=" // https://github.com/mhe/pynrrd/issues/71 // "I don't know if it is a good idea for dcm2niix to mimic Slicer converter tags" Andrey Fedorov /* if ((nDim > 3) && (hdr.dim[4] > 1) && (numDTI < 1)) { if ((d.TE > 0.0) && (!d.isXRay)) fprintf(fp,"MultiVolume.DICOM.EchoTime:=%g\n",d.TE); if (d.flipAngle > 0.0) fprintf(fp,"MultiVolume.DICOM.FlipAngle:=%g\n",d.flipAngle); if (d.TR > 0.0) fprintf(fp,"MultiVolume.DICOM.RepetitionTime:=%g\n",d.TR); fprintf(fp,"MultiVolume.FrameIdentifyingDICOMTagName:=TriggerTime\n"); fprintf(fp,"MultiVolume.FrameIdentifyingDICOMTagUnits:=ms\n"); fprintf(fp,"MultiVolume.FrameLabels:="); double frameTime = d.TR; if (d.triggerDelayTime > 0.0) frameTime = d.triggerDelayTime / (hdr.dim[4] - 1); //GE dce data for (int i = 0; i < (hdr.dim[4]-1); i++) fprintf(fp,"%g,", i * frameTime); fprintf(fp,"%g\n", (hdr.dim[4]-1) * frameTime); fprintf(fp,"MultiVolume.NumberOfFrames:=%d\n",hdr.dim[4]); } */ //DWI values if ((nDim > 3) && (numDTI > 0) && (numDTI < kMaxDTI4D)) { mat33 inv; LOAD_MAT33(inv, hdr.pixdim[1], 0.0, 0.0, 0.0, hdr.pixdim[2], 0.0, 0.0, 0.0, hdr.pixdim[3]); inv = nifti_mat33_inverse(inv); mat33 s; LOAD_MAT33(s, hdr.srow_x[0], hdr.srow_x[1], hdr.srow_x[2], hdr.srow_y[0], hdr.srow_y[1], hdr.srow_y[2], hdr.srow_z[0], hdr.srow_z[1], hdr.srow_z[2]); mat33 mf = nifti_mat33_mul(inv, s); fprintf(fp, "measurement frame: (%g,%g,%g) (%g,%g,%g) (%g,%g,%g)\n", mf.m[0][0], mf.m[1][0], mf.m[2][0], mf.m[0][1], mf.m[1][1], mf.m[2][1], mf.m[0][2], mf.m[1][2], mf.m[2][2]); //modality tag fprintf(fp, "modality:=DWMRI\n"); float b_max = 0.0; for (int i = 0; i < numDTI; i++) if (dti4D->S[i].V[0] > b_max) b_max = dti4D->S[i].V[0]; fprintf(fp, "DWMRI_b-value:=%g\n", b_max); //gradient tag, e.g. DWMRI_gradient_0000:=0.0 0.0 0.0 for (int i = 0; i < numDTI; i++) { float factor = 0.0; if (b_max > 0) factor = sqrt(dti4D->S[i].V[0] / b_max); if ((dti4D->S[i].V[0] > 50.0) && (isSameFloatGE(0.0, dti4D->S[i].V[1])) && (isSameFloatGE(0.0, dti4D->S[i].V[2])) && (isSameFloatGE(0.0, dti4D->S[i].V[3]))) { //On May 2, 2019, at 10:47 AM, Gordon L. Kindlmann <> wrote: //(assuming b_max 2000, we write "isotropic" for the b=2000 isotropic image, and specify the b-value if it is an isotropic image but not b-bax // DWMRI_gradient_0003:=isotropic b=1000 // DWMRI_gradient_0004:=isotropic if (isSameFloatGE(b_max, dti4D->S[i].V[0])) fprintf(fp, "DWMRI_gradient_%04d:=isotropic\n", i); else fprintf(fp, "DWMRI_gradient_%04d:=isotropic b=%g\n", i, dti4D->S[i].V[0]); } else fprintf(fp, "DWMRI_gradient_%04d:=%.17g %.17g %.17g\n", i, factor * dti4D->S[i].V[1], factor * dti4D->S[i].V[2], factor * dti4D->S[i].V[3]); //printf("%g = %g %g %g>>>>\n",dti4D->S[i].V[0], dti4D->S[i].V[1],dti4D->S[i].V[2],dti4D->S[i].V[3]); } } fprintf(fp, "\n"); //blank line: end of NRRD header if (!isGz) fwrite(&im[0], imgsz, 1, fp); fclose(fp); if (!isGz) return EXIT_SUCCESS; //below: gzip file #ifdef myDisableZLib if (strlen(opts.pigzname) < 1) { //internal compression printError("Compiled without gz support, unable to compress %s\n", fname); return EXIT_FAILURE; } #else if (strlen(opts.pigzname) < 1) { //internal compression writeNiiGz(fname, hdr, im, imgsz, opts.gzLevel, true); return EXIT_SUCCESS; } #endif //below pigz strcpy(fname, niiFilename); //without gz strcat(fname, ".raw"); fp = fopen(fname, "wb"); fwrite(&im[0], imgsz, 1, fp); fclose(fp); return pigz_File(fname, opts, imgsz); } // nii_saveNRRD() 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); return nii_saveNRRD(niiFilename, hdr, im, opts, d, dti4D, numDTI); }// nii_saveForeign() #endif void removeSclSlopeInter(struct nifti_1_header *hdr, unsigned char *img) { //NRRD does not have scl_slope scl_inter. Adjust data if possible // https://discourse.slicer.org/t/preserve-image-rescale-and-slope-when-saving-in-nrrd-file/13357 if (isSameFloat(hdr->scl_inter, 0.0) && isSameFloat(hdr->scl_slope, 1.0)) return; if ((!isSameFloat(fmod(hdr->scl_inter, 1.0f), 0.0)) || (!isSameFloat(fmod(hdr->scl_slope, 1.0f), 0.0))) return; int nVox = 1; for (int i = 1; i < 8; i++) if (hdr->dim[i] > 1) nVox = nVox * hdr->dim[i]; if (hdr->datatype == DT_INT16) { int16_t *img16 = (int16_t *)img; int16_t mn, mx; mn = img16[0]; mx = mn; for (int i = 0; i < nVox; i++) { mn = min(mn, img16[i]); mx = max(mx, img16[i]); } float v = (mn * hdr->scl_slope) + hdr->scl_inter; if ((v < -32768) || (v > 32767)) return; v = (mx * hdr->scl_slope) + hdr->scl_inter; if ((v < -32768) || (v > 32767)) return; for (int i = 0; i < nVox; i++) img16[i] = round((img16[i] * hdr->scl_slope) + hdr->scl_inter); hdr->scl_slope = 1.0; hdr->scl_inter = 0.0; return; } if (hdr->datatype == DT_UINT16) { uint16_t *img16 = (uint16_t *)img; uint16_t mn, mx; mn = img16[0]; mx = mn; for (int i = 0; i < nVox; i++) { mn = min(mn, img16[i]); mx = max(mx, img16[i]); } float v = (mn * hdr->scl_slope) + hdr->scl_inter; if ((v < 0) || (v > 65535)) return; v = (mx * hdr->scl_slope) + hdr->scl_inter; if ((v < 0) || (v > 65535)) return; for (int i = 0; i < nVox; i++) img16[i] = round((img16[i] * hdr->scl_slope) + hdr->scl_inter); hdr->scl_slope = 1.0; hdr->scl_inter = 0.0; return; } //printWarning("NRRD unable to record scl_slope/scl_inter %g/%g\n", hdr->scl_slope, hdr->scl_inter); } #ifndef USING_R int nii_saveNII(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) { if (opts.isOnlyBIDS) return EXIT_SUCCESS; if (opts.saveFormat != kSaveFormatNIfTI) { struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); int ret = nii_saveForeign(niiFilename, hdr, im, opts, d, dti4D, 0); free(dti4D); return ret; } hdr.vox_offset = 352; size_t imgsz = nii_ImgBytes(hdr); if (imgsz < 1) { printMessage("Error: Image size is zero bytes %s\n", niiFilename); return EXIT_FAILURE; } #ifndef myDisableGzSizeLimits //see https://github.com/rordenlab/dcm2niix/issues/124 uint64_t kMaxPigz = 4294967264; //https://stackoverflow.com/questions/5272825/detecting-64bit-compile-in-c #ifndef UINTPTR_MAX uint64_t kMaxGz = 2147483647; #elif UINTPTR_MAX == 0xffffffff uint64_t kMaxGz = 2147483647; #elif UINTPTR_MAX == 0xffffffffffffffff uint64_t kMaxGz = kMaxPigz; #else compiler error : unable to determine is 32 or 64 bit #endif #ifndef myDisableZLib if ((opts.isGz) && (strlen(opts.pigzname) < 1) && ((imgsz + hdr.vox_offset) >= kMaxGz)) { //use internal compressor printWarning("Saving uncompressed data: internal compressor unable to process such large files.\n"); if ((imgsz + hdr.vox_offset) < kMaxPigz) printWarning(" Hint: using external compressor (pigz) should help.\n"); } else if ((opts.isGz) && (strlen(opts.pigzname) < 1) && ((imgsz + hdr.vox_offset) < kMaxGz)) { //use internal compressor if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) writeNiiGz(niiFilename, hdr, im, imgsz, opts.gzLevel, false); if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) return EXIT_SUCCESS; } #endif #endif char fname[2048] = {""}; strcpy(fname, niiFilename); strcat(fname, ".nii"); #if defined(_WIN64) || defined(_WIN32) if ((opts.isGz) && (opts.isPipedGz)) printWarning("The 'optimal' piped gz is only available for Unix\n"); #else //if windows else Unix if ((opts.isGz) && (opts.isPipedGz) && (strlen(opts.pigzname) > 0)) { //piped gz if (opts.isVerbose) printMessage(" Optimal piped gz will fail if pigz version < 2.3.4.\n"); char command[768]; strcpy(command, "\""); strcat(command, opts.pigzname); if ((opts.gzLevel > 0) && (opts.gzLevel < 12)) { char newstr[256]; sprintf(newstr, "\" -n -f -%d > \"", opts.gzLevel); strcat(command, newstr); } else strcat(command, "\" -n -f > \""); //current versions of pigz (2.3) built on Windows can hang if the filename is included, presumably because it is not finding the path characters ':\' strcat(command, fname); strcat(command, ".gz\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' //strcat(command, "x.gz\""); //add quotes in case spaces in filename 'pigz "c:\my dir\img.nii"' if (opts.isVerbose) printMessage("Compress: %s\n", command); FILE *pigzPipe; if ((pigzPipe = popen(command, "w")) == NULL) { printError("Unable to open pigz pipe\n"); return EXIT_FAILURE; } if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) fwrite(&hdr, sizeof(hdr), 1, pigzPipe); uint32_t pad = 0; fwrite(&pad, sizeof(pad), 1, pigzPipe); fwrite(&im[0], imgsz, 1, pigzPipe); pclose(pigzPipe); if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) return EXIT_SUCCESS; } #endif FILE *fp = fopen(fname, "wb"); if (!fp) return EXIT_FAILURE; if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, true); //byte-swap endian (e.g. little->big) fwrite(&hdr, sizeof(hdr), 1, fp); uint32_t pad = 0; fwrite(&pad, sizeof(pad), 1, fp); fwrite(&im[0], imgsz, 1, fp); fclose(fp); if (!opts.isSaveNativeEndian) swapEndian(&hdr, im, false); //unbyte-swap endian (e.g. big->little) if ((opts.isGz) && (strlen(opts.pigzname) > 0)) { #ifndef myDisableGzSizeLimits if ((imgsz + hdr.vox_offset) > kMaxPigz) { printWarning("Saving uncompressed data: image too large for pigz.\n"); return EXIT_SUCCESS; } #endif return pigz_File(fname, opts, imgsz); } return EXIT_SUCCESS; } // nii_saveNII() #endif int nii_saveNIIx(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts) { struct TDICOMdata dcm = clear_dicom_data(); return nii_saveNII(niiFilename, hdr, im, opts, dcm); } int nii_saveNII3D(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) { //save 4D series as sequence of 3D volumes struct nifti_1_header hdr1 = hdr; int nVol = 1; for (int i = 4; i < 8; i++) { if (hdr.dim[i] > 1) nVol = nVol * hdr.dim[i]; hdr1.dim[i] = 0; } hdr1.dim[0] = 3; //save as 3D file size_t imgsz = nii_ImgBytes(hdr1); size_t pos = 0; char fname[2048] = {""}; char zeroPad[PATH_MAX] = {""}; double fnVol = nVol; int zeroPadLen = (1 + log10(fnVol)); sprintf(zeroPad, "%%s_%%0%dd", zeroPadLen); for (int i = 1; i <= nVol; i++) { sprintf(fname, zeroPad, niiFilename, i); if (nii_saveNII(fname, hdr1, (unsigned char *)&im[pos], opts, d) == EXIT_FAILURE) return EXIT_FAILURE; pos += imgsz; } return EXIT_SUCCESS; } // nii_saveNII3D() void nii_storeIntegerScaleFactor(int scale, struct nifti_1_header *hdr) { //appends NIfTI header description field with " isN" where N is integer scaling char newstr[256]; sprintf(newstr, " is%d", scale); if ((strlen(newstr) + strlen(hdr->descrip)) < 80) strcat(hdr->descrip, newstr); } void nii_mask12bit(unsigned char *img, struct nifti_1_header *hdr) { //https://github.com/rordenlab/dcm2niix/issues/251 if (hdr->datatype != DT_INT16) return; int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; if (nVox < 1) return; int16_t *img16 = (int16_t *)img; for (int i = 0; i < nVox; i++) img16[i] = img16[i] & 4095; //12 bit data ranges from 0..4095, any other values are overflow } unsigned char * nii_uint16toFloat32(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { if (hdr->datatype != DT_UINT16) return img; int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; if (nVox < 1) return img; unsigned short *img16 = (unsigned short *)img; unsigned char *imOut = (unsigned char *)malloc(nVox * 4); // *4 as 32-bits per voxel, sizeof(float) ) float *imOut32 = (float *)imOut; for (int i = 0; i < nVox; i++) imOut32[i] = (hdr->scl_slope * img16[i]) + hdr->scl_inter; free(img); hdr->scl_slope = 1.0; hdr->scl_inter = 1.0; hdr->datatype = DT_FLOAT32; hdr->bitpix = 32; if (isVerbose) printMessage("Converted uint16 to float32\n"); return imOut; } // nii_uint16toFloat32() void nii_scale16bitSigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { //lossless scaling of INT16 data: e.g. input with range -100...3200 and scl_slope=1 // will be stored as -1000...32000 with scl_slope 0.1 if (hdr->datatype != DT_INT16) return; int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; if (nVox < 1) return; int16_t *img16 = (int16_t *)img; int16_t max16 = img16[0]; int16_t min16 = max16; for (int i = 0; i < nVox; i++) { if (img16[i] < min16) min16 = img16[i]; if (img16[i] > max16) max16 = img16[i]; } int kMx = 32000; //actually 32767 - maybe a bit of padding for interpolation ringing int scale = kMx / (int)max16; if (abs(min16) > max16) scale = kMx / (int)abs(min16); if (scale < 2) { if (isVerbose) printMessage("Sufficient 16-bit range: raw %d..%d\n", min16, max16); return; //already uses dynamic range } hdr->scl_slope = hdr->scl_slope / scale; for (int i = 0; i < nVox; i++) img16[i] = img16[i] * scale; printMessage("Maximizing 16-bit range: raw %d..%d is%d\n", min16, max16, scale); nii_storeIntegerScaleFactor(scale, hdr); } void nii_scale16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { //lossless scaling of UINT16 data: e.g. input with range 0...3200 and scl_slope=1 // will be stored as 0...64000 with scl_slope 0.05 if (hdr->datatype != DT_UINT16) return; int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; if (nVox < 1) return; uint16_t *img16 = (uint16_t *)img; uint16_t max16 = img16[0]; for (int i = 0; i < nVox; i++) if (img16[i] > max16) max16 = img16[i]; int kMx = 64000; //actually 65535 - maybe a bit of padding for interpolation ringing int scale = kMx / (int)max16; if (scale < 2) { if (isVerbose > 0) printMessage("Sufficient unsigned 16-bit range: raw max %d\n", max16); return; //already uses dynamic range } hdr->scl_slope = hdr->scl_slope / scale; for (int i = 0; i < nVox; i++) img16[i] = img16[i] * scale; printMessage("Maximizing 16-bit range: raw max %d is%d\n", max16, scale); nii_storeIntegerScaleFactor(scale, hdr); } #define UINT16_TO_INT16_IF_LOSSLESS #ifdef UINT16_TO_INT16_IF_LOSSLESS void nii_check16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { //default NIfTI 16-bit is signed, set to unusual 16-bit unsigned if required... if (hdr->datatype != DT_UINT16) return; int dim3to7 = 1; for (int i = 3; i < 8; i++) if (hdr->dim[i] > 1) dim3to7 = dim3to7 * hdr->dim[i]; int nVox = hdr->dim[1] * hdr->dim[2] * dim3to7; if (nVox < 1) return; unsigned short *img16 = (unsigned short *)img; unsigned short max16 = img16[0]; //clock_t start = clock(); for (int i = 0; i < nVox; i++) if (img16[i] > max16) max16 = img16[i]; //printMessage("max16= %d vox=%d %fms\n",max16, nVox, ((double)(clock()-start))/1000); if (max16 > 32767) { if (isVerbose > 0) printMessage("Note: 16-bit UNSIGNED integer image. Some tools will convert to 32-bit.\n"); } else { hdr->datatype = DT_INT16; printMessage("UINT16->INT16 Future release will change default. github.com/rordenlab/dcm2niix/issues/338\n"); } } //nii_check16bitUnsigned() #else void nii_check16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int isVerbose) { if (hdr->datatype != DT_UINT16) return; if (isVerbose < 1) return; printMessage("Note: 16-bit UNSIGNED integer image. Some tools will convert to 32-bit.\n"); } #endif //void reportPos(struct TDICOMdata d1) { // printMessage("Instance\t%d\t0020,0032\t%g\t%g\t%g\n", d1.imageNum, d1.patientPosition[1],d1.patientPosition[2],d1.patientPosition[3]); //} int siemensCtKludge(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[]) { //Siemens CT bug: when a user draws an open object graphics object onto a 2D slice this is appended as an additional image, //regardless of slice position. These images do not report number of positions in the volume, so we need tedious leg work to detect uint64_t indx0 = dcmSort[0].indx; if ((nConvert < 2) || (dcmList[indx0].manufacturer != kMANUFACTURER_SIEMENS) || (!isSameFloat(dcmList[indx0].TR, 0.0f))) return nConvert; float prevDx = 0.0; for (int i = 1; i < nConvert; i++) { float dx = intersliceDistance(dcmList[indx0], dcmList[dcmSort[i].indx]); if ((!isSameFloat(dx, 0.0f)) && (dx < prevDx)) { //for (int j = 1; j < nConvert; j++) // reportPos(dcmList[dcmSort[j].indx]); printMessage("Slices skipped: image position not sequential, admonish your vendor (Siemens OOG?)\n"); return i; } prevDx = dx; } return nConvert; //all images in sequential order } // siemensCtKludge() int isSameFloatT(float a, float b, float tolerance) { return (fabs(a - b) <= tolerance); } void adjustOriginForNegativeTilt(struct nifti_1_header *hdr, float shiftPxY) { if (hdr->sform_code > 0) { // Adjust the srow_* offsets using srow_y hdr->srow_x[3] -= shiftPxY * hdr->srow_y[0]; hdr->srow_y[3] -= shiftPxY * hdr->srow_y[1]; hdr->srow_z[3] -= shiftPxY * hdr->srow_y[2]; } if (hdr->qform_code > 0) { // Adjust the quaternion offsets using quatern_* and pixdim mat44 mat = nifti_quatern_to_mat44(hdr->quatern_b, hdr->quatern_c, hdr->quatern_d, hdr->qoffset_x, hdr->qoffset_y, hdr->qoffset_z, hdr->pixdim[1], hdr->pixdim[2], hdr->pixdim[3], hdr->pixdim[0]); hdr->qoffset_x -= shiftPxY * mat.m[1][0]; hdr->qoffset_y -= shiftPxY * mat.m[1][1]; hdr->qoffset_z -= shiftPxY * mat.m[1][2]; } } unsigned char *nii_saveNII3DtiltFloat32(char *niiFilename, struct nifti_1_header *hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, float *sliceMMarray, float gantryTiltDeg, int manufacturer) { //correct for gantry tilt - http://www.mathworks.com/matlabcentral/fileexchange/24458-dicom-gantry-tilt-correction if (opts.isOnlyBIDS) return im; if (gantryTiltDeg == 0.0) return im; struct nifti_1_header hdrIn = *hdr; int nVox2DIn = hdrIn.dim[1] * hdrIn.dim[2]; if ((nVox2DIn < 1) || (hdrIn.dim[0] != 3) || (hdrIn.dim[3] < 3)) return im; if (hdrIn.datatype != DT_FLOAT32) { printMessage("Only able to correct gantry tilt for 16-bit integer or 32-bit float data with at least 3 slices."); return im; } printMessage("Gantry Tilt Correction is new: please validate conversions\n"); float GNTtanPx = tan(gantryTiltDeg / (180 / M_PI)) / hdrIn.pixdim[2]; //tangent(degrees->radian) //unintuitive step: reverse sign for negative gantry tilt, therefore -27deg == +27deg (why @!?#) // seen in http://www.mathworks.com/matlabcentral/fileexchange/28141-gantry-detector-tilt-correction/content/gantry2.m // also validated with actual data... #ifndef newTilt if (manufacturer == kMANUFACTURER_PHILIPS) //see 'Manix' example from Osirix GNTtanPx = -GNTtanPx; else if ((manufacturer == kMANUFACTURER_SIEMENS) && (gantryTiltDeg > 0.0)) GNTtanPx = -GNTtanPx; else if (manufacturer == kMANUFACTURER_GE) ; //do nothing else if (gantryTiltDeg < 0.0) GNTtanPx = -GNTtanPx; //see Toshiba examples from John Muschelli #endif //newTilt float *imIn32 = (float *)im; //create new output image: larger due to skew // compute how many pixels slice must be extended due to skew int s = hdrIn.dim[3] - 1; //top slice float maxSliceMM = fabs(s * hdrIn.pixdim[3]); if (sliceMMarray != NULL) maxSliceMM = fabs(sliceMMarray[s]); int pxOffset = ceil(fabs(GNTtanPx * maxSliceMM)); // printMessage("Tilt extends slice by %d pixels", pxOffset); hdr->dim[2] = hdr->dim[2] + pxOffset; // When there is negative tilt, the image origin must be adjusted for the padding that will be added. if (GNTtanPx < 0) { // printMessage("Adjusting origin for %d pixels padding (float)\n", pxOffset); adjustOriginForNegativeTilt(hdr, pxOffset); } int nVox2D = hdr->dim[1] * hdr->dim[2]; unsigned char *imOut = (unsigned char *)malloc(nVox2D * hdrIn.dim[3] * 4); // *4 as 32-bits per voxel, sizeof(float) ); float *imOut32 = (float *)imOut; //set surrounding voxels to padding (if present) or darkest observed value bool hasPixelPaddingValue = !isnan(d.pixelPaddingValue); float pixelPaddingValue; if (hasPixelPaddingValue) { pixelPaddingValue = d.pixelPaddingValue; } else { // Find darkest pixel value. Note that `hasPixelPaddingValue` remains false so that the darkest value // will not trigger nearest neighbor interpolation below when this value is found in the image. pixelPaddingValue = imIn32[0]; for (int v = 0; v < (nVox2DIn * hdrIn.dim[3]); v++) if (imIn32[v] < pixelPaddingValue) pixelPaddingValue = imIn32[v]; } for (int v = 0; v < (nVox2D * hdrIn.dim[3]); v++) imOut32[v] = pixelPaddingValue; //copy skewed voxels for (int s = 0; s < hdrIn.dim[3]; s++) { //for each slice float sliceMM = s * hdrIn.pixdim[3]; if (sliceMMarray != NULL) sliceMM = sliceMMarray[s]; //variable slice thicknesses //sliceMM -= mmMidZ; //adjust so tilt relative to middle slice if (GNTtanPx < 0) sliceMM -= maxSliceMM; float Offset = GNTtanPx * sliceMM; float fracHi = ceil(Offset) - Offset; //ceil not floor since rI=r-Offset not rI=r+Offset float fracLo = 1.0f - fracHi; for (int r = 0; r < hdr->dim[2]; r++) { //for each row of output float rI = (float)r - Offset; //input row if ((rI >= 0.0) && (rI < hdrIn.dim[2])) { int rLo = floor(rI); int rHi = rLo + 1; if (rHi >= hdrIn.dim[2]) rHi = rLo; rLo = (rLo * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row below rHi = (rHi * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row above int rOut = (r * hdrIn.dim[1]) + (s * nVox2D); //offset to output row for (int c = 0; c < hdrIn.dim[1]; c++) { //for each column float valLo = (float)imIn32[rLo + c]; float valHi = (float)imIn32[rHi + c]; if (hasPixelPaddingValue && (valLo == pixelPaddingValue || valHi == pixelPaddingValue)) { // https://github.com/rordenlab/dcm2niix/issues/262 - Use nearest neighbor interpolation // when at least one of the values is padding. imOut32[rOut + c] = fracHi >= 0.5 ? valHi : valLo; } else { imOut32[rOut + c] = round(valLo * fracLo + valHi * fracHi); } } //for c (each column) } //rI (input row) in range } //for r (each row) } //for s (each slice)*/ free(im); if (sliceMMarray != NULL) return imOut; //we will save after correcting for variable slice thicknesses char niiFilenameTilt[2048] = {""}; strcat(niiFilenameTilt, niiFilename); strcat(niiFilenameTilt, "_Tilt"); nii_saveNII3D(niiFilenameTilt, *hdr, imOut, opts, d); return imOut; } // nii_saveNII3DtiltFloat32() unsigned char *nii_saveNII3Dtilt(char *niiFilename, struct nifti_1_header *hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, float *sliceMMarray, float gantryTiltDeg, int manufacturer) { //correct for gantry tilt - http://www.mathworks.com/matlabcentral/fileexchange/24458-dicom-gantry-tilt-correction if (opts.isOnlyBIDS) return im; if (gantryTiltDeg == 0.0) return im; struct nifti_1_header hdrIn = *hdr; int nVox2DIn = hdrIn.dim[1] * hdrIn.dim[2]; if ((nVox2DIn < 1) || (hdrIn.dim[0] != 3) || (hdrIn.dim[3] < 3)) return im; if (hdrIn.datatype == DT_FLOAT32) return nii_saveNII3DtiltFloat32(niiFilename, hdr, im, opts, d, sliceMMarray, gantryTiltDeg, manufacturer); if (hdrIn.datatype != DT_INT16) { printMessage("Only able to correct gantry tilt for 16-bit integer data with at least 3 slices."); return im; } printMessage("Gantry Tilt Correction is new: please validate conversions\n"); float GNTtanPx = tan(gantryTiltDeg / (180 / M_PI)) / hdrIn.pixdim[2]; //tangent(degrees->radian) //unintuitive step: reverse sign for negative gantry tilt, therefore -27deg == +27deg (why @!?#) // seen in http://www.mathworks.com/matlabcentral/fileexchange/28141-gantry-detector-tilt-correction/content/gantry2.m // also validated with actual data... #ifndef newTilt if (manufacturer == kMANUFACTURER_PHILIPS) //see 'Manix' example from Osirix GNTtanPx = -GNTtanPx; else if ((manufacturer == kMANUFACTURER_SIEMENS) && (gantryTiltDeg > 0.0)) GNTtanPx = -GNTtanPx; else if (manufacturer == kMANUFACTURER_GE) ; //do nothing else if (gantryTiltDeg < 0.0) GNTtanPx = -GNTtanPx; //see Toshiba examples from John Muschelli #endif //newTilt short *imIn16 = (short *)im; //create new output image: larger due to skew // compute how many pixels slice must be extended due to skew int s = hdrIn.dim[3] - 1; //top slice float maxSliceMM = fabs(s * hdrIn.pixdim[3]); if (sliceMMarray != NULL) maxSliceMM = fabs(sliceMMarray[s]); int pxOffset = ceil(fabs(GNTtanPx * maxSliceMM)); // printMessage("Tilt extends slice by %d pixels", pxOffset); hdr->dim[2] = hdr->dim[2] + pxOffset; // When there is negative tilt, the image origin must be adjusted for the padding that will be added. if (GNTtanPx < 0) { // printMessage("Adjusting origin for %d pixels padding (short)\n", pxOffset); adjustOriginForNegativeTilt(hdr, pxOffset); } int nVox2D = hdr->dim[1] * hdr->dim[2]; unsigned char *imOut = (unsigned char *)malloc(nVox2D * hdrIn.dim[3] * 2); // *2 as 16-bits per voxel, sizeof( short) ); short *imOut16 = (short *)imOut; //set surrounding voxels to padding (if present) or darkest observed value bool hasPixelPaddingValue = !isnan(d.pixelPaddingValue); short pixelPaddingValue; if (hasPixelPaddingValue) { pixelPaddingValue = (short)round(d.pixelPaddingValue); } else { // Find darkest pixel value. Note that `hasPixelPaddingValue` remains false so that the darkest value // will not trigger nearest neighbor interpolation below when this value is found in the image. pixelPaddingValue = imIn16[0]; for (int v = 0; v < (nVox2DIn * hdrIn.dim[3]); v++) if (imIn16[v] < pixelPaddingValue) pixelPaddingValue = imIn16[v]; } for (int v = 0; v < (nVox2D * hdrIn.dim[3]); v++) imOut16[v] = pixelPaddingValue; //copy skewed voxels for (int s = 0; s < hdrIn.dim[3]; s++) { //for each slice float sliceMM = s * hdrIn.pixdim[3]; if (sliceMMarray != NULL) sliceMM = sliceMMarray[s]; //variable slice thicknesses //sliceMM -= mmMidZ; //adjust so tilt relative to middle slice if (GNTtanPx < 0) sliceMM -= maxSliceMM; float Offset = GNTtanPx * sliceMM; float fracHi = ceil(Offset) - Offset; //ceil not floor since rI=r-Offset not rI=r+Offset float fracLo = 1.0f - fracHi; for (int r = 0; r < hdr->dim[2]; r++) { //for each row of output float rI = (float)r - Offset; //input row if ((rI >= 0.0) && (rI < hdrIn.dim[2])) { int rLo = floor(rI); int rHi = rLo + 1; if (rHi >= hdrIn.dim[2]) rHi = rLo; rLo = (rLo * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row below rHi = (rHi * hdrIn.dim[1]) + (s * nVox2DIn); //offset to start of row above int rOut = (r * hdrIn.dim[1]) + (s * nVox2D); //offset to output row for (int c = 0; c < hdrIn.dim[1]; c++) { //for each row short valLo = imIn16[rLo + c]; short valHi = imIn16[rHi + c]; if (hasPixelPaddingValue && (valLo == pixelPaddingValue || valHi == pixelPaddingValue)) { // https://github.com/rordenlab/dcm2niix/issues/262 - Use nearest neighbor interpolation // when at least one of the values is padding. imOut16[rOut + c] = fracHi >= 0.5 ? valHi : valLo; } else { imOut16[rOut + c] = round((((float)valLo) * fracLo) + ((float)valHi) * fracHi); } } //for c (each column) } //rI (input row) in range } //for r (each row) } //for s (each slice)*/ free(im); if (sliceMMarray != NULL) return imOut; //we will save after correcting for variable slice thicknesses char niiFilenameTilt[2048] = {""}; strcat(niiFilenameTilt, niiFilename); strcat(niiFilenameTilt, "_Tilt"); nii_saveNII3D(niiFilenameTilt, *hdr, imOut, opts, d); return imOut; } // nii_saveNII3Dtilt() int nii_saveNII3Deq(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d, float *sliceMMarray) { //convert image with unequal slice distances to equal slice distances //sliceMMarray = 0.0 3.0 6.0 12.0 22.0 <- ascending distance from first slice if (opts.isOnlyBIDS) return EXIT_SUCCESS; int nVox2D = hdr.dim[1] * hdr.dim[2]; if ((nVox2D < 1) || (hdr.dim[0] != 3) || (hdr.dim[3] < 3)) return EXIT_FAILURE; if ((hdr.datatype != DT_FLOAT32) && (hdr.datatype != DT_UINT8) && (hdr.datatype != DT_RGB24) && (hdr.datatype != DT_INT16)) { printMessage("Only able to make equidistant slices from 8,16,24-bit integer or 32-bit float image data."); return EXIT_FAILURE; } float mn = sliceMMarray[1] - sliceMMarray[0]; for (int i = 1; i < hdr.dim[3]; i++) { float dx = sliceMMarray[i] - sliceMMarray[i - 1]; if ((dx < mn) && (!isSameFloat(dx, 0.0))) // <- allow slice direction to reverse mn = sliceMMarray[i] - sliceMMarray[i - 1]; } if (mn <= 0.0f) { printMessage("Unable to equalize slice distances: slice order not consistently ascending:\n"); printMessage("dx=[0"); for (int i = 1; i < hdr.dim[3]; i++) printMessage(" %g", sliceMMarray[i - 1]); printMessage("]\n"); printMessage(" Recompiling with '-DmyInstanceNumberOrderIsNotSpatial' might help.\n"); return EXIT_FAILURE; } int slices = hdr.dim[3]; slices = (int)ceil((sliceMMarray[slices - 1] - 0.5 * (sliceMMarray[slices - 1] - sliceMMarray[slices - 2])) / mn); //-0.5: fence post if (slices > (hdr.dim[3] * 2)) { slices = 2 * hdr.dim[3]; mn = (sliceMMarray[hdr.dim[3] - 1]) / (slices - 1); } //printMessage("-->%g mn slices %d orig %d\n", mn, slices, hdr.dim[3]); if (slices < 3) return EXIT_FAILURE; struct nifti_1_header hdrX = hdr; hdrX.dim[3] = slices; hdrX.pixdim[3] = mn; if ((hdr.pixdim[3] != 0.0) && (hdr.pixdim[3] != hdrX.pixdim[3])) { float Scale = hdrX.pixdim[3] / hdr.pixdim[3]; //to do: do I change srow_z or srow_x[2], srow_y[2], srow_z[2], hdrX.srow_z[0] = hdr.srow_z[0] * Scale; hdrX.srow_z[1] = hdr.srow_z[1] * Scale; hdrX.srow_z[2] = hdr.srow_z[2] * Scale; } unsigned char *imX; if (hdr.datatype == DT_FLOAT32) { float *im32 = (float *)im; imX = (unsigned char *)malloc((nVox2D * slices) * 4); //sizeof(float) float *imX32 = (float *)imX; for (int s = 0; s < slices; s++) { float sliceXmm = s * mn; //distance from first slice int sliceXi = (s * nVox2D); //offset for this slice int sHi = 0; while ((sHi < (hdr.dim[3] - 1)) && (sliceMMarray[sHi] < sliceXmm)) sHi += 1; int sLo = sHi - 1; if (sLo < 0) sLo = 0; float mmHi = sliceMMarray[sHi]; float mmLo = sliceMMarray[sLo]; sLo = sLo * nVox2D; sHi = sHi * nVox2D; if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX //for (int v=0; v < nVox2D; v++) // imX16[sliceXi+v] = im16[sHi+v]; memcpy(&imX32[sliceXi], &im32[sHi], nVox2D * sizeof(float)); //memcpy( dest, src, bytes) } else { float fracHi = (sliceXmm - mmLo) / (mmHi - mmLo); float fracLo = 1.0 - fracHi; //weight between two slices for (int v = 0; v < nVox2D; v++) imX32[sliceXi + v] = round(((float)im32[sLo + v] * fracLo) + (float)im32[sHi + v] * fracHi); } } } else if (hdr.datatype == DT_INT16) { short *im16 = (short *)im; imX = (unsigned char *)malloc((nVox2D * slices) * 2); //sizeof( short) ); short *imX16 = (short *)imX; for (int s = 0; s < slices; s++) { float sliceXmm = s * mn; //distance from first slice int sliceXi = (s * nVox2D); //offset for this slice int sHi = 0; while ((sHi < (hdr.dim[3] - 1)) && (sliceMMarray[sHi] < sliceXmm)) sHi += 1; int sLo = sHi - 1; if (sLo < 0) sLo = 0; float mmHi = sliceMMarray[sHi]; float mmLo = sliceMMarray[sLo]; sLo = sLo * nVox2D; sHi = sHi * nVox2D; if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX //for (int v=0; v < nVox2D; v++) // imX16[sliceXi+v] = im16[sHi+v]; memcpy(&imX16[sliceXi], &im16[sHi], nVox2D * sizeof(unsigned short)); //memcpy( dest, src, bytes) } else { float fracHi = (sliceXmm - mmLo) / (mmHi - mmLo); float fracLo = 1.0 - fracHi; //weight between two slices for (int v = 0; v < nVox2D; v++) imX16[sliceXi + v] = round(((float)im16[sLo + v] * fracLo) + (float)im16[sHi + v] * fracHi); } } } else { if (hdr.datatype == DT_RGB24) nVox2D = nVox2D * 3; imX = (unsigned char *)malloc((nVox2D * slices) * 2); //sizeof( short) ); for (int s = 0; s < slices; s++) { float sliceXmm = s * mn; //distance from first slice int sliceXi = (s * nVox2D); //offset for this slice int sHi = 0; while ((sHi < (hdr.dim[3] - 1)) && (sliceMMarray[sHi] < sliceXmm)) sHi += 1; int sLo = sHi - 1; if (sLo < 0) sLo = 0; float mmHi = sliceMMarray[sHi]; float mmLo = sliceMMarray[sLo]; sLo = sLo * nVox2D; sHi = sHi * nVox2D; if ((mmHi == mmLo) || (sliceXmm > mmHi)) { //select only from upper slice TPX memcpy(&imX[sliceXi], &im[sHi], nVox2D); //memcpy( dest, src, bytes) } else { float fracHi = (sliceXmm - mmLo) / (mmHi - mmLo); float fracLo = 1.0 - fracHi; //weight between two slices for (int v = 0; v < nVox2D; v++) imX[sliceXi + v] = round(((float)im[sLo + v] * fracLo) + (float)im[sHi + v] * fracHi); } } } char niiFilenameEq[2048] = {""}; strcat(niiFilenameEq, niiFilename); strcat(niiFilenameEq, "_Eq"); nii_saveNII3D(niiFilenameEq, hdrX, imX, opts, d); free(imX); return EXIT_SUCCESS; } // nii_saveNII3Deq() float PhilipsPreciseVal(float lPV, float lRS, float lRI, float lSS) { if ((lRS * lSS) == 0) //avoid divide by zero return 0.0; else return (lPV * lRS + lRI) / (lRS * lSS); } void PhilipsPrecise(struct TDICOMdata *d, bool isPhilipsFloatNotDisplayScaling, struct nifti_1_header *h, int verbose) { if (d->manufacturer != kMANUFACTURER_PHILIPS) return; //not Philips if (d->isScaleVariesEnh) return; //issue363 rescaled before slice reordering /* if (!isSameFloatGE(0.0, d->RWVScale)) { //https://github.com/rordenlab/dcm2niix/issues/493 h->scl_slope = d->RWVScale; h->scl_inter = d->RWVIntercept; printMessage("Using RWVSlope:RWVIntercept = %g:%g\n",d->RWVScale,d->RWVIntercept); printMessage(" Philips Scaling Values RS:RI:SS = %g:%g:%g (see PMC3998685)\n",d->intenScale,d->intenIntercept,d->intenScalePhilips); if (verbose == 0) return; printMessage("Potential Alternative Intensity Scalings\n"); printMessage(" R = raw value, P = precise value, D = displayed value\n"); printMessage(" RS = rescale slope, RI = rescale intercept, SS = scale slope\n"); printMessage(" D = R * RS + RI , P = D/(RS * SS)\n"); return; }*/ if (d->intenScalePhilips == 0) return; //no Philips Precise //we will report calibrated "FP" values http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3998685/ float l0 = PhilipsPreciseVal(0, d->intenScale, d->intenIntercept, d->intenScalePhilips); float l1 = PhilipsPreciseVal(1, d->intenScale, d->intenIntercept, d->intenScalePhilips); float intenScaleP = d->intenScale; float intenInterceptP = d->intenIntercept; if (l0 != l1) { intenInterceptP = l0; intenScaleP = l1 - l0; } if (isSameFloat(d->intenIntercept, intenInterceptP) && isSameFloat(d->intenScale, intenScaleP)) return; //same result for both methods: nothing to do or report! printMessage("Philips Scaling Values RS:RI:SS = %g:%g:%g (see PMC3998685)\n", d->intenScale, d->intenIntercept, d->intenScalePhilips); if (verbose > 0) { printMessage(" R = raw value, P = precise value, D = displayed value\n"); printMessage(" RS = rescale slope, RI = rescale intercept, SS = scale slope\n"); printMessage(" D = R * RS + RI; P = D/(RS * SS)\n"); printMessage(" D scl_slope:scl_inter = %g:%g\n", d->intenScale, d->intenIntercept); printMessage(" P scl_slope:scl_inter = %g:%g\n", intenScaleP, intenInterceptP); } //#define myUsePhilipsPrecise if (isPhilipsFloatNotDisplayScaling) { if (verbose > 0) printMessage(" Using P values ('-p n ' for D values)\n"); //to change DICOM: //d->intenScale = intenScaleP; //d->intenIntercept = intenInterceptP; //to change NIfTI h->scl_slope = intenScaleP; h->scl_inter = intenInterceptP; d->intenScalePhilips = 0; //so we never run this TWICE! } else if (verbose > 0) printMessage(" Using D values ('-p y ' for P values)\n"); } //PhilipsPrecise() void smooth1D(int num, double *im) { if (num < 3) return; double *src = (double *)malloc(sizeof(double) * num); memcpy(&src[0], &im[0], num * sizeof(double)); //memcpy( dest, src, bytes) double frac = 0.25; for (int i = 1; i < (num - 1); i++) im[i] = (src[i - 1] * frac) + (src[i] * frac * 2) + (src[i + 1] * frac); free(src); } // smooth1D() int nii_saveCrop(char *niiFilename, struct nifti_1_header hdr, unsigned char *im, struct TDCMopts opts, struct TDICOMdata d) { //remove excess neck slices - assumes output of nii_setOrtho() if (opts.isOnlyBIDS) return EXIT_SUCCESS; int nVox2D = hdr.dim[1] * hdr.dim[2]; if ((nVox2D < 1) || (fabs(hdr.pixdim[3]) < 0.001) || (hdr.dim[0] != 3) || (hdr.dim[3] < 128)) return EXIT_FAILURE; if ((hdr.datatype != DT_INT16) && (hdr.datatype != DT_UINT16)) { printMessage("Only able to crop 16-bit volumes."); return EXIT_FAILURE; } short *im16 = (short *)im; unsigned short *imu16 = (unsigned short *)im; float kThresh = 0.09; //more than 9% of max brightness int ventralCrop = 0; //find max value for each slice int slices = hdr.dim[3]; double *sliceSums = (double *)malloc(sizeof(double) * slices); double maxSliceVal = 0.0; for (int i = (slices - 1); i >= 0; i--) { sliceSums[i] = 0; int sliceStart = i * nVox2D; if (hdr.datatype == DT_UINT16) for (int j = 0; j < nVox2D; j++) sliceSums[i] += imu16[j + sliceStart]; else for (int j = 0; j < nVox2D; j++) sliceSums[i] += im16[j + sliceStart]; if (sliceSums[i] > maxSliceVal) maxSliceVal = sliceSums[i]; } if (maxSliceVal <= 0) { free(sliceSums); return EXIT_FAILURE; } smooth1D(slices, sliceSums); for (int i = 0; i < slices; i++) sliceSums[i] = sliceSums[i] / maxSliceVal; //so brightest slice has value 1 //dorsal crop: eliminate slices with more than 5% brightness int dorsalCrop; for (dorsalCrop = (slices - 1); dorsalCrop >= 1; dorsalCrop--) if (sliceSums[dorsalCrop - 1] > kThresh) break; if (dorsalCrop <= 1) { free(sliceSums); return EXIT_FAILURE; } const double kMaxDVmm = 169.0; ventralCrop = dorsalCrop - round(kMaxDVmm / hdr.pixdim[3]); if (ventralCrop < 0) ventralCrop = 0; //apply crop printMessage(" Cropping from slice %d to %d (of %d)\n", ventralCrop, dorsalCrop, slices); struct nifti_1_header hdrX = hdr; slices = dorsalCrop - ventralCrop + 1; hdrX.dim[3] = slices; //translate origin to account for missing slices hdrX.srow_x[3] += hdr.srow_x[2] * ventralCrop; hdrX.srow_y[3] += hdr.srow_y[2] * ventralCrop; hdrX.srow_z[3] += hdr.srow_z[2] * ventralCrop; //convert data unsigned char *imX; imX = (unsigned char *)malloc((nVox2D * slices) * 2); //sizeof( short) ); short *imX16 = (short *)imX; for (int s = 0; s < slices; s++) { int sIn = s + ventralCrop; int sOut = s; sOut = sOut * nVox2D; sIn = sIn * nVox2D; memcpy(&imX16[sOut], &im16[sIn], nVox2D * sizeof(unsigned short)); //memcpy( dest, src, bytes) } char niiFilenameCrop[2048] = {""}; strcat(niiFilenameCrop, niiFilename); strcat(niiFilenameCrop, "_Crop"); const int returnCode = nii_saveNII3D(niiFilenameCrop, hdrX, imX, opts, d); free(imX); return returnCode; } // nii_saveCrop() double dicomTimeToSec(double dicomTime) { //convert HHMMSS to seconds, 135300.024 -> 135259.731 are 0.293 sec apart char acqTimeBuf[64]; snprintf(acqTimeBuf, sizeof acqTimeBuf, "%+013.5f", (double)dicomTime); int ahour, amin; double asec; int count = 0; sscanf(acqTimeBuf, "%3d%2d%lf%n", &ahour, &amin, &asec, &count); if (!count) return -1; return (ahour * 3600) + (amin * 60) + asec; } double acquisitionTimeDifference(struct TDICOMdata *d1, struct TDICOMdata *d2) { if (d1->acquisitionDate != d2->acquisitionDate) return -1; //to do: scans running across midnight double sec1 = dicomTimeToSec(d1->acquisitionTime); double sec2 = dicomTimeToSec(d2->acquisitionTime); //printMessage("%g\n",d2->acquisitionTime); if ((sec1 < 0) || (sec2 < 0)) return -1; return (sec2 - sec1); } void checkDateTimeOrder(struct TDICOMdata *d, struct TDICOMdata *d1) { if (d->acquisitionDate < d1->acquisitionDate) return; //d1 occurred on later date if (d->acquisitionTime <= d1->acquisitionTime) return; //d1 occurred on later (or same) time if (d->imageNum > d1->imageNum) printWarning("Images not sorted in ascending instance number (0020,0013)\n"); else printWarning("Images sorted by instance number [0020,0013](%d..%d), but AcquisitionTime [0008,0032] suggests a different order (%g..%g) \n", d->imageNum, d1->imageNum, d->acquisitionTime, d1->acquisitionTime); } void checkSliceTiming(struct TDICOMdata *d, struct TDICOMdata *d1, int verbose, int isForceSliceTimeHHMMSS) { //detect images with slice timing errors. https://github.com/rordenlab/dcm2niix/issues/126 //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_GE) return; //compute directly from Protocol Block if (d->modality == kMODALITY_PT) return; //issue407 int nSlices = 0; while ((nSlices < kMaxEPI3D) && (d->CSA.sliceTiming[nSlices] >= 0.0)) nSlices++; if (nSlices < 1) return; if (d->CSA.sliceTiming[kMaxEPI3D - 1] < -1.0) //the value -2.0 is used as a flag for negative MosaicRefAcqTimes in checkSliceTimes(), see issue 271 printWarning("Adjusting for negative MosaicRefAcqTimes (issue 271).\n"); bool isSliceTimeHHMMSS = (d->manufacturer == kMANUFACTURER_UIH); if (isForceSliceTimeHHMMSS) isSliceTimeHHMMSS = true; //if (d->isXA10A) isSliceTimeHHMMSS = true; //for XA10 use TimeAfterStart 0x0021,0x1104 -> Siemens de-identification can corrupt acquisition ties https://github.com/rordenlab/dcm2niix/issues/236 if (isSliceTimeHHMMSS) { //handle midnight crossing for (int i = 0; i < nSlices; i++) d->CSA.sliceTiming[i] = dicomTimeToSec(d->CSA.sliceTiming[i]); float minT = d->CSA.sliceTiming[0]; float maxT = minT; for (int i = 0; i < nSlices; i++) { if (d->CSA.sliceTiming[i] < minT) minT = d->CSA.sliceTiming[i]; if (d->CSA.sliceTiming[i] < maxT) maxT = d->CSA.sliceTiming[i]; } //printf("%d %g ---> %g..%g\n", nSlices, d->TR, minT, maxT); float kMidnightSec = 86400; float kNoonSec = 43200; if ((maxT - minT) > kNoonSec) { //volume started before midnight but ended next day! //identify and fix 'Cinderella error' where clock resets at midnight: untested printWarning("Acquisition crossed midnight: check slice timing\n"); for (int i = 0; i < nSlices; i++) if (d->CSA.sliceTiming[i] > kNoonSec) d->CSA.sliceTiming[i] = d->CSA.sliceTiming[i] - kMidnightSec; minT = d->CSA.sliceTiming[0]; for (int i = 0; i < nSlices; i++) if (d->CSA.sliceTiming[i] < minT) minT = d->CSA.sliceTiming[i]; } for (int i = 0; i < nSlices; i++) d->CSA.sliceTiming[i] = d->CSA.sliceTiming[i] - minT; } //XA10/UIH: HHMMSS -> Sec float minT = d->CSA.sliceTiming[0]; float maxT = minT; for (int i = 0; i < kMaxEPI3D; i++) { if (d->CSA.sliceTiming[i] < 0.0) break; if (d->CSA.sliceTiming[i] < minT) minT = d->CSA.sliceTiming[i]; if (d->CSA.sliceTiming[i] > maxT) maxT = d->CSA.sliceTiming[i]; } if (isSliceTimeHHMMSS) //convert HHMMSS to msec for (int i = 0; i < kMaxEPI3D; i++) d->CSA.sliceTiming[i] = dicomTimeToSec(d->CSA.sliceTiming[i]) * 1000.0; float TRms = d->TR; //d->TR in msec! if ((minT != maxT) && (maxT <= TRms)) { if (verbose != 0) printMessage("Slice timing range appears reasonable (range %g..%g, TR=%g ms)\n", minT, maxT, TRms); return; //looks fine } if ((minT == maxT) && (d->is3DAcq)) return; //fine: 3D EPI if ((minT == maxT) && (d->CSA.multiBandFactor == d->CSA.mosaicSlices)) return; //fine: all slices single excitation if ((strlen(d->seriesDescription) > 0) && (strstr(d->seriesDescription, "SBRef") != NULL)) return; //fine: single-band calibration data, the slice timing WILL exceed the TR if (verbose > 1) printMessage("Slice timing range of first volume: range %g..%g, TR=%g ms)\n", minT, maxT, TRms); //check if 2nd image has valid slice timing float minT1 = d1->CSA.sliceTiming[0]; float maxT1 = minT1; for (int i = 0; i < nSlices; i++) { //if (d1->CSA.sliceTiming[i] < 0.0) break; if (d1->CSA.sliceTiming[i] < minT1) minT1 = d1->CSA.sliceTiming[i]; if (d1->CSA.sliceTiming[i] > maxT1) maxT1 = d1->CSA.sliceTiming[i]; } if (verbose > 1) printMessage("Slice timing range of 2nd volume: range %g..%g, TR=%g ms)\n", minT, maxT, TRms); if ((minT1 < maxT1) && (minT1 > 0.0) && ((maxT1 - minT1) <= TRms)) { //issue 429: 2nd volume may not start from zero for (int i = 0; i < nSlices; i++) d1->CSA.sliceTiming[i] -= minT1; maxT1 -= minT1; minT1 -= minT1; } if ((minT1 < 0.0) && (d->rtia_timerGE >= 0.0)) return; //use rtia timer if (minT1 < 0.0) { //https://github.com/neurolabusc/MRIcroGL/issues/31 if (d->modality == kMODALITY_MR) printWarning("Siemens MoCo? Bogus slice timing (range %g..%g, TR=%g seconds)\n", minT1, maxT1, TRms); return; } if ((minT1 == maxT1) || (maxT1 >= TRms)) { //both first and second image corrupted printWarning("Slice timing appears corrupted (range %g..%g, TR=%g ms)\n", minT1, maxT1, TRms); return; } //1st image corrupted, but 2nd looks ok - substitute values from 2nd image for (int i = 0; i < kMaxEPI3D; i++) { d->CSA.sliceTiming[i] = d1->CSA.sliceTiming[i]; if (d1->CSA.sliceTiming[i] < 0.0) break; } d->CSA.multiBandFactor = d1->CSA.multiBandFactor; printMessage("CSA slice timing based on 2nd volume, 1st volume corrupted (CMRR bug, range %g..%g, TR=%g ms)\n", minT, maxT, TRms); } //checkSliceTiming() void sliceTimingXA(struct TDCMsort *dcmSort, struct TDICOMdata *dcmList, struct nifti_1_header *hdr, int verbose, const char *filename, int nConvert) { //Siemens XA10 slice timing // Ignore first volume: For an example of erroneous first volume timing, see series 10 (Functional_w_SMS=3) https://github.com/rordenlab/dcm2niix/issues/240 // an alternative would be to use 0018,9074 - this would need to be converted from DT to Secs, and is scrambled if de-identifies data see enhanced de-identified series 26 from issue 236 uint64_t indx0 = dcmSort[0].indx; //first volume if ((!dcmList[indx0].isXA10A) || (hdr->dim[3] < 1)) return; if ((nConvert == (hdr->dim[3] * hdr->dim[4])) && (hdr->dim[3] < (kMaxEPI3D - 1)) && (hdr->dim[3] > 1) && (hdr->dim[4] > 1)) { //XA11 2D classic for (int v = 0; v < hdr->dim[3]; v++) dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].CSA.sliceTiming[0]; } else if ((nConvert == (hdr->dim[4])) && (hdr->dim[3] < (kMaxEPI3D - 1)) && (hdr->dim[3] > 1) && (hdr->dim[4] > 1)) { //XA10 mosaics - these are missing a lot of information float mn = dcmList[dcmSort[1].indx].CSA.sliceTiming[0]; //get slice timing from second volume for (int v = 0; v < hdr->dim[3]; v++) { dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[1].indx].CSA.sliceTiming[v]; if (dcmList[indx0].CSA.sliceTiming[v] < mn) mn = dcmList[indx0].CSA.sliceTiming[v]; } if (mn < 0.0) mn = 0.0; int mb = 0; for (int v = 0; v < hdr->dim[3]; v++) { dcmList[indx0].CSA.sliceTiming[v] -= mn; if (isSameFloatGE(dcmList[indx0].CSA.sliceTiming[v], 0.0)) mb++; } if ((dcmList[indx0].CSA.multiBandFactor < 2) && (mb > 1)) dcmList[indx0].CSA.multiBandFactor = mb; return; //we have subtracted min } //issue429: subtract min float mn = dcmList[indx0].CSA.sliceTiming[0]; for (int v = 0; v < hdr->dim[3]; v++) mn = min(mn, dcmList[indx0].CSA.sliceTiming[v]); if (isSameFloatGE(mn, 0.0)) return; for (int v = 0; v < hdr->dim[3]; v++) dcmList[indx0].CSA.sliceTiming[v] -= mn; } //sliceTimingXA() void sliceTimeGE(struct TDICOMdata *d, int mb, int dim3, float TR, bool isInterleaved, float geMajorVersion, bool is27r3, float groupDelaysec) { //mb : multiband factor //dim3 : number of slices in volume //TRsec : repetition time in seconds //isInterleaved : interleaved or sequential slice order //geMajorVersion: version, e.g. 29.0 //is27r3 : software release 27.0 R03 or later float sliceTiming[kMaxEPI3D]; //multiband can be fractional! 'extra' slices discarded int nExcitations = ceil(float(dim3) / float(mb)); if ((mb > 1) && (geMajorVersion < 26.0)) { printWarning("Unable to determine slice times for early GE HyperBand.\n"); d->CSA.sliceTiming[0] = -1; return; } 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; float secPerSlice = (TR - groupDelaysec) / (nExcitations); if (!isInterleaved) { for (int i = 0; i < nExcitations; i++) sliceTiming[i] = i * secPerSlice; } else { int nOdd = (nExcitations - 1) / 2; for (int i = 0; i < nExcitations; i++) { if (i % 2 == 0) //ODD slices since we index from 0! sliceTiming[i] = (i / 2) * secPerSlice; else sliceTiming[i] = (nOdd + ((i + 1) / 2)) * secPerSlice; } //for each slice if ((mb > 1) && (is27r3) && (isInterleaved) && (nExcitations > 2) && ((nExcitations % 2) == 0)) { float tmp = sliceTiming[nExcitations - 1]; sliceTiming[nExcitations - 1] = sliceTiming[nExcitations - 3]; sliceTiming[nExcitations - 3] = tmp; //printf("SWAP!\n"); } } //if interleaved for (int i = 0; i < dim3; i++) sliceTiming[i] = sliceTiming[i % nExcitations]; //#define testSliceTimesGE //note that early GE HyperBand sequences reported single-band values in x0021x105E #ifdef testSliceTimesGE float maxErr = 0.0; for (int i = 0; i < dim3; i++) maxErr = max(maxErr, fabs(sliceTiming[i] - d->CSA.sliceTiming[i])); if ((d->CSA.sliceTiming[0] >= 0.0) && (maxErr > 1.0)) { //allow a 1.0 msec tolerance for rounding printMessage("GE estimated slice times differ from reported (max error: %g)\n", maxErr); printMessage("Slice\tEstimated\tReported\n"); for (int i = 0; i < dim3; i++) { printMessage("%d %g %g\n", i, sliceTiming[i], d->CSA.sliceTiming[i]); maxErr = max(maxErr, fabs(sliceTiming[i] - d->CSA.sliceTiming[i])); } } #endif for (int i = 0; i < dim3; i++) d->CSA.sliceTiming[i] = sliceTiming[i]; } // sliceTimeGE() void readSoftwareVersionsGE(char softwareVersionsGE[], int verbose, char geVersionPrefix[], float *geMajorVersion, int *geMajorVersionInt, int *geMinorVersionInt, int *geReleaseVersionInt, bool *is27r3) { // softwareVersionsGE // "27\LX\MR Software release:RX27.0_R02_1831.a" -> 27 // "28\LX\MR29.1_EA_2039.g" -> 29 // geVersionPrefix // RX27.0_R02_1831.a -> RX // MR29.1_EA_2039.g -> MR // geMajorVersion // RX27.0_R02_1831.a -> 27.0 // MR29.1_EA_2039.g -> 29.1 // geMajorVersionInt // RX27.0_R02_1831.a -> 27 // MR29.1_EA_2039.g -> 29 // geMinorVersionInt // RX27.0_R02_1831.a -> 0 // MR29.1_EA_2039.g -> 1 // geReleaseVersionInt // EA->0, R01->1, R02->2, R03->4 // RX27.0_R02_1831.a -> 2 // MR29.1_EA_2039.g -> 0 int len = 0; // If softwareVersionsGE is 27\LX\MR Software release:RX27.0_R02_1831.a char *sepStart = strchr(softwareVersionsGE, ':'); if (sepStart == NULL) { // If softwareVersionsGE is 28\LX\MR29.1_EA_2039.g sepStart = strrchr(softwareVersionsGE, '\\'); if (sepStart == NULL) return; } sepStart += 1; len = 11; 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); memcpy(geVersionPrefix, &c1, 1); memcpy(geVersionPrefix + 1, &c2, 1); if ((c3 == 'E') && (c4 == 'A')) { *geReleaseVersionInt = 0; } free(versionString); *geMajorVersion = (float)*geMajorVersionInt + (float)0.1 * (float)*geMinorVersionInt; *is27r3 = ((*geMajorVersion >= 27.1) || ((*geMajorVersionInt == 27) && (*geReleaseVersionInt >= 3))); if (verbose > 1) { printMessage("GE Software VersionPrefix: %s\n", geVersionPrefix); printMessage("GE Software MajorVersion: %d\n", *geMajorVersionInt); printMessage("GE Software MinorVersion: %d\n", *geMinorVersionInt); printMessage("GE Software ReleaseVersion: %d\n", *geReleaseVersionInt); printMessage("GE Software is27r3: %d\n", *is27r3); } } // readSoftwareVersionsGE() void sliceTimingGE_Testx0021x105E(struct TDICOMdata *d, struct TDCMopts opts, struct nifti_1_header *hdr, struct TDCMsort *dcmSort, struct TDICOMdata *dcmList) { if ((!opts.isTestx0021x105E) || (hdr->dim[3] < 2) || (hdr->dim[4] < 1)) return; if (d->rtia_timerGE <= 0.0) { printMessage("DICOM images do not report RTIA timer(0021,105E)\n"); return; } int j = hdr->dim[3]; float sliceTiming[kMaxEPI3D]; float mn = INFINITY; for (int v = 0; v < hdr->dim[3]; v++) { sliceTiming[v] = dcmList[dcmSort[v + j].indx].rtia_timerGE; mn = min(mn, sliceTiming[v]); } if (mn < 0.0) return; float mxErr = 0.0; for (int v = 0; v < hdr->dim[3]; v++) { sliceTiming[v] = (sliceTiming[v] - mn) * 1000.0; //subtract offset, convert sec -> ms mxErr = max(mxErr, float(fabs(sliceTiming[v] - d->CSA.sliceTiming[v]))); } printMessage("Slice Timing Error between calculated and RTIA timer(0021,105E): %gms\n", mxErr); if ((mxErr < 1.0) && (opts.isVerbose < 1)) return; for (int v = 0; v < hdr->dim[3]; v++) printMessage("\t%g\t%g\n", d->CSA.sliceTiming[v], sliceTiming[v]); } void reportProtocolBlockGE(struct TDICOMdata *d, const char *filename) { #ifdef myReadGeProtocolBlock if ((d->manufacturer != kMANUFACTURER_GE) || (d->protocolBlockStartGE < 1) || (d->protocolBlockLengthGE < 19)) return; int viewOrderGE = -1; int sliceOrderGE = -1; int mbAccel = -1; int nSlices = -1; float groupDelay = 0.0; char ioptGE[3000] = ""; geProtocolBlock(filename, d->protocolBlockStartGE, d->protocolBlockLengthGE, 2, &sliceOrderGE, &viewOrderGE, &mbAccel, &nSlices, &groupDelay, ioptGE); #endif } void sliceTimingGE(struct TDICOMdata *d, const char *filename, struct TDCMopts opts, struct nifti_1_header *hdr, struct TDCMsort *dcmSort, struct TDICOMdata *dcmList) { //we can often read GE slice timing from TriggerTime (0018,1060) or RTIA Timer (0021,105E) // if both of these methods fail, we can often guess based on slice order stored in the Private Protocol Data Block (0025,101B) // this is referred to as "rescue" as we only know the TR, not the TA. So assumes continuous scans with no gap if (d->manufacturer != kMANUFACTURER_GE) return; if ((d->is3DAcq) || (d->isLocalizer) || (hdr->dim[4] < 2)) return; //no need for slice times if (hdr->dim[3] < 2) return; if ((d->protocolBlockStartGE < 128) || (d->protocolBlockLengthGE < 10)) { d->CSA.sliceTiming[0] = -1; printWarning("Unable to determine GE Slice timing, no Protocol Data Block GE (0025,101B): %s\n", filename); return; } //start version check: float geMajorVersion = 0; int geMajorVersionInt = 0, geMinorVersionInt = 0, geReleaseVersionInt = 0; char geVersionPrefix[2] = " "; bool is27r3 = false; readSoftwareVersionsGE(d->softwareVersions, opts.isVerbose, geVersionPrefix, &geMajorVersion, &geMajorVersionInt, &geMinorVersionInt, &geReleaseVersionInt, &is27r3); //readSoftwareVersionsGE(&geMajorVersion); /* //used for oldSliceTimingGE if (!opts.isIgnorex0021x105E) { if ((geMajorVersionInt >= 28) && (d->CSA.sliceTiming[0] >= 0.0)) { //if (opts.isVerbose > 1) printMessage("GEversion %.1f, slice timing from DICOM (0021,105E).\n", geMajorVersion); return; //trust slice timings for versions > 27, see issue 336 } }*/ //end: version check if (d->maxEchoNumGE > 0) printWarning("GE sequence with %d echoes. See issue 359\n", d->maxEchoNumGE); if ((d->protocolBlockStartGE < 1) || (d->protocolBlockLengthGE < 19)) return; #ifdef myReadGeProtocolBlock //GE final desperate attempt to determine slice order // GE does not provide a good estimate for TA: here we use TR, which will be wrong for sparse designs // Also, unclear what happens if slice order is flipped // Therefore, we warning the user that we are guessing int viewOrderGE = -1; int sliceOrderGE = -1; int mbAccel = -1; int nSlices = -1; float groupDelay = 0.0; char ioptGE[3000] = ""; //printWarning("Using GE Protocol Data Block for BIDS data (beware: new feature)\n"); int ok = geProtocolBlock(filename, d->protocolBlockStartGE, d->protocolBlockLengthGE, 0, &sliceOrderGE, &viewOrderGE, &mbAccel, &nSlices, &groupDelay, ioptGE); if (ok != EXIT_SUCCESS) { d->CSA.sliceTiming[0] = -1; printWarning("Unable to estimate slice times: issue decoding GE protocol block.\n"); return; } mbAccel = max(mbAccel, 1); if (nSlices != hdr->dim[3]) //redundant with locationsInAcquisition check? printWarning("Missing DICOMs, number of slices estimated (%d) differs from Protocol Block (0025,101B) report (%d).\n", hdr->dim[3], nSlices); d->CSA.multiBandFactor = max(d->CSA.multiBandFactor, mbAccel); bool isInterleaved = (sliceOrderGE != 0); groupDelay *= 1000.0; //sec -> ms // // Estimate GE Slice Time only for EPI Multi-Phase (epi) or EPI fMRI/BrainWave (epiRT) // // BrainWave (epiRT) if (d->epiVersionGE >= kGE_EPI_PEPOLAR_FWD) printWarning("GE ABCD pepolar research sequence handling is experimental\n");// else if ((d->epiVersionGE == 1) || (strstr(ioptGE, "FMRI") != NULL)) { //-1 = not epi, 0 = epi, 1 = epiRT d->epiVersionGE = 1; d->internalepiVersionGE = 1; // 'EPI'(gradient echo)/'EPI2'(spin echo) if (!isSameFloatGE(groupDelay, d->groupDelay)) printWarning("With epiRT (i.e. FMRI option), Group delay reported in private tag (0043,107C = %g) and Protocol Block (0025,101B = %g) differ.\n", d->groupDelay, groupDelay); } // EPI Multi-Phase (epi) else if ((d->epiVersionGE == 0) || (strstr(ioptGE, "MPh") != NULL)) { //-1 = not epi, 0 = epi, 1 = epiRT d->epiVersionGE = 0; d->internalepiVersionGE = 1; // 'EPI'(gradient echo)/'EPI2'(spin echo) if (groupDelay > 0) { d->TR += groupDelay; d->groupDelay = groupDelay; } // EPI Multi-Phase (epi) with Variable Delays (Unsupported) if (groupDelay < -0.5) { printWarning("SliceTiming Unsupported: GE Multi-Phase EPI with Variable Delays\n"); d->CSA.sliceTiming[0] = -1; return; } } // Diffusion (Unsupported) else if ((d->epiVersionGE == 2) || (d->internalepiVersionGE == 2) || (strstr(ioptGE, "DIFF") != NULL)) { printWarning("Unable to compute slice times for GE Diffusion\n"); d->CSA.sliceTiming[0] = -1.0; return; } // Others (Unsupported) else { printWarning("Unable to compute slice times for this GE dataset\n"); d->CSA.sliceTiming[0] = -1.0; return; } if (opts.isVerbose > 1) { printMessage("GEiopt: %s, groupDelay (%g), internalepiVersionGE (%d), epiVersionGE(%d)\n", ioptGE, groupDelay, d->internalepiVersionGE, d->epiVersionGE); printMessage("GEversion %s%.1f_R0%d, TRms %g, interleaved %d, multiband %d, groupdelayms %g\n", geVersionPrefix, geMajorVersion, geReleaseVersionInt, d->TR, isInterleaved, d->CSA.multiBandFactor, d->groupDelay); } sliceTimeGE(d, d->CSA.multiBandFactor, hdr->dim[3], d->TR, isInterleaved, geMajorVersion, is27r3, d->groupDelay); sliceTimingGE_Testx0021x105E(d, opts, hdr, dcmSort, dcmList); #endif } //sliceTimingGE() void reverseSliceTiming(struct TDICOMdata *d, int verbose, int nSL) { if ((d->CSA.protocolSliceNumber1 == 0) || ((d->CSA.protocolSliceNumber1 == 1))) return; //slices not flipped if (d->is3DAcq) return; //no need for slice times if (d->CSA.sliceTiming[0] < 0.0) return; //no slice times if (nSL > kMaxEPI3D) return; if (nSL < 2) return; if (verbose) printMessage("Slices were spatially flipped, so slice times are flipped\n"); d->CSA.protocolSliceNumber1 = 0; float sliceTiming[kMaxEPI3D]; for (int i = 0; i < nSL; i++) sliceTiming[i] = d->CSA.sliceTiming[i]; for (int i = 0; i < nSL; i++) d->CSA.sliceTiming[i] = sliceTiming[(nSL - 1) - i]; } int sliceTimingSiemens2D(struct TDCMsort *dcmSort, struct TDICOMdata *dcmList, struct nifti_1_header *hdr, int verbose, const char *filename, int nConvert) { //only for Siemens 2D images, use acquisitionTime uint64_t indx0 = dcmSort[0].indx; //first volume if (!(dcmList[indx0].manufacturer == kMANUFACTURER_SIEMENS)) return 0; if (dcmList[indx0].is3DAcq) return 0; //no need for slice times if (dcmList[indx0].CSA.sliceTiming[0] >= 0.0) return 0; //slice times calculated if (dcmList[indx0].CSA.mosaicSlices > 1) return 0; if (nConvert != (hdr->dim[3] * hdr->dim[4])) return 0; if (hdr->dim[3] > (kMaxEPI3D - 1)) return 0; int nZero = 0; //infer multiband: E11C may not populate kPATModeText for (int v = 0; v < hdr->dim[3]; v++) { dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].acquisitionTime; //nb format is HHMMSS we need to handle midnight-crossing and convert to ms, see checkSliceTiming() if (dcmList[indx0].CSA.sliceTiming[v] == dcmList[indx0].CSA.sliceTiming[0]) nZero++; } if ((dcmList[indx0].CSA.multiBandFactor < 2) && (nZero > 1)) dcmList[indx0].CSA.multiBandFactor = nZero; return 1; } void rescueSliceTimingSiemens(struct TDICOMdata *d, int verbose, int nSL, const char *filename) { if (d->is3DAcq) return; //no need for slice times if (d->CSA.multiBandFactor > 1) return; //pattern of multiband slice order unknown if (d->CSA.sliceTiming[0] >= 0.0) return; //slice times calculated if (d->CSA.mosaicSlices < 2) return; //20190807 E11C 2D (not mosaic) files do not report mosaicAcqTimes or multi-band factor. if (nSL < 2) return; if ((d->manufacturer != kMANUFACTURER_SIEMENS) || (d->CSA.SeriesHeader_offset < 1) || (d->CSA.SeriesHeader_length < 1)) return; #ifdef myReadAsciiCsa float shimSetting[8]; char protocolName[kDICOMStrLarge], fmriExternalInfo[kDICOMStrLarge], coilID[kDICOMStrLarge], consistencyInfo[kDICOMStrLarge], coilElements[kDICOMStrLarge], pulseSequenceDetails[kDICOMStrLarge], wipMemBlock[kDICOMStrLarge]; TCsaAscii csaAscii; siemensCsaAscii(filename, &csaAscii, d->CSA.SeriesHeader_offset, d->CSA.SeriesHeader_length, shimSetting, coilID, consistencyInfo, coilElements, pulseSequenceDetails, fmriExternalInfo, protocolName, wipMemBlock); int ucMode = csaAscii.ucMode; if ((ucMode < 1) || (ucMode == 3) || (ucMode > 4)) return; float trSec = d->TR / 1000.0; float delaySec = csaAscii.delayTimeInTR / 1000000.0; float taSec = trSec - delaySec; float sliceTiming[kMaxEPI3D]; for (int i = 0; i < nSL; i++) sliceTiming[i] = i * taSec / nSL * 1000.0; //expected in ms if (ucMode == 1) //asc for (int i = 0; i < nSL; i++) d->CSA.sliceTiming[i] = sliceTiming[i]; if (ucMode == 2) //desc for (int i = 0; i < nSL; i++) d->CSA.sliceTiming[i] = sliceTiming[(nSL - 1) - i]; if (ucMode == 4) { //int int oddInc = 0; //for slices 1,3,5 int evenInc = (nSL + 1) / 2; //for 4 slices 0,1,2,3 we will order [2,0,3,1] for 5 slices [0,3,1,4,2] if (nSL % 2 == 0) { //Siemens interleaved for acquisitions with odd number of slices https://www.mccauslandcenter.sc.edu/crnl/tools/stc oddInc = evenInc; evenInc = 0; } for (int i = 0; i < nSL; i++) { if (i % 2 == 0) { //odd slice 1,3,etc [indexed from 0]! d->CSA.sliceTiming[i] = sliceTiming[oddInc]; //printf("%d %d\n", i, oddInc); oddInc += 1; } else { //even slice d->CSA.sliceTiming[i] = sliceTiming[evenInc]; //printf("%d %d %d\n", i, evenInc, nSL); evenInc += 1; } } } //if ucMode == 3 int //dicm2nii provides sSliceArray.ucImageNumb - similar to protocolSliceNumber1 //if asc_header(s, 'sSliceArray.ucImageNumb'), t = t(nSL:-1:1); end % rev-num #endif } void sliceTimingUIH(struct TDCMsort *dcmSort, struct TDICOMdata *dcmList, struct nifti_1_header *hdr, int verbose, const char *filename, int nConvert) { uint64_t indx0 = dcmSort[0].indx; //first volume if (!(dcmList[indx0].manufacturer == kMANUFACTURER_UIH)) return; if (nConvert != (hdr->dim[3] * hdr->dim[4])) return; if (hdr->dim[3] > (kMaxEPI3D - 1)) return; if (hdr->dim[4] < 2) return; for (int v = 0; v < hdr->dim[3]; v++) dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].acquisitionTime; //nb format is HHMMSS we need to handle midnight-crossing and convert to ms, see checkSliceTiming() } /* void oldSliceTimingGE(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struct nifti_1_header * hdr, int verbose, const char * filename, int nConvert) { //GE check slice timing >>> uint64_t indx0 = dcmSort[0].indx; //first volume if (!(dcmList[indx0].manufacturer == kMANUFACTURER_GE)) return; bool GEsliceTiming_x0018x1060 = false; if ((hdr->dim[3] < (kMaxEPI3D-1)) && (hdr->dim[3] > 1) && (hdr->dim[4] > 1)) { //GE: 1st method for "epi" PSD //0018x1060 is defined in msec: http://dicomlookup.com/lookup.asp?sw=Tnumber&q=(0018,1060) //as of 20190704 dcm2niix expects sliceTiming to be encoded in msec for all vendors GEsliceTiming_x0018x1060 = true; for (int v = 0; v < hdr->dim[3]; v++) { if (dcmList[dcmSort[v].indx].CSA.sliceTiming[0] < 0) GEsliceTiming_x0018x1060 = false; dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].CSA.sliceTiming[0]; //ms 20190704 //dcmList[indx0].CSA.sliceTiming[v] = dcmList[dcmSort[v].indx].CSA.sliceTiming[0] / 1000.0; //ms -> sec prior to 20190704 } //printMessage(">>>>Reading GE slice timing from 0018,1060\n"); //0018,1060 provides time at end of acquisition, not start... if (GEsliceTiming_x0018x1060) { float minT = dcmList[indx0].CSA.sliceTiming[0]; float maxT = minT; for (int v = 0; v < hdr->dim[3]; v++) if (dcmList[indx0].CSA.sliceTiming[v] < minT) minT = dcmList[indx0].CSA.sliceTiming[v]; for (int v = 0; v < hdr->dim[3]; v++) if (dcmList[indx0].CSA.sliceTiming[v] > maxT) maxT = dcmList[indx0].CSA.sliceTiming[v]; for (int v = 0; v < hdr->dim[3]; v++) dcmList[indx0].CSA.sliceTiming[v] = dcmList[indx0].CSA.sliceTiming[v] - minT; if (isSameFloatGE(minT, maxT)) { //ABCD simulated GE DICOMs do not populate 0018,1060 correctly GEsliceTiming_x0018x1060 = false; dcmList[indx0].CSA.sliceTiming[0] = -1.0; //no valid slice times } } //adjust: first slice is time = 0.0 } //GE slice timing from 0018,1060 if ((!GEsliceTiming_x0018x1060) && (hdr->dim[3] < (kMaxEPI3D-1)) && (hdr->dim[3] > 1) && (hdr->dim[4] > 1)) { //printMessage(">>>>Reading GE slice timing from epiRT (0018,1060 did not work)\n"); //GE: 2nd method for "epiRT" PSD //ignore bogus values of first volume https://neurostars.org/t/getting-missing-ge-information-required-by-bids-for-common-preprocessing/1357/6 // this necessarily requires at last two volumes, hence dim[4] > 1 int j = hdr->dim[3]; //since first volume is bogus, we define the volume start time as the first slice in the second volume float minTime = dcmList[dcmSort[j].indx].rtia_timerGE; float maxTime = minTime; for (int v = 0; v < hdr->dim[3]; v++) { if (dcmList[dcmSort[v+j].indx].rtia_timerGE < minTime) minTime = dcmList[dcmSort[v+j].indx].rtia_timerGE; if (dcmList[dcmSort[v+j].indx].rtia_timerGE > maxTime) maxTime = dcmList[dcmSort[v+j].indx].rtia_timerGE; } //compare all slice times in 2nd volume to start time for this volume if (maxTime != minTime) { double scale2Sec = 1.0; if (dcmList[indx0].TR > 0.0) { //issue 286: determine units for rtia_timerGE //See https://github.com/rordenlab/dcm2niix/tree/master/GE // Nikadon's DV24 data stores RTIA Timer as seconds, issue 286 14_LX uses 1/10,000 sec // The slice timing should always be less than the TR (which is in ms) // Below we assume 1/10,000 of a sec if slice time is >90% and less than <100% of a TR // Will not work for sparse designs, but slice timing inappropriate for those datasets float maxSliceTimeFrac = (maxTime-minTime) / dcmList[indx0].TR; //should be slightly less than 1.0 if ((maxSliceTimeFrac > 9.0) && (maxSliceTimeFrac < 10)) scale2Sec = 1.0 / 10000.0; //printMessage(">> %g %g %g\n", maxSliceTimeFrac, scale2Sec, dcmList[indx0].TR); } double scale2ms = scale2Sec * 1000.0; //20190704: convert slice timing values to ms for all vendors for (int v = 0; v < hdr->dim[3]; v++) dcmList[indx0].CSA.sliceTiming[v] = (dcmList[dcmSort[v+j].indx].rtia_timerGE - minTime) * scale2ms; dcmList[indx0].CSA.sliceTiming[hdr->dim[3]] = -1; //detect multi-band int nZero = 0; for (int v = 0; v < hdr->dim[3]; v++) if (isSameFloatGE(dcmList[indx0].CSA.sliceTiming[hdr->dim[3]], 0.0)) nZero ++; if ((nZero > 1) && (nZero < hdr->dim[3]) && ((hdr->dim[3] % nZero) == 0)) dcmList[indx0].CSA.multiBandFactor = nZero; //report times if (verbose > 0) { printMessage("GE slice timing (sec)\n"); printMessage("\tTime\tX\tY\tZ\tInstance\n"); for (int v = 0; v < hdr->dim[3]; v++) { if (v == (hdr->dim[3]-1)) printMessage("...\n"); if ((v < 4) || (v == (hdr->dim[3]-1))) printMessage("\t%g\t%g\t%g\t%g\t%d\n", dcmList[indx0].CSA.sliceTiming[v] / 1000.0, dcmList[dcmSort[v+j].indx].patientPosition[1], dcmList[dcmSort[v+j].indx].patientPosition[2], dcmList[dcmSort[v+j].indx].patientPosition[3], dcmList[dcmSort[v+j].indx].imageNum); } //for v } //verbose > 1 } //if maxTime != minTIme } //GE slice timing from 0021,105E } //oldSliceTimingGE() */ int sliceTimingCore(struct TDCMsort *dcmSort, struct TDICOMdata *dcmList, struct nifti_1_header *hdr, int verbose, const char *filename, int nConvert, struct TDCMopts opts) { int sliceDir = 0; if (hdr->dim[3] < 2) return sliceDir; //uint64_t indx0 = dcmSort[0].indx; //uint64_t indx1 = dcmSort[1].indx; struct TDICOMdata *d0 = &dcmList[dcmSort[0].indx]; uint64_t indx1 = dcmSort[0].indx; if (nConvert > 1) //use 2nd volume as CMRR bug can create bogus slice timing in first volume indx1 = dcmSort[1].indx; struct TDICOMdata *d1 = &dcmList[indx1]; //oldSliceTimingGE(dcmSort, dcmList, hdr, verbose, filename, nConvert); sliceTimingUIH(dcmSort, dcmList, hdr, verbose, filename, nConvert); int isSliceTimeHHMMSS = sliceTimingSiemens2D(dcmSort, dcmList, hdr, verbose, filename, nConvert); sliceTimingXA(dcmSort, dcmList, hdr, verbose, filename, nConvert); checkSliceTiming(d0, d1, verbose, isSliceTimeHHMMSS); rescueSliceTimingSiemens(d0, verbose, hdr->dim[3], filename); //desperate attempts if conventional methods fail if (hdr->dim[3] > 1) sliceDir = headerDcm2Nii2(dcmList[dcmSort[0].indx], dcmList[indx1], hdr, true); //UNCOMMENT NEXT TWO LINES TO RE-ORDER MOSAIC WHERE CSA's protocolSliceNumber does not start with 1 if (dcmList[dcmSort[0].indx].CSA.protocolSliceNumber1 > 1) { printWarning("Weird CSA 'ProtocolSliceNumber' (System/Miscellaneous/ImageNumbering reversed): VALIDATE SLICETIMING AND BVECS\n"); //https://www.healthcare.siemens.com/siemens_hwem-hwem_ssxa_websites-context-root/wcm/idc/groups/public/@global/@imaging/@mri/documents/download/mdaz/nzmy/~edisp/mri_60_graessner-01646277.pdf //see https://github.com/neurolabusc/dcm2niix/issues/40 sliceDir = -1; //not sure how to handle negative determinants? } if (sliceDir < 0) { if ((dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_UIH) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_GE)) dcmList[dcmSort[0].indx].CSA.protocolSliceNumber1 = -1; } sliceTimingGE(d0, filename, opts, hdr, dcmSort, dcmList); //ensure slice times have variability reverseSliceTiming(d0, verbose, hdr->dim[3]); bool allSame = true; for (int i = 0; i < hdr->dim[3]; i++) if (!isSameFloatGE(d0->CSA.sliceTiming[i], d0->CSA.sliceTiming[0])) allSame = false; if (allSame) d0->CSA.sliceTiming[0] = -1.0; return sliceDir; } //sliceTiming() void loadOverlay(char *imgname, unsigned char *img, int offset, int x, int y, int z) { int nvox = x * y * z; size_t imgszRead = (nvox + 7) >> 3; //overlay stored as 1 bit per voxel FILE *file = fopen(imgname, "rb"); if (!file) { printError("Unable to open '%s'\n", imgname); return; } fseek(file, 0, SEEK_END); long fileLen = ftell(file); if (fileLen < (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); //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++) { int byt = (i >> 3); int bit = (i % 8); img[i] = ((bImg[byt] & mask[bit]) != 0); } free(bImg); fclose(file); return; } //loadOverlay() int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts, struct TDTI4D *dti4D, int segVol) { 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 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" #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); if (isnan(dcmList[indx0].gantryTilt)) return EXIT_FAILURE; } #endif //newTilt see issue 254 if (dcmList[indx0].isPrivateCreatorRemap) printWarning("PrivateCreator remapping detected. DICOMs are not archival quality (issue 435).\n"); if (dcmList[indx0].isScaleVariesEnh) //issue363 iVaries = true; if ((dcmList[indx].isXA10A) && (dcmList[indx].CSA.mosaicSlices < 0)) { printMessage("Siemens XA10 Mosaics are not primary images and lack vital data.\n"); printMessage(" See https://github.com/rordenlab/dcm2niix/issues/236\n"); #ifdef mySaveXA10Mosaics int n; printMessage("INPUT REQUIRED FOR %s\n", dcmList[indx].imageBaseName); printMessage("PLEASE ENTER NUMBER OF SLICES IN MOSAIC:\n"); scanf("%d", &n); for (int i = 0; i < nConvert; i++) dcmList[dcmSort[i].indx].CSA.mosaicSlices = n; #endif } if (opts.isIgnoreDerivedAnd2D && dcmList[indx].isDerived) { printMessage("Ignoring derived image(s) of series %ld %s\n", dcmList[indx].seriesNum, nameList->str[indx]); return EXIT_SUCCESS; } if ((opts.isIgnoreDerivedAnd2D) && ((dcmList[indx].isLocalizer) || (strcmp(dcmList[indx].sequenceName, "_tfl2d1") == 0) || (strcmp(dcmList[indx].sequenceName, "_fl3d1_ns") == 0) || (strcmp(dcmList[indx].sequenceName, "_fl2d1") == 0))) { printMessage("Ignoring localizer (sequence '%s') of series %ld %s\n", dcmList[indx].sequenceName, dcmList[indx].seriesNum, nameList->str[indx]); return EXIT_SUCCESS; } if ((opts.isIgnoreDerivedAnd2D) && (nConvert < 2) && (dcmList[indx].CSA.mosaicSlices < 2) && (dcmList[indx].xyzDim[3] < 2)) { printMessage("Ignoring 2D image of series %ld %s\n", dcmList[indx].seriesNum, nameList->str[indx]); return EXIT_SUCCESS; } if (dcmList[indx].manufacturer == kMANUFACTURER_UNKNOWN) printWarning("Unable to determine manufacturer (0008,0070), so conversion is not tuned for vendor.\n"); #ifdef myForce3DPhaseRealImaginary //compiler option: segment each phase/real/imaginary map bool saveAs3D = dcmList[indx].isHasPhase || dcmList[indx].isHasReal || dcmList[indx].isHasImaginary; #else bool saveAs3D = false; #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) { for (int i = 0; i < 24; i++) hdr0.aux_file[i] = 0; //remove dcm.imageComments snprintf(hdr0.aux_file, 24, "%s", opts.imageComments); } if (opts.isVerbose) printMessage("Converting %s\n", nameList->str[indx]); if (img == NULL) return EXIT_FAILURE; size_t imgsz = nii_ImgBytes(hdr0); unsigned char *imgM = (unsigned char *)malloc(imgsz * (uint64_t)nConvert); memcpy(&imgM[0], &img[0], imgsz); free(img); //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) { //next: detect trigger time see example https://www.slicer.org/wiki/Documentation/4.4/Modules/MultiVolumeExplorer double triggerDx = dcmList[dcmSort[nConvert - 1].indx].triggerDelayTime - dcmList[indx0].triggerDelayTime; if ((triggerDx > 0.0) && (dcmList[indx0].aslFlags == kASL_FLAG_NONE)) //issue 384, issue533 dcmList[indx0].triggerDelayTime = triggerDx; //next: determine gantry tilt if (dcmList[indx0].gantryTilt != 0.0f) printWarning("Note these images have gantry tilt of %g degrees (manufacturer ID = %d)\n", dcmList[indx0].gantryTilt, dcmList[indx0].manufacturer); if (hdr0.dim[3] < 2) { //stack volumes with multiple acquisitions int nAcq = 1; //Next line works in theory, but fails with Siemens CT that saves pairs of slices as acquisitions, see example "testSiemensStackAcq" // nAcq = 1+abs( dcmList[dcmSort[nConvert-1].indx].acquNum-dcmList[indx0].acquNum); //therefore, the 'same position' is the most robust solution in the real world. if ((dcmList[indx0].manufacturer == kMANUFACTURER_SIEMENS) && (isSameFloat(dcmList[indx0].TR, 0.0f))) { nConvert = siemensCtKludge(nConvert, dcmSort, dcmList); } //resolve GE discrepancy between tags 0020,1002; 0021,104F; 0054,0081 // e.g. T1 scans can be interpolated in the slice direction, so choose large number // EPI can report total number of slices (dim3*dim4), so choose smaller number if ((nAcq == 1) && (dcmList[indx0].locationsInAcquisitionConflict > 0) && ((nConvert % dcmList[indx0].locationsInAcquisitionConflict) == 0)) { //printMessage(" >>>%d %d %d %d\n", nAcq, nConvert, dcmList[indx0].locationsInAcquisitionConflict, dcmList[indx0].locationsInAcquisition); nAcq = 0; for (int i = 0; i < nConvert; i++) if (isSamePosition(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx])) nAcq++; int nAcqConflict = nConvert / dcmList[indx0].locationsInAcquisitionConflict; if (nAcq == nAcqConflict) { printMessage("Resolved discrepancy between tags (0020,1002; 0021,104F; 0054,0081)\n"); dcmList[indx0].locationsInAcquisition = dcmList[indx0].locationsInAcquisitionConflict; } } if ((nConvert > 1) && (nAcq == 1) && (dcmList[indx0].locationsInAcquisition > 0)) { if ((nConvert % dcmList[indx0].locationsInAcquisition) == 0) nAcq = nConvert / dcmList[indx0].locationsInAcquisition; 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++; } if ((nAcq > 1) && ((nConvert / nAcq) > 1) && ((nConvert % nAcq) == 0)) { hdr0.dim[3] = nConvert / nAcq; hdr0.dim[4] = nAcq; hdr0.dim[0] = 4; if ((dcmList[indx0].locationsInAcquisition > 0) && (dcmList[indx0].locationsInAcquisition != hdr0.dim[3])) printMessage("DICOM images may be missing, expected %d spatial locations per volume, but found %d.\n", dcmList[indx0].locationsInAcquisition, hdr0.dim[3]); } else if ((dcmList[indx0].isXA10A) && (nConvert > nAcq) && (nAcq > 1)) { nAcq -= 1; hdr0.dim[3] = nConvert / nAcq; hdr0.dim[4] = nAcq; hdr0.dim[0] = 4; if ((nAcq > 1) && (nConvert != nAcq)) { printMessage("Slice positions repeated, but number of slices (%d) not divisible by number of repeats (%d): converting only complete volumes.\n", nConvert, nAcq); } } else { hdr0.dim[3] = nConvert; if ((nAcq > 1) && (nConvert != nAcq)) { printMessage("Slice positions repeated, but number of slices (%d) not divisible by number of repeats (%d): missing images?\n", nConvert, nAcq); if (dcmList[indx0].locationsInAcquisition > 0) printMessage("Hint: expected %d locations\n", dcmList[indx0].locationsInAcquisition); } } //next options removed: features now thoroughly detected in nii_loadDir() for (int i = 0; i < nConvert; i++) { //make sure 1st volume describes shared features if (dcmList[dcmSort[i].indx].isHasOverlay) isHasOverlay = true; if (dcmList[dcmSort[i].indx].isCoilVaries) dcmList[indx0].isCoilVaries = true; if (dcmList[dcmSort[i].indx].isMultiEcho) dcmList[indx0].isMultiEcho = true; if (dcmList[dcmSort[i].indx].isNonParallelSlices) dcmList[indx0].isNonParallelSlices = true; if (dcmList[dcmSort[i].indx].isHasPhase) dcmList[indx0].isHasPhase = true; if (dcmList[dcmSort[i].indx].isHasReal) dcmList[indx0].isHasReal = true; if (dcmList[dcmSort[i].indx].isHasImaginary) dcmList[indx0].isHasImaginary = true; } //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) { ensureSequentialSlicePositions(hdr0.dim[3], hdr0.dim[4], dcmSort, dcmList, opts.isVerbose); //issue529 indx0 = dcmSort[0].indx; } //printf("Bogo529\n"); return EXIT_SUCCESS; //note: GE 0008,0032 unreliable, see mb=6 data from sw27.0 20201026 //issue 407 int nTR = 0; for (int i = 0; i < nConvert; i++) if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { dti4D->frameDuration[nTR] = dcmList[dcmSort[i].indx].frameDuration; nTR += 1; if (nTR >= kMaxDTI4D) break; } bool trVaries = false; bool dayVaries = false; float tr = -1.0; float mintr = 1000000; float maxtr = -1.0; float volumeTimeStartFirstStartLast = -1.0; int nVol = 0; uint64_t prevVolIndx = indx0; for (int i = 0; i < nConvert; i++) if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { float trDiff = acquisitionTimeDifference(&dcmList[prevVolIndx], &dcmList[dcmSort[i].indx]); prevVolIndx = dcmSort[i].indx; nVol++; if (trDiff <= 0) continue; mintr = min(mintr, trDiff); maxtr = max(maxtr, trDiff); if (tr < 0) tr = trDiff; if (trDiff < 0) dayVaries = true; float trDiff0 = acquisitionTimeDifference(&dcmList[indx0], &dcmList[dcmSort[i].indx]); volumeTimeStartFirstStartLast = max(volumeTimeStartFirstStartLast, trDiff0); } float toleranceSec = 50.0 / 1000.0; //e.g. 50/1000 = 50ms 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].aslFlags != kASL_FLAG_NONE) { //issue533 int nTR = 0; for (int i = 0; i < nConvert; i++) { if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { dti4D->triggerDelayTime[nTR] = dcmList[dcmSort[i].indx].triggerDelayTime; nTR += 1; if (nTR >= kMaxDTI4D) break; } } //for each volume } //if ASL if ((maxtr - mintr) > toleranceSec) trVaries = true; if (trVaries) { if (dayVaries) printWarning("Seconds between volumes varies (perhaps run through midnight)\n"); else printWarning("Seconds between volumes varies\n"); //issue 407 int nTR = 0; for (int i = 0; i < nConvert; i++) if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { float trDiff = acquisitionTimeDifference(&dcmList[indx0], &dcmList[dcmSort[i].indx]); dti4D->volumeOnsetTime[nTR] = trDiff; dti4D->decayFactor[nTR] = dcmList[dcmSort[i].indx].decayFactor; nTR += 1; if (nTR >= kMaxDTI4D) break; } if (dcmList[indx0].modality != kMODALITY_PT) dti4D->decayFactor[0] = -1.0; //only for PET else hdr0.pixdim[4] = 0.0; // saveAs3D = true; // printWarning("Creating independent volumes as time between volumes varies\n"); if (opts.isVerbose) { printMessage(" OnsetTime = ["); for (int i = 0; i < nConvert; i++) if (isSamePosition(dcmList[indx0], dcmList[dcmSort[i].indx])) { float trDiff = acquisitionTimeDifference(&dcmList[indx0], &dcmList[dcmSort[i].indx]); printMessage(" %g", trDiff); } printMessage(" ]\n"); } } //if trVaries } //if PET //next: detect variable inter-slice distance float dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); #ifdef myInstanceNumberOrderIsNotSpatial if (!isSameFloat(dx, 0.0)) //only for XYZT, not TXYZ: perhaps run for swapDim3Dim4? Extremely rare anomaly 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; #endif bool dxVaries = false; for (int i = 1; i < nConvert; i++) if (!isSameFloatT(dx, intersliceDistance(dcmList[dcmSort[i - 1].indx], dcmList[dcmSort[i].indx]), 0.2)) dxVaries = true; if (hdr0.dim[4] < 2) { if (dxVaries) { sliceMMarray = (float *)malloc(sizeof(float) * nConvert); sliceMMarray[0] = 0.0f; for (int i = 1; i < nConvert; i++) sliceMMarray[i] = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); printWarning("Interslice distance varies in this volume (incompatible with NIfTI format).\n"); if (opts.isVerbose) { printMessage("Dimensions %d %d %d %d nAcq %d nConvert %d\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], nAcq, nConvert); printMessage(" Distance from first slice:\n"); printMessage("dx=[0"); for (int i = 1; i < nConvert; i++) printMessage(" %g", sliceMMarray[i]); printMessage("]\n"); } #ifndef myInstanceNumberOrderIsNotSpatial //kludge to handle single volume without instance numbers (0020,0013), e.g. https://www.morphosource.org/Detail/MediaDetail/Show/media_id/8430 bool 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]); if (isSameFloatGE(dxPrev, 0.0)) slicePositionRepeats++; for (int i = 2; i < nConvert; i++) { float dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); if (dx < dxPrev) isInconsistenSliceDir = true; if (isSameFloatGE(dxPrev, 0.0)) slicePositionRepeats++; dxPrev = dx; } } if ((isInconsistenSliceDir) && (slicePositionRepeats == 1)) { //printWarning("Slice order as defined by instance number not spatially sequential.\n"); //printWarning("Attempting to reorder slices based on spatial position.\n"); ensureSequentialSlicePositions(hdr0.dim[3], hdr0.dim[4], dcmSort, dcmList, opts.isVerbose); dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); hdr0.pixdim[3] = dx; 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; dxVaries = false; dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); for (int i = 1; i < nConvert; i++) if (!isSameFloatT(dx, intersliceDistance(dcmList[dcmSort[i - 1].indx], dcmList[dcmSort[i].indx]), 0.2)) dxVaries = true; for (int i = 1; i < nConvert; i++) sliceMMarray[i] = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); //printf("dx=["); //for (int i = 1; i < nConvert; i++) // printf("%g ", intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[i].indx]) ); //printf("\n"); bool 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]); if (isSameFloatGE(dxPrev, 0.0)) slicePositionRepeats++; for (int i = 2; i < nConvert; i++) { float dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); if (dx < dxPrev) isInconsistenSliceDir = true; if (isSameFloatGE(dxPrev, 0.0)) slicePositionRepeats++; dxPrev = dx; } } if (!dxVaries) { printMessage("Slice re-ordering resolved inter-slice distance variability.\n"); free(sliceMMarray); sliceMMarray = NULL; } } if (isInconsistenSliceDir) { printMessage("Unable to equalize slice distances: slice order not consistently ascending.\n"); printMessage("First spatial position repeated %d times\n", slicePositionRepeats); printError(" Recompiling with '-DmyInstanceNumberOrderIsNotSpatial' might help.\n"); return EXIT_FAILURE; } #endif int imageNumRange = 1 + abs(dcmList[dcmSort[nConvert - 1].indx].imageNum - dcmList[dcmSort[0].indx].imageNum); if ((imageNumRange > 1) && (imageNumRange != nConvert)) { if ((dcmList[dcmSort[0].indx].locationsInAcquisition > 0) && ((nConvert % dcmList[dcmSort[0].indx].locationsInAcquisition) != 0)) printError("Missing images. Found %d images, expected %d slices per volume and instance number (0020,0013) ranges from %d to %d\n", nConvert, dcmList[dcmSort[0].indx].locationsInAcquisition, dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[nConvert - 1].indx].imageNum); else printWarning("Missing images? Expected %d images, but instance number (0020,0013) ranges from %d to %d\n", nConvert, dcmList[dcmSort[0].indx].imageNum, dcmList[dcmSort[nConvert - 1].indx].imageNum); if (opts.isVerbose) { printMessage("instance=["); for (int i = 0; i < nConvert; i++) { printMessage(" %d", dcmList[dcmSort[i].indx].imageNum); } printMessage("]\n"); } } //imageNum not sequential } //dx varies } //not 4D if ((hdr0.dim[4] > 0) && (dxVaries) && (dx == 0.0) && ((dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_UNKNOWN) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_GE) || (dcmList[dcmSort[0].indx].manufacturer == kMANUFACTURER_PHILIPS))) { //Niels Janssen has provided GE sequential multi-phase acquisitions that also require swizzling swapDim3Dim4(hdr0.dim[3], hdr0.dim[4], dcmSort); dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[1].indx]); if (opts.isVerbose) printMessage("Swizzling 3rd and 4th dimensions (XYTZ -> XYZT), assuming interslice distance is %f\n", dx); } if ((dx == 0.0) && (!dxVaries)) { //all images are the same slice - 16 Dec 2014 printWarning("All images appear to be a single slice - please check slice/vector orientation\n"); hdr0.dim[3] = 1; hdr0.dim[4] = nConvert; hdr0.dim[0] = 4; } if ((dx > 0) && (!isSameFloatGE(dx, hdr0.pixdim[3]))) hdr0.pixdim[3] = dx; dcmList[dcmSort[0].indx].xyzMM[3] = dx; //16Sept2014 : correct DICOM for true distance between slice centers: // e.g. MCBI Siemens ToF 0018:0088 reports 16mm SpacingBetweenSlices, but actually 0.5mm } else if (hdr0.dim[4] < 2) { hdr0.dim[4] = nConvert; hdr0.dim[0] = 4; } else { hdr0.dim[5] = nConvert; hdr0.dim[0] = 5; } if (((dcmList[indx0].manufacturer == kMANUFACTURER_TOSHIBA) || (dcmList[indx0].manufacturer == kMANUFACTURER_CANON)) && (dcmList[indx0].CSA.numDti < 1) && (nConvert > 1) && (hdr0.dim[4] > 1)) { //TOSHIBA omits 0018,9087 from B=0 volumes in diffusion series. Since most diffusion series start with B=0 volume, this would cause us to detect a diffusion series for (int i = 1; i < nConvert; i++) if (dcmList[dcmSort[i].indx].CSA.numDti > 0) dcmList[indx0].CSA.numDti = 1; } //printMessage(" %d %d %d %d %lu\n", hdr0.dim[1], hdr0.dim[2], hdr0.dim[3], hdr0.dim[4], (unsigned long)[imgM length]); struct nifti_1_header hdrI; //double time = -1.0; if ((!opts.isOnlyBIDS) && (nConvert > 1)) { //for (int i = 0; i < nConvert; i++) // printMessage("%d\t%s\n", i, nameList->str[indx]); //int iStart = 1; //if (isReorder) iStart = 0; //for (int i = 1; i < nConvert; i++) { //<- works except where ensureSequentialSlicePositions() changes 1st slice for (int i = 0; i < nConvert; i++) { //stack additional images indx = dcmSort[i].indx; //double time2 = dcmList[dcmSort[i].indx].acquisitionTime; //if (time != time2) // printWarning("%g\n", time2); //time = time2; //if (headerDcm2Nii(dcmList[indx], &hdrI) == EXIT_FAILURE) return EXIT_FAILURE; img = nii_loadImgXL(nameList->str[indx], &hdrI, dcmList[indx], iVaries, opts.compressFlag, opts.isVerbose, dti4D); if (img == NULL) return EXIT_FAILURE; if ((hdr0.dim[1] != hdrI.dim[1]) || (hdr0.dim[2] != hdrI.dim[2]) || (hdr0.bitpix != hdrI.bitpix)) { printError("Image dimensions differ %s %s", nameList->str[dcmSort[0].indx], nameList->str[indx]); free(imgM); free(img); return EXIT_FAILURE; } memcpy(&imgM[(uint64_t)i * imgsz], &img[0], imgsz); free(img); } } //skip if we are only creating BIDS if (hdr0.dim[4] > 1) //for 4d datasets, last volume should be acquired before first checkDateTimeOrder(&dcmList[dcmSort[0].indx], &dcmList[dcmSort[nConvert - 1].indx]); } if (opts.isVerbose > 1) reportProtocolBlockGE(&dcmList[indx0], nameList->str[dcmSort[0].indx]); int sliceDir = sliceTimingCore(dcmSort, dcmList, &hdr0, opts.isVerbose, nameList->str[dcmSort[0].indx], nConvert, opts); #ifdef myReportSliceFilenames if (sliceDir < 0) { for (int i = nConvert; i > 0; --i) printMessage("|%d|%s\n", i, nameList->str[dcmSort[i - 1].indx]); } else { for (int i = 0; i < nConvert; i++) printMessage("|%d|%s\n", i, nameList->str[dcmSort[i].indx]); } #endif if (strlen(dcmList[dcmSort[0].indx].protocolName) < 1) //beware: tProtocolName can vary within a series "t1+AF8-mpr+AF8-ns+AF8-sag+AF8-p2+AF8-iso" vs "T1_mprage_ns_sag_p2_iso 1.0mm_192" rescueProtocolName(&dcmList[dcmSort[0].indx], nameList->str[dcmSort[0].indx]); //move before headerDcm2Nii2 checkSliceTiming(&dcmList[indx0], &dcmList[indx1]); char pathoutname[2048] = {""}; if (nii_createFilename(dcmList[dcmSort[0].indx], pathoutname, opts) == EXIT_FAILURE) { //make sure call is subsequent to rescueProtocolName() free(imgM); return EXIT_FAILURE; } if (strlen(pathoutname) < 1) { free(imgM); return EXIT_FAILURE; } // skip converting if user has specified one or more series, but has not specified this one if (opts.numSeries > 0) { //issue453: moved to before saveBIDS int i = 0; 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; for (; i < opts.numSeries; i++) { if (isSameDouble(opts.seriesNumber[i], seriesNum)) break; } if (i == opts.numSeries) return EXIT_SUCCESS; } if (opts.numSeries >= 0) //issue453 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 free(imgM); return EXIT_SUCCESS; } if ((segVol >= 0) && (hdr0.dim[4] > 1)) { int inVol = hdr0.dim[4]; int nVol = 0; for (int v = 0; v < inVol; v++) if (dti4D->gradDynVol[v] == segVol) nVol++; if (nVol < 1) { printError("Series %d does not exist\n", segVol); return EXIT_FAILURE; } size_t imgsz4D = imgsz; if (nVol < 2) hdr0.dim[0] = 3; //3D hdr0.dim[4] = 1; size_t imgsz3D = nii_ImgBytes(hdr0); unsigned char *img4D = (unsigned char *)malloc(imgsz4D); memcpy(&img4D[0], &imgM[0], imgsz4D); free(imgM); imgM = (unsigned char *)malloc(imgsz3D * nVol); int outVol = 0; for (int v = 0; v < inVol; v++) { if ((dti4D->gradDynVol[v] == segVol) && (outVol < nVol)) { memcpy(&imgM[outVol * imgsz3D], &img4D[v * imgsz3D], imgsz3D); outVol++; } } hdr0.dim[4] = nVol; imgsz = nii_ImgBytes(hdr0); free(img4D); saveAs3D = false; } // Prevent these DICOM files from being reused. for (int i = 0; i < nConvert; ++i) dcmList[dcmSort[i].indx].converted2NII = 1; if (opts.numSeries < 0) { //report series number but do not convert int segVolEcho = segVol; if ((dcmList[dcmSort[0].indx].echoNum > 1) && (segVolEcho <= 0)) segVolEcho = dcmList[dcmSort[0].indx].echoNum + 1; if (segVolEcho >= 0) { printMessage("\t%u.%d\t%s\n", dcmList[dcmSort[0].indx].seriesUidCrc, segVolEcho - 1, pathoutname); //printMessage("\t%ld.%d\t%s\n", dcmList[dcmSort[0].indx].seriesNum, segVol-1, pathoutname); } else { printMessage("\t%u\t%s\n", dcmList[dcmSort[0].indx].seriesUidCrc, pathoutname); //printMessage("\t%ld\t%s\n", dcmList[dcmSort[0].indx].seriesNum, pathoutname); } printMessage(" %s\n", nameList->str[dcmSort[0].indx]); return EXIT_SUCCESS; } struct nifti_1_header hdrrx = hdr0; bool isFlipZ = false; if (sliceDir < 0) { 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! } nii_saveText(pathoutname, dcmList[dcmSort[0].indx], opts, &hdr0, nameList->str[indx]); int numADC = 0; int *volOrderIndex = nii_saveDTI(pathoutname, nConvert, dcmSort, dcmList, opts, sliceDir, dti4D, &numADC, hdr0.dim[4]); PhilipsPrecise(&dcmList[dcmSort[0].indx], opts.isPhilipsFloatNotDisplayScaling, &hdr0, opts.isVerbose); if ((dcmList[dcmSort[0].indx].bitsStored == 12) && (dcmList[dcmSort[0].indx].bitsAllocated == 16)) nii_mask12bit(imgM, &hdr0); if ((opts.saveFormat == kSaveFormatMGH) && (hdr0.datatype == DT_UINT16)) imgM = nii_uint16toFloat32(imgM, &hdr0, opts.isVerbose); if ((opts.isMaximize16BitRange == kMaximize16BitRange_True) && (hdr0.datatype == DT_INT16)) { nii_scale16bitSigned(imgM, &hdr0, opts.isVerbose); //allow INT16 to use full dynamic range } else if ((opts.isMaximize16BitRange == kMaximize16BitRange_True) && (hdr0.datatype == DT_UINT16) && (!dcmList[dcmSort[0].indx].isSigned)) { 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)) printWarning("Siemens XA DICOM inadequate for robust conversion (issue 236)\n"); if ((dcmList[dcmSort[0].indx].isXA10A) && (nConvert > 1)) printWarning("Siemens XA exported as classic not enhanced DICOM (issue 236)\n"); 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]); #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"); #ifndef USING_R else fflush(stdout); //GUI buffers printf, display all results #endif //3D-EPI vs 3D SPACE/MPRAGE/ETC bool isFlipY = false; bool isSetOrtho = false; if ((opts.isRotate3DAcq) && (dcmList[dcmSort[0].indx].is3DAcq) && (!dcmList[dcmSort[0].indx].isEPI) && (hdr0.dim[3] > 1) && (hdr0.dim[0] < 4)) { bool isSliceEquidistant = true; //issue539 if ((nConvert > 0) && (sliceMMarray != NULL)){ float dx = sliceMMarray[1] - sliceMMarray[0]; float thr = fabs(dx) * 0.1; for (int i = 2; i < nConvert; i++) if (fabs(dx- (sliceMMarray[i]-sliceMMarray[i-1])) > (thr) ) { printWarning("Unable to rotate 3D volume: slices not equidistant: %g != %g\n", dx, sliceMMarray[i]-sliceMMarray[i-1]); isSliceEquidistant = false; break; } } if (isSliceEquidistant) { imgM = nii_setOrtho(imgM, &hdr0); isSetOrtho = true; } } else if (opts.isFlipY) { //(FLIP_Y) //(dcmList[indx0].CSA.mosaicSlices < 2) && imgM = nii_flipY(imgM, &hdr0); isFlipY = true; } else printMessage("DICOM row order preserved: may appear upside down in tools that ignore spatial transforms\n"); if ((dcmList[dcmSort[0].indx].epiVersionGE == kGE_EPI_PEPOLAR_REV) || (dcmList[dcmSort[0].indx].epiVersionGE == kGE_EPI_PEPOLAR_FWD_REV_FLIP) || (dcmList[dcmSort[0].indx].epiVersionGE == kGE_EPI_PEPOLAR_REV_FWD_FLIP)) { imgM = nii_flipImgY(imgM, &hdr0); } //begin: gantry tilt we need to save the shear in the transform mat44 sForm; LOAD_MAT44(sForm, hdr0.srow_x[0], hdr0.srow_x[1], hdr0.srow_x[2], hdr0.srow_x[3], hdr0.srow_y[0], hdr0.srow_y[1], hdr0.srow_y[2], hdr0.srow_y[3], hdr0.srow_z[0], hdr0.srow_z[1], hdr0.srow_z[2], hdr0.srow_z[3]); if (!isSameFloatGE(dcmList[indx0].gantryTilt, 0.0)) { float thetaRad = dcmList[indx0].gantryTilt * M_PI / 180.0; float c = cos(thetaRad); if (!isSameFloatGE(c, 0.0)) { mat33 shearMat; LOAD_MAT33(shearMat, 1.0, 0.0, 0.0, 0.0, 1.0, sin(thetaRad) / c, 0.0, 0.0, 1.0); mat33 s; LOAD_MAT33(s, hdr0.srow_x[0], hdr0.srow_x[1], hdr0.srow_x[2], hdr0.srow_y[0], hdr0.srow_y[1], hdr0.srow_y[2], hdr0.srow_z[0], hdr0.srow_z[1], hdr0.srow_z[2]); s = nifti_mat33_mul(shearMat, s); mat44 shearForm; LOAD_MAT44(shearForm, s.m[0][0], s.m[0][1], s.m[0][2], hdr0.srow_x[3], s.m[1][0], s.m[1][1], s.m[1][2], hdr0.srow_y[3], s.m[2][0], s.m[2][1], s.m[2][2], hdr0.srow_z[3]); setQSForm(&hdr0, shearForm, true); } //avoid div/0: cosine not zero } //if gantry tilt //end: gantry tilt we need to save the shear in the transform int returnCode = EXIT_FAILURE; #ifndef myNoSave // Indicates success or failure of the (last) save if (opts.saveFormat != kSaveFormatNIfTI) removeSclSlopeInter(&hdr0, imgM); //printMessage(" x--> %d ----\n", nConvert); if (!opts.isRGBplanar) //save RGB as packed RGBRGBRGB... instead of planar RRR..RGGG..GBBB..B imgM = nii_planar2rgb(imgM, &hdr0, true); //NIfTI is packed while Analyze was planar if ((hdr0.dim[4] > 1) && (saveAs3D)) returnCode = nii_saveNII3D(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); else { if (volOrderIndex) //reorder volumes imgM = reorderVolumes(&hdr0, imgM, volOrderIndex); #ifndef USING_R if ((opts.isIgnoreDerivedAnd2D) && (numADC > 0)) printMessage("Ignoring derived diffusion image(s). Better isotropic and ADC maps can be generated later processing.\n"); if ((!opts.isIgnoreDerivedAnd2D) && (numADC > 0)) { //ADC maps can disrupt analysis: save a copy with the ADC map, and another without char pathoutnameADC[2048] = {""}; strcat(pathoutnameADC, pathoutname); strcat(pathoutnameADC, "_ADC"); if (opts.isSave3D) nii_saveNII3D(pathoutnameADC, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); else nii_saveNII(pathoutnameADC, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); } if (isHasOverlay) { //each series can have up to 16 overlays, overlays may not be on all slices for (int j = 0; j < kMaxOverlay; j++) { bool isOverlay = false; for (int i = 0; i < nConvert; i++) if (dcmList[dcmSort[i].indx].overlayStart[j] > 0) isOverlay = true; if (!isOverlay) continue; char pathoutnameROI[2048] = {""}; strcat(pathoutnameROI, pathoutname); char append[128] = {""}; sprintf(append, "_ROI%d", j + 1); strcat(pathoutnameROI, append); struct nifti_1_header hdrr = hdrrx; hdrr.dim[0] = 3; if (hdrr.dim[1] < 1) hdrr.dim[1] = 1; if (hdrr.dim[2] < 1) hdrr.dim[2] = 1; if (hdrr.dim[3] < 1) hdrr.dim[3] = 1; hdrr.dim[4] = 1; hdrr.bitpix = 8; hdrr.datatype = 2; hdrr.scl_inter = 0.0; hdrr.scl_slope = 1.0; int nvox = hdrr.dim[1] * hdrr.dim[2] * hdrr.dim[3]; unsigned char *imgR = (unsigned char *)malloc(nvox); for (int v = 0; v < nvox; v++) imgR[v] = 0; if (nConvert == 1) { int indx = dcmSort[0].indx; loadOverlay(nameList->str[indx], imgR, dcmList[indx].overlayStart[j], hdrr.dim[1], hdrr.dim[2], hdrr.dim[3]); } else if (nConvert == hdrr.dim[3]) { for (int i = 0; i < nConvert; i++) { int indx = dcmSort[i].indx; if (dcmList[indx].overlayStart[j] > 0) { unsigned char *imgRS = imgR + (i * hdrr.dim[1] * hdrr.dim[2]); loadOverlay(nameList->str[indx], imgRS, dcmList[indx].overlayStart[j], hdrr.dim[1], hdrr.dim[2], 1); } //if overlay on slice } //for each volume } // if (isFlipZ) imgR = nii_flipZ(imgR, &hdrr); if (isSetOrtho) imgR = nii_setOrtho(imgR, &hdrr); if (isFlipY) imgR = nii_flipY(imgR, &hdrr); nii_saveNII(pathoutnameROI, hdrr, imgR, opts, dcmList[dcmSort[0].indx]); } } #endif imgM = removeADC(&hdr0, imgM, numADC); #ifndef USING_R if (iVaries) printMessage("Saving as 32-bit float (slope, intercept or bits allocated varies).\n"); if (opts.saveFormat != kSaveFormatNIfTI) returnCode = nii_saveForeign(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx], dti4D, dcmList[indx0].CSA.numDti); else if (opts.isSave3D) returnCode = nii_saveNII3D(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); else returnCode = nii_saveNII(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); #endif } #endif if (dcmList[indx0].gantryTilt != 0.0) { setQSForm(&hdr0, sForm, true); //if (dcmList[indx0].isResampled) { //we no detect based on image orientation https://github.com/rordenlab/dcm2niix/issues/253 // printMessage("Tilt correction skipped: 0008,2111 reports RESAMPLED\n"); //} else if (opts.isTiltCorrect) { imgM = nii_saveNII3Dtilt(pathoutname, &hdr0, imgM, opts, dcmList[dcmSort[0].indx], sliceMMarray, dcmList[indx0].gantryTilt, dcmList[indx0].manufacturer); strcat(pathoutname, "_Tilt"); } else printMessage("Tilt correction skipped\n"); } if ((sliceMMarray != NULL) && (!isSetOrtho)) { if (dcmList[indx0].isResampled) { printMessage("Slice thickness correction skipped: 0008,2111 reports RESAMPLED\n"); } else returnCode = nii_saveNII3Deq(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx], sliceMMarray); free(sliceMMarray); } //3D-EPI vs 3D SPACE/MPRAGE/ETC if ((opts.isRotate3DAcq) && (opts.isCrop) && (dcmList[indx0].is3DAcq) && (!dcmList[indx0].isEPI) && (hdr0.dim[3] > 1) && (hdr0.dim[0] < 4)) //for T1 scan: && (dcmList[indx0].TE < 25) returnCode = nii_saveCrop(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); //n.b. must be run AFTER nii_setOrtho()! #ifdef USING_R // Note that for R, only one image should be created per series // Hence this extra test if (returnCode != EXIT_SUCCESS) returnCode = nii_saveNII(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); if (returnCode == EXIT_SUCCESS) nii_saveAttributes(dcmList[dcmSort[0].indx], hdr0, opts, nameList->str[dcmSort[0].indx]); #endif free(imgM); if (dcmList[dcmSort[0].indx].xyzDim[0] > 1) returnCode = kEXIT_INCOMPLETE_VOLUMES_FOUND; //issue515 return returnCode; //EXIT_SUCCESS; } // saveDcm2NiiCore() int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[], struct TDICOMdata dcmList[], struct TSearchList *nameList, struct TDCMopts opts, struct TDTI4D *dti4D) { //this wrapper does nothing if all the images share the same echo time and scale // however, it segments images when these properties vary uint64_t indx = dcmSort[0].indx; if ((!dcmList[indx].isScaleOrTEVaries) || (dcmList[indx].xyzDim[4] < 2)) return saveDcm2NiiCore(nConvert, dcmSort, dcmList, nameList, opts, dti4D, -1); if ((dcmList[indx].xyzDim[4]) && (dti4D->sliceOrder[0] < 0)) { printError("Unexpected error for image with varying echo time or intensity scaling\n"); return EXIT_FAILURE; } 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 int echoNum[kMaxDTI4D]; int echo = 1; for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) echoNum[i] = 0; echoNum[0] = 1; for (int i = 1; i < dcmList[indx].xyzDim[4]; i++) { for (int j = 0; j < i; j++) if (dti4D->TE[i] == dti4D->TE[j]) echoNum[i] = echoNum[j]; if (echoNum[i] == 0) { echo++; echoNum[i] = echo; } } if (echo > 1) dcmList[indx].isMultiEcho = true; //check for repeated volumes int series = 1; for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) dti4D->gradDynVol[i] = 0; dti4D->gradDynVol[0] = 1; 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])) dti4D->gradDynVol[i] = dti4D->gradDynVol[j]; if (dti4D->gradDynVol[i] == 0) { series++; dti4D->gradDynVol[i] = series; } } //bvec/bval saved for each series (real, phase, magnitude, imaginary) https://github.com/rordenlab/dcm2niix/issues/219 TDTI4D dti4Ds = *dti4D; bool isHasDti = (dcmList[indx].CSA.numDti > 0); if ((isHasDti) && (dcmList[indx].CSA.numDti == dcmList[indx].xyzDim[4])) { int nDti = 0; for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) { if (dti4D->gradDynVol[i] == 1) { dti4Ds.S[nDti].V[0] = dti4Ds.S[i].V[0]; dti4Ds.S[nDti].V[1] = dti4Ds.S[i].V[1]; dti4Ds.S[nDti].V[2] = dti4Ds.S[i].V[2]; dti4Ds.S[nDti].V[3] = dti4Ds.S[i].V[3]; nDti++; } } dcmList[indx].CSA.numDti = nDti; } //save each series bool isScaleVariesEnh = dcmList[indx].isScaleVariesEnh; //issue363: any variation in any image float intenScale = dcmList[indx].intenScale; float intenIntercept = dcmList[indx].intenIntercept; float intenScalePhilips = dcmList[indx].intenScalePhilips; float RWVIntercept = dcmList[indx].RWVIntercept; float RWVScale = dcmList[indx].RWVScale; for (int s = 1; s <= series; s++) { //issue461: assert these values as saveDcm2NiiCore modifies them when it applies Philips scaling dcmList[indx].intenScale = intenScale; dcmList[indx].intenIntercept = intenIntercept; dcmList[indx].intenScalePhilips = intenScalePhilips; dcmList[indx].RWVIntercept = RWVIntercept; dcmList[indx].RWVScale = RWVScale; //for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) //for each volume // printf("%g<<triggerDelayTime[i]); for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) { //for each volume if (dti4D->gradDynVol[i] == s) { //dti4D->gradDynVol[i] = s; //nVol ++; dcmList[indx].TE = dti4D->TE[i]; //dcmList[indx].intenScale = dti4D->intenScale[i]; //dcmList[indx].intenIntercept = dti4D->intenIntercept[i]; //dcmList[indx].intenScalePhilips = dti4D->intenScalePhilips[i]; //dcmList[indx].RWVScale = dti4D->RWVScale[i]; //dcmList[indx].RWVIntercept = dti4D->RWVIntercept[i]; dcmList[indx].isHasPhase = dti4D->isPhase[i]; dcmList[indx].isHasReal = dti4D->isReal[i]; dcmList[indx].isHasImaginary = dti4D->isImaginary[i]; dcmList[indx].triggerDelayTime = dti4D->triggerDelayTime[i]; dcmList[indx].isHasMagnitude = false; dcmList[indx].echoNum = echoNum[i]; break; } } dcmList[indx].isScaleVariesEnh = false; if (isScaleVariesEnh) { //check if intensity scale varies for this particular output image, this will force 32-bit output int nz = 0; for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) { //for each volume if (dti4D->gradDynVol[i] == s) { for (int z = 0; z < dcmList[indx].xyzDim[3]; z++) { //for each slice int ix = (i * dcmList[indx].xyzDim[3]) + z; dti4Ds.intenScale[nz] = dti4D->intenScale[ix]; dti4Ds.intenIntercept[nz] = dti4D->intenIntercept[ix]; dti4Ds.intenScalePhilips[nz] = dti4D->intenScalePhilips[ix]; dti4Ds.RWVIntercept[nz] = dti4D->RWVIntercept[ix]; dti4Ds.RWVScale[nz] = dti4D->RWVScale[ix]; nz++; } //for z: each slice } //if series matches } //for each volume for (int i = 0; i < nz; i++) { if (dti4Ds.intenIntercept[i] != dti4Ds.intenIntercept[0]) dcmList[indx].isScaleVariesEnh = true; if (dti4Ds.intenScale[i] != dti4Ds.intenScale[0]) dcmList[indx].isScaleVariesEnh = true; if (dti4Ds.intenScalePhilips[i] != dti4Ds.intenScalePhilips[0]) dcmList[indx].isScaleVariesEnh = true; } dcmList[indx].intenScale = dti4Ds.intenScale[0]; dcmList[indx].intenIntercept = dti4Ds.intenIntercept[0]; dcmList[indx].intenScalePhilips = dti4Ds.intenScalePhilips[0]; dcmList[indx].RWVIntercept = dti4Ds.RWVIntercept[0]; dcmList[indx].RWVScale = dti4Ds.RWVScale[0]; } if (s > 1) dcmList[indx].CSA.numDti = 0; //only save bvec for first type (magnitude) int ret2 = saveDcm2NiiCore(nConvert, dcmSort, dcmList, nameList, opts, &dti4Ds, s); if (ret2 != EXIT_SUCCESS) ret = ret2; //return EXIT_SUCCESS only if ALL are successful } return ret; } // saveDcm2Nii() void fillTDCMsort(struct TDCMsort &tdcmref, const uint64_t indx, const struct TDICOMdata &dcmdata) { // Copy the relevant parts of dcmdata to tdcmref. tdcmref.indx = indx; //printf("series/image %d %d\n", dcmdata.seriesNum, dcmdata.imageNum); tdcmref.img = ((uint64_t)dcmdata.seriesNum << 32) + dcmdata.imageNum; for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; ++i) tdcmref.dimensionIndexValues[i] = dcmdata.dimensionIndexValues[i]; //lines below added to cope with extreme anonymization // https://github.com/rordenlab/dcm2niix/issues/211 if (tdcmref.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 1] != 0) return; //Since dimensionIndexValues are indexed from 1, 0 indicates unused // we leverage this as a hail mary attempt to distinguish images with identical series and instance numbers //See Correction Number CP-1242: // "Clarify in the description of dimension indices ... start from 1" // 0008,0032 stored as HHMMSS.FFFFFF, there are 86400000 ms per day // dimensionIndexValues stored as uint32, so encode acquisition time in ms uint32_t h = trunc(dcmdata.acquisitionTime / 10000.0); double tm = dcmdata.acquisitionTime - (h * 10000.0); uint32_t m = trunc(tm / 100.0); tm = tm - (m * 100.0); uint32_t ms = round(tm * 1000); ms += (h * 3600000) + (m * 60000); //printf("HHMMSS.FFFF %.5f -> %d ms\n", dcmdata.acquisitionTime, ms); tdcmref.dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS - 1] = ms; } // fillTDCMsort() int compareTDCMsort(void const *item1, void const *item2) { //for quicksort http://blog.ablepear.com/2011/11/objective-c-tuesdays-sorting-arrays.html struct TDCMsort const *dcm1 = (const struct TDCMsort *)item1; struct TDCMsort const *dcm2 = (const struct TDCMsort *)item2; //to do: detect duplicates with SOPInstanceUID (0008,0018) - accurate but slow text comparison int retval = 0; // tie if (dcm1->img < dcm2->img) retval = -1; else if (dcm1->img > dcm2->img) retval = 1; //printf("%d %d\n", dcm1->img, dcm2->img); //for(int i=0; i < MAX_NUMBER_OF_DIMENSIONS; i++) // printf("%d %d\n", dcm1->dimensionIndexValues[i], dcm2->dimensionIndexValues[i]); if (retval != 0) return retval; //sorted images // Check the dimensionIndexValues (useful for enhanced DICOM 4D series). // ->img is basically behaving as a (seriesNum, imageNum) sort key // concatenated into a (large) integer for qsort. That is unwieldy when // dimensionIndexValues need to be compared, because the existence of // uint128_t, uint256_t, etc. is not guaranteed. This sorts by // (seriesNum, ImageNum, div[0], div[1], ...), or if you think of it as a // number, the dimensionIndexValues come after the decimal point. for (int i = 0; i < MAX_NUMBER_OF_DIMENSIONS; ++i) { if (dcm1->dimensionIndexValues[i] < dcm2->dimensionIndexValues[i]) return -1; else if (dcm1->dimensionIndexValues[i] > dcm2->dimensionIndexValues[i]) return 1; } return retval; } //compareTDCMsort() int isSameFloatDouble(double a, double b) { //Kludge for bug in 0002,0016="DIGITAL_JACKET", 0008,0070="GE MEDICAL SYSTEMS" DICOM data: Orient field (0020:0037) can vary 0.00604261 == 0.00604273 !!! // return (a == b); //niave approach does not have any tolerance for rounding errors return (fabs(a - b) <= 0.0001); } struct TWarnings { //generate a warning only once per set bool manufacturerVaries, modalityVaries, derivedVaries, acqNumVaries, dimensionVaries, dateTimeVaries, studyUidVaries, echoVaries, triggerVaries, phaseVaries, coilVaries, forceStackSeries, seriesUidVaries, nameVaries, nameEmpty, orientVaries; }; TWarnings setWarnings() { TWarnings r; r.manufacturerVaries = false; r.modalityVaries = false; r.derivedVaries = false; r.acqNumVaries = false; r.dimensionVaries = false; r.dateTimeVaries = false; r.studyUidVaries = false; r.phaseVaries = false; r.echoVaries = false; r.triggerVaries = false; r.coilVaries = false; r.seriesUidVaries = false; r.forceStackSeries = false; r.nameVaries = false; r.nameEmpty = false; r.orientVaries = false; return r; } bool isSameSet(struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts *opts, struct TWarnings *warnings, bool *isMultiEcho, bool *isNonParallelSlices, bool *isCoilVaries) { //returns true if d1 and d2 should be stacked together as a single output if (!d1.isValid) return false; if (!d2.isValid) return false; if ((opts->isVerbose) && (d1.seriesNum == d2.seriesNum)) { //one would never want to combine in these situations: only raise warning for verbose modes to help troubleshooting if ((d1.manufacturer != d2.manufacturer) && (!warnings->manufacturerVaries)) { printMessage("Volumes not stacked: manufacturer varies.\n"); warnings->manufacturerVaries = true; } if ((d1.modality != d2.modality) && (!warnings->modalityVaries)) { printMessage("Volumes not stacked: modality varies.\n"); warnings->modalityVaries = true; } if ((d1.isDerived != d2.isDerived) && (!warnings->derivedVaries)) { printMessage("Volumes not stacked: derived varies.\n"); warnings->derivedVaries = true; } } if (d1.manufacturer != d2.manufacturer) return false; //do not stack data from different vendors if (d1.modality != d2.modality) return false; //do not stack MR and CT data! if (d1.isDerived != d2.isDerived) return false; //do not stack raw and derived image types bool isForceStackSeries = false; if ((opts->isForceStackDCE) && (d1.isStackableSeries) && (d2.isStackableSeries) && (d1.seriesNum != d2.seriesNum)) { if (!warnings->forceStackSeries) printMessage("Volumes stacked despite varying series number (use '-m o' to turn off merging).\n"); warnings->forceStackSeries = true; isForceStackSeries = true; } if ((d1.manufacturer == kMANUFACTURER_SIEMENS) && (strcmp(d1.protocolName, d2.protocolName) == 0) && (strlen(d1.softwareVersions) > 4) && (strlen(d1.sequenceName) > 4) && (strlen(d2.sequenceName) > 4)) { if (strstr(d1.sequenceName, "_ep_b") && strstr(d2.sequenceName, "_ep_b") && (strstr(d1.softwareVersions, "VB13") || strstr(d1.softwareVersions, "VB12"))) { //Siemens B12/B13 users with a "DWI" but not "DTI" license would ofter create multi-series acquisitions if (!warnings->forceStackSeries) printMessage("Diffusion images stacked despite varying series number (early Siemens DTI).\n"); warnings->forceStackSeries = true; isForceStackSeries = true; } } if (isForceStackSeries) ; else if ((d1.isXA10A) && (d2.isXA10A) && (d1.seriesNum > 1000) && (d2.seriesNum > 1000)) { //kludge XA10A (0020,0011) increments [16001, 16002, ...] https://github.com/rordenlab/dcm2niix/issues/236 //images from series 16001,16002 report different study times (0008,0030)! if ((d1.seriesNum / 1000) != (d2.seriesNum / 1000)) return false; } else if (d1.seriesNum != d2.seriesNum) return false; #ifdef mySegmentByAcq if (d1.acquNum != d2.acquNum) return false; #endif bool isSameStudyInstanceUID = false; if ((strlen(d1.studyInstanceUID) > 1) && (strlen(d2.studyInstanceUID) > 1)) { if (strcmp(d1.studyInstanceUID, d2.studyInstanceUID) == 0) isSameStudyInstanceUID = true; } bool isSameTime = isSameFloatDouble(d1.dateTime, d2.dateTime); if ((isSameStudyInstanceUID) && (d1.isXA10A) && (d2.isXA10A)) isSameTime = true; //kludge XA10A 0008,0030 incorrect https://github.com/rordenlab/dcm2niix/issues/236 bool isDimensionVaries = ((d1.xyzDim[1] != d2.xyzDim[1]) || (d1.xyzDim[2] != d2.xyzDim[2]) || (d1.xyzDim[3] != d2.xyzDim[3])); if ((!isSameStudyInstanceUID) && (!isSameTime)) { if (opts->isForceStackDCE) { if (!warnings->studyUidVaries) printMessage("Slices stacked despite Study Date/Time (0008,0020;0008,0030) and Study UID (0020,000E) variation %12.12f ~= %12.12f\n", d1.dateTime, d2.dateTime); warnings->studyUidVaries = true; } else { if (!warnings->studyUidVaries) printMessage("Slices not stacked: Study Date/Time (0008,0020;0008,0030) and Study UID (0020,000E) varies %12.12f ~= %12.12f\n", d1.dateTime, d2.dateTime); warnings->studyUidVaries = true; return false; } } if (isDimensionVaries) { if (!warnings->dimensionVaries) printMessage("Slices not stacked: dimensions vary across slices\n"); warnings->dimensionVaries = true; return false; } #ifndef myIgnoreStudyTime if (!isSameTime) { //beware, some vendors incorrectly store Image Time (0008,0033) as Study Time (0008,0030). if (!warnings->dateTimeVaries) printMessage("Slices not stacked: Study Date/Time (0008,0020;0008,0030) varies %12.12f ~= %12.12f\n", d1.dateTime, d2.dateTime); warnings->dateTimeVaries = true; return false; } #endif if ((opts->isForceStackSameSeries == 1) || ((opts->isForceStackSameSeries == 2) && (d1.isXRay))) { // "isForceStackSameSeries == 2" will automatically stack CT scans but not MR //if (((!(isSameFloat(d1.TE, d2.TE))) || (d1.echoNum != d2.echoNum)) && (!d1.isXRay)) { // *isMultiEcho = true; //} 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)) { if (!warnings->phaseVaries) printMessage("Slices not stacked: some are phase/real/imaginary/phase maps, others are not. Instances %d %d\n", d1.imageNum, d2.imageNum); warnings->phaseVaries = true; return false; } //if ((d1.TE != d2.TE) || (d1.echoNum != d2.echoNum)) { 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 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; } if ((d1.triggerDelayTime != d2.triggerDelayTime) && (d1.manufacturer == kMANUFACTURER_PHILIPS) && (d1.aslFlags == kASL_FLAG_NONE)) { //issue 384 if (!warnings->triggerVaries) printMessage("Slices not stacked: trigger time varies\n"); warnings->triggerVaries = true; return false; } if (d1.coilCrc != d2.coilCrc) { if (opts->isForceStackDCE) { if (!warnings->coilVaries) printMessage("Slices stacked despite coil variation '%s' vs '%s' (use '-m o' to turn off merging)\n", d1.coilName, d2.coilName); warnings->coilVaries = true; *isCoilVaries = true; } else { if (!warnings->coilVaries) printMessage("Slices not stacked: coil varies '%s' vs '%s'\n", d1.coilName, d2.coilName); warnings->coilVaries = true; *isCoilVaries = true; return false; } } if ((strlen(d1.protocolName) < 1) && (strlen(d2.protocolName) < 1)) { if (!warnings->nameEmpty) printWarning("Empty protocol name(s) (0018,1030)\n"); warnings->nameEmpty = true; } else if ((strcmp(d1.protocolName, d2.protocolName) != 0)) { if (!warnings->nameVaries) printMessage("Slices not stacked: protocol name varies '%s' != '%s'\n", d1.protocolName, d2.protocolName); warnings->nameVaries = true; return false; } if ((*isNonParallelSlices) && (d1.CSA.mosaicSlices > 1)) return false; //issue481 if ((!isSameFloatGE(d1.orient[1], d2.orient[1]) || !isSameFloatGE(d1.orient[2], d2.orient[2]) || !isSameFloatGE(d1.orient[3], d2.orient[3]) || !isSameFloatGE(d1.orient[4], d2.orient[4]) || !isSameFloatGE(d1.orient[5], d2.orient[5]) || !isSameFloatGE(d1.orient[6], d2.orient[6]))) { if ((!warnings->orientVaries) && (!d1.isNonParallelSlices) && (!d1.isLocalizer)) printMessage("Slices not stacked: orientation varies (vNav or localizer?) [%g %g %g %g %g %g] != [%g %g %g %g %g %g]\n", d1.orient[1], d1.orient[2], d1.orient[3], d1.orient[4], d1.orient[5], d1.orient[6], d2.orient[1], d2.orient[2], d2.orient[3], d2.orient[4], d2.orient[5], d2.orient[6]); warnings->orientVaries = true; *isNonParallelSlices = true; return false; } if (d1.acquNum != d2.acquNum) { if ((!warnings->acqNumVaries) && (opts->isVerbose)) //virtually always people want to stack these printMessage("Slices stacked despite varying acquisition numbers (if this is not desired recompile with 'mySegmentByAcq')\n"); warnings->acqNumVaries = true; } if ((!isForceStackSeries) && (d1.seriesUidCrc != d2.seriesUidCrc)) { if (!warnings->seriesUidVaries) printMessage("Slices not stacked: series instance UID varies (duplicates all other properties)\n"); warnings->seriesUidVaries = true; return false; } return true; } // isSameSet() void freeNameList(struct TSearchList nameList) { if (nameList.numItems > 0) { unsigned long n = nameList.numItems; if (n > nameList.maxItems) n = nameList.maxItems; //assigned if (nameList->numItems < nameList->maxItems) for (unsigned long i = 0; i < n; i++) free(nameList.str[i]); } free(nameList.str); } int singleDICOM(struct TDCMopts *opts, char *fname) { if (isDICOMfile(fname) == 0) { printError("Not a DICOM image : %s\n", fname); return 0; } struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc(sizeof(struct TDICOMdata)); struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); struct TSearchList nameList; struct TDCMprefs prefs; opts2Prefs(opts, &prefs); nameList.maxItems = 1; // larger requires more memory, smaller more passes nameList.str = (char **)malloc((nameList.maxItems + 1) * sizeof(char *)); //reserve one pointer (32 or 64 bits) per potential file nameList.numItems = 0; nameList.str[nameList.numItems] = (char *)malloc(strlen(fname) + 1); strcpy(nameList.str[nameList.numItems], fname); nameList.numItems++; TDCMsort *dcmSort = (TDCMsort *)malloc(sizeof(TDCMsort)); dcmList[0].converted2NII = 1; dcmList[0] = readDICOMx(nameList.str[0], &prefs, dti4D); //ignore compile warning - memory only freed on first of 2 passes //dcmList[0] = readDICOMv(nameList.str[0], opts->isVerbose, opts->compressFlag, dti4D); //ignore compile warning - memory only freed on first of 2 passes fillTDCMsort(dcmSort[0], 0, dcmList[0]); int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, dti4D); freeNameList(nameList); free(dti4D); free(dcmSort); free(dcmList); return ret; } // singleDICOM() size_t fileBytes(const char *fname) { FILE *fp = fopen(fname, "rb"); if (!fp) return 0; fseek(fp, 0, SEEK_END); size_t fileLen = ftell(fp); fclose(fp); return fileLen; } //fileBytes() int searchDirForDICOM(char *path, struct TSearchList *nameList, int maxDepth, int depth, struct TDCMopts *opts) { int ret = kEXIT_NOMINAL; tinydir_dir dir; tinydir_open(&dir, path); while (dir.has_next) { tinydir_file file; file.is_dir = 0; //avoids compiler warning: this is set by tinydir_readfile tinydir_readfile(&dir, &file); //printMessage("%s\n", file.name); char filename[768] = ""; strcat(filename, path); strcat(filename, kFileSep); strcat(filename, file.name); if ((file.is_dir) && (depth < maxDepth) && (file.name[0] != '.')) { int tmp = searchDirForDICOM(filename, nameList, maxDepth, depth + 1, opts); if (tmp != kEXIT_NOMINAL) ret = tmp; //e.g. found ecat } else if (!file.is_reg) //ignore files "." and ".." ; else if ((strlen(file.name) < 1) || (file.name[0] == '.')) ; //printMessage("skipping hidden file %s\n", file.name); else if ((strlen(file.name) == 8) && (strcicmp(file.name, "DICOMDIR") == 0)) ; //printMessage("skipping DICOMDIR\n"); else if ((isDICOMfile(filename) > 0) || (isExt(filename, ".par"))) { if (nameList->numItems < nameList->maxItems) { nameList->str[nameList->numItems] = (char *)malloc(strlen(filename) + 1); strcpy(nameList->str[nameList->numItems], filename); } nameList->numItems++; //printMessage("dcm %lu %s \n",nameList->numItems, filename); #ifndef USING_R } else { if (fileBytes(filename) > 2048) { int tmp = convert_foreign(filename, *opts); if (tmp == EXIT_SUCCESS) ret = tmp; //e.g. found ecat } #ifdef MY_DEBUG printMessage("Not a dicom:\t%s\n", filename); #endif #endif } tinydir_next(&dir); } tinydir_close(&dir); return ret; } // searchDirForDICOM() int removeDuplicates(int nConvert, struct TDCMsort dcmSort[]) { //done AFTER sorting, so duplicates will be sequential if (nConvert < 2) return nConvert; int nDuplicates = 0; for (int i = 1; i < nConvert; i++) { if (compareTDCMsort(&dcmSort[i], &dcmSort[i - 1]) == 0) { nDuplicates++; } else { dcmSort[i - nDuplicates].img = dcmSort[i].img; dcmSort[i - nDuplicates].indx = dcmSort[i].indx; for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; ++j) dcmSort[i - nDuplicates].dimensionIndexValues[j] = dcmSort[i].dimensionIndexValues[j]; } } if (nDuplicates > 0) printMessage("%d images have identical time, series, acquisition and instance values. DUPLICATES REMOVED.\n", nDuplicates); return nConvert - nDuplicates; } // removeDuplicates() int removeDuplicatesVerbose(int nConvert, struct TDCMsort dcmSort[], struct TSearchList *nameList) { //done AFTER sorting, so duplicates will be sequential if (nConvert < 2) return nConvert; int nDuplicates = 0; for (int i = 1; i < nConvert; i++) { if (compareTDCMsort(&dcmSort[i], &dcmSort[i - 1]) == 0) { printMessage("\t%s\t=\t%s\n", nameList->str[dcmSort[i - 1].indx], nameList->str[dcmSort[i].indx]); nDuplicates++; } else { dcmSort[i - nDuplicates].img = dcmSort[i].img; dcmSort[i - nDuplicates].indx = dcmSort[i].indx; for (int j = 0; j < MAX_NUMBER_OF_DIMENSIONS; ++j) dcmSort[i - nDuplicates].dimensionIndexValues[j] = dcmSort[i].dimensionIndexValues[j]; } } if (nDuplicates > 0) printMessage("%d images have identical time, series, acquisition and instance values. Duplicates removed.\n", nDuplicates); return nConvert - nDuplicates; } // removeDuplicatesVerbose() int convert_parRec(char *fnm, struct TDCMopts opts) { //sample dataset from Ed Gronenschild struct TSearchList nameList; int ret = EXIT_FAILURE; nameList.numItems = 1; nameList.maxItems = 1; nameList.str = (char **)malloc((nameList.maxItems + 1) * sizeof(char *)); //we reserve one pointer (32 or 64 bits) per potential file struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc(nameList.numItems * sizeof(struct TDICOMdata)); nameList.str[0] = (char *)malloc(strlen(fnm) + 1); strcpy(nameList.str[0], fnm); //nameList.str[0] = (char *)malloc(strlen(opts.indir)+1); //strcpy(nameList.str[0],opts.indir); struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); dcmList[0] = nii_readParRec(nameList.str[0], opts.isVerbose, dti4D, false); struct TDCMsort dcmSort[1]; dcmSort[0].indx = 0; if (dcmList[0].isValid) ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, opts, dti4D); free(dti4D); free(dcmList); //if (nConvertTotal == 0) if (nameList.numItems < 1) printMessage("No valid PAR/REC files were found\n"); freeNameList(nameList); return ret; } // convert_parRec() int copyFile(char *src_path, char *dst_path) { #define BUFFSIZE 32768 unsigned char buffer[BUFFSIZE]; FILE *fin = fopen(src_path, "rb"); if (fin == NULL) { printError("Check file permissions: Unable to open input %s\n", src_path); return EXIT_SUCCESS; } if (is_fileexists(dst_path)) { if (true) { printWarning("Naming conflict (duplicates?): '%s' '%s'\n", src_path, dst_path); return EXIT_SUCCESS; } else { printError("File naming conflict. Existing file %s\n", dst_path); return EXIT_FAILURE; } } FILE *fou = fopen(dst_path, "wb"); if (fou == NULL) { printError("Check file permission. Unable to open output %s\n", dst_path); return EXIT_FAILURE; } size_t bytes; while ((bytes = fread(buffer, 1, BUFFSIZE, fin)) != 0) { if (fwrite(buffer, 1, bytes, fou) != bytes) { printError("Unable to write %zu bytes to output %s\n", bytes, dst_path); return EXIT_FAILURE; } } fclose(fin); fclose(fou); return EXIT_SUCCESS; } #ifdef USING_R // This implementation differs enough from the mainline one to be separated int searchDirRenameDICOM(char *path, int maxDepth, int depth, struct TDCMopts *opts) { // The tinydir_open_sorted function reads the whole directory at once, // which is necessary in this context since we may be creating new // files in the same directory, which we don't want to further examine tinydir_dir dir; int count = 0; if (tinydir_open_sorted(&dir, path) != 0) return -1; for (size_t i = 0; i < dir.n_files; i++) { // If this directory entry is a subdirectory, search it recursively tinydir_file &file = dir._files[i]; const std::string sourcePath = std::string(path) + kFileSep + file.name; char *sourcePathPtr = const_cast(sourcePath.c_str()); if ((file.is_dir) && (depth < maxDepth) && (file.name[0] != '.')) { const int subdirectoryCount = searchDirRenameDICOM(sourcePathPtr, maxDepth, depth + 1, opts); if (subdirectoryCount < 0) { tinydir_close(&dir); return -1; } count += subdirectoryCount; } else if (file.is_reg && strlen(file.name) > 0 && file.name[0] != '.' && strcicmp(file.name, "DICOMDIR") != 0 && isDICOMfile(sourcePathPtr)) { TDICOMdata dcm = readDICOM(sourcePathPtr); if (dcm.imageNum > 0) { if ((opts->isIgnoreDerivedAnd2D) && ((dcm.isLocalizer) || (strcmp(dcm.sequenceName, "_tfl2d1") == 0) || (strcmp(dcm.sequenceName, "_fl3d1_ns") == 0) || (strcmp(dcm.sequenceName, "_fl2d1") == 0))) { printMessage("Ignoring localizer %s\n", sourcePathPtr); opts->ignoredPaths.push_back(sourcePath); } else if ((opts->isIgnoreDerivedAnd2D && dcm.isDerived)) { printMessage("Ignoring derived %s\n", sourcePathPtr); opts->ignoredPaths.push_back(sourcePath); } else { // Create an initial file name char outname[PATH_MAX] = {""}; if (dcm.echoNum > 1) dcm.isMultiEcho = true; nii_createFilename(dcm, outname, *opts); // If the file name part of the target path has no extension, add ".dcm" std::string targetPath(outname); std::string targetStem, targetExtension; const size_t periodLoc = targetPath.find_last_of('.'); if (periodLoc == targetPath.length() - 1) { targetStem = targetPath.substr(0, targetPath.length() - 1); targetExtension = ".dcm"; } else if (periodLoc == std::string::npos || periodLoc < targetPath.find_last_of("\\/")) { targetStem = targetPath; targetExtension = ".dcm"; } else { targetStem = targetPath.substr(0, periodLoc); targetExtension = targetPath.substr(periodLoc); } // Deduplicate the target path to avoid overwriting existing files targetPath = targetStem + targetExtension; GetRNGstate(); while (is_fileexists(targetPath.c_str())) { std::ostringstream suffix; unsigned suffixValue = static_cast(round(R::unif_rand() * (R_pow_di(2.0, 24) - 1.0))); suffix << std::hex << std::setfill('0') << std::setw(6) << suffixValue; targetPath = targetStem + "_" + suffix.str() + targetExtension; } PutRNGstate(); // Copy the file, unless the source and target paths are the same if (targetPath.compare(sourcePath) == 0) { if (opts->isVerbose > 1) printMessage("Skipping %s, which would be copied onto itself\n", sourcePathPtr); } else if (copyFile(sourcePathPtr, const_cast(targetPath.c_str())) == EXIT_SUCCESS) { opts->sourcePaths.push_back(sourcePath); opts->targetPaths.push_back(targetPath); count++; if (opts->isVerbose > 0) printMessage("Copying %s -> %s\n", sourcePathPtr, targetPath.c_str()); } else { printWarning("Unable to copy to path %s\n", targetPath.c_str()); } } } } } return count; } #else int searchDirRenameDICOM(char *path, int maxDepth, int depth, struct TDCMopts *opts) { int retAll = 0; tinydir_dir dir; if (tinydir_open_sorted(&dir, path) != 0) { if (opts->isVerbose > 0) printMessage("Unable to open %s\n", path); return -1; } if (dir.n_files < 1) { if (opts->isVerbose > 0) printMessage("No files in %s\n", path); return 0; } if (opts->isVerbose > 0) printMessage("Found %zu items in %s\n", dir.n_files, path); //%lu -> %zu for (size_t i = 0; i < dir.n_files; i++) { // If this directory entry is a subdirectory, search it recursively tinydir_file &file = dir._files[i]; char filename[768] = ""; strcat(filename, path); strcat(filename, kFileSep); strcat(filename, file.name); if ((file.is_dir) && (depth < maxDepth) && (file.name[0] != '.')) { int retSub = searchDirRenameDICOM(filename, maxDepth, depth + 1, opts); if (retSub < 0) return retSub; retAll += retSub; } else if (!file.is_reg) //ignore files "." and ".." ; else if ((strlen(file.name) < 1) || (file.name[0] == '.')) ; //printMessage("skipping hidden file %s\n", file.name); else if ((strlen(file.name) == 8) && (strcicmp(file.name, "DICOMDIR") == 0)) ; //printMessage("skipping DICOMDIR\n"); else if (isDICOMfile(filename) > 0) { //printMessage("dcm %s \n", filename); struct TDICOMdata dcm = readDICOM(filename); //ignore compile warning - memory only freed on first of 2 passes //~ if ((dcm.isValid) &&((dcm.totalSlicesIn4DOrder != NULL) ||(dcm.patientPositionNumPhilips > 1) || (dcm.CSA.numDti > 1))) { //4D dataset: dti4D arrays require huge amounts of RAM - write this immediately if (dcm.imageNum > 0) { //use imageNum instead of isValid to convert non-images (kWaveformSq will have instance number but is not a valid image) if ((opts->isIgnoreDerivedAnd2D) && ((dcm.isLocalizer) || (strcmp(dcm.sequenceName, "_tfl2d1") == 0) || (strcmp(dcm.sequenceName, "_fl3d1_ns") == 0) || (strcmp(dcm.sequenceName, "_fl2d1") == 0))) { printMessage("Ignoring localizer %s\n", filename); } else if ((opts->isIgnoreDerivedAnd2D && dcm.isDerived)) { printMessage("Ignoring derived %s\n", filename); } else { char outname[PATH_MAX] = {""}; if (dcm.echoNum > 1) dcm.isMultiEcho = true; //last resort: Siemens gives different echoes the same image number: avoid overwriting, e.g "-f %r.dcm" should generate "1.dcm", "1_e2.dcm" for multi-echo volumes nii_createFilename(dcm, outname, *opts); //if (isDcmExt) strcat (outname,".dcm"); int ret = copyFile(filename, outname); if (ret != EXIT_SUCCESS) { printError("Unable to rename all DICOM images.\n"); return -1; } retAll += 1; if (opts->isVerbose > 0) printMessage("Renaming %s -> %s\n", filename, outname); } } } tinydir_next(&dir); } tinydir_close(&dir); return retAll; } // searchDirForDICOM() #endif // USING_R //Timing #define myTimer //"BubbleSort" method uses nested "for i = 0..nDCM; for j = i+1..nDCM" // the alternative is to quick-sort based on seriesUID and only test for matches in buckets where seriesUID matches // the advantage of the bubble sort method is that it has been used extensively // the quick sort method should be faster when handling thousands of files. // difference very small for typical datasets (~0.1s for 3200 DICOMs) //#define myBubbleSort #ifndef myBubbleSort struct TCRCsort { uint64_t indx; uint32_t crc; }; void fillTCRCsort(struct TCRCsort &tcrcref, const uint64_t indx, const uint32_t crc) { tcrcref.indx = indx; tcrcref.crc = crc; } int compareTCRCsort(void const *item1, void const *item2) { //for quicksort http://blog.ablepear.com/2011/11/objective-c-tuesdays-sorting-arrays.html struct TCRCsort const *dcm1 = (const struct TCRCsort *)item1; struct TCRCsort const *dcm2 = (const struct TCRCsort *)item2; if (dcm1->crc < dcm2->crc) return -1; else if (dcm1->crc > dcm2->crc) return 1; return 0; //tie } #endif #ifdef myTimer int reportProgress(int progressPct, float frac) { int newProgressPct = round(100.0 * frac); const int kMinPct = 5; //e.g. if 10 then report 0.1, 0.2, 0.3... newProgressPct = (newProgressPct / kMinPct) * kMinPct; //if MinPct is 5 and we are 87 percent done report 85% if (newProgressPct == progressPct) return progressPct; if (newProgressPct != progressPct) //only report for change printProgress((float)newProgressPct / 100.0); return newProgressPct; } #endif int nii_loadDirCore(char *indir, struct TDCMopts *opts) { struct TSearchList nameList; int nConvertTotal = 0; #if defined(_WIN64) || defined(_WIN32) || defined(USING_R) nameList.maxItems = 24000; // larger requires more memory, smaller more passes #else //UNIX, not R nameList.maxItems = 96000; // larger requires more memory, smaller more passes #endif //progress variables const float kStage1Frac = 0.05; //e.g. finding files requires ~05pct const float kStage2Frac = 0.45; //e.g. reading headers and converting 4D files requires ~45pct const float kStage3Frac = 0.50; //e.g. converting 2D/3D files to 3D/4D files requires ~50pct int progressPct = 0; //proportion correct, 0..100 if (opts->isProgress) progressPct = reportProgress(-1, 0.0); //report 0% #ifdef myTimer clock_t start = clock(); #endif if ((is_fileNotDir(opts->indir)) && isExt(opts->indir, ".txt")) { nameList.str = (char **)malloc((nameList.maxItems + 1) * sizeof(char *)); //reserve one pointer (32 or 64 bits) per potential file nameList.numItems = 0; FILE *fp = fopen(opts->indir, "r"); //textDICOM if (fp == NULL) return EXIT_FAILURE; char dcmname[2048]; while (fgets(dcmname, sizeof(dcmname), fp)) { int sz = (int)strlen(dcmname); if (sz > 0 && dcmname[sz - 1] == '\n') dcmname[sz - 1] = 0; //Unix LF if (sz > 1 && dcmname[sz - 2] == '\r') dcmname[sz - 2] = 0; //Windows CR/LF if ((!is_fileexists(dcmname)) || (!is_fileNotDir(dcmname))) { //<-this will accept meta data fclose(fp); printError("Problem with file '%s'\n", dcmname); return EXIT_FAILURE; } if (nameList.numItems < nameList.maxItems) { nameList.str[nameList.numItems] = (char *)malloc(strlen(dcmname) + 1); strcpy(nameList.str[nameList.numItems], dcmname); } nameList.numItems++; } fclose(fp); if (nameList.numItems >= nameList.maxItems) { printError("Too many file names in '%s'\n", opts->indir); return EXIT_FAILURE; } if (nameList.numItems < 1) return kEXIT_NO_VALID_FILES_FOUND; printMessage("Found %lu files in '%s'\n", nameList.numItems, opts->indir); } else { //1: find filenames of dicom files: up to two passes if we found more files than we allocated memory for (int i = 0; i < 2; i++) { nameList.str = (char **)malloc((nameList.maxItems + 1) * sizeof(char *)); //reserve one pointer (32 or 64 bits) per potential file nameList.numItems = 0; int ret = searchDirForDICOM(indir, &nameList, opts->dirSearchDepth, 0, opts); if (ret == EXIT_SUCCESS) //e.g. converted ECAT nConvertTotal++; if (nameList.numItems <= nameList.maxItems) break; freeNameList(nameList); nameList.maxItems = nameList.numItems + 1; //printMessage("Second pass required, found %ld images\n", nameList.numItems); } if (nameList.numItems < 1) { if ((opts->dirSearchDepth > 0) && (nConvertTotal < 1)) printError("Unable to find any DICOM images in %s (or subfolders %d deep)\n", indir, opts->dirSearchDepth); else //keep silent for dirSearchDepth = 0 - presumably searching multiple folders { }; free(nameList.str); //ignore compile warning - memory only freed on first of 2 passes if (nConvertTotal > 0) return EXIT_SUCCESS; //e.g. converted ECAT return kEXIT_NO_VALID_FILES_FOUND; } } size_t nDcm = nameList.numItems; printMessage("Found %lu DICOM file(s)\n", nameList.numItems); //includes images and other non-image DICOMs #ifdef myTimer if (opts->isProgress > 1) printMessage("Stage 1 (Count number of DICOMs) required %f seconds.\n", ((float)(clock() - start)) / CLOCKS_PER_SEC); start = clock(); #endif if (opts->isProgress) progressPct = reportProgress(progressPct, kStage1Frac); //proportion correct, 0..100 // struct TDICOMdata dcmList [nameList.numItems]; //<- this exhausts the stack for large arrays struct TDICOMdata *dcmList = (struct TDICOMdata *)malloc(nameList.numItems * sizeof(struct TDICOMdata)); struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); struct TDCMprefs prefs; opts2Prefs(opts, &prefs); bool compressionWarning = false; bool convertError = false; bool isDcmExt = isExt(opts->filename, ".dcm"); // "%r.dcm" with multi-echo should generate "1.dcm", "1e2.dcm" if (isDcmExt) opts->filename[strlen(opts->filename) - 4] = 0; // "%s_%r.dcm" -> "%s_%r" //consider OpenMP // g++-9 -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 -fopenmp for (int i = 0; i < (int)nDcm; i++) { if ((isExt(nameList.str[i], ".par")) && (isDICOMfile(nameList.str[i]) < 1)) { //strcpy(opts->indir, nameList.str[i]); //set to original file name, not path dcmList[i].converted2NII = 1; int ret = convert_parRec(nameList.str[i], *opts); if (ret == EXIT_SUCCESS) nConvertTotal++; else convertError = true; continue; } dcmList[i] = readDICOMx(nameList.str[i], &prefs, dti4D); //ignore compile warning - memory only freed on first of 2 passes //dcmList[i] = readDICOMv(nameList.str[i], opts->isVerbose, opts->compressFlag, dti4D); //ignore compile warning - memory only freed on first of 2 passes if (opts->isIgnoreSeriesInstanceUID) dcmList[i].seriesUidCrc = dcmList[i].seriesNum; //if (!dcmList[i].isValid) printf(">>>>Not a valid DICOM %s\n", nameList.str[i]); if ((dcmList[i].isValid) && ((dti4D->sliceOrder[0] >= 0) || (dcmList[i].CSA.numDti > 1))) { //4D dataset: dti4D arrays require huge amounts of RAM - write this immediately struct TDCMsort dcmSort[1]; fillTDCMsort(dcmSort[0], i, dcmList[i]); dcmList[i].converted2NII = 1; int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, dti4D); if (ret == EXIT_SUCCESS) nConvertTotal++; else convertError = true; } if ((dcmList[i].compressionScheme != kCompressNone) && (!compressionWarning) && (opts->compressFlag != kCompressNone)) { compressionWarning = true; //generate once per conversion rather than once per image printMessage("Image Decompression is new: please validate conversions\n"); } if (opts->isProgress) progressPct = reportProgress(progressPct, kStage1Frac + (kStage2Frac * (float)i / (float)nDcm)); //proportion correct, 0..100 } #ifdef myTimer if (opts->isProgress > 1) printMessage("Stage 2 (Read DICOM headers, Convert 4D) required %f seconds.\n", ((float)(clock() - start)) / CLOCKS_PER_SEC); start = clock(); #endif if (opts->isRenameNotConvert) { free(dcmList); free(dti4D); return EXIT_SUCCESS; } #ifdef USING_R if (opts->isScanOnly) { TWarnings warnings = setWarnings(); // Create the first series from the first DICOM file TDicomSeries firstSeries; char firstSeriesName[2048] = ""; nii_createFilename(dcmList[0], firstSeriesName, *opts); firstSeries.name = firstSeriesName; firstSeries.representativeData = dcmList[0]; firstSeries.files.push_back(nameList.str[0]); opts->series.push_back(firstSeries); // Iterate over the remaining files for (size_t i = 1; i < nDcm; i++) { bool matched = false; // If the file matches an existing series, add it to the corresponding file list for (int j = 0; j < opts->series.size(); j++) { bool isMultiEcho = false, isNonParallelSlices = false, isCoilVaries = false; if (isSameSet(opts->series[j].representativeData, dcmList[i], opts, &warnings, &isMultiEcho, &isNonParallelSlices, &isCoilVaries)) { opts->series[j].files.push_back(nameList.str[i]); matched = true; break; } } // If not, create a new series object if (!matched) { TDicomSeries nextSeries; char nextSeriesName[2048] = ""; nii_createFilename(dcmList[i], nextSeriesName, *opts); nextSeries.name = nextSeriesName; nextSeries.representativeData = dcmList[i]; nextSeries.files.push_back(nameList.str[i]); opts->series.push_back(nextSeries); } } // To avoid a spurious warning below nConvertTotal = nDcm; } else { #endif #ifdef myBubbleSort //3: stack DICOMs with the same Series struct TWarnings warnings = setWarnings(); for (int i = 0; i < (int)nDcm; i++) { if ((dcmList[i].converted2NII == 0) && (dcmList[i].isValid)) { int nConvert = 0; bool isMultiEcho = false; bool isNonParallelSlices = false; bool isCoilVaries = false; for (int j = i; j < (int)nDcm; j++) if (isSameSet(dcmList[i], dcmList[j], opts, &warnings, &isMultiEcho, &isNonParallelSlices, &isCoilVaries)) nConvert++; if (nConvert < 1) nConvert = 1; //prevents compiler warning for next line: never executed since j=i always causes nConvert ++ TDCMsort *dcmSort = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); nConvert = 0; for (int j = i; j < (int)nDcm; j++) { isMultiEcho = false; isCoilVaries = false; isNonParallelSlices = false; if (isSameSet(dcmList[i], dcmList[j], opts, &warnings, &isMultiEcho, &isNonParallelSlices, &isCoilVaries)) { dcmList[j].converted2NII = 1; //do not reprocess repeats fillTDCMsort(dcmSort[nConvert], j, dcmList[j]); nConvert++; } else { if (isNonParallelSlices) { dcmList[i].isNonParallelSlices = true; dcmList[j].isNonParallelSlices = true; } if (isMultiEcho) { dcmList[i].isMultiEcho = true; dcmList[j].isMultiEcho = true; } if (isCoilVaries) { dcmList[i].isCoilVaries = true; dcmList[j].isCoilVaries = true; } } //unable to stack images: mark files that may need file name dis-ambiguation } qsort(dcmSort, nConvert, sizeof(struct TDCMsort), compareTDCMsort); //sort based on series and image numbers.... //dcmList[dcmSort[0].indx].isMultiEcho = isMultiEcho; if (opts->isVerbose) nConvert = removeDuplicatesVerbose(nConvert, dcmSort, &nameList); else //nConvert = removeDuplicatesVerbose(nConvert, dcmSort, &nameList); nConvert = removeDuplicates(nConvert, dcmSort); int ret = saveDcm2Nii(nConvert, dcmSort, dcmList, &nameList, *opts, dti4D); if (ret == EXIT_SUCCESS) nConvertTotal += nConvert; else convertError = true; free(dcmSort); } //convert all images of this series } #else //avoid bubble sort - dont check all images for match, only those with identical series instance UID //3: stack DICOMs with the same Series struct TWarnings warnings = setWarnings(); //sort by series instance UID ... avoids bubble-sort penalty TCRCsort *crcSort = (TCRCsort *)malloc(nDcm * sizeof(TCRCsort)); for (int i = 0; i < (int)nDcm; i++) fillTCRCsort(crcSort[i], i, dcmList[i].seriesUidCrc); qsort(crcSort, nDcm, sizeof(struct TCRCsort), compareTCRCsort); //sort based on series and image numbers.... int *convertIdxs = (int *)malloc(sizeof(int) * (nDcm)); for (int i = 0; i < (int)nDcm; i++) { int ii = crcSort[i].indx; if (dcmList[ii].converted2NII) continue; if (!dcmList[ii].isValid) continue; int nConvert = 0; bool isMultiEcho = false; bool isNonParallelSlices = false; bool isCoilVaries = false; int jMax = i; for (int j = i; j < (int)nDcm; j++) { int ji = crcSort[j].indx; if (dcmList[ii].seriesUidCrc != dcmList[ji].seriesUidCrc) break; //seriesUID no longer matches no need to examine any subsequent images jMax = j; if (isSameSet(dcmList[ii], dcmList[ji], opts, &warnings, &isMultiEcho, &isNonParallelSlices, &isCoilVaries)) { dcmList[ji].converted2NII = 1; //do not reprocess repeats convertIdxs[nConvert] = ji; nConvert++; } } //for all images with same seriesUID as first one if ((isNonParallelSlices) && (dcmList[ii].CSA.mosaicSlices > 1) && (nConvert > 0)) { //issue481: if ANY volumes are non-parallel, save ALL as 3D printWarning("Saving mosaics with non-parallel slices as 3D (issue 481)\n"); for (int j = i; j < (int)nDcm; j++) { int ji = crcSort[j].indx; if (dcmList[ii].seriesUidCrc != dcmList[ji].seriesUidCrc) break; dcmList[ji].converted2NII = 1; dcmList[ji].isNonParallelSlices = true; if (isMultiEcho) dcmList[ji].isMultiEcho = true; if (isCoilVaries) dcmList[ji].isCoilVaries = true; struct TDCMsort dcmSort[1]; fillTDCMsort(dcmSort[0], ji, dcmList[ji]); int ret = saveDcm2Nii(1, dcmSort, dcmList, &nameList, *opts, dti4D); if (ret == EXIT_SUCCESS) nConvertTotal++; else convertError = true; } continue; } //issue481 //issue 381: ensure all images are informed if there are variations in echo, parallel slices, coil name: if (isMultiEcho) for (int j = i; j <= jMax; j++) { int ji = crcSort[j].indx; dcmList[ji].isMultiEcho = true; } if (isNonParallelSlices) for (int j = i; j <= jMax; j++) { int ji = crcSort[j].indx; dcmList[ji].isNonParallelSlices = true; } if (isCoilVaries) for (int j = i; j <= jMax; j++) { int ji = crcSort[j].indx; dcmList[ji].isCoilVaries = true; } TDCMsort *dcmSort = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); for (int j = 0; j < nConvert; j++) fillTDCMsort(dcmSort[j], convertIdxs[j], dcmList[convertIdxs[j]]); qsort(dcmSort, nConvert, sizeof(struct TDCMsort), compareTDCMsort); //sort based on series and image numbers.... if (opts->isVerbose) nConvert = removeDuplicatesVerbose(nConvert, dcmSort, &nameList); else nConvert = removeDuplicates(nConvert, dcmSort); int ret = saveDcm2Nii(nConvert, dcmSort, dcmList, &nameList, *opts, dti4D); if (ret == EXIT_SUCCESS) nConvertTotal += nConvert; else convertError = true; free(dcmSort); if (opts->isProgress) progressPct = reportProgress(progressPct, kStage1Frac + kStage2Frac + (kStage3Frac * (float)nConvertTotal / (float)nDcm)); //proportion correct, 0..100 } free(convertIdxs); free(crcSort); #endif #ifdef USING_R } #endif #ifdef myTimer if (opts->isProgress > 1) printMessage("Stage 3 (Convert 2D and 3D images) required %f seconds.\n", ((float)(clock() - start)) / CLOCKS_PER_SEC); #endif if (opts->isProgress) progressPct = reportProgress(progressPct, 1); //proportion correct, 0..100 free(dcmList); free(dti4D); freeNameList(nameList); if (convertError) { if (nConvertTotal == 0) return EXIT_FAILURE; //nothing converted printError("Converted %d of %zu files\n", nConvertTotal, nDcm); //printError("Converted %d of %lu files\n", nConvertTotal, nDcm); return kEXIT_SOME_OK_SOME_BAD; //partial failure } if (nConvertTotal == 0) { printMessage("No valid DICOM images were found\n"); //we may have found valid DICOM files but they are not DICOM images return kEXIT_NO_VALID_FILES_FOUND; } return EXIT_SUCCESS; } //nii_loadDirCore() int nii_loadDirOneDirAtATime(char *path, struct TDCMopts *opts, int maxDepth, int depth) { //return kEXIT_NO_VALID_FILES_FOUND if no files in ANY sub folders //return EXIT_FAILURE if ANY failure //return EXIT_SUCCESS if no failures and at least one image converted int ret = nii_loadDirCore(path, opts); if (ret == EXIT_FAILURE) return ret; tinydir_dir dir; tinydir_open(&dir, path); while (dir.has_next) { tinydir_file file; file.is_dir = 0; //avoids compiler warning: this is set by tinydir_readfile tinydir_readfile(&dir, &file); char filename[768] = ""; strcat(filename, path); strcat(filename, kFileSep); strcat(filename, file.name); if ((file.is_dir) && (depth < maxDepth) && (file.name[0] != '.')) { int retSub = nii_loadDirOneDirAtATime(filename, opts, maxDepth, depth + 1); if (retSub == EXIT_FAILURE) return retSub; if (retSub == EXIT_SUCCESS) ret = retSub; } tinydir_next(&dir); } tinydir_close(&dir); return ret; } int nii_loadDir(struct TDCMopts *opts) { //Identifies all the DICOM files in a folder and its subfolders if (strlen(opts->indir) < 1) { printMessage("No input\n"); return EXIT_FAILURE; } char indir[512]; strcpy(indir, opts->indir); bool isFile = is_fileNotDir(indir); if (isFile) //if user passes ~/dicom/mr1.dcm we will look at all files in ~/dicom dropFilenameFromPath(opts->indir); dropTrailingFileSep(opts->indir); if (!is_dir(opts->indir, true)) { printError("Input folder invalid: %s\n", opts->indir); return kEXIT_INPUT_FOLDER_INVALID; } #ifdef USING_R // Full file paths are only used by R/divest when reorganising DICOM files if (opts->isRenameNotConvert) { #endif if (strlen(opts->outdir) < 1) { strcpy(opts->outdir, opts->indir); } else dropTrailingFileSep(opts->outdir); if (!is_dir(opts->outdir, true)) { #ifdef myUseInDirIfOutDirUnavailable printWarning("Output folder invalid %s will try %s\n", opts->outdir, opts->indir); strcpy(opts->outdir, opts->indir); #else printError("Output folder invalid: %s\n", opts->outdir); return kEXIT_OUTPUT_FOLDER_INVALID; #endif } //check file permissions if ((opts->isCreateBIDS != false) || (opts->isOnlyBIDS != true)) { //output files expected: either BIDS or images int w = access(opts->outdir, W_OK); if (w != 0) { #ifdef USE_CWD_IF_OUTDIR_NO_WRITE char outdir[512]; strcpy(outdir, opts->outdir); strcpy(opts->outdir, opts->indir); w = access(opts->outdir, W_OK); if (w != 0) { printError("Unable to write to output folder: %s\n", outdir); return kEXIT_OUTPUT_FOLDER_READ_ONLY; } else printWarning("Writing to working directory, unable to write to output folder: %s\n", outdir); #else printError("Unable to write to output folder: %s\n", opts->outdir); return kEXIT_OUTPUT_FOLDER_READ_ONLY; #endif } } #ifdef USING_R } #endif getFileNameX(opts->indirParent, opts->indir, 512); #ifndef USING_R if (isFile && ((isExt(indir, ".v")))) return convert_foreign(indir, *opts); #endif if (isFile && ((isExt(indir, ".par")) || (isExt(indir, ".rec")))) { char pname[512], rname[512]; strcpy(pname, indir); strcpy(rname, indir); changeExt(pname, "PAR"); changeExt(rname, "REC"); #ifndef _MSC_VER //Linux is case sensitive, #include if (access(rname, F_OK) != 0) changeExt(rname, "rec"); if (access(pname, F_OK) != 0) changeExt(pname, "par"); #endif if (is_fileNotDir(rname) && is_fileNotDir(pname)) { //strcpy(opts->indir, pname); //set to original file name, not path return convert_parRec(pname, *opts); }; } if (isFile && (opts->isOnlySingleFile) && isExt(indir, ".txt")) { strcpy(opts->indir, indir); return nii_loadDirCore(opts->indir, opts); } if (opts->isRenameNotConvert) { int nConvert = searchDirRenameDICOM(opts->indir, opts->dirSearchDepth, 0, opts); if (nConvert < 0) return kEXIT_RENAME_ERROR; #ifdef USING_R printMessage("Renamed %d DICOMs\n", nConvert); #else printMessage("Converted %d DICOMs\n", nConvert); #endif return EXIT_SUCCESS; } if ((isFile) && (opts->isOnlySingleFile)) return singleDICOM(opts, indir); if (opts->isOneDirAtATime) { int maxDepth = opts->dirSearchDepth; opts->dirSearchDepth = 0; strcpy(indir, opts->indir); return nii_loadDirOneDirAtATime(indir, opts, maxDepth, 0); } else return nii_loadDirCore(opts->indir, opts); } // nii_loadDir() #if defined(_WIN64) || defined(_WIN32) || defined(USING_R) #else //UNIX, not R int findpathof(char *pth, const char *exe) { //Find executable by searching the PATH environment variable. // http://www.linuxquestions.org/questions/programming-9/get-full-path-of-a-command-in-c-117965/ char *searchpath; char *beg, *end; int stop, found; size_t len; if (strchr(exe, '/') != NULL) { if (realpath(exe, pth) == NULL) return 0; return is_exe(pth); } searchpath = getenv("PATH"); if (searchpath == NULL) return 0; if (strlen(searchpath) <= 0) return 0; beg = searchpath; stop = 0; found = 0; do { end = strchr(beg, ':'); if (end == NULL) { len = strlen(beg); if (len == 0) return 0; //gcc 8.1 warning: specified bound depends on the length of the source argument //https://developers.redhat.com/blog/2018/05/24/detecting-string-truncation-with-gcc-8/ //strncpy(pth, beg, len); strcpy(pth, beg); stop = 1; } else { strncpy(pth, beg, end - beg); pth[end - beg] = '\0'; len = end - beg; } //gcc8.1 warning: specified bound depends on the length of the source argument //if (pth[len - 1] != '/') strncat(pth, "/", 1); if (pth[len - 1] != '/') strcat(pth, "/"); strncat(pth, exe, PATH_MAX - len); found = is_exe(pth); if (!stop) beg = end + 1; } while (!stop && !found); if (!found) strcpy(pth, ""); return found; } #endif #ifndef USING_R void readFindPigz(struct TDCMopts *opts, const char *argv[]) { #if defined(_WIN64) || defined(_WIN32) strcpy(opts->pigzname, "pigz.exe"); if (!is_exe(opts->pigzname)) { #if defined(__APPLE__) #ifdef myDisableZLib printMessage("Compression requires %s in the same folder as the executable http://macappstore.org/pigz/\n", opts->pigzname); #else //myUseZLib if (opts->isVerbose > 0) printMessage("Compression will be faster with %s in the same folder as the executable http://macappstore.org/pigz/\n", opts->pigzname); #endif strcpy(opts->pigzname, ""); #else #ifdef myDisableZLib printMessage("Compression requires %s in the same folder as the executable\n", opts->pigzname); #else //myUseZLib if (opts->isVerbose > 0) printMessage("Compression will be faster with %s in the same folder as the executable\n", opts->pigzname); #endif strcpy(opts->pigzname, ""); #endif } else strcpy(opts->pigzname, ".\\pigz"); //drop #else char str[PATH_MAX]; //possible pigz names const char *names[] = { "pigz", "pigz_mricron", "pigz_afni", }; #define n_nam (sizeof(names) / sizeof(const char *)) for (int n = 0; n < (int)n_nam; n++) { if (findpathof(str, names[n])) { strcpy(opts->pigzname, str); //printMessage("Found pigz: %s\n", str); return; } } //possible pigz paths const char *pths[] = { "/usr/local/bin/", "/usr/bin/", }; #define n_pth (sizeof(pths) / sizeof(const char *)) char exepth[PATH_MAX]; strcpy(exepth, argv[0]); dropFilenameFromPath(exepth); //, opts.pigzname); char appendChar[2] = {"a"}; appendChar[0] = kPathSeparator; if (exepth[strlen(exepth) - 1] != kPathSeparator) strcat(exepth, appendChar); //see if pigz in any path for (int n = 0; n < (int)n_nam; n++) { //printf ("%d: %s\n", i, names[n]); for (int p = 0; p < (int)n_pth; p++) { strcpy(str, pths[p]); strcat(str, names[n]); if (is_exe(str)) goto pigzFound; } //p //check exepth strcpy(str, exepth); strcat(str, names[n]); if (is_exe(str)) goto pigzFound; } //n //Failure: #if defined(__APPLE__) #ifdef myDisableZLib printMessage("Compression requires 'pigz' to be installed http://macappstore.org/pigz/\n"); #else //myUseZLib if (opts->isVerbose > 0) printMessage("Compression will be faster with 'pigz' installed http://macappstore.org/pigz/\n"); #endif #else //if APPLE else ... #ifdef myDisableZLib printMessage("Compression requires 'pigz' to be installed\n"); #else //myUseZLib if (opts->isVerbose > 0) printMessage("Compression will be faster with 'pigz' installed\n"); #endif #endif return; pigzFound: //Success strcpy(opts->pigzname, str); //printMessage("Found pigz: %s\n", str); #endif } //readFindPigz() #endif 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); #endif #ifdef myEnableJasper opts->compressFlag = kCompressYes; //JASPER for JPEG2000 #else #ifdef myDisableOpenJPEG opts->compressFlag = kCompressNone; //no decompressor #else opts->compressFlag = kCompressYes; //OPENJPEG for JPEG2000 #endif #endif //printMessage("%d %s\n",opts->compressFlag, opts->compressname); strcpy(opts->indir, ""); strcpy(opts->outdir, ""); strcpy(opts->imageComments, ""); opts->isOnlySingleFile = false; //convert all files in a directory, not just a single file opts->isOneDirAtATime = false; opts->isRenameNotConvert = false; opts->isForceStackSameSeries = 2; //automatic: stack CTs, do not stack MRI opts->isForceStackDCE = true; opts->isIgnoreSeriesInstanceUID = false; opts->isIgnoreDerivedAnd2D = false; opts->isForceOnsetTimes = true; opts->isPhilipsFloatNotDisplayScaling = true; opts->isCrop = false; opts->isRotate3DAcq = true; opts->isGz = false; opts->isSaveNativeEndian = true; opts->isAddNamePostFixes = true; //e.g. "_e2" added for second echo opts->isTestx0021x105E = false; //GE test slice times stored in 0021,105E opts->isIgnoreTriggerTimes = false; opts->saveFormat = kSaveFormatNIfTI; opts->isPipedGz = false; //e.g. pipe data directly to pigz instead of saving uncompressed to disk opts->isSave3D = false; opts->dirSearchDepth = 5; opts->isProgress = 0; opts->nameConflictBehavior = kNAME_CONFLICT_ADD_SUFFIX; #ifdef myDisableZLib opts->gzLevel = 6; #else opts->gzLevel = MZ_DEFAULT_LEVEL; //-1; #endif //opts->isMaximize16BitRange = kMaximize16BitRange_False; //e.g. if INT16 image has range 0..500 scale to be 0..50000 with hdr.scl_slope = hdr.scl_slope * 0.01 opts->isMaximize16BitRange = kMaximize16BitRange_Raw; //future version will use this option as default: preserve UINT16 even if it can be losslessly converted to INT16 opts->isFlipY = true; //false: images in raw DICOM orientation, true: image rows flipped to cartesian coordinates opts->isRGBplanar = false; //false for NIfTI (RGBRGB...), true for Analyze (RRR..RGGG..GBBB..B) opts->isCreateBIDS = true; opts->isOnlyBIDS = false; opts->isSortDTIbyBVal = false; #ifdef myNoAnonymizeBIDS opts->isAnonymizeBIDS = false; #else opts->isAnonymizeBIDS = true; #endif opts->isCreateText = false; #ifdef myDebug opts->isVerbose = true; #else opts->isVerbose = false; #endif opts->isTiltCorrect = true; opts->numSeries = 0; memset(opts->seriesNumber, 0, sizeof(opts->seriesNumber)); strcpy(opts->filename, "%f_%p_%t_%s"); } // setDefaultOpts() #if defined(_WIN64) || defined(_WIN32) //windows has unusual file permissions for many users - lets save preferences to the registry void saveIniFile(struct TDCMopts opts) { HKEY hKey; if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\dcm2nii", 0, KEY_SET_VALUE, &hKey) != ERROR_SUCCESS) { RegCloseKey(hKey); return; } printMessage("Saving defaults to registry\n"); DWORD dwValue = opts.isGz; RegSetValueExA(hKey, "isGZ", 0, REG_DWORD, reinterpret_cast(&dwValue), sizeof(dwValue)); dwValue = opts.isMaximize16BitRange; RegSetValueExA(hKey, "isMaximize16BitRange", 0, REG_DWORD, reinterpret_cast(&dwValue), sizeof(dwValue)); RegSetValueExA(hKey, "filename", 0, REG_SZ, (LPBYTE)opts.filename, strlen(opts.filename) + 1); RegCloseKey(hKey); } //saveIniFile() void readIniFile(struct TDCMopts *opts, const char *argv[]) { setDefaultOpts(opts, argv); HKEY hKey; DWORD vSize = 0; DWORD dwDataType = 0; DWORD dwValue = 0; //RegOpenKeyEx(RegOpenKeyEx, key, 0, accessRights, keyHandle); //if(RegOpenKeyEx(HKEY_CURRENT_USER,(WCHAR)"Software\\dcm2nii", 0, KEY_QUERY_VALUE,&hKey) != ERROR_SUCCESS) { if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\dcm2nii", 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS) { RegCloseKey(hKey); return; } vSize = sizeof(dwValue); //if(RegQueryValueExA(hKey,"isGZ", 0, (LPDWORD )&dwDataType, (&dwValue), &vSize) == ERROR_SUCCESS) if (RegQueryValueExA(hKey, "isGZ", 0, (LPDWORD)&dwDataType, reinterpret_cast(&dwValue), &vSize) == ERROR_SUCCESS) opts->isGz = dwValue; if (RegQueryValueExA(hKey, "isMaximize16BitRange", 0, (LPDWORD)&dwDataType, reinterpret_cast(&dwValue), &vSize) == ERROR_SUCCESS) opts->isMaximize16BitRange = dwValue; vSize = 512; char buffer[512]; if (RegQueryValueExA(hKey, "filename", 0, NULL, (LPBYTE)buffer, &vSize) == ERROR_SUCCESS) strcpy(opts->filename, buffer); RegCloseKey(hKey); } //readIniFile() #else //for Unix we will save preferences in a hidden text file in the home directory #define STATUSFILENAME "/.dcm2nii.ini" void readIniFile(struct TDCMopts *opts, const char *argv[]) { setDefaultOpts(opts, argv); sprintf(opts->optsname, "%s%s", getenv("HOME"), STATUSFILENAME); FILE *fp = fopen(opts->optsname, "r"); if (fp == NULL) return; char Setting[20], Value[255]; //while ( fscanf(fp, "%[^=]=%s\n", Setting, Value) == 2 ) { //while ( fscanf(fp, "%[^=]=%s\n", Setting, Value) == 2 ) { while (fscanf(fp, "%[^=]=%[^\n]\n", Setting, Value) == 2) { //printMessage(">%s<->'%s'\n",Setting,Value); if (strcmp(Setting, "isGZ") == 0) opts->isGz = atoi(Value); if (strcmp(Setting, "isMaximize16BitRange") == 0) opts->isMaximize16BitRange = atoi(Value); else if (strcmp(Setting, "isBIDS") == 0) opts->isCreateBIDS = atoi(Value); else if (strcmp(Setting, "filename") == 0) strcpy(opts->filename, Value); } fclose(fp); } // readIniFile() void saveIniFile(struct TDCMopts opts) { FILE *fp = fopen(opts.optsname, "w"); //printMessage("%s\n",localfilename); if (fp == NULL) return; printMessage("Saving defaults file %s\n", opts.optsname); fprintf(fp, "isGZ=%d\n", opts.isGz); fprintf(fp, "isMaximize16BitRange=%d\n", opts.isMaximize16BitRange); fprintf(fp, "isBIDS=%d\n", opts.isCreateBIDS); fprintf(fp, "filename=%s\n", opts.filename); fclose(fp); } //saveIniFile() #endif dcm2niix-1.0.20211006/console/nii_dicom_batch.h000066400000000000000000000061421412761503300206740ustar00rootroot00000000000000// // #ifndef MRIpro_nii_batch_h #define MRIpro_nii_batch_h #ifdef __cplusplus extern "C" { #endif #include //requires VS 2015 or later #include #ifndef USING_R #include "nifti1.h" #endif #include "nii_dicom.h" #ifdef USING_R struct TDicomSeries { std::string name; TDICOMdata representativeData; std::vector files; }; #endif #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 #define kMaximize16BitRange_False 0 //e.g. raw UINT16 values 0..4095 saved as INT16 (e.g. AFNI preserves INT16 "short", converts UINT16 to float32) #define kMaximize16BitRange_True 1 //e.g. raw UINT16 values 0..4095 saved as 0..61425 UINT16 (SPM free precision) #define kMaximize16BitRange_Raw 2 //e.g. raw UINT16 values 0..4095 saved as UINT16 (retains raw data type, AFNI would convert to float32) #define kSaveFormatNIfTI 0 #define kSaveFormatNRRD 1 #define kSaveFormatMGH 2 #define MAX_NUM_SERIES 16 struct TDCMopts { bool isIgnoreTriggerTimes, isTestx0021x105E, isAddNamePostFixes, isSaveNativeEndian, isOneDirAtATime, isRenameNotConvert, isSave3D, isGz, isPipedGz, isFlipY, isCreateBIDS, isSortDTIbyBVal, isAnonymizeBIDS, isOnlyBIDS, isCreateText, isForceOnsetTimes,isIgnoreDerivedAnd2D, isPhilipsFloatNotDisplayScaling, isTiltCorrect, isRGBplanar, isOnlySingleFile, isForceStackDCE, isIgnoreSeriesInstanceUID, isRotate3DAcq, isCrop; int saveFormat, isMaximize16BitRange, isForceStackSameSeries, nameConflictBehavior, isVerbose, isProgress, compressFlag, dirSearchDepth, gzLevel; //support for compressed data 0=none, char filename[512], outdir[512], indir[512], pigzname[512], optsname[512], indirParent[512], imageComments[24]; double seriesNumber[MAX_NUM_SERIES]; //requires double must store -1 (report but do not convert) as well as seriesUidCrc (uint32) long numSeries; #ifdef USING_R bool isScanOnly; void *imageList; std::vector series; // Used when sorting a directory std::vector sourcePaths; std::vector targetPaths; std::vector ignoredPaths; #endif }; void saveIniFile (struct TDCMopts opts); void setDefaultOpts (struct TDCMopts *opts, const char * argv[]); //either "setDefaultOpts(opts,NULL)" or "setDefaultOpts(opts,argv)" where argv[0] is path to search 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); 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); #ifdef __cplusplus } #endif #endif dcm2niix-1.0.20211006/console/nii_foreign.cpp000066400000000000000000000414271412761503300204310ustar00rootroot00000000000000#include "nii_foreign.h" #include "nifti1.h" #include "nifti1_io_core.h" #include "nii_dicom.h" #include "nii_dicom_batch.h" //#include "nifti1_io_core.h" #include "print.h" #include #include //requires VS 2015 or later #include #include #include #include #ifdef _MSC_VER #include #define getcwd _getcwd #define chdir _chrdir #include "io.h" #include //#define snprintMessage _snprintMessage //#define vsnprintMessage _vsnprintMessage #define strcasecmp _stricmp #define strncasecmp _strnicmp #else #include #endif #ifndef Float32 #define Float32 float #endif #ifndef uint32 #define uint32 uint32_t #endif #ifdef __GNUC__ #define PACK(...) __VA_ARGS__ __attribute__((__packed__)) #else #define PACK(...) __pragma(pack(push, 1)) __VA_ARGS__ __pragma(pack(pop)) #endif /*nii_readEcat7 2017 by Chris Rorden, BSD license http://www.turkupetcentre.net/software/libdoc/libtpcimgio/ecat7_8h_source.html#l00060 http://www.turkupetcentre.net/software/libdoc/libtpcimgio/ecat7r_8c_source.html#l00717 http://xmedcon.sourcearchive.com/documentation/0.10.7-1build1/ecat7_8h_source.html https://github.com/BIC-MNI/minc-tools/tree/master/conversion/ecattominc https://github.com/nipy/nibabel/blob/ec4567fb09b4472c5a4bb9a13dbcc9eb0a63d875/nibabel/ecat.py */ void strClean(char *cString) { int len = (int)strlen(cString); if (len < 1) return; for (int i = 0; i < len; i++) { char c = cString[i]; if ((c < char(32)) || (c == char(127)) || (c == char(255))) cString[i] = 0; if ((c == ' ') || (c == ',') || (c == '^') || (c == '/') || (c == '\\') || (c == '%') || (c == '*')) cString[i] = '_'; } } unsigned char *readEcat7(const char *fname, struct TDICOMdata *dcm, struct nifti_1_header *hdr, struct TDCMopts opts, bool isWarnIfNotEcat) { //data type #define ECAT7_BYTE 1 #define ECAT7_VAXI2 2 #define ECAT7_VAXI4 3 #define ECAT7_VAXR4 4 #define ECAT7_IEEER4 5 #define ECAT7_SUNI2 6 #define ECAT7_SUNI4 7 //file types //#define ECAT7_UNKNOWN 0 #define ECAT7_2DSCAN 1 #define ECAT7_IMAGE16 2 #define ECAT7_ATTEN 3 #define ECAT7_2DNORM 4 #define ECAT7_POLARMAP 5 #define ECAT7_VOLUME8 6 #define ECAT7_VOLUME16 7 #define ECAT7_PROJ 8 #define ECAT7_PROJ16 9 #define ECAT7_IMAGE8 10 #define ECAT7_3DSCAN 11 #define ECAT7_3DSCAN8 12 #define ECAT7_3DNORM 13 #define ECAT7_3DSCANFIT 14 PACK(typedef struct { char magic[14], original_filename[32]; uint16_t sw_version, system_type, file_type; char serial_number[10]; uint32 scan_start_time; char isotope_name[8]; Float32 isotope_halflife; char radiopharmaceutical[32]; Float32 gantry_tilt, gantry_rotation, bed_elevation, intrinsic_tilt; int16_t wobble_speed, transm_source_type; Float32 distance_scanned, transaxial_fov; uint16_t angular_compression, coin_samp_mode, axial_samp_mode; Float32 ecat_calibration_factor; uint16_t calibration_unitS, calibration_units_type, compression_code; char study_type[12], patient_id[16], accession_number[16], patient_name[32], patient_sex, patient_dexterity; Float32 patient_age, patient_height, patient_weight; uint32 patient_birth_date; char physician_name[32], operator_name[32], study_description[32]; uint16_t acquisition_type, patient_orientation; char facility_name[20]; uint16_t num_planes, num_frames, num_gates, num_bed_pos; Float32 init_bed_position; Float32 bed_position[15]; Float32 plane_separation; uint16_t lwr_sctr_thres, lwr_true_thres, upr_true_thres; char user_process_code[10]; uint16_t acquisition_mode; Float32 bin_size, branching_fraction; uint32 dose_start_time; Float32 dosage, well_counter_corr_factor; char data_units[32]; uint16_t septa_state; char fill[12]; }) ecat_main_hdr; PACK(typedef struct { int16_t data_type, num_dimensions, x_dimension, y_dimension, z_dimension; Float32 x_offset, y_offset, z_offset, recon_zoom, scale_factor; int16_t image_min, image_max; Float32 x_pixel_size, y_pixel_size, z_pixel_size; int32_t frame_duration, frame_start_time; int16_t filter_code; Float32 x_resolution, y_resolution, z_resolution, num_r_elements, num_angles, z_rotation_angle, decay_corr_fctr; int32_t processing_code, gate_duration, r_wave_offset, num_accepted_beats; Float32 filter_cutoff_frequenc, filter_resolution, filter_ramp_slope; int16_t filter_order; Float32 filter_scatter_fraction, filter_scatter_slope; char annotation[40]; Float32 mtx[9], rfilter_cutoff, rfilter_resolution; int16_t rfilter_code, rfilter_order; Float32 zfilter_cutoff, zfilter_resolution; int16_t zfilter_code, zfilter_order; Float32 mtx_1_4, mtx_2_4, mtx_3_4; int16_t scatter_type, recon_type, recon_views, fill_cti[87], fill_user[49]; }) ecat_img_hdr; PACK(typedef struct { int32_t hdr[4], r[31][4]; }) ecat_list_hdr; bool swapEndian = false; size_t n; FILE *f; ecat_main_hdr mhdr; f = fopen(fname, "rb"); if (f) n = fread(&mhdr, sizeof(mhdr), 1, f); if (!f || n != 1) { printMessage("Problem reading ECAT7 file!\n"); fclose(f); return NULL; } if ((mhdr.magic[0] != 'M') || (mhdr.magic[1] != 'A') || (mhdr.magic[2] != 'T') || (mhdr.magic[3] != 'R') || (mhdr.magic[4] != 'I') || (mhdr.magic[5] != 'X')) { if (isWarnIfNotEcat) printMessage("Signature not 'MATRIX' (ECAT7): '%s'\n", fname); fclose(f); return NULL; } swapEndian = mhdr.file_type > 255; if (swapEndian) { nifti_swap_2bytes(2, &mhdr.sw_version); nifti_swap_2bytes(1, &mhdr.file_type); //nifti_swap_2bytes(1, &mhdr.num_frames); nifti_swap_4bytes(1, &mhdr.ecat_calibration_factor); nifti_swap_4bytes(1, &mhdr.isotope_halflife); nifti_swap_4bytes(2, &mhdr.dosage); } if ((mhdr.file_type < ECAT7_2DSCAN) || (mhdr.file_type > ECAT7_3DSCANFIT)) { printMessage("Unknown ECAT file type %d\n", mhdr.file_type); fclose(f); return NULL; } //read list matrix ecat_list_hdr lhdr; fseek(f, 512, SEEK_SET); size_t nRead = fread(&lhdr, sizeof(lhdr), 1, f); if (nRead != 1) { printMessage("Error reading ECAT file (list header)\n"); fclose(f); return NULL; } if (swapEndian) nifti_swap_4bytes(128, &lhdr.hdr[0]); //offset to first image int img_StartBytes = lhdr.r[0][1] * 512; //load image header for first image fseek(f, img_StartBytes - 512, SEEK_SET); //image header is block immediately before image ecat_img_hdr ihdr; nRead = fread(&ihdr, sizeof(ihdr), 1, f); if (nRead != 1) { printMessage("Error reading ECAT file (image header)\n"); fclose(f); return NULL; } if (swapEndian) { nifti_swap_2bytes(5, &ihdr.data_type); nifti_swap_4bytes(5, &ihdr.x_offset); nifti_swap_2bytes(2, &ihdr.image_min); nifti_swap_4bytes(5, &ihdr.x_pixel_size); nifti_swap_2bytes(1, &ihdr.filter_code); nifti_swap_4bytes(14, &ihdr.x_resolution); nifti_swap_2bytes(1, &ihdr.filter_order); nifti_swap_4bytes(2, &ihdr.filter_scatter_fraction); nifti_swap_4bytes(11, &ihdr.mtx); nifti_swap_2bytes(2, &ihdr.rfilter_code); nifti_swap_4bytes(2, &ihdr.zfilter_cutoff); nifti_swap_2bytes(2, &ihdr.zfilter_code); nifti_swap_4bytes(3, &ihdr.mtx_1_4); nifti_swap_2bytes(3, &ihdr.scatter_type); } if ((ihdr.data_type != ECAT7_BYTE) && (ihdr.data_type != ECAT7_SUNI2) && (ihdr.data_type != ECAT7_SUNI4)) { printMessage("Unknown or unsupported ECAT data type %d\n", ihdr.data_type); fclose(f); return NULL; } int bytesPerVoxel = 2; if (ihdr.data_type == ECAT7_BYTE) bytesPerVoxel = 1; if (ihdr.data_type == ECAT7_SUNI4) bytesPerVoxel = 4; //next: read offsets for each volume: data not saved sequentially (each volume preceded by its own ecat_img_hdr) int num_vol = 0; bool isAbort = false; bool isScaleFactorVaries = false; #define kMaxVols 16000 size_t *imgOffsets = (size_t *)malloc(sizeof(size_t) * (kMaxVols)); float *imgSlopes = (float *)malloc(sizeof(float) * (kMaxVols)); ecat_img_hdr ihdrN; while ((lhdr.hdr[0] + lhdr.hdr[3]) == 31) { //while valid list if (num_vol > 0) { //read the next list fseek(f, 512 * (lhdr.hdr[1] - 1), SEEK_SET); nRead = fread(&lhdr, 512, 1, f); if (nRead != 1) { printMessage("Error reading ECAT file (yet another list header)\n"); fclose(f); return NULL; } if (swapEndian) nifti_swap_4bytes(128, &lhdr.hdr[0]); } if ((lhdr.hdr[0] + lhdr.hdr[3]) != 31) break; //if valid list if (lhdr.hdr[3] < 1) break; for (int k = 0; k < lhdr.hdr[3]; k++) { //check images' ecat_img_hdr matches first fseek(f, (lhdr.r[k][1] - 1) * 512, SEEK_SET); //image header is block immediately before image nRead = fread(&ihdrN, sizeof(ihdrN), 1, f); if (nRead != 1) { printMessage("Error reading ECAT file (yet another image header)\n"); fclose(f); return NULL; } if (swapEndian) { nifti_swap_2bytes(5, &ihdrN.data_type); nifti_swap_4bytes(5, &ihdrN.x_offset); nifti_swap_2bytes(2, &ihdrN.image_min); nifti_swap_4bytes(5, &ihdrN.x_pixel_size); nifti_swap_2bytes(1, &ihdrN.filter_code); nifti_swap_4bytes(14, &ihdrN.x_resolution); nifti_swap_2bytes(1, &ihdrN.filter_order); nifti_swap_4bytes(2, &ihdrN.filter_scatter_fraction); nifti_swap_4bytes(11, &ihdrN.mtx); nifti_swap_2bytes(2, &ihdrN.rfilter_code); nifti_swap_4bytes(2, &ihdrN.zfilter_cutoff); nifti_swap_2bytes(2, &ihdrN.zfilter_code); nifti_swap_4bytes(3, &ihdrN.mtx_1_4); nifti_swap_2bytes(3, &ihdrN.scatter_type); } if (ihdr.scale_factor != ihdrN.scale_factor) isScaleFactorVaries = true; if ((ihdr.data_type != ihdrN.data_type) || (ihdr.x_dimension != ihdrN.x_dimension) || (ihdr.y_dimension != ihdrN.y_dimension) || (ihdr.z_dimension != ihdrN.z_dimension)) { printError("Error: ECAT volumes have varying image dimensions\n"); isAbort = true; } if (num_vol < kMaxVols) { imgOffsets[num_vol] = (size_t)lhdr.r[k][1]; imgSlopes[num_vol] = ihdrN.scale_factor; } num_vol++; } if ((lhdr.hdr[0] > 0) || (isAbort)) break; //this list contains empty volumes: all lists have been read } //read all image offsets //report error reading image offsets if ((num_vol < 1) || (isAbort) || (num_vol >= kMaxVols)) { printMessage("Failure to extract ECAT7 images\n"); if (num_vol >= kMaxVols) printMessage("Increase kMaxVols"); fclose(f); free(imgOffsets); free(imgSlopes); return NULL; } if ((isScaleFactorVaries) && (bytesPerVoxel != 2)) { printError("ECAT scale factor varies between volumes (check for updates) '%s'\n", fname); fclose(f); free(imgOffsets); free(imgSlopes); return NULL; } //load image data unsigned char *img = NULL; if ((isScaleFactorVaries) && (bytesPerVoxel == 2)) { //we need to convert volumes from 16-bit to 32-bit to preserve scaling factors int num_vox = ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension; size_t bytesPerVolumeIn = (size_t)(num_vox * bytesPerVoxel); //bytesPerVoxel == 2 unsigned char *imgIn = (unsigned char *)malloc(bytesPerVolumeIn); int16_t *img16i = (int16_t *)imgIn; bytesPerVoxel = 4; size_t bytesPerVolume = (size_t)(num_vox * bytesPerVoxel); img = (unsigned char *)malloc((size_t)(bytesPerVolume * num_vol)); float *img32 = (float *)img; for (int v = 0; v < num_vol; v++) { fseek(f, imgOffsets[v] * 512, SEEK_SET); nRead = fread(&imgIn[0], 1, bytesPerVolumeIn, f); if (nRead != bytesPerVolumeIn) { printMessage("%zu Error reading ECAT file (offset %zu bytes %zu)\n", nRead, imgOffsets[v] * 512, bytesPerVolumeIn); fclose(f); return NULL; } if (swapEndian) nifti_swap_2bytes(num_vox, imgIn); int volOffset = v * num_vox; float scale = imgSlopes[v] * mhdr.ecat_calibration_factor; for (int i = 0; i < num_vox; i++) img32[i + volOffset] = (img16i[i] * scale); } //we have applied the scale factors to the data, so eliminate them ihdr.scale_factor = 1.0; mhdr.ecat_calibration_factor = 1.0; } else { //if isScaleFactorVaries else simple conversion size_t bytesPerVolume = ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension * bytesPerVoxel; img = (unsigned char *)malloc(bytesPerVolume * num_vol); for (int v = 0; v < num_vol; v++) { fseek(f, imgOffsets[v] * 512, SEEK_SET); size_t sz = fread(&img[v * bytesPerVolume], 1, bytesPerVolume, f); if (sz != bytesPerVolume) { free(img); return NULL; } } if ((swapEndian) && (bytesPerVoxel == 2)) nifti_swap_2bytes(ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension * num_vol, img); if ((swapEndian) && (bytesPerVoxel == 4)) nifti_swap_4bytes(ihdr.x_dimension * ihdr.y_dimension * ihdr.z_dimension * num_vol, img); } printWarning("ECAT support VERY experimental (Spatial transforms unknown)\n"); free(imgOffsets); free(imgSlopes); fclose(f); //fill DICOM header float timeBetweenVolumes = ihdr.frame_duration; if (num_vol > 1) timeBetweenVolumes = (float)(ihdrN.frame_start_time - ihdr.frame_start_time) / (float)(num_vol - 1); //copy and clean strings (ECAT can use 0x0D as a string terminator) strncpy(dcm->patientName, mhdr.patient_name, 32); strncpy(dcm->patientID, mhdr.patient_id, 16); strncpy(dcm->accessionNumber, mhdr.accession_number, 16); strncpy(dcm->seriesDescription, mhdr.study_description, 32); strncpy(dcm->protocolName, mhdr.study_type, 12); strncpy(dcm->imageComments, mhdr.isotope_name, 8); strncpy(dcm->procedureStepDescription, mhdr.radiopharmaceutical, 32); strClean(dcm->patientName); strClean(dcm->patientID); strClean(dcm->accessionNumber); strClean(dcm->seriesDescription); strClean(dcm->protocolName); strClean(dcm->imageComments); strClean(dcm->procedureStepDescription); dcm->ecat_dosage = mhdr.dosage; dcm->ecat_isotope_halflife = mhdr.isotope_halflife; if (opts.isVerbose) { printMessage("ECAT7 details for '%s'\n", fname); printMessage(" Software version %d\n", mhdr.sw_version); printMessage(" System Type %d\n", mhdr.system_type); printMessage(" Frame duration %dms\n", ihdr.frame_duration); printMessage(" Time between volumes %gms\n", timeBetweenVolumes); printMessage(" Patient name '%s'\n", dcm->patientName); printMessage(" Patient ID '%s'\n", dcm->patientID); printMessage(" Accession number '%s'\n", dcm->accessionNumber); printMessage(" Study description '%s'\n", dcm->seriesDescription); printMessage(" Study type '%s'\n", dcm->protocolName); printMessage(" Isotope name '%s'\n", dcm->imageComments); printMessage(" Isotope halflife %gs\n", mhdr.isotope_halflife); printMessage(" Radiopharmaceutical '%s'\n", dcm->procedureStepDescription); printMessage(" Dosage %gbequerels/cc\n", mhdr.dosage); if (!isScaleFactorVaries) { printMessage(" Scale factor %12.12g\n", ihdr.scale_factor); printMessage(" ECAT calibration factor %8.12g\n", mhdr.ecat_calibration_factor); } printMessage(" NIfTI scale slope %12.12g\n", ihdr.scale_factor * mhdr.ecat_calibration_factor); } dcm->manufacturer = kMANUFACTURER_SIEMENS; //dcm->manufacturersModelName = itoa(mhdr.system_type); sprintf(dcm->manufacturersModelName, "%d", mhdr.system_type); dcm->bitsAllocated = bytesPerVoxel * 8; if (isScaleFactorVaries) dcm->isFloat = true; dcm->bitsStored = 15; //ensures 16-bit images saved as INT16 not UINT16 dcm->samplesPerPixel = 1; dcm->xyzMM[1] = ihdr.x_pixel_size * 10.0; //cm -> mm dcm->xyzMM[2] = ihdr.y_pixel_size * 10.0; //cm -> mm dcm->xyzMM[3] = ihdr.z_pixel_size * 10.0; //cm -> mm dcm->TR = timeBetweenVolumes; dcm->xyzDim[1] = ihdr.x_dimension; dcm->xyzDim[2] = ihdr.y_dimension; dcm->xyzDim[3] = ihdr.z_dimension; dcm->xyzDim[4] = num_vol; //create a NIfTI header headerDcm2Nii(*dcm, hdr, false); //here we mimic SPM's spatial starting estimate SForm mat44 m44; LOAD_MAT44(m44, -hdr->pixdim[1], 0.0f, 0.0f, ((float)dcm->xyzDim[1] - 2.0) / 2.0 * dcm->xyzMM[1], 0.0f, -hdr->pixdim[2], 0.0f, ((float)dcm->xyzDim[2] - 2.0) / 2.0 * dcm->xyzMM[2], 0.0f, 0.0f, -hdr->pixdim[3], ((float)dcm->xyzDim[3] - 2.0) / 2.0 * dcm->xyzMM[3]); setQSForm(hdr, m44, false); //make sure image does not include a spatial matrix bool isMatrix = false; for (int i = 0; i < 9; i++) if (ihdr.mtx[i] != 0.0) isMatrix = true; if (isMatrix) printWarning("ECAT volume appears to store spatial transformation matrix (please check for updates)\n"); hdr->scl_slope = ihdr.scale_factor * mhdr.ecat_calibration_factor; if (mhdr.gantry_tilt != 0.0) printMessage("Warning: ECAT gantry tilt not supported %g\n", mhdr.gantry_tilt); return img; } int convert_foreign(const char *fn, struct TDCMopts opts) { struct nifti_1_header hdr; struct TDICOMdata dcm = clear_dicom_data(); unsigned char *img = NULL; img = readEcat7(fn, &dcm, &hdr, opts, false); //false: silent, do not report if file is not ECAT format if (!img) return EXIT_FAILURE; char niiFilename[1024]; int ret = nii_createFilename(dcm, niiFilename, opts); if (ret != EXIT_SUCCESS) { printError("Failed to save ECAT as '%s'\n", niiFilename); return ret; } printMessage("Saving ECAT as '%s'\n", niiFilename); //struct TDTI4D dti4D; //nii_SaveBIDS(niiFilename, dcm, opts, &dti4D, &hdr, fn); nii_SaveBIDS(niiFilename, dcm, opts, &hdr, fn); ret = nii_saveNIIx(niiFilename, hdr, img, opts); free(img); return ret; } // convert_foreign() dcm2niix-1.0.20211006/console/nii_foreign.h000066400000000000000000000004401412761503300200640ustar00rootroot00000000000000//Attempt to open non-DICOM image #ifndef _NII_FOREIGN_ #define _NII_FOREIGN_ #ifdef __cplusplus extern "C" { #endif #include "nii_dicom_batch.h" //int open_foreign (const char *fn); int convert_foreign (const char *fn, struct TDCMopts opts); #ifdef __cplusplus } #endif #endif dcm2niix-1.0.20211006/console/nii_ortho.cpp000066400000000000000000000274701412761503300201350ustar00rootroot00000000000000#ifndef USING_R #include "nifti1.h" #endif #include "nifti1_io_core.h" #include "nii_ortho.h" #include #include #include #include //requires VS 2015 or later #include #include #include #include #include //#include #include #ifndef _MSC_VER #include #endif //#define MY_DEBUG //verbose text reporting #include "print.h" typedef struct { int v[3]; } vec3i; mat33 matDotMul33(mat33 a, mat33 b) // in Matlab: ret = a'.*b { mat33 ret; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { ret.m[i][j] = a.m[i][j] * b.m[j][i]; } } return ret; } mat33 matMul33(mat33 a, mat33 b) // mult = a * b { mat33 mult; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { mult.m[j][i] = 0; for (int k = 0; k < 3; k++) mult.m[j][i] += a.m[j][k] * b.m[k][i]; } } return mult; } float getOrthoResidual(mat33 orig, mat33 transform) { mat33 mat = matDotMul33(orig, transform); float ret = 0; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { ret = ret + (mat.m[i][j]); } } return ret; } mat33 getBestOrient(mat44 R, vec3i flipVec) //flipVec reports flip: [1 1 1]=no flips, [-1 1 1] flip X dimension { mat33 ret, newmat, orig; LOAD_MAT33(orig, R.m[0][0], R.m[0][1], R.m[0][2], R.m[1][0], R.m[1][1], R.m[1][2], R.m[2][0], R.m[2][1], R.m[2][2]); float best = 0; //FLT_MAX; float newval; for (int rot = 0; rot < 6; rot++) { //6 rotations switch (rot) { case 0: LOAD_MAT33(newmat, flipVec.v[0], 0, 0, 0, flipVec.v[1], 0, 0, 0, flipVec.v[2]); break; case 1: LOAD_MAT33(newmat, flipVec.v[0], 0, 0, 0, 0, flipVec.v[1], 0, flipVec.v[2], 0); break; case 2: LOAD_MAT33(newmat, 0, flipVec.v[0], 0, flipVec.v[1], 0, 0, 0, 0, flipVec.v[2]); break; case 3: LOAD_MAT33(newmat, 0, flipVec.v[0], 0, 0, 0, flipVec.v[1], flipVec.v[2], 0, 0); break; case 4: LOAD_MAT33(newmat, 0, 0, flipVec.v[0], flipVec.v[1], 0, 0, 0, flipVec.v[2], 0); break; case 5: LOAD_MAT33(newmat, 0, 0, flipVec.v[0], 0, flipVec.v[1], 0, flipVec.v[2], 0, 0); break; } newval = getOrthoResidual(orig, newmat); if (newval > best) { best = newval; ret = newmat; } } return ret; } bool isMat44Canonical(mat44 R) //returns true if diagonals >0 and all others =0 // no rotation is necessary - already in perfect orthogonal alignment { for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if ((i == j) && (R.m[i][j] <= 0)) return false; if ((i != j) && (R.m[i][j] != 0)) return false; } //j } //i return true; } vec3i setOrientVec(mat33 m) // Assumes isOrthoMat NOT computed on INVERSE, hence return INVERSE of solution... //e.g. [-1,2,3] means reflect x axis, [2,1,3] means swap x and y dimensions { vec3i ret = {{0, 0, 0}}; //mat33 m = {-1,0,0, 0,1,0, 0,0,1}; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (m.m[i][j] > 0) ret.v[j] = i + 1; if (m.m[i][j] < 0) ret.v[j] = -(i + 1); } //j } //i return ret; } mat44 setMat44Vec(mat33 m33, vec3 Translations) //convert a 3x3 rotation matrix to a 4x4 matrix where the last column stores translations and the last row is 0 0 0 1 { mat44 m44; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { m44.m[i][j] = m33.m[i][j]; } } m44.m[0][3] = Translations.v[0]; m44.m[1][3] = Translations.v[1]; m44.m[2][3] = Translations.v[2]; m44.m[3][0] = 0; m44.m[3][1] = 0; m44.m[3][2] = 0; m44.m[3][3] = 1; return m44; } mat44 sFormMat(struct nifti_1_header *h) { mat44 s; s.m[0][0] = h->srow_x[0]; s.m[0][1] = h->srow_x[1]; s.m[0][2] = h->srow_x[2]; s.m[0][3] = h->srow_x[3]; s.m[1][0] = h->srow_y[0]; s.m[1][1] = h->srow_y[1]; s.m[1][2] = h->srow_y[2]; s.m[1][3] = h->srow_y[3]; s.m[2][0] = h->srow_z[0]; s.m[2][1] = h->srow_z[1]; s.m[2][2] = h->srow_z[2]; s.m[2][3] = h->srow_z[3]; s.m[3][0] = 0; s.m[3][1] = 0; s.m[3][2] = 0; s.m[3][3] = 1; return s; } void mat2sForm(struct nifti_1_header *h, mat44 s) { h->srow_x[0] = s.m[0][0]; h->srow_x[1] = s.m[0][1]; h->srow_x[2] = s.m[0][2]; h->srow_x[3] = s.m[0][3]; h->srow_y[0] = s.m[1][0]; h->srow_y[1] = s.m[1][1]; h->srow_y[2] = s.m[1][2]; h->srow_y[3] = s.m[1][3]; h->srow_z[0] = s.m[2][0]; h->srow_z[1] = s.m[2][1]; h->srow_z[2] = s.m[2][2]; h->srow_z[3] = s.m[2][3]; } size_t *orthoOffsetArray(int dim, int stepBytesPerVox) { //return lookup table of length dim with values incremented by stepBytesPerVox // e.g. if Dim=10 and stepBytes=2: 0,2,4..18, is stepBytes=-2 18,16,14...0 size_t *lut = (size_t *)malloc(dim * sizeof(size_t)); if (stepBytesPerVox > 0) lut[0] = 0; else lut[0] = -stepBytesPerVox * (dim - 1); if (dim > 1) for (int i = 1; i < dim; i++) lut[i] = lut[i - 1] + (size_t)stepBytesPerVox; return lut; } //orthoOffsetArray() //void reOrientImg( unsigned char * restrict img, vec3i outDim, vec3i outInc, int bytePerVox, int nvol) { void reOrientImg(unsigned char *img, vec3i outDim, vec3i outInc, int bytePerVox, int nvol) { //reslice data to new orientation //generate look up tables size_t *xLUT = orthoOffsetArray(outDim.v[0], bytePerVox * outInc.v[0]); size_t *yLUT = orthoOffsetArray(outDim.v[1], bytePerVox * outInc.v[1]); size_t *zLUT = orthoOffsetArray(outDim.v[2], bytePerVox * outInc.v[2]); //convert data size_t bytePerVol = bytePerVox * outDim.v[0] * outDim.v[1] * outDim.v[2]; //number of voxels in spatial dimensions [1,2,3] size_t o = 0; //output address uint8_t *inbuf = (uint8_t *)malloc(bytePerVol); //we convert 1 volume at a time uint8_t *outbuf = (uint8_t *)img; //source image for (int vol = 0; vol < nvol; vol++) { memcpy(&inbuf[0], &outbuf[vol * bytePerVol], bytePerVol); //copy source volume for (int z = 0; z < outDim.v[2]; z++) for (int y = 0; y < outDim.v[1]; y++) for (int x = 0; x < outDim.v[0]; x++) { memcpy(&outbuf[o], &inbuf[xLUT[x] + yLUT[y] + zLUT[z]], bytePerVox); o = o + bytePerVox; } //for each x } //for each volume //free arrays free(inbuf); free(xLUT); free(yLUT); free(zLUT); } //reOrientImg unsigned char *reOrient(unsigned char *img, struct nifti_1_header *h, vec3i orientVec, mat33 orient, vec3 minMM) //e.g. [-1,2,3] means reflect x axis, [2,1,3] means swap x and y dimensions { size_t nvox = h->dim[1] * h->dim[2] * h->dim[3]; if (nvox < 1) return img; vec3i outDim = {{0, 0, 0}}; vec3i outInc = {{0, 0, 0}}; for (int i = 0; i < 3; i++) { //set dimension, pixdim and outDim.v[i] = h->dim[abs(orientVec.v[i])]; if (abs(orientVec.v[i]) == 1) outInc.v[i] = 1; if (abs(orientVec.v[i]) == 2) outInc.v[i] = h->dim[1]; if (abs(orientVec.v[i]) == 3) outInc.v[i] = h->dim[1] * h->dim[2]; if (orientVec.v[i] < 0) outInc.v[i] = -outInc.v[i]; //flip } //for each dimension int nvol = 1; //convert all non-spatial volumes from source to destination for (int vol = 4; vol < 8; vol++) { if (h->dim[vol] > 1) nvol = nvol * h->dim[vol]; } reOrientImg(img, outDim, outInc, h->bitpix / 8, nvol); //now change the header.... vec3 outPix = {{h->pixdim[abs(orientVec.v[0])], h->pixdim[abs(orientVec.v[1])], h->pixdim[abs(orientVec.v[2])]}}; for (int i = 0; i < 3; i++) { h->dim[i + 1] = outDim.v[i]; h->pixdim[i + 1] = outPix.v[i]; } mat44 s = sFormMat(h); mat33 mat; //computer transform LOAD_MAT33(mat, s.m[0][0], s.m[0][1], s.m[0][2], s.m[1][0], s.m[1][1], s.m[1][2], s.m[2][0], s.m[2][1], s.m[2][2]); mat = matMul33(mat, orient); s = setMat44Vec(mat, minMM); //add offset mat2sForm(h, s); h->qform_code = h->sform_code; //apply to the quaternion as well float dumdx, dumdy, dumdz; nifti_mat44_to_quatern(s, &h->quatern_b, &h->quatern_c, &h->quatern_d, &h->qoffset_x, &h->qoffset_y, &h->qoffset_z, &dumdx, &dumdy, &dumdz, &h->pixdim[0]); return img; } //reOrient() float getDistance(vec3 v, vec3 min) //scalar distance between two 3D points - Pythagorean theorem { return sqrt(pow((v.v[0] - min.v[0]), 2) + pow((v.v[1] - min.v[1]), 2) + pow((v.v[2] - min.v[2]), 2)); } vec3 xyz2mm(mat44 R, vec3 v) { vec3 ret; for (int i = 0; i < 3; i++) { ret.v[i] = ((R.m[i][0] * v.v[0]) + (R.m[i][1] * v.v[1]) + (R.m[i][2] * v.v[2]) + R.m[i][3]); } return ret; } vec3 minCornerFlip(struct nifti_1_header *h, vec3i *flipVec) //orthogonal rotations and reflections applied as 3x3 matrices will cause the origin to shift // a simple solution is to first compute the most left, posterior, inferior voxel in the source image // this voxel will be at location i,j,k = 0,0,0, so we can simply use this as the offset for the final 4x4 matrix... { int i, j, minIndex; vec3i flipVecs[8]; vec3 corner[8], min; mat44 s = sFormMat(h); for (int i = 0; i < 8; i++) { if (i & 1) flipVecs[i].v[0] = -1; else flipVecs[i].v[0] = 1; if (i & 2) flipVecs[i].v[1] = -1; else flipVecs[i].v[1] = 1; if (i & 4) flipVecs[i].v[2] = -1; else flipVecs[i].v[2] = 1; corner[i] = setVec3(0, 0, 0); //assume no reflections if ((flipVecs[i].v[0]) < 1) corner[i].v[0] = h->dim[1] - 1; //reflect X if ((flipVecs[i].v[1]) < 1) corner[i].v[1] = h->dim[2] - 1; //reflect Y if ((flipVecs[i].v[2]) < 1) corner[i].v[2] = h->dim[3] - 1; //reflect Z corner[i] = xyz2mm(s, corner[i]); } //find extreme edge from ALL corners.... min = corner[0]; for (i = 1; i < 8; i++) { for (j = 0; j < 3; j++) { if (corner[i].v[j] < min.v[j]) min.v[j] = corner[i].v[j]; } } float dx; //observed distance from corner float min_dx = getDistance(corner[0], min); minIndex = 0; //index of corner closest to min //see if any corner is closer to absmin than the first one... for (i = 1; i < 8; i++) { dx = getDistance(corner[i], min); if (dx < min_dx) { min_dx = dx; minIndex = i; } } min = corner[minIndex]; //this is the single corner closest to min from all *flipVec = flipVecs[minIndex]; return min; } #ifdef MY_DEBUG void reportMat44o(char *str, mat44 A) { printMessage("%s = [%g %g %g %g; %g %g %g %g; %g %g %g %g; 0 0 0 1]\n", str, A.m[0][0], A.m[0][1], A.m[0][2], A.m[0][3], A.m[1][0], A.m[1][1], A.m[1][2], A.m[1][3], A.m[2][0], A.m[2][1], A.m[2][2], A.m[2][3]); } #endif unsigned char *nii_setOrtho(unsigned char *img, struct nifti_1_header *h) { if ((h->dim[1] < 1) || (h->dim[2] < 1) || (h->dim[3] < 1)) return img; if ((h->sform_code == NIFTI_XFORM_UNKNOWN) && (h->qform_code != NIFTI_XFORM_UNKNOWN)) { //only q-form provided mat44 q = nifti_quatern_to_mat44(h->quatern_b, h->quatern_c, h->quatern_d, h->qoffset_x, h->qoffset_y, h->qoffset_z, h->pixdim[1], h->pixdim[2], h->pixdim[3], h->pixdim[0]); mat2sForm(h, q); //convert q-form to s-form h->sform_code = h->qform_code; } if (h->sform_code == NIFTI_XFORM_UNKNOWN) { #ifdef MY_DEBUG printMessage("No Q or S spatial transforms - assuming canonical orientation"); #endif return img; } mat44 s = sFormMat(h); if (isMat44Canonical(s)) { #ifdef MY_DEBUG printMessage("Image in perfect alignment: no need to reorient"); #endif return img; } vec3i flipV; vec3 minMM = minCornerFlip(h, &flipV); mat33 orient = getBestOrient(s, flipV); vec3i orientVec = setOrientVec(orient); if ((orientVec.v[0] == 1) && (orientVec.v[1] == 2) && (orientVec.v[2] == 3)) { #ifdef MY_DEBUG printMessage("Image already near best orthogonal alignment: no need to reorient\n"); #endif return img; } bool is24 = false; if (h->bitpix == 24) { //RGB stored as planar data. treat as 3 8-bit slices return img; /*is24 = true; h->bitpix = 8; h->dim[3] = h->dim[3] * 3;*/ } img = reOrient(img, h, orientVec, orient, minMM); if (is24) { h->bitpix = 24; h->dim[3] = h->dim[3] / 3; } #ifdef MY_DEBUG printMessage("NewRotation= %d %d %d\n", orientVec.v[0], orientVec.v[1], orientVec.v[2]); printMessage("MinCorner= %.2f %.2f %.2f\n", minMM.v[0], minMM.v[1], minMM.v[2]); reportMat44o((char *)"input", s); s = sFormMat(h); reportMat44o((char *)"output", s); #endif return img; } dcm2niix-1.0.20211006/console/nii_ortho.h000066400000000000000000000005331412761503300175710ustar00rootroot00000000000000#ifndef _NIFTI_ORTHO_CORE_ #define _NIFTI_ORTHO_CORE_ #ifdef __cplusplus extern "C" { #endif #ifndef USING_R #include "nifti1.h" #endif void mat2sForm (struct nifti_1_header *h, mat44 s); bool isMat44Canonical(mat44 R); unsigned char * nii_setOrtho(unsigned char* img, struct nifti_1_header *h); #ifdef __cplusplus } #endif #endif dcm2niix-1.0.20211006/console/notarize.sh000077500000000000000000000056231412761503300176250ustar00rootroot00000000000000#!/bin/bash set -e #com.${COMPANY_NAME}.${APP_NAME} e.g. com.mricro.niimath COMPANY_NAME=mycompany APP_NAME=dcm2niix APP_SPECIFIC_PASSWORD=abcd-efgh-ijkl-mnop APPLE_ID_USER=myname@gmail.com APPLE_ID_INSTALL="Developer ID Installer: My Name" APPLE_ID_APP="Developer ID Application: My Name" if [[ "$APPLE_ID_USER" == "myname@gmail.com" ]] then echo "You need to set your personal IDs and password" exit 1 fi g++ -O3 -sectcreate __TEXT __info_plist Info.plist -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 dcm2niixX86 -DmyDisableOpenJPEG -target x86_64-apple-macos10.12 -mmacosx-version-min=10.12 g++ -O3 -sectcreate __TEXT __info_plist Info.plist -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 dcm2niixARM -DmyDisableOpenJPEG -target arm64-apple-macos11 -mmacosx-version-min=11.0 # Create the universal binary. strip ./dcm2niixARM; strip ./dcm2niixX86 lipo -create -output ${APP_NAME} dcm2niixARM dcm2niixX86 rm ./dcm2niixARM; rm ./dcm2niixX86 # Create a staging area for the installer package. mkdir -p usr/local/bin # Move the binary into the staging area. mv ${APP_NAME} usr/local/bin # Sign the binary. codesign --timestamp --options=runtime -s "${APPLE_ID_APP}" -v usr/local/bin/${APP_NAME} # Build the package. pkgbuild --identifier "com.${COMPANY_NAME}.${APP_NAME}.pkg" --sign "${APPLE_ID_INSTALL}" --timestamp --root usr/local --install-location /usr/local/ ${APP_NAME}.pkg # Submit the package to the notarization service. xcrun altool --notarize-app --primary-bundle-id "com.${COMPANY_NAME}.${APP_NAME}.pkg" --username $APPLE_ID_USER --password $APP_SPECIFIC_PASSWORD --file ${APP_NAME}.pkg --output-format xml > upload_log_file.txt # now we need to query apple's server to the status of notarization # when the "xcrun altool --notarize-app" command is finished the output plist # will contain a notarization-upload->RequestUUID key which we can use to check status echo "Checking status..." sleep 50 REQUEST_UUID=`/usr/libexec/PlistBuddy -c "Print :notarization-upload:RequestUUID" upload_log_file.txt` while true; do xcrun altool --notarization-info $REQUEST_UUID -u $APPLE_ID_USER -p $APP_SPECIFIC_PASSWORD --output-format xml > request_log_file.txt # parse the request plist for the notarization-info->Status Code key which will # be set to "success" if the package was notarized STATUS=`/usr/libexec/PlistBuddy -c "Print :notarization-info:Status" request_log_file.txt` if [ "$STATUS" != "in progress" ]; then break fi # echo $STATUS echo "$STATUS" sleep 10 done # download the log file to view any issues /usr/bin/curl -o log_file.txt `/usr/libexec/PlistBuddy -c "Print :notarization-info:LogFileURL" request_log_file.txt` # staple echo "Stapling..." xcrun stapler staple ${APP_NAME}.pkg xcrun stapler validate ${APP_NAME}.pkg open log_file.txt dcm2niix-1.0.20211006/console/print.h000066400000000000000000000052501412761503300167340ustar00rootroot00000000000000//This unit allows us to re-direct text messages // For standard C programs send text messages to the console via "printf" // The XCode project shows how you can re-direct these messages to a NSTextView // For QT programs, we can sent text messages to the cout buffer // The QT project shows how you can re-direct these to a Qtextedit // For R programs, we can intercept these messages. #ifndef _R_PRINT_H_ #define _R_PRINT_H_ #include #ifdef USING_R #define R_USE_C99_IN_CXX #include #define printMessage(...) do { Rprintf("[dcm2niix info] "); Rprintf(__VA_ARGS__); } while (0) #define printWarning(...) do { Rprintf("[dcm2niix WARNING] "); Rprintf(__VA_ARGS__); } while (0) #define printError(...) do { Rprintf("[dcm2niix ERROR] "); Rprintf(__VA_ARGS__); } while (0) #define printError(frac) do { Rprintf("[dcm2niix PROGRESS] %g", frac); } while (0) #else #ifdef myUseCOut //for piping output to Qtextedit // printf and cout buffers are not the same // #define printMessage(...) ({fprintf(stdout,__VA_ARGS__);}) #include template< typename... Args > void printMessage( const char* format, Args... args ) { //std::printf( format, args... ); //fprintf(stdout,"Short read on %s: Expected 512, got %zd\n",path, bytes_read); int length = std::snprintf( nullptr, 0, format, args... ); if ( length <= 0 ) return; char* buf = new char[length + 1]; std::snprintf( buf, length + 1, format, args... ); std::cout << buf; delete[] buf; } #define printError(...) do { printMessage("Error: "); printMessage(__VA_ARGS__);} while(0) #define printProgress(frac) do { printMessage("Progress: %g\n", frac);} while(0) #else #include #define printMessage printf //#define printMessageError(...) fprintf (stderr, __VA_ARGS__) #define printProgress(frac) do { printMessage("Progress: %g\n", frac);} while(0) #ifdef myErrorStdOut //for XCode MRIcro project, pipe errors to stdout not stderr #define printError(...) do { printMessage("Error: "); printMessage(__VA_ARGS__);} while(0) #else #define printError(...) do { fprintf (stderr,"Error: "); fprintf (stderr, __VA_ARGS__);} while(0) #endif #endif //myUseCOut //n.b. use ({}) for multi-line macros http://www.geeksforgeeks.org/multiline-macros-in-c/ //these next lines work on GCC but not _MSC_VER // #define printWarning(...) ({printMessage("Warning: "); printMessage(__VA_ARGS__);}) // #define printError(...) ({ printMessage("Error: "); printMessage(__VA_ARGS__);}) #define printWarning(...) do {printMessage("Warning: "); printMessage(__VA_ARGS__);} while(0) #endif //USING_R #endif //_R_PRINT_H_ dcm2niix-1.0.20211006/console/tinydir.h000066400000000000000000000210061412761503300172570ustar00rootroot00000000000000/* Copyright (c) 2013-2014, Cong Xu All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef TINYDIR_H #define TINYDIR_H #include #include #include #ifdef _MSC_VER #define WIN32_LEAN_AND_MEAN #include #pragma warning (disable : 4996) #else #include #include #endif /* types */ #define _TINYDIR_PATH_MAX 4096 #ifdef _MSC_VER /* extra chars for the "\\*" mask */ #define _TINYDIR_PATH_EXTRA 2 #else #define _TINYDIR_PATH_EXTRA 0 #endif #define _TINYDIR_FILENAME_MAX 256 #ifdef _MSC_VER #define _TINYDIR_FUNC static __inline #else #define _TINYDIR_FUNC static __inline__ #endif typedef struct { char path[_TINYDIR_PATH_MAX]; char name[_TINYDIR_FILENAME_MAX]; int is_dir; int is_reg; #ifdef _MSC_VER #else struct stat _s; #endif } tinydir_file; typedef struct { char path[_TINYDIR_PATH_MAX]; int has_next; size_t n_files; tinydir_file *_files; #ifdef _MSC_VER HANDLE _h; WIN32_FIND_DATA _f; #else DIR *_d; struct dirent *_e; #endif } tinydir_dir; /* declarations */ _TINYDIR_FUNC int tinydir_open(tinydir_dir *dir, const char *path); _TINYDIR_FUNC int tinydir_open_sorted(tinydir_dir *dir, const char *path); _TINYDIR_FUNC void tinydir_close(tinydir_dir *dir); _TINYDIR_FUNC int tinydir_next(tinydir_dir *dir); _TINYDIR_FUNC int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file); _TINYDIR_FUNC int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i); _TINYDIR_FUNC int tinydir_open_subdir_n(tinydir_dir *dir, size_t i); _TINYDIR_FUNC int _tinydir_file_cmp(const void *a, const void *b); /* definitions*/ _TINYDIR_FUNC int tinydir_open(tinydir_dir *dir, const char *path) { if (dir == NULL || path == NULL || strlen(path) == 0) { errno = EINVAL; return -1; } if (strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) { errno = ENAMETOOLONG; return -1; } /* initialise dir */ dir->_files = NULL; #ifdef _MSC_VER dir->_h = INVALID_HANDLE_VALUE; #else dir->_d = NULL; #endif tinydir_close(dir); strcpy(dir->path, path); #ifdef _MSC_VER strcat(dir->path, "\\*"); dir->_h = FindFirstFile(dir->path, &dir->_f); dir->path[strlen(dir->path) - 2] = '\0'; if (dir->_h == INVALID_HANDLE_VALUE) #else dir->_d = opendir(path); if (dir->_d == NULL) #endif { errno = ENOENT; goto bail; } /* read first file */ dir->has_next = 1; #ifndef _MSC_VER dir->_e = readdir(dir->_d); if (dir->_e == NULL) { dir->has_next = 0; } #endif return 0; bail: tinydir_close(dir); return -1; } _TINYDIR_FUNC int tinydir_open_sorted(tinydir_dir *dir, const char *path) { /* Count the number of files first, to pre-allocate the files array */ size_t n_files = 0; if (tinydir_open(dir, path) == -1) { return -1; } while (dir->has_next) { n_files++; if (tinydir_next(dir) == -1) { goto bail; } } tinydir_close(dir); if (tinydir_open(dir, path) == -1) { return -1; } dir->n_files = 0; dir->_files = (tinydir_file *)malloc(sizeof *dir->_files * n_files); if (dir->_files == NULL) { errno = ENOMEM; goto bail; } while (dir->has_next) { tinydir_file *p_file; dir->n_files++; p_file = &dir->_files[dir->n_files - 1]; if (tinydir_readfile(dir, p_file) == -1) { goto bail; } if (tinydir_next(dir) == -1) { goto bail; } /* Just in case the number of files has changed between the first and second reads, terminate without writing into unallocated memory */ if (dir->n_files == n_files) { break; } } qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp); return 0; bail: tinydir_close(dir); return -1; } _TINYDIR_FUNC void tinydir_close(tinydir_dir *dir) { if (dir == NULL) { return; } memset(dir->path, 0, sizeof(dir->path)); dir->has_next = 0; dir->n_files = 0; if (dir->_files != NULL) { free(dir->_files); } dir->_files = NULL; #ifdef _MSC_VER if (dir->_h != INVALID_HANDLE_VALUE) { FindClose(dir->_h); } dir->_h = INVALID_HANDLE_VALUE; #else if (dir->_d) { closedir(dir->_d); } dir->_d = NULL; dir->_e = NULL; #endif } _TINYDIR_FUNC int tinydir_next(tinydir_dir *dir) { if (dir == NULL) { errno = EINVAL; return -1; } if (!dir->has_next) { errno = ENOENT; return -1; } #ifdef _MSC_VER if (FindNextFile(dir->_h, &dir->_f) == 0) #else dir->_e = readdir(dir->_d); if (dir->_e == NULL) #endif { dir->has_next = 0; #ifdef _MSC_VER if (GetLastError() != ERROR_SUCCESS && GetLastError() != ERROR_NO_MORE_FILES) { tinydir_close(dir); errno = EIO; return -1; } #endif } return 0; } _TINYDIR_FUNC int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file) { if (dir == NULL || file == NULL) { errno = EINVAL; return -1; } #ifdef _MSC_VER if (dir->_h == INVALID_HANDLE_VALUE) #else if (dir->_e == NULL) #endif { errno = ENOENT; return -1; } if (strlen(dir->path) + strlen( #ifdef _MSC_VER dir->_f.cFileName #else dir->_e->d_name #endif ) + 1 + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) { /* the path for the file will be too long */ errno = ENAMETOOLONG; return -1; } if (strlen( #ifdef _MSC_VER dir->_f.cFileName #else dir->_e->d_name #endif ) >= _TINYDIR_FILENAME_MAX) { errno = ENAMETOOLONG; return -1; } strcpy(file->path, dir->path); strcat(file->path, "/"); strcpy(file->name, #ifdef _MSC_VER dir->_f.cFileName #else dir->_e->d_name #endif ); /* Limit the number of bytes copied to the maximum length of the name, to avoid spurious compiler warnings about possible overlap */ strncat(file->path, file->name, _TINYDIR_FILENAME_MAX); #ifndef _MSC_VER if (stat(file->path, &file->_s) == -1) { return -1; } #endif file->is_dir = #ifdef _MSC_VER !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); #else S_ISDIR(file->_s.st_mode); #endif file->is_reg = #ifdef _MSC_VER !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || ( !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) && !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) && #ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) && #endif #ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) && #endif !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) && !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)); #else S_ISREG(file->_s.st_mode); #endif return 0; } _TINYDIR_FUNC int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i) { if (dir == NULL || file == NULL) { errno = EINVAL; return -1; } if (i >= dir->n_files) { errno = ENOENT; return -1; } memcpy(file, &dir->_files[i], sizeof(tinydir_file)); return 0; } _TINYDIR_FUNC int tinydir_open_subdir_n(tinydir_dir *dir, size_t i) { char path[_TINYDIR_PATH_MAX]; if (dir == NULL) { errno = EINVAL; return -1; } if (i >= dir->n_files || !dir->_files[i].is_dir) { errno = ENOENT; return -1; } strcpy(path, dir->_files[i].path); tinydir_close(dir); if (tinydir_open_sorted(dir, path) == -1) { return -1; } return 0; } _TINYDIR_FUNC int _tinydir_file_cmp(const void *a, const void *b) { const tinydir_file *fa = (const tinydir_file *)a; const tinydir_file *fb = (const tinydir_file *)b; if (fa->is_dir != fb->is_dir) { return -(fa->is_dir - fb->is_dir); } return strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX); } #endif dcm2niix-1.0.20211006/console/ucm.cmake000066400000000000000000000600361412761503300172200ustar00rootroot00000000000000# # ucm.cmake - useful cmake macros # # Copyright (c) 2016 Viktor Kirilov # # Distributed under the MIT Software License # See accompanying file LICENSE.txt or copy at # https://opensource.org/licenses/MIT # # The documentation can be found at the library's page: # https://github.com/onqtam/ucm cmake_minimum_required(VERSION 2.8.12) include(CMakeParseArguments) # optionally include cotire - the git submodule might not be inited (or the user might have already included it) if(NOT COMMAND cotire) include(${CMAKE_CURRENT_LIST_DIR}/../cotire/CMake/cotire.cmake OPTIONAL) endif() if(COMMAND cotire AND "1.7.9" VERSION_LESS "${COTIRE_CMAKE_MODULE_VERSION}") set(ucm_with_cotire 1) else() set(ucm_with_cotire 0) endif() # option(UCM_UNITY_BUILD "Enable unity build for targets registered with the ucm_add_target() macro" OFF) # option(UCM_NO_COTIRE_FOLDER "Do not use a cotire folder in the solution explorer for all unity and cotire related targets" ON) # ucm_add_flags # Adds compiler flags to CMAKE__FLAGS or to a specific config macro(ucm_add_flags) cmake_parse_arguments(ARG "C;CXX;CLEAR_OLD" "" "CONFIG" ${ARGN}) if(NOT ARG_CONFIG) set(ARG_CONFIG " ") endif() foreach(CONFIG ${ARG_CONFIG}) # determine to which flags to add if(NOT ${CONFIG} STREQUAL " ") string(TOUPPER ${CONFIG} CONFIG) set(CXX_FLAGS CMAKE_CXX_FLAGS_${CONFIG}) set(C_FLAGS CMAKE_C_FLAGS_${CONFIG}) else() set(CXX_FLAGS CMAKE_CXX_FLAGS) set(C_FLAGS CMAKE_C_FLAGS) endif() # clear the old flags if(${ARG_CLEAR_OLD}) if("${ARG_CXX}" OR NOT "${ARG_C}") set(${CXX_FLAGS} "") endif() if("${ARG_C}" OR NOT "${ARG_CXX}") set(${C_FLAGS} "") endif() endif() # add all the passed flags foreach(flag ${ARG_UNPARSED_ARGUMENTS}) if("${ARG_CXX}" OR NOT "${ARG_C}") set(${CXX_FLAGS} "${${CXX_FLAGS}} ${flag}") endif() if("${ARG_C}" OR NOT "${ARG_CXX}") set(${C_FLAGS} "${${C_FLAGS}} ${flag}") endif() endforeach() endforeach() endmacro() # ucm_set_flags # Sets the CMAKE__FLAGS compiler flags or for a specific config macro(ucm_set_flags) ucm_add_flags(CLEAR_OLD ${ARGN}) endmacro() # ucm_add_linker_flags # Adds linker flags to CMAKE__LINKER_FLAGS or to a specific config macro(ucm_add_linker_flags) cmake_parse_arguments(ARG "CLEAR_OLD;EXE;MODULE;SHARED;STATIC" "" "CONFIG" ${ARGN}) if(NOT ARG_CONFIG) set(ARG_CONFIG " ") endif() foreach(CONFIG ${ARG_CONFIG}) string(TOUPPER "${CONFIG}" CONFIG) if(NOT ${ARG_EXE} AND NOT ${ARG_MODULE} AND NOT ${ARG_SHARED} AND NOT ${ARG_STATIC}) set(ARG_EXE 1) set(ARG_MODULE 1) set(ARG_SHARED 1) set(ARG_STATIC 1) endif() set(flags_configs "") if(${ARG_EXE}) if(NOT "${CONFIG}" STREQUAL " ") list(APPEND flags_configs CMAKE_EXE_LINKER_FLAGS_${CONFIG}) else() list(APPEND flags_configs CMAKE_EXE_LINKER_FLAGS) endif() endif() if(${ARG_MODULE}) if(NOT "${CONFIG}" STREQUAL " ") list(APPEND flags_configs CMAKE_MODULE_LINKER_FLAGS_${CONFIG}) else() list(APPEND flags_configs CMAKE_MODULE_LINKER_FLAGS) endif() endif() if(${ARG_SHARED}) if(NOT "${CONFIG}" STREQUAL " ") list(APPEND flags_configs CMAKE_SHARED_LINKER_FLAGS_${CONFIG}) else() list(APPEND flags_configs CMAKE_SHARED_LINKER_FLAGS) endif() endif() if(${ARG_STATIC}) if(NOT "${CONFIG}" STREQUAL " ") list(APPEND flags_configs CMAKE_STATIC_LINKER_FLAGS_${CONFIG}) else() list(APPEND flags_configs CMAKE_STATIC_LINKER_FLAGS) endif() endif() # clear the old flags if(${ARG_CLEAR_OLD}) foreach(flags ${flags_configs}) set(${flags} "") endforeach() endif() # add all the passed flags foreach(flag ${ARG_UNPARSED_ARGUMENTS}) foreach(flags ${flags_configs}) set(${flags} "${${flags}} ${flag}") endforeach() endforeach() endforeach() endmacro() # ucm_set_linker_flags # Sets the CMAKE__LINKER_FLAGS linker flags or for a specific config macro(ucm_set_linker_flags) ucm_add_linker_flags(CLEAR_OLD ${ARGN}) endmacro() # ucm_gather_flags # Gathers all lists of flags for printing or manipulation macro(ucm_gather_flags with_linker result) set(${result} "") # add the main flags without a config list(APPEND ${result} CMAKE_C_FLAGS) list(APPEND ${result} CMAKE_CXX_FLAGS) if(${with_linker}) list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS) list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS) list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS) list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS) endif() if("${CMAKE_CONFIGURATION_TYPES}" STREQUAL "" AND NOT "${CMAKE_BUILD_TYPE}" STREQUAL "") # handle single config generators - like makefiles/ninja - when CMAKE_BUILD_TYPE is set string(TOUPPER ${CMAKE_BUILD_TYPE} config) list(APPEND ${result} CMAKE_C_FLAGS_${config}) list(APPEND ${result} CMAKE_CXX_FLAGS_${config}) if(${with_linker}) list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS_${config}) list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS_${config}) list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS_${config}) list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS_${config}) endif() else() # handle multi config generators (like msvc, xcode) foreach(config ${CMAKE_CONFIGURATION_TYPES}) string(TOUPPER ${config} config) list(APPEND ${result} CMAKE_C_FLAGS_${config}) list(APPEND ${result} CMAKE_CXX_FLAGS_${config}) if(${with_linker}) list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS_${config}) list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS_${config}) list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS_${config}) list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS_${config}) endif() endforeach() endif() endmacro() # ucm_set_runtime # Sets the runtime (static/dynamic) for msvc/gcc macro(ucm_set_runtime) cmake_parse_arguments(ARG "STATIC;DYNAMIC" "" "" ${ARGN}) if(ARG_UNPARSED_ARGUMENTS) message(FATAL_ERROR "unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") endif() if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" STREQUAL "") message(AUTHOR_WARNING "ucm_set_runtime() does not support clang yet!") endif() ucm_gather_flags(0 flags_configs) # add/replace the flags # note that if the user has messed with the flags directly this function might fail # - for example if with MSVC and the user has removed the flags - here we just switch/replace them if("${ARG_STATIC}") foreach(flags ${flags_configs}) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.4.7) # option "-static-libstdc++" available since GCC 4.5 if(NOT ${flags} MATCHES "-static-libstdc\\+\\+") set(${flags} "${${flags}} -static-libstdc++") endif() endif() if(NOT ${flags} MATCHES "-static-libgcc") set(${flags} "${${flags}} -static-libgcc") endif() elseif(MSVC) if(${flags} MATCHES "/MD") string(REGEX REPLACE "/MD" "/MT" ${flags} "${${flags}}") endif() endif() endforeach() elseif("${ARG_DYNAMIC}") foreach(flags ${flags_configs}) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") if(${flags} MATCHES "-static-libstdc\\+\\+") string(REGEX REPLACE "-static-libstdc\\+\\+" "" ${flags} "${${flags}}") endif() if(${flags} MATCHES "-static-libgcc") string(REGEX REPLACE "-static-libgcc" "" ${flags} "${${flags}}") endif() elseif(MSVC) if(${flags} MATCHES "/MT") string(REGEX REPLACE "/MT" "/MD" ${flags} "${${flags}}") endif() endif() endforeach() endif() endmacro() # ucm_print_flags # Prints all compiler flags for all configurations macro(ucm_print_flags) ucm_gather_flags(1 flags_configs) message("") foreach(flags ${flags_configs}) message("${flags}: ${${flags}}") endforeach() message("") endmacro() # ucm_count_sources # Counts the number of source files macro(ucm_count_sources) cmake_parse_arguments(ARG "" "RESULT" "" ${ARGN}) if(${ARG_RESULT} STREQUAL "") message(FATAL_ERROR "Need to pass RESULT and a variable name to ucm_count_sources()") endif() set(result 0) foreach(SOURCE_FILE ${ARG_UNPARSED_ARGUMENTS}) if("${SOURCE_FILE}" MATCHES \\.\(c|C|cc|cp|cpp|CPP|c\\+\\+|cxx|i|ii\)$) math(EXPR result "${result} + 1") endif() endforeach() set(${ARG_RESULT} ${result}) endmacro() # ucm_include_file_in_sources # Includes the file to the source with compiler flags macro(ucm_include_file_in_sources) cmake_parse_arguments(ARG "" "HEADER" "" ${ARGN}) if(${ARG_HEADER} STREQUAL "") message(FATAL_ERROR "Need to pass HEADER and a header file to ucm_include_file_in_sources()") endif() foreach(src ${ARG_UNPARSED_ARGUMENTS}) if(${src} MATCHES \\.\(c|C|cc|cp|cpp|CPP|c\\+\\+|cxx\)$) # get old flags get_source_file_property(old_compile_flags ${src} COMPILE_FLAGS) if(old_compile_flags STREQUAL "NOTFOUND") set(old_compile_flags "") endif() # update flags if(MSVC) set_source_files_properties(${src} PROPERTIES COMPILE_FLAGS "${old_compile_flags} /FI\"${CMAKE_CURRENT_SOURCE_DIR}/${ARG_HEADER}\"") else() set_source_files_properties(${src} PROPERTIES COMPILE_FLAGS "${old_compile_flags} -include \"${CMAKE_CURRENT_SOURCE_DIR}/${ARG_HEADER}\"") endif() endif() endforeach() endmacro() # ucm_dir_list # Returns a list of subdirectories for a given directory macro(ucm_dir_list thedir result) file(GLOB sub-dir "${thedir}/*") set(list_of_dirs "") foreach(dir ${sub-dir}) if(IS_DIRECTORY ${dir}) get_filename_component(DIRNAME ${dir} NAME) LIST(APPEND list_of_dirs ${DIRNAME}) endif() endforeach() set(${result} ${list_of_dirs}) endmacro() # ucm_trim_front_words # Trims X times the front word from a string separated with "/" and removes # the front "/" characters after that (used for filters for visual studio) macro(ucm_trim_front_words source out num_filter_trims) set(result "${source}") set(counter 0) while(${counter} LESS ${num_filter_trims}) MATH(EXPR counter "${counter} + 1") # removes everything at the front up to a "/" character string(REGEX REPLACE "^([^/]+)" "" result "${result}") # removes all consecutive "/" characters from the front string(REGEX REPLACE "^(/+)" "" result "${result}") endwhile() set(${out} ${result}) endmacro() # ucm_remove_files # Removes source files from a list of sources (path is the relative path for it to be found) macro(ucm_remove_files) cmake_parse_arguments(ARG "" "FROM" "" ${ARGN}) if("${ARG_UNPARSED_ARGUMENTS}" STREQUAL "") message(FATAL_ERROR "Need to pass some relative files to ucm_remove_files()") endif() if(${ARG_FROM} STREQUAL "") message(FATAL_ERROR "Need to pass FROM and a variable name to ucm_remove_files()") endif() foreach(cur_file ${ARG_UNPARSED_ARGUMENTS}) list(REMOVE_ITEM ${ARG_FROM} ${cur_file}) endforeach() endmacro() # ucm_remove_directories # Removes all source files from the given directories from the sources list macro(ucm_remove_directories) cmake_parse_arguments(ARG "" "FROM" "MATCHES" ${ARGN}) if("${ARG_UNPARSED_ARGUMENTS}" STREQUAL "") message(FATAL_ERROR "Need to pass some relative directories to ucm_remove_directories()") endif() if(${ARG_FROM} STREQUAL "") message(FATAL_ERROR "Need to pass FROM and a variable name to ucm_remove_directories()") endif() foreach(cur_dir ${ARG_UNPARSED_ARGUMENTS}) foreach(cur_file ${${ARG_FROM}}) string(REGEX MATCH ${cur_dir} res ${cur_file}) if(NOT "${res}" STREQUAL "") if("${ARG_MATCHES}" STREQUAL "") list(REMOVE_ITEM ${ARG_FROM} ${cur_file}) else() foreach(curr_ptrn ${ARG_MATCHES}) string(REGEX MATCH ${curr_ptrn} res ${cur_file}) if(NOT "${res}" STREQUAL "") list(REMOVE_ITEM ${ARG_FROM} ${cur_file}) break() endif() endforeach() endif() endif() endforeach() endforeach() endmacro() # ucm_add_files_impl macro(ucm_add_files_impl result trim files) foreach(cur_file ${files}) SET(${result} ${${result}} ${cur_file}) get_filename_component(FILEPATH ${cur_file} PATH) ucm_trim_front_words("${FILEPATH}" FILEPATH "${trim}") # replacing forward slashes with back slashes so filters can be generated (back slash used in parsing...) STRING(REPLACE "/" "\\" FILTERS "${FILEPATH}") SOURCE_GROUP("${FILTERS}" FILES ${cur_file}) endforeach() endmacro() # ucm_add_files # Adds files to a list of sources macro(ucm_add_files) cmake_parse_arguments(ARG "" "TO;FILTER_POP" "" ${ARGN}) if("${ARG_UNPARSED_ARGUMENTS}" STREQUAL "") message(FATAL_ERROR "Need to pass some relative files to ucm_add_files()") endif() if(${ARG_TO} STREQUAL "") message(FATAL_ERROR "Need to pass TO and a variable name to ucm_add_files()") endif() if("${ARG_FILTER_POP}" STREQUAL "") set(ARG_FILTER_POP 0) endif() ucm_add_files_impl(${ARG_TO} ${ARG_FILTER_POP} "${ARG_UNPARSED_ARGUMENTS}") endmacro() # ucm_add_dir_impl macro(ucm_add_dir_impl result rec trim dirs_in additional_ext) set(dirs "${dirs_in}") # handle the "" and "." cases if("${dirs}" STREQUAL "" OR "${dirs}" STREQUAL ".") set(dirs "./") endif() foreach(cur_dir ${dirs}) # to circumvent some linux/cmake/path issues - barely made it work... if(cur_dir STREQUAL "./") set(cur_dir "") else() set(cur_dir "${cur_dir}/") endif() # since unix is case sensitive - add these valid extensions too # we don't use "UNIX" but instead "CMAKE_HOST_UNIX" because we might be cross # compiling (for example emscripten) under windows and UNIX may be set to 1 # Also OSX is case insensitive like windows... set(additional_file_extensions "") if(CMAKE_HOST_UNIX AND NOT APPLE) set(additional_file_extensions "${cur_dir}*.CPP" "${cur_dir}*.C" "${cur_dir}*.H" "${cur_dir}*.HPP" ) endif() foreach(ext ${additional_ext}) list(APPEND additional_file_extensions "${cur_dir}*.${ext}") endforeach() # find all sources and set them as result FILE(GLOB found_sources RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" # https://gcc.gnu.org/onlinedocs/gcc-4.4.1/gcc/Overall-Options.html#index-file-name-suffix-71 # sources "${cur_dir}*.cpp" "${cur_dir}*.cxx" "${cur_dir}*.c++" "${cur_dir}*.cc" "${cur_dir}*.cp" "${cur_dir}*.c" "${cur_dir}*.i" "${cur_dir}*.ii" # headers "${cur_dir}*.h" "${cur_dir}*.h++" "${cur_dir}*.hpp" "${cur_dir}*.hxx" "${cur_dir}*.hh" "${cur_dir}*.inl" "${cur_dir}*.inc" "${cur_dir}*.ipp" "${cur_dir}*.ixx" "${cur_dir}*.txx" "${cur_dir}*.tpp" "${cur_dir}*.tcc" "${cur_dir}*.tpl" ${additional_file_extensions}) SET(${result} ${${result}} ${found_sources}) # set the proper filters ucm_trim_front_words("${cur_dir}" cur_dir "${trim}") # replacing forward slashes with back slashes so filters can be generated (back slash used in parsing...) STRING(REPLACE "/" "\\" FILTERS "${cur_dir}") SOURCE_GROUP("${FILTERS}" FILES ${found_sources}) endforeach() if(${rec}) foreach(cur_dir ${dirs}) ucm_dir_list("${cur_dir}" subdirs) foreach(subdir ${subdirs}) ucm_add_dir_impl(${result} ${rec} ${trim} "${cur_dir}/${subdir}" "${additional_ext}") endforeach() endforeach() endif() endmacro() # ucm_add_dirs # Adds all files from directories traversing them recursively to a list of sources # and generates filters according to their location (accepts relative paths only). # Also this macro trims X times the front word from the filter string for visual studio filters. macro(ucm_add_dirs) cmake_parse_arguments(ARG "RECURSIVE" "TO;FILTER_POP" "ADDITIONAL_EXT" ${ARGN}) if(${ARG_TO} STREQUAL "") message(FATAL_ERROR "Need to pass TO and a variable name to ucm_add_dirs()") endif() if("${ARG_FILTER_POP}" STREQUAL "") set(ARG_FILTER_POP 0) endif() ucm_add_dir_impl(${ARG_TO} ${ARG_RECURSIVE} ${ARG_FILTER_POP} "${ARG_UNPARSED_ARGUMENTS}" "${ARG_ADDITIONAL_EXT}") endmacro() # ucm_add_target # Adds a target eligible for cotiring - unity build and/or precompiled header macro(ucm_add_target) cmake_parse_arguments(ARG "UNITY" "NAME;TYPE;PCH_FILE;CPP_PER_UNITY" "UNITY_EXCLUDED;SOURCES" ${ARGN}) if(NOT "${ARG_UNPARSED_ARGUMENTS}" STREQUAL "") message(FATAL_ERROR "Unrecognized options passed to ucm_add_target()") endif() if("${ARG_NAME}" STREQUAL "") message(FATAL_ERROR "Need to pass NAME and a name for the target to ucm_add_target()") endif() set(valid_types EXECUTABLE STATIC SHARED MODULE) list(FIND valid_types "${ARG_TYPE}" is_type_valid) if(${is_type_valid} STREQUAL "-1") message(FATAL_ERROR "Need to pass TYPE and the type for the target [EXECUTABLE/STATIC/SHARED/MODULE] to ucm_add_target()") endif() if("${ARG_SOURCES}" STREQUAL "") message(FATAL_ERROR "Need to pass SOURCES and a list of source files to ucm_add_target()") endif() # init with the global unity flag set(do_unity ${UCM_UNITY_BUILD}) # check the UNITY argument if(NOT ARG_UNITY) set(do_unity FALSE) endif() # if target is excluded through the exclusion list list(FIND UCM_UNITY_BUILD_EXCLUDE_TARGETS ${ARG_NAME} is_target_excluded) if(NOT ${is_target_excluded} STREQUAL "-1") set(do_unity FALSE) endif() # unity build only for targets with > 1 source file (otherwise there will be an additional unnecessary target) if(do_unity) # optimization ucm_count_sources(${ARG_SOURCES} RESULT num_sources) if(${num_sources} LESS 2) set(do_unity FALSE) endif() endif() set(wanted_cotire ${do_unity}) # if cotire cannot be used if(do_unity AND NOT ucm_with_cotire) set(do_unity FALSE) endif() # inform the developer that the current target might benefit from a unity build if(NOT ARG_UNITY AND ${UCM_UNITY_BUILD}) ucm_count_sources(${ARG_SOURCES} RESULT num_sources) if(${num_sources} GREATER 1) message(AUTHOR_WARNING "Target '${ARG_NAME}' may benefit from a unity build.\nIt has ${num_sources} sources - enable with UNITY flag") endif() endif() # prepare for the unity build set(orig_target ${ARG_NAME}) if(do_unity) # the original target will be added with a different name than the requested set(orig_target ${ARG_NAME}_ORIGINAL) # exclude requested files from unity build of the current target foreach(excluded_file "${ARG_UNITY_EXCLUDED}") set_source_files_properties(${excluded_file} PROPERTIES COTIRE_EXCLUDED TRUE) endforeach() endif() # add the original target if(${ARG_TYPE} STREQUAL "EXECUTABLE") add_executable(${orig_target} ${ARG_SOURCES}) else() add_library(${orig_target} ${ARG_TYPE} ${ARG_SOURCES}) endif() if(do_unity) # set the number of unity cpp files to be used for the unity target if(NOT "${ARG_CPP_PER_UNITY}" STREQUAL "") set_property(TARGET ${orig_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "${ARG_CPP_PER_UNITY}") else() set_property(TARGET ${orig_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "100") endif() if(NOT "${ARG_PCH_FILE}" STREQUAL "") set_target_properties(${orig_target} PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "${ARG_PCH_FILE}") else() set_target_properties(${orig_target} PROPERTIES COTIRE_ENABLE_PRECOMPILED_HEADER FALSE) endif() # add a unity target for the original one with the name intended for the original set_target_properties(${orig_target} PROPERTIES COTIRE_UNITY_TARGET_NAME ${ARG_NAME}) # this is the library call that does the magic cotire(${orig_target}) set_target_properties(clean_cotire PROPERTIES FOLDER "CMakePredefinedTargets") # disable the original target and enable the unity one get_target_property(unity_target_name ${orig_target} COTIRE_UNITY_TARGET_NAME) set_target_properties(${orig_target} PROPERTIES EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1) set_target_properties(${unity_target_name} PROPERTIES EXCLUDE_FROM_ALL 0 EXCLUDE_FROM_DEFAULT_BUILD 0) # also set the name of the target output as the original one set_target_properties(${unity_target_name} PROPERTIES OUTPUT_NAME ${ARG_NAME}) if(UCM_NO_COTIRE_FOLDER) # reset the folder property so all unity targets dont end up in a single folder in the solution explorer of VS set_target_properties(${unity_target_name} PROPERTIES FOLDER "") endif() set_target_properties(all_unity PROPERTIES FOLDER "CMakePredefinedTargets") elseif(NOT "${ARG_PCH_FILE}" STREQUAL "") set(wanted_cotire TRUE) if(ucm_with_cotire) set_target_properties(${orig_target} PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) set_target_properties(${orig_target} PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "${ARG_PCH_FILE}") cotire(${orig_target}) set_target_properties(clean_cotire PROPERTIES FOLDER "CMakePredefinedTargets") endif() endif() # print a message if the target was requested to be cotired but it couldn't if(wanted_cotire AND NOT ucm_with_cotire) if(NOT COMMAND cotire) message(AUTHOR_WARNING "Target \"${ARG_NAME}\" not cotired because cotire isn't loaded") else() message(AUTHOR_WARNING "Target \"${ARG_NAME}\" not cotired because cotire is older than the required version") endif() endif() endmacro() dcm2niix-1.0.20211006/console/ujpeg.cpp000066400000000000000000000761741412761503300172620ustar00rootroot00000000000000// NanoJPEG -- KeyJ's Tiny Baseline JPEG Decoder // version 1.3.5 (2016-11-14) // Copyright (c) 2009-2016 Martin J. Fiedler // published under the terms of the MIT license // // 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. /////////////////////////////////////////////////////////////////////////////// // DOCUMENTATION SECTION // // read this if you want to know what this is all about // /////////////////////////////////////////////////////////////////////////////// // INTRODUCTION // ============ // // This is a minimal decoder for baseline JPEG images. It accepts memory dumps // of JPEG files as input and generates either 8-bit grayscale or packed 24-bit // RGB images as output. It does not parse JFIF or Exif headers; all JPEG files // are assumed to be either grayscale or YCbCr. CMYK or other color spaces are // not supported. All YCbCr subsampling schemes with power-of-two ratios are // supported, as are restart intervals. Progressive or lossless JPEG is not // supported. // Summed up, NanoJPEG should be able to decode all images from digital cameras // and most common forms of other non-progressive JPEG images. // The decoder is not optimized for speed, it's optimized for simplicity and // small code. Image quality should be at a reasonable level. A bicubic chroma // upsampling filter ensures that subsampled YCbCr images are rendered in // decent quality. The decoder is not meant to deal with broken JPEG files in // a graceful manner; if anything is wrong with the bitstream, decoding will // simply fail. // The code should work with every modern C compiler without problems and // should not emit any warnings. It uses only (at least) 32-bit integer // arithmetic and is supposed to be endianness independent and 64-bit clean. // However, it is not thread-safe. // COMPILE-TIME CONFIGURATION // ========================== // // The following aspects of NanoJPEG can be controlled with preprocessor // defines: // // _NJ_EXAMPLE_PROGRAM = Compile a main() function with an example // program. // _NJ_INCLUDE_HEADER_ONLY = Don't compile anything, just act as a header // file for NanoJPEG. Example: // #define _NJ_INCLUDE_HEADER_ONLY // #include "nanojpeg.c" // int main(void) { // njInit(); // // your code here // njDone(); // } // NJ_USE_LIBC=1 = Use the malloc(), free(), memset() and memcpy() // functions from the standard C library (default). // NJ_USE_LIBC=0 = Don't use the standard C library. In this mode, // external functions njAlloc(), njFreeMem(), // njFillMem() and njCopyMem() need to be defined // and implemented somewhere. // NJ_USE_WIN32=0 = Normal mode (default). // NJ_USE_WIN32=1 = If compiling with MSVC for Win32 and // NJ_USE_LIBC=0, NanoJPEG will use its own // implementations of the required C library // functions (default if compiling with MSVC and // NJ_USE_LIBC=0). // NJ_CHROMA_FILTER=1 = Use the bicubic chroma upsampling filter // (default). // NJ_CHROMA_FILTER=0 = Use simple pixel repetition for chroma upsampling // (bad quality, but faster and less code). // API // === // // For API documentation, read the "header section" below. // EXAMPLE // ======= // // A few pages below, you can find an example program that uses NanoJPEG to // convert JPEG files into PGM or PPM. To compile it, use something like // gcc -O3 -D_NJ_EXAMPLE_PROGRAM -o nanojpeg nanojpeg.c // You may also add -std=c99 -Wall -Wextra -pedantic -Werror, if you want :) // The only thing you might need is -Wno-shift-negative-value, because this // code relies on the target machine using two's complement arithmetic, but // the C standard does not, even though *any* practically useful machine // nowadays uses two's complement. /////////////////////////////////////////////////////////////////////////////// // HEADER SECTION // // copy and paste this into nanojpeg.h if you want // /////////////////////////////////////////////////////////////////////////////// #ifndef _NANOJPEG_H #define _NANOJPEG_H // nj_result_t: Result codes for njDecode(). typedef enum _nj_result { NJ_OK = 0, // no error, decoding successful NJ_NO_JPEG, // not a JPEG file NJ_UNSUPPORTED, // unsupported format NJ_OUT_OF_MEM, // out of memory NJ_INTERNAL_ERR, // internal error NJ_SYNTAX_ERROR, // syntax error __NJ_FINISHED, // used internally, will never be reported } nj_result_t; // njInit: Initialize NanoJPEG. // For safety reasons, this should be called at least one time before using // using any of the other NanoJPEG functions. void njInit(void); // njDecode: Decode a JPEG image. // Decodes a memory dump of a JPEG file into internal buffers. // Parameters: // jpeg = The pointer to the memory dump. // size = The size of the JPEG file. // Return value: The error code in case of failure, or NJ_OK (zero) on success. nj_result_t njDecode(const void* jpeg, const int size); // njGetWidth: Return the width (in pixels) of the most recently decoded // image. If njDecode() failed, the result of njGetWidth() is undefined. int njGetWidth(void); // njGetHeight: Return the height (in pixels) of the most recently decoded // image. If njDecode() failed, the result of njGetHeight() is undefined. int njGetHeight(void); // njIsColor: Return 1 if the most recently decoded image is a color image // (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result // of njGetWidth() is undefined. int njIsColor(void); // njGetImage: Returns the decoded image data. // Returns a pointer to the most recently image. The memory layout it byte- // oriented, top-down, without any padding between lines. Pixels of color // images will be stored as three consecutive bytes for the red, green and // blue channels. This data format is thus compatible with the PGM or PPM // file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8. // If njDecode() failed, the result of njGetImage() is undefined. unsigned char* njGetImage(void); // njGetImageSize: Returns the size (in bytes) of the image data returned // by njGetImage(). If njDecode() failed, the result of njGetImageSize() is // undefined. int njGetImageSize(void); // njDone: Uninitialize NanoJPEG. // Resets NanoJPEG's internal state and frees all memory that has been // allocated at run-time by NanoJPEG. It is still possible to decode another // image after a njDone() call. void njDone(void); #endif//_NANOJPEG_H /////////////////////////////////////////////////////////////////////////////// // CONFIGURATION SECTION // // adjust the default settings for the NJ_ defines here // /////////////////////////////////////////////////////////////////////////////// #ifndef NJ_USE_LIBC #define NJ_USE_LIBC 1 #endif #ifndef NJ_USE_WIN32 #ifdef _MSC_VER #define NJ_USE_WIN32 (!NJ_USE_LIBC) #else #define NJ_USE_WIN32 0 #endif #endif #ifndef NJ_CHROMA_FILTER #define NJ_CHROMA_FILTER 1 #endif /////////////////////////////////////////////////////////////////////////////// // EXAMPLE PROGRAM // // just define _NJ_EXAMPLE_PROGRAM to compile this (requires NJ_USE_LIBC) // /////////////////////////////////////////////////////////////////////////////// #ifdef _NJ_EXAMPLE_PROGRAM #include #include #include int main(int argc, char* argv[]) { int size; char *buf; FILE *f; if (argc < 2) { printf("Usage: %s []\n", argv[0]); return 2; } f = fopen(argv[1], "rb"); if (!f) { printf("Error opening the input file.\n"); return 1; } fseek(f, 0, SEEK_END); size = (int) ftell(f); buf = (char*) malloc(size); fseek(f, 0, SEEK_SET); size = (int) fread(buf, 1, size, f); fclose(f); njInit(); if (njDecode(buf, size)) { free((void*)buf); printf("Error decoding the input file.\n"); return 1; } free((void*)buf); f = fopen((argc > 2) ? argv[2] : (njIsColor() ? "nanojpeg_out.ppm" : "nanojpeg_out.pgm"), "wb"); if (!f) { printf("Error opening the output file.\n"); return 1; } fprintf(f, "P%d\n%d %d\n255\n", njIsColor() ? 6 : 5, njGetWidth(), njGetHeight()); fwrite(njGetImage(), 1, njGetImageSize(), f); fclose(f); njDone(); return 0; } #endif /////////////////////////////////////////////////////////////////////////////// // IMPLEMENTATION SECTION // // you may stop reading here // /////////////////////////////////////////////////////////////////////////////// #ifndef _NJ_INCLUDE_HEADER_ONLY #ifdef _MSC_VER #define NJ_INLINE static __inline #define NJ_FORCE_INLINE static __forceinline #else #define NJ_INLINE static inline #define NJ_FORCE_INLINE static inline #endif #if NJ_USE_LIBC #include #include #define njAllocMem malloc #define njFreeMem free #define njFillMem memset #define njCopyMem memcpy #elif NJ_USE_WIN32 #include #define njAllocMem(size) ((void*) LocalAlloc(LMEM_FIXED, (SIZE_T)(size))) #define njFreeMem(block) ((void) LocalFree((HLOCAL) block)) NJ_INLINE void njFillMem(void* block, unsigned char value, int count) { __asm { mov edi, block mov al, value mov ecx, count rep stosb } } NJ_INLINE void njCopyMem(void* dest, const void* src, int count) { __asm { mov edi, dest mov esi, src mov ecx, count rep movsb } } #else extern void* njAllocMem(int size); extern void njFreeMem(void* block); extern void njFillMem(void* block, unsigned char byte, int size); extern void njCopyMem(void* dest, const void* src, int size); #endif typedef struct _nj_code { unsigned char bits, code; } nj_vlc_code_t; typedef struct _nj_cmp { int cid; int ssx, ssy; int width, height; int stride; int qtsel; int actabsel, dctabsel; int dcpred; unsigned char *pixels; } nj_component_t; typedef struct _nj_ctx { nj_result_t error; const unsigned char *pos; int size; int length; int width, height; int mbwidth, mbheight; int mbsizex, mbsizey; int ncomp; nj_component_t comp[3]; int qtused, qtavail; unsigned char qtab[4][64]; nj_vlc_code_t vlctab[4][65536]; int buf, bufbits; int block[64]; int rstinterval; unsigned char *rgb; } nj_context_t; static nj_context_t nj; static const char njZZ[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 }; NJ_FORCE_INLINE unsigned char njClip(const int x) { return (x < 0) ? 0 : ((x > 0xFF) ? 0xFF : (unsigned char) x); } #define W1 2841 #define W2 2676 #define W3 2408 #define W5 1609 #define W6 1108 #define W7 565 #ifdef USING_R NJ_INLINE int shiftLeft(int i, int p) { unsigned u = *(unsigned *)(&i); u <<= p; return int(u); } #else #define shiftLeft(i,p) (i) << (p) #endif NJ_INLINE void njRowIDCT(int* blk) { int x0, x1, x2, x3, x4, x5, x6, x7, x8; if (!((x1 = shiftLeft(blk[4], 11)) | (x2 = blk[6]) | (x3 = blk[2]) | (x4 = blk[1]) | (x5 = blk[7]) | (x6 = blk[5]) | (x7 = blk[3]))) { blk[0] = blk[1] = blk[2] = blk[3] = blk[4] = blk[5] = blk[6] = blk[7] = shiftLeft(blk[0], 3); return; } x0 = shiftLeft(blk[0], 11); x0 += 128; x8 = W7 * (x4 + x5); x4 = x8 + (W1 - W7) * x4; x5 = x8 - (W1 + W7) * x5; x8 = W3 * (x6 + x7); x6 = x8 - (W3 - W5) * x6; x7 = x8 - (W3 + W5) * x7; x8 = x0 + x1; x0 -= x1; x1 = W6 * (x3 + x2); x2 = x1 - (W2 + W6) * x2; x3 = x1 + (W2 - W6) * x3; x1 = x4 + x6; x4 -= x6; x6 = x5 + x7; x5 -= x7; x7 = x8 + x3; x8 -= x3; x3 = x0 + x2; x0 -= x2; x2 = (181 * (x4 + x5) + 128) >> 8; x4 = (181 * (x4 - x5) + 128) >> 8; blk[0] = (x7 + x1) >> 8; blk[1] = (x3 + x2) >> 8; blk[2] = (x0 + x4) >> 8; blk[3] = (x8 + x6) >> 8; blk[4] = (x8 - x6) >> 8; blk[5] = (x0 - x4) >> 8; blk[6] = (x3 - x2) >> 8; blk[7] = (x7 - x1) >> 8; } NJ_INLINE void njColIDCT(const int* blk, unsigned char *out, int stride) { int x0, x1, x2, x3, x4, x5, x6, x7, x8; if (!((x1 = shiftLeft(blk[8*4], 8)) | (x2 = blk[8*6]) | (x3 = blk[8*2]) | (x4 = blk[8*1]) | (x5 = blk[8*7]) | (x6 = blk[8*5]) | (x7 = blk[8*3]))) { x1 = njClip(((blk[0] + 32) >> 6) + 128); for (x0 = 8; x0; --x0) { *out = (unsigned char) x1; out += stride; } return; } x0 = shiftLeft(blk[0], 8); x0 += 8192; x8 = W7 * (x4 + x5) + 4; x4 = (x8 + (W1 - W7) * x4) >> 3; x5 = (x8 - (W1 + W7) * x5) >> 3; x8 = W3 * (x6 + x7) + 4; x6 = (x8 - (W3 - W5) * x6) >> 3; x7 = (x8 - (W3 + W5) * x7) >> 3; x8 = x0 + x1; x0 -= x1; x1 = W6 * (x3 + x2) + 4; x2 = (x1 - (W2 + W6) * x2) >> 3; x3 = (x1 + (W2 - W6) * x3) >> 3; x1 = x4 + x6; x4 -= x6; x6 = x5 + x7; x5 -= x7; x7 = x8 + x3; x8 -= x3; x3 = x0 + x2; x0 -= x2; x2 = (181 * (x4 + x5) + 128) >> 8; x4 = (181 * (x4 - x5) + 128) >> 8; *out = njClip(((x7 + x1) >> 14) + 128); out += stride; *out = njClip(((x3 + x2) >> 14) + 128); out += stride; *out = njClip(((x0 + x4) >> 14) + 128); out += stride; *out = njClip(((x8 + x6) >> 14) + 128); out += stride; *out = njClip(((x8 - x6) >> 14) + 128); out += stride; *out = njClip(((x0 - x4) >> 14) + 128); out += stride; *out = njClip(((x3 - x2) >> 14) + 128); out += stride; *out = njClip(((x7 - x1) >> 14) + 128); } #define njThrow(e) do { nj.error = e; return; } while (0) #define njCheckError() do { if (nj.error) return; } while (0) static int njShowBits(int bits) { unsigned char newbyte; if (!bits) return 0; while (nj.bufbits < bits) { if (nj.size <= 0) { nj.buf = shiftLeft(nj.buf, 8) | 0xFF; nj.bufbits += 8; continue; } newbyte = *nj.pos++; nj.size--; nj.bufbits += 8; nj.buf = shiftLeft(nj.buf, 8) | newbyte; if (newbyte == 0xFF) { if (nj.size) { unsigned char marker = *nj.pos++; nj.size--; switch (marker) { case 0x00: case 0xFF: break; case 0xD9: nj.size = 0; break; default: if ((marker & 0xF8) != 0xD0) nj.error = NJ_SYNTAX_ERROR; else { nj.buf = shiftLeft(nj.buf, 8) | marker; nj.bufbits += 8; } } } else nj.error = NJ_SYNTAX_ERROR; } } return (nj.buf >> (nj.bufbits - bits)) & ((1 << bits) - 1); } NJ_INLINE void njSkipBits(int bits) { if (nj.bufbits < bits) (void) njShowBits(bits); nj.bufbits -= bits; } NJ_INLINE int njGetBits(int bits) { int res = njShowBits(bits); njSkipBits(bits); return res; } NJ_INLINE void njByteAlign(void) { nj.bufbits &= 0xF8; } static void njSkip(int count) { nj.pos += count; nj.size -= count; nj.length -= count; if (nj.size < 0) nj.error = NJ_SYNTAX_ERROR; } NJ_INLINE unsigned short njDecode16(const unsigned char *pos) { return (pos[0] << 8) | pos[1]; } static void njDecodeLength(void) { if (nj.size < 2) njThrow(NJ_SYNTAX_ERROR); nj.length = njDecode16(nj.pos); if (nj.length > nj.size) njThrow(NJ_SYNTAX_ERROR); njSkip(2); } NJ_INLINE void njSkipMarker(void) { njDecodeLength(); njSkip(nj.length); } NJ_INLINE void njDecodeSOF(void) { int i, ssxmax = 0, ssymax = 0; nj_component_t* c; njDecodeLength(); njCheckError(); if (nj.length < 9) njThrow(NJ_SYNTAX_ERROR); if (nj.pos[0] != 8) njThrow(NJ_UNSUPPORTED); nj.height = njDecode16(nj.pos+1); nj.width = njDecode16(nj.pos+3); if (!nj.width || !nj.height) njThrow(NJ_SYNTAX_ERROR); nj.ncomp = nj.pos[5]; njSkip(6); switch (nj.ncomp) { case 1: case 3: break; default: njThrow(NJ_UNSUPPORTED); } if (nj.length < (nj.ncomp * 3)) njThrow(NJ_SYNTAX_ERROR); for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { c->cid = nj.pos[0]; if (!(c->ssx = nj.pos[1] >> 4)) njThrow(NJ_SYNTAX_ERROR); if (c->ssx & (c->ssx - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two if (!(c->ssy = nj.pos[1] & 15)) njThrow(NJ_SYNTAX_ERROR); if (c->ssy & (c->ssy - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two if ((c->qtsel = nj.pos[2]) & 0xFC) njThrow(NJ_SYNTAX_ERROR); njSkip(3); nj.qtused |= 1 << c->qtsel; if (c->ssx > ssxmax) ssxmax = c->ssx; if (c->ssy > ssymax) ssymax = c->ssy; } if (nj.ncomp == 1) { c = nj.comp; c->ssx = c->ssy = ssxmax = ssymax = 1; } nj.mbsizex = shiftLeft(ssxmax, 3); nj.mbsizey = shiftLeft(ssymax, 3); nj.mbwidth = (nj.width + nj.mbsizex - 1) / nj.mbsizex; nj.mbheight = (nj.height + nj.mbsizey - 1) / nj.mbsizey; for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { c->width = (nj.width * c->ssx + ssxmax - 1) / ssxmax; c->height = (nj.height * c->ssy + ssymax - 1) / ssymax; c->stride = nj.mbwidth * shiftLeft(c->ssx, 3); if (((c->width < 3) && (c->ssx != ssxmax)) || ((c->height < 3) && (c->ssy != ssymax))) njThrow(NJ_UNSUPPORTED); if (!(c->pixels = (unsigned char*) njAllocMem(c->stride * nj.mbheight * shiftLeft(c->ssy, 3)))) njThrow(NJ_OUT_OF_MEM); } if (nj.ncomp == 3) { nj.rgb = (unsigned char*) njAllocMem(nj.width * nj.height * nj.ncomp); if (!nj.rgb) njThrow(NJ_OUT_OF_MEM); } njSkip(nj.length); } NJ_INLINE void njDecodeDHT(void) { int codelen, currcnt, remain, spread, i, j; nj_vlc_code_t *vlc; static unsigned char counts[16]; njDecodeLength(); njCheckError(); while (nj.length >= 17) { i = nj.pos[0]; if (i & 0xEC) njThrow(NJ_SYNTAX_ERROR); if (i & 0x02) njThrow(NJ_UNSUPPORTED); i = (i | (i >> 3)) & 3; // combined DC/AC + tableid value for (codelen = 1; codelen <= 16; ++codelen) counts[codelen - 1] = nj.pos[codelen]; njSkip(17); vlc = &nj.vlctab[i][0]; remain = spread = 65536; for (codelen = 1; codelen <= 16; ++codelen) { spread >>= 1; currcnt = counts[codelen - 1]; if (!currcnt) continue; if (nj.length < currcnt) njThrow(NJ_SYNTAX_ERROR); remain -= shiftLeft(currcnt, 16 - codelen); if (remain < 0) njThrow(NJ_SYNTAX_ERROR); for (i = 0; i < currcnt; ++i) { unsigned char code = nj.pos[i]; for (j = spread; j; --j) { vlc->bits = (unsigned char) codelen; vlc->code = code; ++vlc; } } njSkip(currcnt); } while (remain--) { vlc->bits = 0; ++vlc; } } if (nj.length) njThrow(NJ_SYNTAX_ERROR); } NJ_INLINE void njDecodeDQT(void) { int i; unsigned char *t; njDecodeLength(); njCheckError(); while (nj.length >= 65) { i = nj.pos[0]; if (i & 0xFC) njThrow(NJ_SYNTAX_ERROR); nj.qtavail |= 1 << i; t = &nj.qtab[i][0]; for (i = 0; i < 64; ++i) t[i] = nj.pos[i + 1]; njSkip(65); } if (nj.length) njThrow(NJ_SYNTAX_ERROR); } NJ_INLINE void njDecodeDRI(void) { njDecodeLength(); njCheckError(); if (nj.length < 2) njThrow(NJ_SYNTAX_ERROR); nj.rstinterval = njDecode16(nj.pos); njSkip(nj.length); } static int njGetVLC(nj_vlc_code_t* vlc, unsigned char* code) { int value = njShowBits(16); int bits = vlc[value].bits; if (!bits) { nj.error = NJ_SYNTAX_ERROR; return 0; } njSkipBits(bits); value = vlc[value].code; if (code) *code = (unsigned char) value; bits = value & 15; if (!bits) return 0; value = njGetBits(bits); if (value < (1 << (bits - 1))) value += (shiftLeft(-1, bits)) + 1; return value; } NJ_INLINE void njDecodeBlock(nj_component_t* c, unsigned char* out) { unsigned char code = 0; int value, coef = 0; njFillMem(nj.block, 0, sizeof(nj.block)); c->dcpred += njGetVLC(&nj.vlctab[c->dctabsel][0], NULL); nj.block[0] = (c->dcpred) * nj.qtab[c->qtsel][0]; do { value = njGetVLC(&nj.vlctab[c->actabsel][0], &code); if (!code) break; // EOB if (!(code & 0x0F) && (code != 0xF0)) njThrow(NJ_SYNTAX_ERROR); coef += (code >> 4) + 1; if (coef > 63) njThrow(NJ_SYNTAX_ERROR); nj.block[(int) njZZ[coef]] = value * nj.qtab[c->qtsel][coef]; } while (coef < 63); for (coef = 0; coef < 64; coef += 8) njRowIDCT(&nj.block[coef]); for (coef = 0; coef < 8; ++coef) njColIDCT(&nj.block[coef], &out[coef], c->stride); } NJ_INLINE void njDecodeScan(void) { int i, mbx, mby, sbx, sby; int rstcount = nj.rstinterval, nextrst = 0; nj_component_t* c; njDecodeLength(); njCheckError(); if (nj.length < (4 + 2 * nj.ncomp)) njThrow(NJ_SYNTAX_ERROR); if (nj.pos[0] != nj.ncomp) njThrow(NJ_UNSUPPORTED); njSkip(1); for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { if (nj.pos[0] != c->cid) njThrow(NJ_SYNTAX_ERROR); if (nj.pos[1] & 0xEE) njThrow(NJ_SYNTAX_ERROR); c->dctabsel = nj.pos[1] >> 4; c->actabsel = (nj.pos[1] & 1) | 2; njSkip(2); } if (nj.pos[0] || (nj.pos[1] != 63) || nj.pos[2]) njThrow(NJ_UNSUPPORTED); njSkip(nj.length); for (mbx = mby = 0;;) { for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) for (sby = 0; sby < c->ssy; ++sby) for (sbx = 0; sbx < c->ssx; ++sbx) { njDecodeBlock(c, &c->pixels[shiftLeft((mby * c->ssy + sby) * c->stride + mbx * c->ssx + sbx, 3)]); njCheckError(); } if (++mbx >= nj.mbwidth) { mbx = 0; if (++mby >= nj.mbheight) break; } if (nj.rstinterval && !(--rstcount)) { njByteAlign(); i = njGetBits(16); if (((i & 0xFFF8) != 0xFFD0) || ((i & 7) != nextrst)) njThrow(NJ_SYNTAX_ERROR); nextrst = (nextrst + 1) & 7; rstcount = nj.rstinterval; for (i = 0; i < 3; ++i) nj.comp[i].dcpred = 0; } } nj.error = __NJ_FINISHED; } #if NJ_CHROMA_FILTER #define CF4A (-9) #define CF4B (111) #define CF4C (29) #define CF4D (-3) #define CF3A (28) #define CF3B (109) #define CF3C (-9) #define CF3X (104) #define CF3Y (27) #define CF3Z (-3) #define CF2A (139) #define CF2B (-11) #define CF(x) njClip(((x) + 64) >> 7) NJ_INLINE void njUpsampleH(nj_component_t* c) { const int xmax = c->width - 3; unsigned char *out, *lin, *lout; int x, y; out = (unsigned char*) njAllocMem((c->width * c->height) << 1); if (!out) njThrow(NJ_OUT_OF_MEM); lin = c->pixels; lout = out; for (y = c->height; y; --y) { lout[0] = CF(CF2A * lin[0] + CF2B * lin[1]); lout[1] = CF(CF3X * lin[0] + CF3Y * lin[1] + CF3Z * lin[2]); lout[2] = CF(CF3A * lin[0] + CF3B * lin[1] + CF3C * lin[2]); for (x = 0; x < xmax; ++x) { lout[(x << 1) + 3] = CF(CF4A * lin[x] + CF4B * lin[x + 1] + CF4C * lin[x + 2] + CF4D * lin[x + 3]); lout[(x << 1) + 4] = CF(CF4D * lin[x] + CF4C * lin[x + 1] + CF4B * lin[x + 2] + CF4A * lin[x + 3]); } lin += c->stride; lout += c->width << 1; lout[-3] = CF(CF3A * lin[-1] + CF3B * lin[-2] + CF3C * lin[-3]); lout[-2] = CF(CF3X * lin[-1] + CF3Y * lin[-2] + CF3Z * lin[-3]); lout[-1] = CF(CF2A * lin[-1] + CF2B * lin[-2]); } c->width <<= 1; c->stride = c->width; njFreeMem((void*)c->pixels); c->pixels = out; } NJ_INLINE void njUpsampleV(nj_component_t* c) { const int w = c->width, s1 = c->stride, s2 = s1 + s1; unsigned char *out, *cin, *cout; int x, y; out = (unsigned char*) njAllocMem((c->width * c->height) << 1); if (!out) njThrow(NJ_OUT_OF_MEM); for (x = 0; x < w; ++x) { cin = &c->pixels[x]; cout = &out[x]; *cout = CF(CF2A * cin[0] + CF2B * cin[s1]); cout += w; *cout = CF(CF3X * cin[0] + CF3Y * cin[s1] + CF3Z * cin[s2]); cout += w; *cout = CF(CF3A * cin[0] + CF3B * cin[s1] + CF3C * cin[s2]); cout += w; cin += s1; for (y = c->height - 3; y; --y) { *cout = CF(CF4A * cin[-s1] + CF4B * cin[0] + CF4C * cin[s1] + CF4D * cin[s2]); cout += w; *cout = CF(CF4D * cin[-s1] + CF4C * cin[0] + CF4B * cin[s1] + CF4A * cin[s2]); cout += w; cin += s1; } cin += s1; *cout = CF(CF3A * cin[0] + CF3B * cin[-s1] + CF3C * cin[-s2]); cout += w; *cout = CF(CF3X * cin[0] + CF3Y * cin[-s1] + CF3Z * cin[-s2]); cout += w; *cout = CF(CF2A * cin[0] + CF2B * cin[-s1]); } c->height <<= 1; c->stride = c->width; njFreeMem((void*) c->pixels); c->pixels = out; } #else NJ_INLINE void njUpsample(nj_component_t* c) { int x, y, xshift = 0, yshift = 0; unsigned char *out, *lin, *lout; while (c->width < nj.width) { c->width <<= 1; ++xshift; } while (c->height < nj.height) { c->height <<= 1; ++yshift; } out = (unsigned char*) njAllocMem(c->width * c->height); if (!out) njThrow(NJ_OUT_OF_MEM); lin = c->pixels; lout = out; for (y = 0; y < c->height; ++y) { lin = &c->pixels[(y >> yshift) * c->stride]; for (x = 0; x < c->width; ++x) lout[x] = lin[x >> xshift]; lout += c->width; } c->stride = c->width; njFreeMem((void*) c->pixels); c->pixels = out; } #endif NJ_INLINE void njConvert(void) { int i; nj_component_t* c; for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) { #if NJ_CHROMA_FILTER while ((c->width < nj.width) || (c->height < nj.height)) { if (c->width < nj.width) njUpsampleH(c); njCheckError(); if (c->height < nj.height) njUpsampleV(c); njCheckError(); } #else if ((c->width < nj.width) || (c->height < nj.height)) njUpsample(c); #endif if ((c->width < nj.width) || (c->height < nj.height)) njThrow(NJ_INTERNAL_ERR); } if (nj.ncomp == 3) { // convert to RGB int x, yy; unsigned char *prgb = nj.rgb; const unsigned char *py = nj.comp[0].pixels; const unsigned char *pcb = nj.comp[1].pixels; const unsigned char *pcr = nj.comp[2].pixels; for (yy = nj.height; yy; --yy) { for (x = 0; x < nj.width; ++x) { int y = py[x] << 8; int cb = pcb[x] - 128; int cr = pcr[x] - 128; *prgb++ = njClip((y + 359 * cr + 128) >> 8); *prgb++ = njClip((y - 88 * cb - 183 * cr + 128) >> 8); *prgb++ = njClip((y + 454 * cb + 128) >> 8); } py += nj.comp[0].stride; pcb += nj.comp[1].stride; pcr += nj.comp[2].stride; } } else if (nj.comp[0].width != nj.comp[0].stride) { // grayscale -> only remove stride unsigned char *pin = &nj.comp[0].pixels[nj.comp[0].stride]; unsigned char *pout = &nj.comp[0].pixels[nj.comp[0].width]; int y; for (y = nj.comp[0].height - 1; y; --y) { njCopyMem(pout, pin, nj.comp[0].width); pin += nj.comp[0].stride; pout += nj.comp[0].width; } nj.comp[0].stride = nj.comp[0].width; } } void njInit(void) { njFillMem(&nj, 0, sizeof(nj_context_t)); } void njDone(void) { int i; for (i = 0; i < 3; ++i) if (nj.comp[i].pixels) njFreeMem((void*) nj.comp[i].pixels); if (nj.rgb) njFreeMem((void*) nj.rgb); njInit(); } nj_result_t njDecode(const void* jpeg, const int size) { njDone(); nj.pos = (const unsigned char*) jpeg; nj.size = size & 0x7FFFFFFF; if (nj.size < 2) return NJ_NO_JPEG; if ((nj.pos[0] ^ 0xFF) | (nj.pos[1] ^ 0xD8)) return NJ_NO_JPEG; njSkip(2); while (!nj.error) { if ((nj.size < 2) || (nj.pos[0] != 0xFF)) return NJ_SYNTAX_ERROR; njSkip(2); switch (nj.pos[-1]) { case 0xC0: njDecodeSOF(); break; case 0xC4: njDecodeDHT(); break; case 0xDB: njDecodeDQT(); break; case 0xDD: njDecodeDRI(); break; case 0xDA: njDecodeScan(); break; case 0xFE: njSkipMarker(); break; default: if ((nj.pos[-1] & 0xF0) == 0xE0) njSkipMarker(); else return NJ_UNSUPPORTED; } } if (nj.error != __NJ_FINISHED) return nj.error; nj.error = NJ_OK; njConvert(); return nj.error; } int njGetWidth(void) { return nj.width; } int njGetHeight(void) { return nj.height; } int njIsColor(void) { return (nj.ncomp != 1); } unsigned char* njGetImage(void) { return (nj.ncomp == 1) ? nj.comp[0].pixels : nj.rgb; } int njGetImageSize(void) { return nj.width * nj.height * nj.ncomp; } #endif // _NJ_INCLUDE_HEADER_ONLYdcm2niix-1.0.20211006/console/ujpeg.h000066400000000000000000000046421412761503300167160ustar00rootroot00000000000000#ifndef _NANOJPEG_H #define _NANOJPEG_H // nj_result_t: Result codes for njDecode(). typedef enum _nj_result { NJ_OK = 0, // no error, decoding successful NJ_NO_JPEG, // not a JPEG file NJ_UNSUPPORTED, // unsupported format NJ_OUT_OF_MEM, // out of memory NJ_INTERNAL_ERR, // internal error NJ_SYNTAX_ERROR, // syntax error __NJ_FINISHED // used internally, will never be reported } nj_result_t; // njInit: Initialize NanoJPEG. // For safety reasons, this should be called at least one time before using // using any of the other NanoJPEG functions. void njInit(void); // njDecode: Decode a JPEG image. // Decodes a memory dump of a JPEG file into internal buffers. // Parameters: // jpeg = The pointer to the memory dump. // size = The size of the JPEG file. // Return value: The error code in case of failure, or NJ_OK (zero) on success. nj_result_t njDecode(const void* jpeg, const int size); // njGetWidth: Return the width (in pixels) of the most recently decoded // image. If njDecode() failed, the result of njGetWidth() is undefined. int njGetWidth(void); // njGetHeight: Return the height (in pixels) of the most recently decoded // image. If njDecode() failed, the result of njGetHeight() is undefined. int njGetHeight(void); // njIsColor: Return 1 if the most recently decoded image is a color image // (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result // of njGetWidth() is undefined. int njIsColor(void); // njGetImage: Returns the decoded image data. // Returns a pointer to the most recently image. The memory layout it byte- // oriented, top-down, without any padding between lines. Pixels of color // images will be stored as three consecutive bytes for the red, green and // blue channels. This data format is thus compatible with the PGM or PPM // file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8. // If njDecode() failed, the result of njGetImage() is undefined. unsigned char* njGetImage(void); // njGetImageSize: Returns the size (in bytes) of the image data returned // by njGetImage(). If njDecode() failed, the result of njGetImageSize() is // undefined. int njGetImageSize(void); // njDone: Uninitialize NanoJPEG. // Resets NanoJPEG's internal state and frees all memory that has been // allocated at run-time by NanoJPEG. It is still possible to decode another // image after a njDone() call. void njDone(void); #endif//_NANOJPEG_H dcm2niix-1.0.20211006/cpfiles.command000077500000000000000000000005241412761503300167540ustar00rootroot00000000000000#!/bin/sh cd /Users/rorden/Documents/cocoa/dcm2niix/console #cp ni*.c ../qtGui #cp ni*.cpp ../qtGui #cp ni*.h ../qtGui #cp tinydir.h ../qtGui #cp ni*.c ../wxWidgets #cp ni*.cpp ../wxWidgets #cp ni*.h ../wxWidgets #cp tinydir.h ../wxWidgets cp *.c ../xcode/dcm2/core cp *.cpp ../xcode/dcm2/core cp *.h ../xcode/dcm2/core #myDisableMiniZ dcm2niix-1.0.20211006/dcm_qa/000077500000000000000000000000001412761503300152075ustar00rootroot00000000000000dcm2niix-1.0.20211006/dcm_qa_nih/000077500000000000000000000000001412761503300160455ustar00rootroot00000000000000dcm2niix-1.0.20211006/dcm_qa_uih/000077500000000000000000000000001412761503300160545ustar00rootroot00000000000000dcm2niix-1.0.20211006/docs/000077500000000000000000000000001412761503300147135ustar00rootroot00000000000000dcm2niix-1.0.20211006/docs/CMakeLists.txt000066400000000000000000000023361412761503300174570ustar00rootroot00000000000000# Copyright 2016, The dcm2niix contributors # # This program is free software: you can redistribute it and/or modify it under # the terms of the new BSD license. See the license.txt file or visit # https://opensource.org/licenses/BSD-3-Clause for details. find_program(SPHINX_EXECUTABLE NAMES sphinx-build HINTS $ENV{SPHINX_DIR} PATH_SUFFIXES bin DOC "Sphinx documentation generator") if (NOT SPHINX_EXECUTABLE) message(FATAL_ERROR "sphinx-build executable not found") endif () set(SPHINX_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/source") set(SPHINX_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/build") set(SPHINX_CACHE_DIR "${CMAKE_CURRENT_BINARY_DIR}/doctrees") set(SPHINX_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/man") configure_file("${SPHINX_SOURCE_DIR}/conf.py" "${SPHINX_BUILD_DIR}/conf.py" COPYONLY) add_custom_target(build-man ALL COMMAND ${SPHINX_EXECUTABLE} -b man -c "${SPHINX_BUILD_DIR}" -d "${SPHINX_CACHE_DIR}" "${SPHINX_SOURCE_DIR}" "${SPHINX_OUTPUT_DIR}" COMMENT "Generating man pages with Sphinx") install(DIRECTORY "${SPHINX_OUTPUT_DIR}/" DESTINATION "share/man/man1") dcm2niix-1.0.20211006/docs/source/000077500000000000000000000000001412761503300162135ustar00rootroot00000000000000dcm2niix-1.0.20211006/docs/source/conf.py000066400000000000000000000234351412761503300175210ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # dcm2niix documentation build configuration file, created by # sphinx-quickstart on Tue Dec 20 16:22:24 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'dcm2niix' copyright = u'2021 The dcm2niix contributors' author = u'The dcm2niix contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = u'1.0' # The full version, including alpha/beta/rc tags. release = u'1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. # " v documentation" by default. # # html_title = u'dcm2niix v1.0' # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'dcm2niixdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'dcm2niix.tex', u'dcm2niix Documentation', author, 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # It false, will not define \strong, \code, itleref, \crossref ... but only # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added # packages. # # latex_keep_old_macro_names = True # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('dcm2niix', 'dcm2niix', u'DICOM to NifTI converter', ['This manual was developed by Ghislain Antony Vaillant and is maintained by the community'], 1), ('dcm2niibatch', 'dcm2niibatch', u'DICOM to NifTI batch converter', ['This manual was developed and is maintained by Benjamin Irving '], 1) ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'dcm2niix', u'dcm2niix Documentation', author, 'dcm2niix', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False dcm2niix-1.0.20211006/docs/source/dcm2niibatch.rst000066400000000000000000000045371412761503300213050ustar00rootroot00000000000000:orphan: dcm2niibatch manual =================== Synopsis -------- **dcm2niixbatch** <*configuration-file*> Description ----------- Most medical imaging devices save images in some variation of the popular DICOM format. However, most scientific tools expect medical images to be stored in the comparatively simpler NIfTI format. **dcm2niix** is designed to perform such conversion from DICOM to NIfTI with a simple command-line interface. **dcm2niibatch** acts as a wrapper around **dcm2niix** and allows the processing of multiple DICOM sequences by specifying the list of files and settings in a yaml text file. The makes processing a dataset of DICOM scans simpler and more easily repeatable. In addition, yaml files are designed to be both human and machine readable and a script can easily be written in many programming languages to automatically create a yaml file based on an existing folder structure. Please be advised that **dcm2niix** and **dcm2niibatch** have been developed for research purposes only and should not be considered a clinical tool. Configuration file format ------------------------- Perform a batch conversion of multiple dicoms using **dcm2niibatch**, which is run by passing a configuration file e.g *dcm2niibatch batch_config.yml* The configuration file should be in yaml format as shown in example *batch_config.yaml* .. code-block:: yaml Options: isGz: false isFlipY: false isVerbose: false isCreateBIDS: false isOnlySingleFile: false Files: - in_dir: /path/to/first/folder out_dir: /path/to/output/folder filename: dcemri - in_dir: /path/to/second/folder out_dir: /path/to/output/folder filename: fa3 You can add as many files as you want to convert as long as this structure stays consistent. Note that a dash must separate each file. in_dir Path to the dicom files out_dir Path to save the nifti file filename File name of nifti file to save See also -------- :manpage:`dcm2niix(1)` Licensing --------- Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty. dcm2niix-1.0.20211006/docs/source/dcm2niix.rst000066400000000000000000000111171412761503300204630ustar00rootroot00000000000000:orphan: dcm2niix manual =============== Synopsis -------- **dcm2niix** [*options*] <*sourcedir*> Description ----------- Most medical imaging devices save images in some variation of the popular DICOM format. However, most scientific tools expect medical images to be stored in the comparatively simpler NIfTI format. **dcm2niix** is designed to perform such conversion from DICOM to NIfTI with a simple command-line interface. Please be advised that **dcm2niix** has been developed for research purposes only and should not be considered a clinical tool. Options ------- -1..-9 gz compression level (1=fastest..9=smallest, default 6) -b Save additional BIDS metadata to a side-car .json file (default y). The "i"nput-only option reads DICOMs but saves neither BIDS nor NIfTI. -ba anonymize BIDS (default y). If "n"o, side-car may report patient name, age and weight. -f Format string for the output filename(s). The following specifiers are supported: - %a, antenna (coil) name - %b, basename (filename of 1st DICOM file) - %c, comments - %d, description - %e, echo number - %f, folder name - %i, patient ID - %j, series instance UID - %k, study instance UID - %m, manufacturer - %n, patient name - %o, media object instance UID - %p, protocol - %r, instance number (of 1st DICOM file) - %s, series number - %t, time - %u, acquisition number - %v, vendor - %x, study ID - %z, sequence name The default format string is "%p_%e_%4s". -g Generate defaults file (default n) If "y", create default file on completion If "n", default will not be written If "o", only reset and write defaults If "i", the values of the defaults file are ignored : reset defaults], default n) -h Show help -i Ignore derived, localizer and 2D images (default n) -l Losslessly scale 16-bit integers to use maximal dynamic range (default o). If "y", then intensity rescaled to use full 16-bit range. If "n", data not scaled uint16 will be saved as int16. If "o", original data and datatype preserved. -m Merge slices from the same series regardless of study time, echo, coil, orientation, etc. (default 2). If "2", automatic based on image modality. -n Only convert this series CRC number. Provide a negative number for listing of series CRC numbers in input folder. -o Output directory where the converted files should be saved. If unspecified, the files are saved within the specified source directory. -p Use Philips precise float (rather than display) scaling. -r Rename instead of convert DICOMs. Useful for organizing images. -s Convert a single file only. -t Save patient details as text notes. -u Update check: attempts to see if newer version is available. -v <2/y/n> Enable verbose output. "n" for succinct, "y" for verbose, "2" for high verbosity -x Crop images. This will attempt to remove excess neck from 3D acquisitions. If "i", images are neither cropped nor rotated to canonical space. -z Desired compression method. The "y"es option uses the external program pigz if available. The "i" option compresses the image using the slower built-in compression routines. --big-endian Byte order (default o). Optimal is machine native --progress Slicer format progress information (y/n, default n) --ignore_trigger_times Disregard values in 0018,1060 and 0020,9153 --terse Omit filename post-fixes (can cause overwrites) --version Report version and terminate --xml Slicer format features Licensing --------- Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty. The dcm2niix project is distributed under the BSD 2-Clause License. dcm2niix-1.0.20211006/docs/source/index.rst000066400000000000000000000006541412761503300200610ustar00rootroot00000000000000.. dcm2niix documentation master file, created by sphinx-quickstart on Tue Dec 20 16:22:24 2016. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to dcm2niix's documentation! ==================================== Contents: .. toctree:: :maxdepth: 2 Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` dcm2niix-1.0.20211006/license.txt000066400000000000000000000045511412761503300161530ustar00rootroot00000000000000The following files are from different authors and have their own licenses nifti.h, nifti1_io.h/nifti1_io_core.cpp is public domain http://niftilib.sourceforge.net http://sourceforge.net/projects/niftilib/files/latest/download ujpeg.h/ujpeg.cpp uses the MIT license (see file for license text) http://keyj.emphy.de/nanojpeg/ miniz.c is optional, it is public domain (http://unlicense.org) https://github.com/richgel999/miniz OpenJpeg is optional, it uses a BSD license https://github.com/uclouvain/openjpeg/blob/master/LICENSE CharLS is optional, it uses a BSD license https://github.com/team-charls/charls/blob/master/LICENSE.md zlib is optional and includes a simple license https://www.zlib.net/zlib_license.html Jasper is optional and provides its own license https://github.com/mdadams/jasper/blob/master/LICENSE --- The Software has been developed for research purposes only and is not a clinical tool Copyright (c) 2014-2021 Chris Rorden. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright owner nor the name of this project (dcm2niix) may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT OWNER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. dcm2niix-1.0.20211006/notarize.command000077500000000000000000000054511412761503300171660ustar00rootroot00000000000000#!/bin/bash basedir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Assumes ~/.bash_profile is set correctly, e.g. # CODE_SIGN_SIGNATURE="Developer ID Application: John Doe" # APPLE_ID_USER=doe@gmail.com # DCM2NIIX_SPECIFIC_PASSWORD=bbzj-zowb-bzji-ynkl # export APPLE_ID_USER DCM2NIIX_SPECIFIC_PASSWORD CODE_SIGN_SIGNATURE CODE_SIGN_SIGNATURE=$CODE_SIGN_SIGNATURE APPLE_ID_USER=$APPLE_ID_USER APP_SPECIFIC_PASSWORD=$DCM2NIIX_SPECIFIC_PASSWORD cd ${basedir} mkdir build && cd build cmake -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_JPEGLS=ON -DUSE_OPENJPEG=ON .. make cd bin # Clean up temporary files rm -f dcm2niix_macOS.dmg rm -f upload_log_file.txt rm -f request_log_file.txt rm -f log_file.txt # https://stackoverflow.com/questions/2870992/automatic-exit-from-bash-shell-script-on-error # terminate on error set -e echo "Verifying Info.plist" launchctl plist dcm2niix echo "Code signing dcm2niix..." codesign -vvv --force --strict --options=runtime --timestamp -s "$CODE_SIGN_SIGNATURE" dcm2niix codesign --verify --verbose --strict dcm2niix echo "Creating disk image..." hdiutil create -volname dcm2niix -srcfolder `pwd` -ov -format UDZO -layout SPUD -fs HFS+J dcm2niix_macOS.dmg # Notarizing with Apple... echo "Uploading..." xcrun altool --notarize-app -t osx --file dcm2niix_macOS.dmg --primary-bundle-id com.mricro.dcm2niix -u $APPLE_ID_USER -p $APP_SPECIFIC_PASSWORD --output-format xml > upload_log_file.txt # WARNING: if there is a 'product-errors' key in upload_log_file.txt something went wrong # we could parse it here and bail but not sure how to check for keys existing with PListBuddy # /usr/libexec/PlistBuddy -c "Print :product-errors:0:message" upload_log_file.txt # now we need to query apple's server to the status of notarization # when the "xcrun altool --notarize-app" command is finished the output plist # will contain a notarization-upload->RequestUUID key which we can use to check status echo "Checking status..." sleep 20 REQUEST_UUID=`/usr/libexec/PlistBuddy -c "Print :notarization-upload:RequestUUID" upload_log_file.txt` while true; do xcrun altool --notarization-info $REQUEST_UUID -u $APPLE_ID_USER -p $APP_SPECIFIC_PASSWORD --output-format xml > request_log_file.txt # parse the request plist for the notarization-info->Status Code key which will # be set to "success" if the package was notarized STATUS=`/usr/libexec/PlistBuddy -c "Print :notarization-info:Status" request_log_file.txt` if [ "$STATUS" != "in progress" ]; then break fi # echo $STATUS echo "$STATUS" sleep 10 done # download the log file to view any issues /usr/bin/curl -o log_file.txt `/usr/libexec/PlistBuddy -c "Print :notarization-info:LogFileURL" request_log_file.txt` # staple echo "Stapling..." xcrun stapler staple dcm2niix_macOS.dmg xcrun stapler validate dcm2niix_macOS.dmg open log_file.txt dcm2niix-1.0.20211006/rmfiles.command000077500000000000000000000004751412761503300167750ustar00rootroot00000000000000#!/bin/sh #remove duplicates cd /Users/rorden/Documents/cocoa/dcm2niix rm ./qtGui/ni*.cpp rm ./qtGui/ni*.h rm ./qtGui/tinydir.h rm ./wxWidgets/ni*.cpp rm ./wxWidgets/ni*.h rm ./wxWidgets/tinydir.h rm ./xcode/dcm2/core/*.c rm ./xcode/dcm2/core/*.cpp rm ./xcode/dcm2/core/*.h rm ./xcode/dcm2/core/tinydir.h