diff --git a/BIDS/README.md b/BIDS/README.md new file mode 100644 index 00000000..bf596b66 --- /dev/null +++ b/BIDS/README.md @@ -0,0 +1,332 @@ +## 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 | diff --git a/BIDS/extract_units.py b/BIDS/extract_units.py new file mode 100755 index 00000000..035f3b8d --- /dev/null +++ b/BIDS/extract_units.py @@ -0,0 +1,84 @@ +#!/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) diff --git a/COMPILE.md b/COMPILE.md index fec61b75..b8bcc99b 100644 --- a/COMPILE.md +++ b/COMPILE.md @@ -159,4 +159,3 @@ 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 ``` - diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md new file mode 100644 index 00000000..dcb0e593 --- /dev/null +++ b/CONTRIBUTE.md @@ -0,0 +1,29 @@ +#### 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 +``` diff --git a/Canon/README.md b/Canon/README.md index 7ee79845..6a458856 100644 --- a/Canon/README.md +++ b/Canon/README.md @@ -17,7 +17,7 @@ Since the acquisition by Canon, these public tags are no longer populated for im (0020,4000) LT [b=1500(0.445,0.000,0.895)] # 26, 1 ImageComments ``` -In contrast, when exporting images as enhanced (4D) DICOM, information is stored in public tags and does appear to compensate for phase encode polarity. These coordinates are with respect to the scanner bore, not image space. A Canon classic DICOM DWI image may report: +In contrast, 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 @@ -36,4 +36,4 @@ The [BIDS format](https://bids.neuroimaging.io) can record several sequence prop - [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). \ No newline at end of file + - [Canon 3T Galan Diffusion Dataset](https://github.com/neurolabusc/dcm_qa_canon). diff --git a/ERRORS.md b/ERRORS.md index 89d270b3..57afb609 100644 --- a/ERRORS.md +++ b/ERRORS.md @@ -26,4 +26,3 @@ Below is a list of possible return values from running dcm2niix. | 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`) | - diff --git a/FILENAMING.md b/FILENAMING.md index e1090ab8..890796a0 100644 --- a/FILENAMING.md +++ b/FILENAMING.md @@ -24,7 +24,7 @@ You request the output file name with the `-f` argument. For example, consider y - %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) + - %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))* @@ -38,7 +38,7 @@ In general dcm2niix creates images with 3D dimensions, or 4 dimensions when the - _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 asumes all 2D slices that form a 3D stack are equidistant. Therefore, dcm2niix reslices the input data to generate an equidistant volume. + - _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 @@ -87,4 +87,4 @@ dcm2niix will attempt to write your image using the naming scheme you specify wi [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. \ No newline at end of file +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. diff --git a/GE/README.md b/GE/README.md index 6f6b8226..0ac92e96 100644 --- a/GE/README.md +++ b/GE/README.md @@ -2,6 +2,22 @@ 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). @@ -94,4 +110,5 @@ Anatomical localizers (e.g. scout images) are quick-and-dirty scans used to posi - [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. \ No newline at end of file + - The dcm2niix [wiki](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage) includes examples of diffusion data, slice timing, and other variations. + diff --git a/Mediso/README.md b/Mediso/README.md new file mode 100644 index 00000000..02f83576 --- /dev/null +++ b/Mediso/README.md @@ -0,0 +1,9 @@ +## 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 diff --git a/PARREC/README.md b/PARREC/README.md index fba3e5ee..a75c91ef 100644 --- a/PARREC/README.md +++ b/PARREC/README.md @@ -1,9 +1,9 @@ ## About -dcm2niix attempts to convert Philips PAR/REC format images to NIfTI. While this format remains popular with users, it is slowly being superceded 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. +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 descision has been taken to allow the information to be more easily extended as the PAR file was getting increasingly limited. +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 @@ -43,5 +43,3 @@ Note that for Philips (unlike DICOM) the For PAR/REC the acquisition (%u) and se ## 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. - - diff --git a/Philips/README.md b/Philips/README.md index 97fa3138..b48aa7d8 100644 --- a/Philips/README.md +++ b/Philips/README.md @@ -18,9 +18,9 @@ Therefore, dcm2niix will ignore the IPP enclosed in 2005,140F unless no alternat ## 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 cacluations 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. +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 withing 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. +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). @@ -33,7 +33,7 @@ Philips thinks in terms of three different representations (using the terminolog | 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 varibility reported by the rescale tags, at the cost of disk space. +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. @@ -71,6 +71,14 @@ Formulas: 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"). @@ -128,15 +136,23 @@ MyCustomDirections ``` +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 [appoximate 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. +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 @@ -167,4 +183,5 @@ Prior versions of dcm2niix used different methods to sort images. However, these - [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) - - [Enhanced DICOMs](https://github.com/neurolabusc/dcm_qa_enh) \ No newline at end of file + - Classic and enhanced [ASL Examples](https://github.com/neurolabusc/dcm_qa_philips_asl) + - [Enhanced DICOMs](https://github.com/neurolabusc/dcm_qa_enh) diff --git a/README.md b/README.md index 45e63545..f34ce206 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ 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. @@ -27,9 +30,13 @@ DICOM provides many ways to store/compress image data, known as [transfer syntax [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/ouput /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`. +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). @@ -43,7 +50,7 @@ There are a couple ways to install dcm2niix * `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 you can run `brew install dcm2niix`. + - 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`. @@ -58,7 +65,7 @@ It is often easier to download and install a precompiled version. However, you c Ubuntu: `sudo apt-get install cmake pkg-config` -MacOS: `brew install cmake pkg-config` +MacOS: `brew install cmake pkg-config` or `sudo port install cmake pkgconfig` **Basic build:** ```bash @@ -98,6 +105,7 @@ If you have any problems with the cmake build script described above or want to ## 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. @@ -108,6 +116,7 @@ If you have any problems with the cmake build script described above or want to - [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. @@ -119,7 +128,7 @@ If you have any problems with the cmake build script described above or want to The following tools exploit dcm2niix - - [abcd-dicom2bids](https://github.com/DCAN-Labs/abcd-dicom2bids) selectively downloads high quality ABCD datasets. + - [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). @@ -130,14 +139,15 @@ The following tools exploit dcm2niix - [Brain imAgiNg Analysis iN Arcana (Banana)](https://pypi.org/project/banana/) is a collection of brain imaging analysis workflows, it uses dcm2niix for format conversions. - [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. - - [dcm2niix can help convert data from the Adolescent Brain Cognitive Development (ABCD) DICOM to BIDS](https://github.com/ABCD-STUDY/abcd-dicom2bids) - [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. @@ -145,14 +155,16 @@ The following tools exploit 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. + - [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. @@ -162,12 +174,15 @@ The following tools exploit dcm2niix - [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. - - [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. - - [pydcm2niix is a Python module for working with dcm2niix](https://github.com/jstutters/pydcm2niix). + - [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). diff --git a/Siemens/README.md b/Siemens/README.md index 51975e7c..8609f80a 100644 --- a/Siemens/README.md +++ b/Siemens/README.md @@ -10,7 +10,7 @@ The DICOM images exported by the X-series is radically different than the V-seri X-series users are strongly encouraged to export data using the "Enhanced" format and to not use any of the "Anonymize" features on the console. The consequences of these options is discussed in detail in [issue 236](https://github.com/rordenlab/dcm2niix/issues/236). Siemens notes `We highly recommend that the Enhanced DICOM format be used. This is because this format retains far more information in the header`. Failure to export data in this format has led to catastrophic data loss for numerous users (for publicly reported details see issues [203](https://github.com/rordenlab/dcm2niix/issues/203), [236](https://github.com/rordenlab/dcm2niix/issues/236), [240](https://github.com/rordenlab/dcm2niix/issues/240), [274](https://github.com/rordenlab/dcm2niix/issues/274), [303](https://github.com/rordenlab/dcm2niix/issues/303), [370](https://github.com/rordenlab/dcm2niix/issues/370), [394](https://github.com/rordenlab/dcm2niix/issues/394)). This reflects limitations of the DICOM data, not dcm2niix. -While X-series consoles allow users to export data as enhanced, mosaic or classic 2D formats, choosing an option other than enhanced dramatically degrades the meta data. Note that the Siemens considers mosaic images `secondary capture` data intended for quality assurance only. The mosaic scans lack several "Type 1" DICOM properties, necessarily limiting conversion. The non-mosaic 2D enhanced DICOMs are compact and efficient, but appear to have limited details relative to the enhanced output. This is unfortunate, as for the V-series the mosaic format has major benefits, so users may be in the habit of prefering mosaic export. Finally, each of the formats (enhanced, mosaic, classic) can be exported as anonymized. The Siemens console anonymization of current XA10A (Fall 2018) strips many useful tags. Siemens suggests `the use an offline/in-house anonymization software instead`. Another limitation of the current X-series format is that it retains no versioning details beyond the minor version for software and hardware stepping (e.g. versions are merely XA10 or XA11 with no details for service packs). If you use a X-series, you are strongly encouraged to manually log every hardware or software upgrade to allow future analyses to identify and regress out any effects of these modifications. Since the X-series format does not have a CSA header, dcm2niix will attempt to use the new private DICOM tags to populate the BIDS file. These tags are described in [issue 240](https://github.com/rordenlab/dcm2niix/issues/240). +While X-series consoles allow users to export data as enhanced, mosaic or classic 2D formats, choosing an option other than enhanced dramatically degrades the meta data. Note that the Siemens considers mosaic images `secondary capture` data intended for quality assurance only. The mosaic scans lack several "Type 1" DICOM properties, necessarily limiting conversion. 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). @@ -56,56 +56,8 @@ For Siemens V-series systems from the B-generation onward (around 2005), the mos ## 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. - -ep2d_pcasl, ep2d_pcasl_UI_PHC //pCASL 2D [Danny J.J. Wang](http://www.loft-lab.org) - - LabelOffset - - PostLabelDelay - - NumRFBlocks - - RFGap - - MeanGzx10 - - PhiAdjust - -tgse_pcasl //pCASL 3D [Danny J.J. Wang](http://www.loft-lab.org) - - RFGap - - MeanGzx10 - - T1 - -ep2d_pasl //PASL 2D Siemens Product - - InversionTime - - SaturationStopTime - -tgse_pasl //PASL 3D [Siemens Product](http://adni.loni.usc.edu/wp-content/uploads/2010/05/ADNI3_Basic_Siemens_Skyra_E11.pdf) - - BolusDuration - - InversionTime - -ep2d_fairest //PASL 2D http://www.pubmed.com/11746944 http://www.pubmed.com/21606572 - - PostInversionDelay - - PostLabelDelay - -to_ep2d_VEPCASL //pCASL 2D specific tags - Oxford (Thomas OKell) - - InversionTime - - BolusDuration - - TagRFFlipAngle - - TagRFDuration - - TagRFSeparation - - MeanTagGradient - - TagGradientAmplitude - - TagDuration - - MaximumT1Opt - - InitialPostLabelDelay [Array] - -jw_tgse_VEPCASL //pCASL 3D Oxford - - TagRFFlipAngle - - TagRFDuration - - TagRFSeparation - - MaximumT1Opt - - Tag0 - - Tag1 - - Tag2 - - Tag3 - - InitialPostLabelDelay [Array] - +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). @@ -114,4 +66,3 @@ jw_tgse_VEPCASL //pCASL 3D Oxford - [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). - diff --git a/SuperBuild/SuperBuild.cmake b/SuperBuild/SuperBuild.cmake index 9b6fe36f..2a0a956f 100644 --- a/SuperBuild/SuperBuild.cmake +++ b/SuperBuild/SuperBuild.cmake @@ -68,7 +68,7 @@ if(USE_OPENJPEG) endif() if(OPENJPEG_FOUND AND NOT ${OPENJPEG_INCLUDE_DIRS} MATCHES "gdcmopenjpeg") - set(OpenJPEG_DIR ${OPENJPEG_LIBDIR}/openjpeg-2.1 CACHE PATH "Path to OpenJPEG configuration file" FORCE) + 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") @@ -156,4 +156,3 @@ option(BUILD_DOCS "Build documentation (manpages)" OFF) if(BUILD_DOCS) add_subdirectory(docs) endif() - diff --git a/Troubleshooting/README.md b/Troubleshooting/README.md index 8ebffb10..a05c6015 100644 --- a/Troubleshooting/README.md +++ b/Troubleshooting/README.md @@ -1,6 +1,6 @@ ## 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 vender has interpreted the DICOM standard. Due to these factors, dcm2niix may not always create the results you expect. +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). diff --git a/UIH/README.md b/UIH/README.md index fbbf4fb7..2a2d9d64 100644 --- a/UIH/README.md +++ b/UIH/README.md @@ -8,55 +8,57 @@ Shan C Young provided the [following information](https://github.com/rordenlab/d 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| -Tag ID | Tag Name | VR | VM | Description | Sample --- | -- | -- | -- | -- | -- -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 |   |   - ->00201041 | 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 diff --git a/VERSIONS.md b/VERSIONS.md index a1a371e7..0f61dc09 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -139,4 +139,4 @@ - Support for CT scans with gantry tilt and varying distance between slices. 11-Oct-2014 - - Initial public release. \ No newline at end of file + - Initial public release. diff --git a/batch_config.yml b/batch_config.yml index c6b892a9..d22a987c 100644 --- a/batch_config.yml +++ b/batch_config.yml @@ -13,4 +13,4 @@ Files: - in_dir: /path/to/second/folder out_dir: /path/to/output/folder - filename: fa3 \ No newline at end of file + filename: fa3 diff --git a/console/jpg_0XC3.cpp b/console/jpg_0XC3.cpp index 53ffdcfb..54e5dc4b 100644 --- a/console/jpg_0XC3.cpp +++ b/console/jpg_0XC3.cpp @@ -1,507 +1,513 @@ +#include "jpg_0XC3.h" +#include "print.h" #include //requires VS 2015 or later -#include +#include #include #include -#include -#include "jpg_0XC3.h" -#include "print.h" +#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() +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() +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 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() + 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() +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() + 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 entires 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(...) ({printError(__VA_ARGS__); free(lRawRA); return NULL;}) - #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 + 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() - } 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); +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 - } - 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 ((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]; + } 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() + } //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() diff --git a/console/main_console.cpp b/console/main_console.cpp index 2adc4982..a5aaf799 100644 --- a/console/main_console.cpp +++ b/console/main_console.cpp @@ -1,6 +1,6 @@ -// main.m dcm2niix +//main_console.cpp dcm2niix // by Chris Rorden on 3/22/14, see license.txt -// Copyright (c) 2014 Chris Rorden. All rights reserved. +// 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 @@ -12,7 +12,6 @@ // 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 @@ -25,202 +24,201 @@ // 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 -#include +#include //#include -#include // clock_t, clock, CLOCKS_PER_SEC -#include -#include "nii_dicom_batch.h" -#include "nii_dicom.h" #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; +#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; +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'); + 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 instead of NIfTI (y/n, 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"); +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 + 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 +#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'; + //#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(" --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); +//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 - #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(" --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); + 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') ) +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]); + 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); +#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; - } + 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." + 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); @@ -233,347 +231,372 @@ int 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"); + //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 +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"); + //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)) { +#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], "--terse")) { + 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")) { + } else if (!strcmp(argv[i], "--version")) { printf("%s\n", kDCMdate); - return kEXIT_REPORT_VERSION; - } else if (( ! strcmp(argv[i], "--progress")) && ((i+1) < argc)) { + 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")) { + 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 + } 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] == '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.isSaveNRRD = true; - } 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 (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; + 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; + 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') { + } 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)) { +#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; - - //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] == '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"); + 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 +#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 +//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; - } + 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); +#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) +#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; +#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; } diff --git a/console/main_console_batch.cpp b/console/main_console_batch.cpp index b0cd383b..e968962e 100644 --- a/console/main_console_batch.cpp +++ b/console/main_console_batch.cpp @@ -1,7 +1,7 @@ // main.m dcm2niix // by Chris Rorden on 3/22/14, see license.txt // Copyright (c) 2014 Chris Rorden. All rights reserved. -// yaml batch suport by Benjamin Irving, 2016 - maintains copyright +// yaml batch support by Benjamin Irving, 2016 - maintains copyright #include //requires VS 2015 or later #ifdef _MSC_VER diff --git a/console/makefile b/console/makefile index d88192fd..49084991 100644 --- a/console/makefile +++ b/console/makefile @@ -26,8 +26,8 @@ ifneq ($(OS),Windows_NT) # 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 + # 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: diff --git a/console/miniz.c b/console/miniz.c index 9ab92891..47d10109 100644 --- a/console/miniz.c +++ b/console/miniz.c @@ -15,7 +15,7 @@ Since DICOM images are inherently limited to 2gb, dcm2niix will keep using v1.15 * 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 occured 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() + 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. diff --git a/console/nifti1.h b/console/nifti1.h index 680d1637..eb935e05 100644 --- a/console/nifti1.h +++ b/console/nifti1.h @@ -873,7 +873,7 @@ typedef struct { unsigned char r,g,b; } rgb_byte ; 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 displacment + - 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 */ diff --git a/console/nifti1_io_core.cpp b/console/nifti1_io_core.cpp index 9935af68..5f85a78c 100644 --- a/console/nifti1_io_core.cpp +++ b/console/nifti1_io_core.cpp @@ -855,10 +855,3 @@ vec3 nifti_mat33_eig3(double bxx, double bxy, double bxz, double byy, double byz //printf("bvec = [%g 0 0; 0 %g 0; 0 0 %g]\n", v3.v[0], v3.v[1], v3.v[2]); return v3; } - - - - - - - diff --git a/console/nii_dicom.cpp b/console/nii_dicom.cpp index 9194a637..8cba19d2 100644 --- a/console/nii_dicom.cpp +++ b/console/nii_dicom.cpp @@ -1,415 +1,424 @@ //#define MY_DEBUG #if defined(_WIN64) || defined(_WIN32) - #include //write to registry +#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 +#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 +#include #endif //#include //clock() #ifndef USING_R #include "nifti1.h" #endif -#include "print.h" +#include "jpg_0XC3.h" +#include "nifti1_io_core.h" #include "nii_dicom.h" -#include -#include // discriminate files from folders -#include -#include +#include "print.h" #include //toupper +#include #include -#include #include -#include "jpg_0XC3.h" -#include #include -#include "nifti1_io_core.h" +#include +#include +#include +#include // discriminate files from folders +#include #ifdef USING_R - #undef isnan - #define isnan ISNAN +#undef isnan +#define isnan ISNAN #endif #ifndef myDisableClassicJPEG - #ifdef myTurboJPEG - #include - #else - #include "ujpeg.h" - #endif +#ifdef myTurboJPEG +#include +#else +#include "ujpeg.h" +#endif #endif #ifdef myEnableJasper - #include +#include #endif #ifndef myDisableOpenJPEG - #include "openjpeg.h" +#include "openjpeg.h" #ifdef myEnableJasper - ERROR: YOU CAN NOT COMPILE WITH myEnableJasper AND NOT myDisableOpenJPEG OPTIONS SET SIMULTANEOUSLY +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; //ouput 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() +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; + 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); +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); +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; +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; +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; +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() -/*static OPJ_BOOL opj_seek_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 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_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); +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); + opj_image_destroy(jpx); cleanup2: - free(dx.buf); - opj_stream_destroy(stream); - opj_destroy_codec(codec); - return ret; + free(dx.buf); + opj_stream_destroy(stream); + opj_destroy_codec(codec); + return ret; } -#endif //if +#endif //myDisableOpenJPEG #ifndef M_PI -#define M_PI 3.14159265358979323846 +#define M_PI 3.14159265358979323846 #endif float deFuzz(float v) { - if (fabs(v) < 0.00001) - return 0; - else - return 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])); + 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])); + 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; +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); + 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; + } + 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); + 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; - //if (isnan(pos)) - 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]); + } + 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 + 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 + } + } + } + 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 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; + 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; + 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 @@ -417,11 +426,10 @@ void setQSForm(struct nifti_1_header *h, mat44 Q44i, bool isVerbose) { 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; - //foo is abs(R) - 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]); + 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])) @@ -443,7 +451,7 @@ ivec3 maxCol(mat33 R) { 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 + ixyz.v[2] = 6 - ixyz.v[1] - ixyz.v[0]; //sum of 1+2+3 return ixyz; } @@ -457,28 +465,28 @@ int sign(float x) { } // Subfunction: get dicom xform matrix and related info -// This is a direct port of Xiangrui Li's dicm2nii function +// 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); + 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]; + 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]); + 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; @@ -487,36 +495,34 @@ mat44 xform_mat(struct TDICOMdata d) { 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)) + 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]); + 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)) { + 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); + //reportMat44((char*)"iR44",R44); return R44; } else if (true) { -//SliceNormalVector TO DO + //SliceNormalVector TO DO printMessage("Not completed"); #ifndef USING_R exit(2); @@ -527,710 +533,723 @@ mat44 xform_mat(struct TDICOMdata d) { #ifndef USING_R exit(1); #else - return R44; + 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 + 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 -/*mat44 doQuadruped(mat44 m) { - mat44 m_in = m; - mat44 rot; - LOAD_MAT44(rot, 1.0,0.0,0.0,0.0, - 0.0,0.0,-1.0,0.0, - 0.0,-1.0,0.0,0.0); - return nifti_mat44_mul( rot, m_in ); -}*/ - -// 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); +// 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 + 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]); + 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; + 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; +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); + 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; +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.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.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 + 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.interp3D = -1; - for (int i = 0; i < kMaxOverlay; i++) - d.overlayStart[i] = 0; - d.isHasOverlay = false; - d.isPrivateCreatorRemap = false; + 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; + 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; + 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] = ' '; - } +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] = ' '; - } +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] = ' '; +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; +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; +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; +#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; +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; +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; +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? +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); +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() -/* -//the code below trims strings after integer -// does not appear required not http://en.cppreference.com/w/cpp/string/byte/atoi -// "(atoi) Discards any whitespace characters until the first non-whitespace character is found, then takes as many characters as possible to form a valid integer" -int dcmStrInt (const int lByteLength, const unsigned char lBuffer[]) {//read int stored as a string -//returns first integer e.g. if 0043,1039 is "1000\8\0\0" the result will be 1000 - if (lByteLength < 1) return 0; //error - bool isOK = false; - int i = 0; - for (i = 0; i <= lByteLength; i++) { - if ((lBuffer[i] >= '0') && (lBuffer[i] <= '9')) - isOK = true; - else if (isOK) - break; - } - if (!isOK) return 0; //error - char * cString = (char *)malloc(sizeof(char) * (i + 1)); - cString[i] =0; - memcpy(cString, (const unsigned char*)(&lBuffer[0]), i); - int ret = atoi(cString); - free(cString); - return ret; -} //dcmStrInt() -*/ - -int dcmStrInt (const int lByteLength, const unsigned char lBuffer[]) {//read int stored as a string -//#ifdef _MSC_VER - char * cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); -//#else -// char cString[lByteLength + 1]; -//#endif - cString[lByteLength] =0; - memcpy(cString, (const unsigned char*)(&lBuffer[0]), lByteLength); - //printMessage(" --> *%s* %s%s\n",cString, &lBuffer[0],&lBuffer[1]); - int ret = atoi(cString); -//#ifdef _MSC_VER + +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); -//#endif 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 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])== '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; + 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 + printWarning("Unknown manufacturer %s\n", cString); + //#ifdef _MSC_VER free(cString); -//#endif + //#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]; +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) { -//#ifdef _MSC_VER - char * cString = (char *)malloc(sizeof(char) * (itemCSA.xx2_Len + 1)); -//#else - // char cString[itemCSA.xx2_Len]; -//#endif - 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; -//#ifdef _MSC_VER - free(cString); -//#endif - } - } //for each item - return false; +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; + return; if (itemsOK > kMaxEPI3D) { printError("Please increase kMaxEPI3D and recompile\n"); return; @@ -1239,14 +1258,14 @@ void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3D minTimeValue = CSA->sliceTiming[0]; for (int z = 0; z < itemsOK; z++) if (CSA->sliceTiming[z] < minTimeValue) - minTimeValue = CSA->sliceTiming[z]; + 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 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 + 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; } @@ -1254,7 +1273,7 @@ void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3D timeValue1 = CSA->sliceTiming[0]; int nTimeZero = 0; if (CSA->sliceTiming[0] == 0) - nTimeZero++; + nTimeZero++; int minTimeIndex = 0; int maxTimeIndex = minTimeIndex; minTimeValue = CSA->sliceTiming[0]; @@ -1263,18 +1282,19 @@ void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3D 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]); + printMessage("%g\t", CSA->sliceTiming[z]); if (CSA->sliceTiming[z] == 0) nTimeZero++; if (CSA->sliceTiming[z] < minTimeValue) { minTimeValue = CSA->sliceTiming[z]; - minTimeIndex = (float) z; + minTimeIndex = (float)z; } if (CSA->sliceTiming[z] > maxTimeValue) { maxTimeValue = CSA->sliceTiming[z]; - maxTimeIndex = (float) z; + maxTimeIndex = (float)z; } - if (CSA->sliceTiming[z] == timeValue1) CSA->multiBandFactor++; + if (CSA->sliceTiming[z] == timeValue1) + CSA->multiBandFactor++; } if (isVerbose > 1) printMessage("\n"); @@ -1286,17 +1306,17 @@ void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3D } 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 + 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 ((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"); @@ -1310,414 +1330,427 @@ void checkSliceTimes(struct TCSAdata *CSA, int itemsOK, int isVerbose, bool is3D } //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 (true) { - printMessage("%d CSA of %s %d\n",lPos, tagCSA.name, tagCSA.nitems); - float * vals = (float *)malloc(sizeof(float) * (tagCSA.nitems + 1)); - csaMultiFloat (&buff[lPos], tagCSA.nitems,vals, &itemsOK); - if (itemsOK > 0) { - for (int z = 1; z <= itemsOK; z++) //find index and value of fastest time - printMessage("%g\t", vals[z]); - printMessage("\n"); - } - }*/ - - 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) ){ +//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]; + 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; + } + } 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) { +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]); + 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]); +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; -//#ifdef _MSC_VER - char * cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); -//#else -// char cString[lByteLength + 1]; -//#endif - 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] == '\\') )){ - //x strlcpy(temp,&cString[lStart],i-lStart+1); - 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); -//#ifdef _MSC_VER +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); -//#endif } //dcmMultiFloat() -float dcmStrFloat (const int lByteLength, const unsigned char lBuffer[]) { //read float stored as a string -//#ifdef _MSC_VER - char * cString = (char *)malloc(sizeof(char) * (lByteLength + 1)); -//#else -// char cString[lByteLength + 1]; -//#endif - memcpy(cString, (char*)&lBuffer[0], lByteLength); - cString[lByteLength] = 0; //null terminate - float ret = (float) atof(cString); -//#ifdef _MSC_VER +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); -//#endif return ret; } //dcmStrFloat() int headerDcm2Nii(struct TDICOMdata d, struct nifti_1_header *h, bool isComputeSForm) { - //printMessage("bytes %dx%dx%d %d, %d\n",d.XYZdim[1],d.XYZdim[2],d.XYZdim[3], d.Allocbits_per_pixel, d.samplesPerPixel); 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 represenation 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 + 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->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; + 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); +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; +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 > file_name) && (strlen(ext) < 1)) - // p_extension--; - if (p_extension) - strcpy(++p_extension, ext); +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) { +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; + 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); +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->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; +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 +#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 +#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 +#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 +#define kv1 47 +#define kv2 45 +#define kv3 46 //the following do not exist in V41 or earlier -#define kASL 48 +#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]; + 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]" + 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; @@ -1725,83 +1758,83 @@ int kbval = 33; //V3: 27 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) { + 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); + 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; + 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; + kPositionAP = 15; + kPositionFH = 16; + kPositionRL = 17; kSliceOrients = 19; kXmm = 22; kYmm = 23; @@ -1809,351 +1842,374 @@ int kbval = 33; //V3: 27 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]); + } 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 + 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; + 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.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.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]) ) { + 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]); + 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; + } + 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)) { + } 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]; + 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 < minSlice) + minSlice = slice; if (slice > maxSlice) { maxSlice = slice; d.patientPositionLast[1] = cols[kPositionRL]; - d.patientPositionLast[2] = cols[kPositionAP]; - d.patientPositionLast[3] = cols[kPositionFH]; + d.patientPositionLast[2] = cols[kPositionAP]; + d.patientPositionLast[3] = cols[kPositionFH]; } - int volStep = maxNumberOfDynamics; + 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); +#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 = 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); + 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 * (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)); + 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)); + 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 (seq1 < 0) + seq1 = seq; + if (seq > maxSeq) + maxSeq = seq; + if (seq != seq1) { //sequence varies within this PAR file if (!isSequenceWarning) { - isSequenceWarning =true; + 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); + 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; + 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; - } + 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; - } + 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 (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; - } + 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 + 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->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]; @@ -2162,15 +2218,14 @@ int kbval = 33; //V3: 27 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; + 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 - //printf("%d %g %g\n", numSlice2D, cols[kRI], cols[kRS]); - dti4D->intenIntercept[numSlice2D] = cols[kRI]; + 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]; } @@ -2180,36 +2235,36 @@ int kbval = 33; //V3: 27 //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; + 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)) { + } + //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; - } + 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) { + 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]; @@ -2222,35 +2277,39 @@ int kbval = 33; //V3: 27 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; + 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 + } + 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]; @@ -2259,158 +2318,169 @@ int kbval = 33; //V3: 27 int j = dti4D->sliceOrder[i]; dti4D->intenIntercept[i] = tmp.intenIntercept[j]; dti4D->intenScale[i] = tmp.intenScale[j]; - dti4D->intenScalePhilips[i] = tmp.intenScalePhilips[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; + 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; + 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); + 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"); - } + 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("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 + 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]); + 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) ); + 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); + } //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 + 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; + 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) ); + 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; + 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) ); + 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], + 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); + 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]; @@ -2427,12 +2497,15 @@ int kbval = 33; //V3: 27 } //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 ((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 ) ) { + //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]; @@ -2440,7 +2513,7 @@ int kbval = 33; //V3: 27 d.patientPositionLast[2] = y.v[1]; d.patientPositionLast[3] = y.v[2]; //printWarning(" Flipping slice order: please verify %s\n", parname); - }else { + } else { //printWarning(" NOT Flipping slice order: please verify %s\n", parname); d.patientPosition[1] = y.v[0]; d.patientPosition[2] = y.v[1]; @@ -2449,366 +2522,312 @@ int kbval = 33; //V3: 27 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 + //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 (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; + } + //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; + //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; + 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; +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 -//#ifdef _MSC_VER - unsigned char * line = (unsigned char *)malloc(sizeof(unsigned char) * (lineBytes)); -//#else -// unsigned char line[lineBytes]; -//#endif - 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 -//#ifdef _MSC_VER +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); -//#endif - return bImg; + 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 +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; + return bImg; } // nii_flipImgZ() -/*unsigned char * nii_reorderSlices(unsigned char* bImg, struct nifti_1_header *h, struct TDTI4D *dti4D){ - //flip slice order - Philips scanners can save data in non-contiguous order - //if ((h->dim[3] < 2) || (h->dim[4] > 1)) return bImg; - return bImg; - if (h->dim[3] < 2) return bImg; - if (h->dim[3] >= kMaxDTI4D) { - printWarning("Unable to reorder slices (%d > %d)\n", h->dim[3], kMaxDTI4D); - return bImg; - } - printError("OBSOLETE<<< Slices not spatially contiguous: please check output [new feature]\n"); return bImg; - int dim4to7 = 1; - for (int i = 4; i < 8; i++) - if (h->dim[i] > 1) dim4to7 = dim4to7 * h->dim[i]; - int sliceBytes = h->dim[1] * h->dim[2] * h->bitpix/8; - if (sliceBytes < 0) return bImg; - size_t volBytes = sliceBytes * h->dim[3]; - unsigned char *srcImg = (unsigned char *)malloc(volBytes); - //printMessage("Reordering %d volumes\n", dim4to7); - for (int v = 0; v < dim4to7; v++) { - //for (int v = 0; v < 1; v++) { - - size_t volStart = v * volBytes; - memcpy(&srcImg[0], &bImg[volStart], volBytes); //dest, src, size - for (int z = 0; z < h->dim[3]; z++) { //for each slice - int src = dti4D->S[z].sliceNumberMrPhilips - 1; //-1 as Philips indexes slices from 1 not 0 - if ((v > 0) && (dti4D->S[0].sliceNumberMrPhilipsVol2 >= 0)) - src = dti4D->S[z].sliceNumberMrPhilipsVol2 - 1; - //printMessage("Reordering volume %d slice %d\n", v, dti4D->S[z].sliceNumberMrPhilips); - if ((src < 0) || (src >= h->dim[3])) continue; - memcpy(&bImg[volStart+(src*sliceBytes)], &srcImg[z*sliceBytes], sliceBytes); //dest, src, size - } - } - free(srcImg); - return bImg; -}// nii_reorderSlices() -*/ -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 +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 = 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+0] << 4) + (img[i12+1] >> 4); - } else { - val = ((img[i12+0] & 0x0F) << 8) + img[i12+1]; - } - - //if ((i % 2) != 1) { - // val = img[i12+0] + ((img[i12+1] & 0xF0) << 4); - //} else { - // val = (img[i12+0] & 0x0F) + (img[i12+1] << 4); - //} - val = val & 0xFFF; - img[i16+0] = val & 0xFF; - img[i16+1] = (val >> 8) & 0xFF; - } -} //conv12bit16bit()*/ - -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; - } + 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) { - size_t imgsz = nii_ImgBytes(hdr); - size_t imgszRead = imgsz; - if (bitsAllocated == 12) - imgszRead = round(imgsz * 0.75); - FILE *file = fopen(imgname , "rb"); +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; - } + printError("Unable to open '%s'\n", imgname); + return NULL; + } fseek(file, 0, SEEK_END); - long fileLen=ftell(file); - if (fileLen < (imgszRead+(long) hdr.vox_offset)) { - //previously (fileLen < (imgszRead+hdr.vox_offset)) - // FileSize < (ImageSize+HeaderSize): 42399788 < (42398702+1086) - // FileSize < (ImageSize+HeaderSize): 42399788 < ( 42399792.00) - //note hdr.vox_offset is a float, and without a type-cast it can lead to unusual values - //https://www.nitrc.org/forum/message.php?msg_id=27155 - printMessage("FileSize < (ImageSize+HeaderSize): %ld < (%zu+%ld) \n", fileLen, imgszRead, (long)hdr.vox_offset); - //printMessage("FileSize < (ImageSize+HeaderSize): %ld < (%zu) \n", fileLen, imgszRead+(long)hdr.vox_offset); - printWarning("File not large enough to store image data: %s\n", imgname); - return NULL; - } - fseek(file, (long) hdr.vox_offset, 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); + 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; - } + printError("Only loaded %zu of %zu bytes for %s\n", sz, imgszRead, imgname); + return NULL; + } if (bitsAllocated == 12) - conv12bit16bit(bImg, hdr); - return bImg; + conv12bit16bit(bImg, hdr); + return bImg; } //nii_loadImgCore() -unsigned char * nii_planar2rgb(unsigned char* bImg, struct nifti_1_header *hdr, int isPlanar) { +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; + 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]; + 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)); + 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; + int sliceOffsetB = sliceOffsetR + 2 * sliceBytes8; //printMessage("planar->rgb %dx%dx%d\n", hdr->dim[1],hdr->dim[2], dim3to7); - int i = 0; + 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]; + bImg[i++] = slice24[sliceOffsetR + rgb]; + bImg[i++] = slice24[sliceOffsetG + rgb]; + bImg[i++] = slice24[sliceOffsetB + rgb]; } sliceOffsetRGB += sliceBytes24; } //for each slice @@ -2816,129 +2835,111 @@ unsigned char * nii_planar2rgb(unsigned char* bImg, struct nifti_1_header *hdr, 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 intesity 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 +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]; - if (dim3to7 < 2) return bImg; - uint64_t sliceBytes = hdr->dim[1]*hdr->dim[2]*hdr->bitpix/8; - unsigned char *sliceImg = (unsigned char *)malloc( sliceBytes); - uint32_t *idx = (uint32_t *)malloc( dim3to7 * sizeof(uint32_t)); - for (int i = 0; i < dim3to7; i++) //for each volume - idx[i] = i; - for (int i = 0; i < dim3to7; i++) { //for each volume - int fromSlice = idx[dti4D->sliceOrder[i]]; - //if (i < 10) printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", i, toSlice); - if (i != fromSlice) { - uint64_t inPos = fromSlice * sliceBytes; - uint64_t outPos = i * sliceBytes; - memcpy( &sliceImg[0], &bImg[outPos], sliceBytes); //dest, src -> copy slice about to be overwritten - memcpy( &bImg[outPos], &bImg[inPos], sliceBytes); //dest, src - memcpy( &bImg[inPos], &sliceImg[0], sliceBytes); // - idx[i] = fromSlice; - } - } - free(idx); - free(sliceImg); - return bImg; -}*/ - -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 + 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 + 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); @@ -2947,325 +2948,265 @@ unsigned char * nii_reorderSlicesX(unsigned char* bImg, struct nifti_1_header *h } else if (i != fromSlice) { uint64_t inPos = fromSlice * sliceBytes; uint64_t outPos = i * sliceBytes; - memcpy( &bImg[outPos], &outImg[inPos], sliceBytes); - } - } - free(outImg); - return bImg; + memcpy(&bImg[outPos], &outImg[inPos], sliceBytes); + } + } + free(outImg); + return bImg; } -/*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 - //if (sliceOrder == NULL) return bImg; - 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; - //printMessage(" NOT reordering %d Philips slices.\n", dim3to7); return bImg; - uint64_t sliceBytes = hdr->dim[1]*hdr->dim[2]*hdr->bitpix/8; - unsigned char *sliceImg = (unsigned char *)malloc( sliceBytes); - //for (int i = 0; i < dim3to7; i++) { //for each volume - uint64_t imgSz = nii_ImgBytes(*hdr); - //this uses a lot of RAM, someday this could be done in place... - unsigned char *outImg = (unsigned char *)malloc( imgSz); - memcpy( &outImg[0],&bImg[0], imgSz); - - for (int i = 0; i < dim3to7; i++) { //for each volume - int toSlice = dti4D->sliceOrder[i]; - //if (i < 10) printMessage(" ===> Moving slice from/to positions\t%d\t%d\n", i, toSlice); - if (i != toSlice) { - uint64_t inPos = i * sliceBytes; - uint64_t outPos = toSlice * sliceBytes; - memcpy( &bImg[outPos], &outImg[inPos], sliceBytes); - } - } - free(sliceImg); - free(outImg); - return bImg; -}*/ - -/*unsigned char * nii_XYTZ_XYZT(unsigned char* bImg, struct nifti_1_header *hdr, int seqRepeats) { - //Philips can save time as 3rd dimensions, NIFTI requires time is 4th dimension - int dim4to7 = 1; - for (int i = 4; i < 8; i++) - if (hdr->dim[i] > 1) dim4to7 = dim4to7 * hdr->dim[i]; - if ((hdr->dim[3] < 2) || (dim4to7 < 2)) return bImg; - printMessage("Converting XYTZ to XYZT with %d slices (Z) and %d volumes (T).\n",hdr->dim[3], dim4to7); - if ((dim4to7 % seqRepeats) != 0) { - printError("Patient position repeats %d times, but this does not evenly divide number of volumes (%d)\n", seqRepeats,dim4to7); - seqRepeats = 1; - } - uint64_t typeRepeats = dim4to7 / seqRepeats; - uint64_t sliceBytes = hdr->dim[1]*hdr->dim[2]*hdr->bitpix/8; - uint64_t seqBytes = sliceBytes * seqRepeats; - uint64_t typeBytes = seqBytes * hdr->dim[3]; - uint64_t imgSz = nii_ImgBytes(*hdr); - //this uses a lot of RAM, someday this could be done in place... - unsigned char *outImg = (unsigned char *)malloc( imgSz); - //memcpy(&tempImg[0], &bImg[0], imgSz); - uint64_t origPos = 0; - uint64_t Pos = 0; // - for (int t = 0; t < (int)typeRepeats; t++) { //for each volume - for (int s = 0; s < seqRepeats; s++) { - origPos = (t*typeBytes) +s*sliceBytes; - for (int z = 0; z < hdr->dim[3]; z++) { //for each slice - memcpy( &outImg[Pos],&bImg[origPos], sliceBytes); - Pos += sliceBytes; - origPos += seqBytes; - } - }//for s - } - free(bImg); - return outImg; -} //nii_XYTZ_XYZT() -*/ - -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; +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; +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; + long offset; + long size; }; -TJPEG * decode_JPEG_SOF_0XC3_stack (const char *fn, int skipBytes, bool isVerbose, int frames, bool isLittleEndian) { +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("Tag %#x length %d end at %ld\n", 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; + 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, bool 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; - } - //printMessage("JPEG %fms\n", ((double)(clock()-start))/1000); - 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; +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 @@ -3277,49 +3218,49 @@ unsigned char * nii_loadImgJPEGC3(char* imgname, struct nifti_1_header hdr, stru #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"); +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); + 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); + 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); + 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); + 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); + 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; @@ -3328,111 +3269,112 @@ unsigned char * nii_loadImgJPEG50(char* imgname, struct TDICOMdata dcm) { #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; +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; +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); +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; + 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"); + FILE *file = fopen(imgname, "rb"); if (!file) { - printError("Unable to open %s\n", imgname); - return NULL; - } + 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); + 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); + 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: + 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; + 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; + i += 2; } else { - tImg[o] = cImg[i]; - o ++; + tImg[o] = cImg[i]; + o++; } } //for i free(cImg); @@ -3442,61 +3384,61 @@ unsigned char * nii_loadImgPMSCT_RLE1(char* imgname, struct nifti_1_header hdr, //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 + 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; + 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"); +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; - } + 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); + 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); + 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()); + 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)) { @@ -3507,8 +3449,8 @@ unsigned char * nii_loadImgRLE(char* imgname, struct nifti_1_header hdr, struct 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); + 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); @@ -3518,8 +3460,8 @@ unsigned char * nii_loadImgRLE(char* imgname, struct nifti_1_header hdr, struct //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; + if ((dcm.samplesPerPixel == 1) && (littleEndianPlatform())) //endian, except for RGB + vx = (bytesPerSample - 1) - i; while (vx < imgsz) { int8_t n = (int8_t)cImg[offset]; offset++; @@ -3531,7 +3473,7 @@ unsigned char * nii_loadImgRLE(char* imgname, struct nifti_1_header hdr, struct int8_t v = cImg[offset]; offset++; if (vx >= imgsz) - ;//printMessage("literal overflow %d %d\n", r, reps); + ; //printMessage("literal overflow %d %d\n", r, reps); else bImg[vx] = v; vx = vx + bytesPerSample; @@ -3542,7 +3484,7 @@ unsigned char * nii_loadImgRLE(char* imgname, struct nifti_1_header hdr, struct int reps = -(int)n + 1; for (int r = 0; r < reps; r++) { if (vx >= imgsz) - ;//printMessage("repeat overflow %d\n", reps); + ; //printMessage("repeat overflow %d\n", reps); else bImg[vx] = v; vx = vx + bytesPerSample; @@ -3551,69 +3493,69 @@ unsigned char * nii_loadImgRLE(char* imgname, struct nifti_1_header hdr, struct } //while vx < imgsz } //for i < bytesPerSample free(cImg); - return bImg; + 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 +#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" +//-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" +//-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) { +unsigned char *nii_loadImgJPEGLS(char *imgname, struct nifti_1_header hdr, struct TDICOMdata dcm) { //load compressed data - FILE *file = fopen(imgname , "rb"); + FILE *file = fopen(imgname, "rb"); if (!file) { - printError("Unable to open %s\n", imgname); - return NULL; - } + 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); + 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); + 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 (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 + unsigned char *bImg = (unsigned char *)malloc(imgsz); //binary output JlsParameters params = {}; - #ifdef myEnableJPEGLS1 - if(JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms) != OK ) { - #else +#ifdef myEnableJPEGLS1 + if (JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms) != OK) { +#else using namespace charls; if (JpegLsReadHeader(cImg, dcm.imageBytes, ¶ms, nullptr) != ApiResult::OK) { - #endif +#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 +#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; @@ -3622,122 +3564,114 @@ unsigned char * nii_loadImgJPEGLS(char* imgname, struct nifti_1_header hdr, stru } #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 > 0)); - 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); - 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); - /* we will do this in nii_dicom_batch #ifdef obsolete_mosaic_flip - img = nii_flipImgY(img, hdr); - #endif*/ - } - 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); - - //~ - /*if (((dcm.patientPositionSequentialRepeats * 2) == dcm.patientPositionRepeats) && (dcm.isHasPhase) && (dcm.isHasMagnitude)) { - hdr->dim[3] = hdr->dim[3] / 2; - hdr->dim[4] = hdr->dim[4] * 2; +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; - printMessage("Splitting Phase+Magnitude into two volumes for %d slices (Z) and %d volumes (T).\n",hdr->dim[3], hdr->dim[4]); - }*/ - headerDcm2NiiSForm(dcm,dcm, hdr, false); - return img; + } + 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)}; + 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; + //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; +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; - } + 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; + 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 @@ -3745,161 +3679,152 @@ int isDICOMfile(const char * fname) { //0=NotDICOM, 1=DICOM, 2=Maybe(not Part 10 // 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 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; +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); +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; +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; +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() + _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); +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() + _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); +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_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() +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. @@ -3907,36 +3832,38 @@ void set_isAtFirstPatientPosition_tvd(struct TVolumeDiffusion* ptvd, const bool // 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 +// 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. +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 + 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 @@ -3948,120 +3875,128 @@ void _update_tvd(struct TVolumeDiffusion* ptvd) { 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 + 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; + 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 ((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"); + 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]); + 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("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() + } + 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; + 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 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); +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; +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; +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; +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) { +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; + if (dcm1.dimIdx[i] < dcm2.dimIdx[i]) + return true; + else if (dcm1.dimIdx[i] > dcm2.dimIdx[i]) + return false; + } + return false; } //compareTDCMdimRev() #else @@ -4070,12 +4005,11 @@ 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; + 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() @@ -4084,630 +4018,677 @@ 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; + 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 readDICOMv(char * fname, int isVerbose, int compressFlag, struct TDTI4D *dti4D) { +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) + 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"); + 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); + 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); + 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 +//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 +#else size_t MaxBufferSz = 1000000; //ideally size of DICOM header, but this varies from 2D to 4D files - #endif +#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); + unsigned char *buffer = (unsigned char *)malloc(MaxBufferSz + 1); if (!buffer) { - printError( "Memory exhausted!"); - fclose(file); + 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 + 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 kXRayExposure 0x0018+(0x1152 << 16 ) -#define kConvolutionKernel 0x0018+(0x1210 << 16 ) //SH -#define kFrameDuration 0x0018+(0x1242 << 16 ) //IS -#define kReceiveCoilName 0x0018+(0x1250 << 16 ) // SH -#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 kEchoPlanarPulseSequence 0x0018+uint32_t(0x9018 << 16 ) //'CS' 'YES'/'NO' -#define kRectilinearPhaseEncodeReordering 0x0018+uint32_t(0x9034 << 16) //'CS' 'REVERSE_LINEAR'/'LINEAR' -#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 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 -#define kSeriesPlaneGE 0x0019+(0x1017<< 16 ) //SS -#define kDwellTime 0x0019+(0x1018<< 16 ) //IS in NSec, see https://github.com/rordenlab/dcm2niix/issues/127 +#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 kSliceTimeSiemens 0x0019+(0x1029<< 16) ///FD -#define kDiffusionGradientDirectionSiemens 0x0019+(0x100E<< 16 ) //FD -#define kNumberOfDiffusionDirectionGE 0x0019+(0x10E0<< 16) ///DS NumberOfDiffusionDirection:UserData24 -#define kLastScanLoc 0x0019+(0x101B<< 16 ) -#define kRawDataRunNumberGE 0x0019+(0x10A2<< 16 ) //SL -#define kMaxEchoNumGE 0x0019+(0x10a9<< 16 ) //DS -#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 kPulseSequenceNameGE 0x0019+(0x109C<< 16 ) //LO 'epiRT' or 'epi' -#define kInternalPulseSequenceNameGE 0x0019+(0x109E<< 16 ) //LO 'EPI' or 'EPI2' -#define kSharedFunctionalGroupsSequence 0x5200+uint32_t(0x9229<< 16 ) // SQ -#define kPerFrameFunctionalGroupsSequence 0x5200+uint32_t(0x9230<< 16 ) // SQ -#define kBandwidthPerPixelPhaseEncode 0x0019+(0x1028<< 16 ) //FD -#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 kTemporalPositionIdentifier 0x0020+(0x0100 << 16 ) //IS -#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 ) +// 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 +#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 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 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 kMultiBandGE 0x0043+(0x10B6 << 16 ) //LO -#define kAcquisitionMatrixText 0x0051+(0x100B << 16 ) //LO -#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 +#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 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 kMRAcquisitionTypePhilips 0x2005+(0x106F << 16) -#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 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 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 +// 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 - //uint32_t privateCreatorMask = 0; //0 -> none - //uint32_t privateCreatorRemap = 0; //0 -> none + 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; - uint32_t dimensionIndexPointer[MAX_NUMBER_OF_DIMENSIONS]; - size_t dimensionIndexPointerCounter = 0; - int maxInStackPositionNumber = 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 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; - 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 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); + // Allocating a large array on the stack, as below, vexes valgrind and may cause overflow + std::vector dcmDim(kMaxSlice2D); #else - TDCMdim dcmDim[kMaxSlice2D]; + 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; + 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; - } + 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; +#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 (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]); - 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 (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); } - 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]; + 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; } - 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 - /* - if (d.manufacturer == kMANUFACTURER_BRUKER) { - for(size_t i = 1; i < dimensionIndexPointerCounter; i++){ - if (dimensionIndexPointer[i] == kInStackPositionNumber){ - //swap with first - dimensionIndexOrder[i] = 0; - dimensionIndexOrder[0] = i; - } - } - }*/ //Canon and Bruker reverse dimensionIndexItem order relative to Philips: new versions introduce compareTDCMdimRev - int ndim = nDimIndxVal; - //printf("%d: %d %d %d %d\n", ndim, d.dimensionIndexValues[0], d.dimensionIndexValues[1], d.dimensionIndexValues[2], d.dimensionIndexValues[3]); - 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; - 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; + 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; @@ -4719,2232 +4700,2384 @@ uint32_t kSequenceDelimitationItemTag = 0xFFFE +(0xE0DD << 16 ); 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 (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 + 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'; - //} - } - /* //Handle SQs: for explicit these have VR=SQ - if ((vr[0] == 'S') && (vr[1] == 'Q')) { - //http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html - uint32_t special = dcmInt(4,&buffer[lPos],d.isLittleEndian); - uint32_t slen = dcmInt(4,&buffer[lPos+4],d.isLittleEndian); - //if (d.isExplicitVR) - // slen = dcmInt(4,&buffer[lPos+8],d.isLittleEndian); + 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; - //printError(" SPECIAL >>>>t%04x,%04x %08x %08x\n", groupElement & 65535,groupElement>>16, special, slen); + if (slen != kUndefinedLen) { + nNestPos++; + if (nNestPos >= kMaxNestPost) + nNestPos = kMaxNestPost - 1; + nestPos[nNestPos] = slen + lFileOffset + lPos; + } + lLength = 4; + sqDepth++; //return d; - is2005140FSQ = (groupElement == kPrivatePerFrameSq); - //if (isNextSQis2005140FSQ) is2005140FSQ = true; - //isNextSQis2005140FSQ = false; - if (special == kSequenceDelimitationItemTag) { - //unknown - } else if (slen == kUndefinedLen) { - sqDepth++; - if ((sqDepthPrivate == 0) && ((groupElement & 65535) % 2)) - sqDepthPrivate = sqDepth; //in a private SQ: ignore contents - } else if ((is2005140FSQ) || ((groupElement & 65535) % 2)) {//private SQ of known length - lets jump over this! - slen = lFileOffset + lPos + slen; - if ((sqEndPrivate < 0) || (slen > sqEndPrivate)) //sqEndPrivate is signed - sqEndPrivate = slen; //if nested private SQs, remember the end address of the top parent SQ + } 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; } } - //next: look for required tags - if ((groupElement == kItemTag) && (isEncapsulatedData)) { - d.imageBytes = dcmInt(4,&buffer[lPos],d.isLittleEndian); - printMessage("compressed data %d-> %ld\n",d.imageBytes, lPos); - - d.imageBytes = dcmInt(4,&buffer[lPos-4],d.isLittleEndian); - printMessage("compressed data %d-> %ld\n",d.imageBytes, lPos); - if (d.imageBytes > 128) { - encapsulatedDataFragments++; - if (encapsulatedDataFragmentStart == 0) - encapsulatedDataFragmentStart = (int)lPos + (int)lFileOffset; - } - } - if ((sqEndPrivate > 0) && ((lFileOffset + lPos) > sqEndPrivate)) - sqEndPrivate = -1; //end of private SQ with defined length - if (groupElement == kSequenceDelimitationItemTag) { //end of private SQ with undefined length - sqDepth--; - if (sqDepth < sqDepthPrivate) { - sqDepthPrivate = 0; //no longer in a private SQ - } - } - if (sqDepth < 0) sqDepth = 0;*/ - 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) { - encapsulatedDataFragments++; - if (encapsulatedDataFragmentStart == 0) - encapsulatedDataFragmentStart = (int)lPos + (int)lFileOffset; - } - } - if ((isIconImageSequence) && ((groupElement & 0x0028) == 0x0028 )) groupElement = kUnused; //ignore icon dimensions - #ifdef salvageAgfa //issue435 + 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 + 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 (((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 + //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) { + 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 ); + //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 ); + 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 ); + 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 ); + 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 ); + 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) + 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); + privateCreatorRemaps[nRemaps] = privateCreatorRemap; + //printf("new remapping %04x,%04x -> %04x,%04x\n", privateCreatorMask & 65535, privateCreatorMask >> 16, privateCreatorRemap & 65535, privateCreatorRemap >> 16); if (isVerbose > 1) - printf("new remapping (%d) %04x,%02xxy -> %04x,%02xxy\n", nRemaps, privateCreatorMask & 65535, privateCreatorMask >> 24, privateCreatorRemap & 65535, privateCreatorRemap >> 24); + 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; + // 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; + 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) - printf("remapping %04x,%04x -> %04x,%04x\n", groupElement & 65535, groupElement >> 16, remappedGroupElement & 65535, remappedGroupElement >> 16); - groupElement = remappedGroupElement; + 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 + skipRemap: +#endif // salvageAgfa if ((lLength % 2) != 0) { //https://www.nitrc.org/forum/forum.php?thread_id=11827&forum_id=4703 - printf("Illegal DICOM tag %04x,%04x (odd element length %d): %s\n", groupElement & 65535,groupElement>>16, lLength, fname); + 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; - //printMessage("Lossy JPEG: please decompress with Osirix or dcmdjpg. %s\n", transferSyntax); - //d.imageStart = 1;//abort as invalid (imageStart MUST be >128) - //uJPEG does not decode these: ..53 ...55 - // } else if (strcmp(transferSyntax, "1.2.840.10008.1.2.4.53") == 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 < 6) || (strstr(impTxt, "OSIRIX") == NULL) ) break; - printError("OSIRIX Detected\n"); - break; }*/ - 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>%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) } - 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); - 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; - 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 kContentTime : - // char contentTimeTxt[kDICOMStr]; - // dcmStr(lLength, &buffer[lPos], contentTimeTxt); - // contentTime = atof(contentTimeTxt); - // 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; + } + 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; - 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 kEchoPlanarPulseSequence : // CS [YES],[NO] - if (lLength < 2) break; - if (toupper(buffer[lPos]) == 'Y') - d.isEPI = true; - 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 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 kInversionTimes : {//issue 380 - if ((lLength < 8) || ((lLength % 8) != 0)) break; - d.TI = dcmFloatDouble(lLength, &buffer[lPos], d.isLittleEndian); - /* - //see issue385 : Philips reports Implausible InversionTimes - int nTI = lLength / 8; - if (nTI > 1) { - bool isTIvaries = false; - for (int i = 1; i < nTI; i++) { - float ti = dcmFloatDouble(8, &buffer[lPos+(i*8)],d.isLittleEndian); - if (!isSameFloatGE(ti, d.TI)) isTIvaries = true; - } - if (isTIvaries) printWarning("0018,9079 reports multiple inversion times: %s\n", fname); - } - */ - 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; - /*case kDiffusionBFactorSiemens : - if (d.manufacturer == kMANUFACTURER_SIEMENS) - printMessage("last scan location %f\n,",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 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") != NULL) && (strstr(epiStr, "epi2") == NULL)){ - d.epiVersionGE = 0; //-1 = not epi, 0 = epi, 1 = epiRT - } - if (strstr(epiStr, "epi2") != NULL){ - d.epiVersionGE = 2; //-1 = not epi, 0 = epi, 1 = epiRT, 2 = epi2 - } - if (strstr(epiStr, "epiRT") != NULL){ - d.epiVersionGE = 1; //-1 = not epi, 0 = epi, 1 = epiRT - } - 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 (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 - } - 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; + 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) } - 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 (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 + } //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; - 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; + if (acquisitionTimesGE_UIH >= kMaxEPI3D) break; - case kTemporalPositionIndex: - temporalPositionIndex = dcmInt(4,&buffer[lPos],d.isLittleEndian); - if (temporalPositionIndex > maxTemporalPositionIndex) maxTemporalPositionIndex = temporalPositionIndex; + 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; - 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 (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 kXYSpacing: - // dcmMultiFloat(lLength, (char*)&buffer[lPos], 2, d.xyzMM); - // 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); + 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; - //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 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 : { //0065,1037 - //0.03712929804225321\-0.5522387869760447\-0.8328587749392602 - 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: { + 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 : - /*if (d.manufacturer == kMANUFACTURER_PHILIPS) { - //Private SQs can report different (more precise?) pixel bandwidth values than in the main header! - // https://github.com/rordenlab/dcm2niix/issues/170 - if (is2005140FSQ) break; - if ((lFileOffset + lPos) < sqEndPrivate) break; //inside private SQ, SQ has defined length - if (sqDepthPrivate > 0) break; //inside private SQ, SQ has undefined length - }*/ - 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 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)); + 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; - 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 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 kStackSliceNumber: { //https://github.com/Kevin-Mattheus-Moerman/GIBBON/blob/master/dicomDict/PMS-R32-dict.txt - int stackSliceNumber = dcmInt(lLength,&buffer[lPos],d.isLittleEndian); - printMessage("StackSliceNumber %d\n",stackSliceNumber); - 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 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; + 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; - 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 kDiffusionBFactor: // 2001,1003 - // if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition)) { - // d.CSA.numDti++; //increment with BFactor: on Philips slices with B=0 have B-factor but no diffusion directions - // if (d.CSA.numDti == 2) { //First time we know that this is a 4D DTI dataset - // //d.dti4D = (TDTI *)malloc(kMaxDTI4D * sizeof(TDTI)); - // 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]; - // } - // d.CSA.dtiV[0] = dcmFloat(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]; - // /*if ((d.CSA.numDti > 0) && (d.CSA.numDti <= kMaxDTIv)) - // d.CSA.dtiV[d.CSA.numDti-1][0] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian);*/ - // } - // break; - - /* - case kDiffusionDirectionPhilips: {// - //note not useful: does not report precise direction, both B=0 and Isotropic scans labelled "I" so does not tell us if image is oblique - //http://incenter.medical.philips.com/doclib/enc/fetch/2000/4504/577242/577256/588723/5144873/5144488/5144982/DICOM_Conformance_Statement_Ingenia_R4.1.pdf%3fnodeid%3d8124182%26vernum%3d-2 - //CS: Possible values: P (PreparationDirection), M (MeasurementDirection),S (Selection Direction),O(Oblique Direction),I (Isotropic),Only applicable for diffusion scans. - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - char diffDir[kDICOMStr]; - dcmStr(lLength, &buffer[lPos], diffDir); - printf(">>%s %s\n", diffDir, fname); + d.imageStart = (int)lPos + (int)lFileOffset; + d.imageBytes = lLength; + break; + case kPrivateCreator: { + if (d.manufacturer != kMANUFACTURER_UNKNOWN) 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 - // //d.dti4D = (TDTI *)malloc(kMaxDTI4D * sizeof(TDTI)); - // 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]); + 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; + 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 kSharedFunctionalGroupsSequence: - // if ((d.manufacturer == kMANUFACTURER_SIEMENS) && isAtFirstPatientPosition) { - // break; // For now - need to figure out how to get the nested - // // part of buffer[lPos]. - // } - // break; - - //case kSliceNumberMrPhilips : - // sliceNumberMrPhilips3D = dcmStrInt(lLength, &buffer[lPos]); - // 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); + 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; - 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); + if (lLength < 2) break; - case kDiffusionDirectionRL: - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - vRLPhilips = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - set_diffusion_directionPhilips(&volDiffusion, vRLPhilips, 0); + if (toupper(buffer[lPos]) == 'Y') + d.isPartialFourier = true; + break; + case kWaterFatShiftPhilips: + if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - case kDiffusionDirectionAP: - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - vAPPhilips = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - set_diffusion_directionPhilips(&volDiffusion, vAPPhilips, 1); + d.waterFatShift = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + break; + case kDiffusionDirectionRL: + if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - case kDiffusionDirectionFH: - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - vFHPhilips = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - set_diffusion_directionPhilips(&volDiffusion, vFHPhilips, 2); + vRLPhilips = dcmFloat(lLength, &buffer[lPos], d.isLittleEndian); + set_diffusion_directionPhilips(&volDiffusion, vRLPhilips, 0); + break; + case kDiffusionDirectionAP: + if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - // case kDiffusionDirectionRL: - // if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition)) { - // d.CSA.dtiV[1] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - // if ((d.CSA.numDti > 1) && (d.CSA.numDti < kMaxDTI4D)) - // dti4D->S[d.CSA.numDti-1].V[1] = d.CSA.dtiV[1]; - // } - // /*if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition) && (d.CSA.numDti > 0) && (d.CSA.numDti <= kMaxDTIv)) - // d.CSA.dtiV[d.CSA.numDti-1][1] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian);*/ - // break; - // case kDiffusionDirectionAP: - // if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition)) { - // d.CSA.dtiV[2] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - // if ((d.CSA.numDti > 1) && (d.CSA.numDti < kMaxDTI4D)) - // dti4D->S[d.CSA.numDti-1].V[2] = d.CSA.dtiV[2]; - // } - // /*if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition) && (d.CSA.numDti > 0) && (d.CSA.numDti <= kMaxDTIv)) - // d.CSA.dtiV[d.CSA.numDti-1][2] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian);*/ - // break; - // case kDiffusionDirectionFH: - // if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition)) { - // d.CSA.dtiV[3] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian); - // if ((d.CSA.numDti > 1) && (d.CSA.numDti < kMaxDTI4D)) - // dti4D->S[d.CSA.numDti-1].V[3] = d.CSA.dtiV[3]; - // //printMessage("dti XYZ %g %g %g\n",d.CSA.dtiV[1],d.CSA.dtiV[2],d.CSA.dtiV[3]); - // } - // /*if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (isAtFirstPatientPosition) && (d.CSA.numDti > 0) && (d.CSA.numDti <= kMaxDTIv)) - // d.CSA.dtiV[d.CSA.numDti-1][3] = dcmFloat(lLength, &buffer[lPos],d.isLittleEndian);*/ - // //http://www.na-mic.org/Wiki/index.php/NAMIC_Wiki:DTI:DICOM_for_DWI_and_DTI - // 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; - //case kMRImageGradientOrientationNumber : - // if (d.manufacturer == kMANUFACTURER_PHILIPS) - // MRImageGradientOrientationNumber = dcmStrInt(lLength, &buffer[lPos]); - break; - case kMRImageDiffBValueNumber: - if (d.manufacturer != kMANUFACTURER_PHILIPS) break; - philMRImageDiffBValueNumber = 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 kObjectGraphics: - // printMessage("---->%d,",lLength); - // 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"); + 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) + 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; - if (phasePolarityFlag == kGE_PHASE_ENCODING_POLARITY_UNFLIPPED) + else 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; } - //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 + } + 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 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 + } + 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 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 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 kTemporalPositionIdentifier : - // temporalPositionIdentifier = dcmStrInt(lLength, &buffer[lPos]); - // 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 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)) { - lLength = 0; - isEncapsulatedData = true; - encapsulatedDataImageStart = (int)lPos + (int)lFileOffset; - } - 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/ - + 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); + 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]=='A') && (vr[1]=='T')) isStr = xxx; - 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] == '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; + 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') || (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] = '_'; + 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); + } 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) { - printError("Compressed image stored as %d fragments: decompress with gdcmconv, Osirix, dcmdjpeg or dcmjp2k %s\n", encapsulatedDataFragments, fname); - } else { - d.imageStart = encapsulatedDataFragmentStart; - } - } else if ((isEncapsulatedData) && (d.imageStart < 128)) { - //http://www.dclunie.com/medical-image-faq/html/part6.html + 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)) ) { + 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); + char acquisitionDateTxt[kDICOMStr]; + memcpy(acquisitionDateTxt, acquisitionDateTimeTxt, kYYYYMMDDlen); acquisitionDateTxt[kYYYYMMDDlen] = '\0'; // IMPORTANT! - d.acquisitionDate = atof(acquisitionDateTxt); - char acquisitionTimeTxt[kDICOMStr]; + d.acquisitionDate = atof(acquisitionDateTxt); + char acquisitionTimeTxt[kDICOMStr]; int timeLen = (int)strlen(acquisitionDateTimeTxt) - kYYYYMMDDlen; - strncpy(acquisitionTimeTxt, &acquisitionDateTimeTxt[kYYYYMMDDlen], timeLen); + 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 (?). - } - /* 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])) - 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 ((d.manufacturer == kMANUFACTURER_GE) && (d.modality == kMODALITY_MR) && (strlen(d.seriesDescription) > 1)) { - //issue 473, 476: swap protocolName and seriesDescription - char tempTxt[kDICOMStr] = ""; - strcpy(tempTxt, d.protocolName); - strcpy(d.protocolName, d.seriesDescription); - strcpy(d.seriesDescription, tempTxt); - }*/ - if ((strlen(d.protocolName) < 1) && (strlen(d.seriesDescription) > 1)) + } + 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)) + 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 - - //d.isValid = false; - - // if (!isOrient) { - // if (d.isNonImage) - // printWarning("Spatial orientation ambiguous (tag 0020,0037 not found) [probably not important: derived image]: %s\n", fname); - // else if (((d.manufacturer == kMANUFACTURER_SIEMENS)) && (d.samplesPerPixel != 1)) - // printWarning("Spatial orientation ambiguous (tag 0020,0037 not found) [perhaps derived FA that is not required]: %s\n", fname); - // else - // printWarning("Spatial orientation ambiguous (tag 0020,0037 not found): %s\n", fname); - // } -/*if (d.isHasMagnitude) - printError("=====> mag %d %d\n", d.patientPositionRepeats, d.patientPositionSequentialRepeats ); -if (d.isHasPhase) - printError("=====> phase %d %d\n", d.patientPositionRepeats, d.patientPositionSequentialRepeats ); - - printError("=====> reps %d %d\n", d.patientPositionRepeats, d.patientPositionSequentialRepeats ); -*/ - /*if ((patientPositionSequentialRepeats > 1) && ( (d.xyzDim[3] % patientPositionSequentialRepeats) == 0 )) { - //will require Converting XYTZ to XYZT - //~ d.numberOfDynamicScans = d.xyzDim[3] / d.patientPositionSequentialRepeats; - //~ d.xyzDim[4] = d.xyzDim[3] / d.numberOfDynamicScans; - numberOfDynamicScans = d.xyzDim[3] / patientPositionSequentialRepeats; - d.xyzDim[4] = d.xyzDim[3] / numberOfDynamicScans; - - d.xyzDim[3] = d.numberOfDynamicScans; - }*/ - if (numberOfFrames == 0) numberOfFrames = d.xyzDim[3]; + 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; @@ -6957,77 +7090,83 @@ if (d.isHasPhase) 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]; - } - } + 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 + 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); + 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"); + Rf_error("Irrecoverable error during conversion"); #else - exit (kEXIT_CORRUPT_FILE_FOUND); + 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 ((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. + //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]; @@ -7035,24 +7174,60 @@ if (d.isHasPhase) 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] < 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 (mx[j] != mn[j]) maxVariableItem = j; } 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]) + if (mn[i] != mx[i]) printMessage(" Dimension %d Range: %d..%d\n", i, mn[i], mx[i]); - } - } //verbose > 1 + } + } //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; + 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) @@ -7066,54 +7241,63 @@ if (d.isHasPhase) 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++) { + // 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 + 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; - if (d.xyzDim[3] > 1) j = 1; for (int i = 0; i < d.xyzDim[4]; i++) { - int slice = j+(i * d.xyzDim[3]); + 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->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; + 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; + 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 ((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++) { @@ -7123,203 +7307,219 @@ if (d.isHasPhase) 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] ); + 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 + 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 - /* //Attempt to append ADC - printMessage("CXC grad %g %d %d\n", philDTI[0].V[0], maxGradNum, d.xyzDim[4]); - if ((maxGradNum > 1) && ((maxGradNum+1) == d.xyzDim[4]) ) { - //ADC map (non-zero b-value with zero vector length) - if (isVerbose) - printMessage("Final volume does not have an associated 0020,9157. Assuming final volume is an ADC/isotropic map\n", philDTI[0].V[0], maxGradNum, d.xyzDim[4]); - philDTI[maxGradNum].V[0] = 1000.0; - philDTI[maxGradNum].V[1] = 0.0; - philDTI[maxGradNum].V[2] = 0.0; - philDTI[maxGradNum].V[3] = 0.0; - maxGradNum++; - }*/ - /*if ((minGradNum >= 1) && ((maxGradNum-minGradNum+1) == d.xyzDim[4])) { - //see ADNI DWI data for 018_S_4868 - the gradient numbers are in the range 2..37 for 36 volumes - no gradient number 1! - if (philDTI[minGradNum -1].V[0] >= 0) { - if (isVerbose) - printMessage("Using %d diffusion data directions coded by DimensionIndexValues\n", maxGradNum); - int off = 0; - if (minGradNum > 1) { - off = minGradNum - 1; - printWarning("DimensionIndexValues (0020,9157) is not indexed from 1 (range %d..%d). Please validate results\n", minGradNum, maxGradNum); - } - for (int i = 0; i < d.xyzDim[4]; i++) { - dti4D->S[i].V[0] = philDTI[i+off].V[0]; - dti4D->S[i].V[1] = philDTI[i+off].V[1]; - dti4D->S[i].V[2] = philDTI[i+off].V[2]; - dti4D->S[i].V[3] = philDTI[i+off].V[3]; - if (isVerbose > 1) - printMessage(" grad %d b=%g vec=%gx%gx%g\n", i, dti4D->S[i].V[0], dti4D->S[i].V[1], dti4D->S[i].V[2], dti4D->S[i].V[3]); - } - d.CSA.numDti = maxGradNum - off; - } - }*/ + } //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; + 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)); + d.seriesUidCrc = mz_crc32X((unsigned char *)&d.protocolName, strlen(d.protocolName)); } - if ((d.manufacturer == kMANUFACTURER_PHILIPS) && (!isTriggerSynced)) //issue408 - d.triggerDelayTime = 0.0; + //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; + 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)); + d.seriesUidCrc = mz_crc32X((unsigned char *)&seriesTimeTxt, strlen(seriesTimeTxt)); } - if (((d.manufacturer == kMANUFACTURER_TOSHIBA) || (d.manufacturer == kMANUFACTURER_CANON))&& (B0Philips > 0.0)) {//issue 388 + 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); + 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]); + 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]); + 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; - } - +#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)); + // 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; + 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; + 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 + 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)) ) { +#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 } - if (hasDwiDirectionality) d.isVectorFromBMatrix = false; //issue 265: Philips/Siemens have both directionality and bmatrix, Bruker only has bmatrix - /* - fixed 2/2019 by modifying to kDiffusionBFactor, kDiffusionDirectionRL, kDiffusionDirectionAP, kDiffusionDirectionFH - if ((d.xyzDim[3] == 1) && (numDimensionIndexValues < 1) && (d.manufacturer == kMANUFACTURER_PHILIPS) && (B0Philips >= 0.0)) { - //Special case: old Philips Classic DWI storing vectors in 0019,10bb, 0019,10bc - //printf(">>>>%g %g %g %g\n",B0Philips, vRLPhilips, vAPPhilips, vFHPhilips); - d.CSA.dtiV[0] = B0Philips; - d.CSA.dtiV[1] = vRLPhilips; - d.CSA.dtiV[2] = vAPPhilips; - d.CSA.dtiV[3] = vFHPhilips; - d.CSA.numDti = 1; - } - */ + //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("%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); + //printMessage("buffer usage %d %d %d\n",d.imageStart, lPos+lFileOffset, MaxBufferSz); return d; } // readDICOM() -struct TDICOMdata readDICOM(char * fname) { - struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); //unused - TDICOMdata ret = readDICOMv(fname, false, kCompressSupport, dti4D); +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() - diff --git a/console/nii_dicom.h b/console/nii_dicom.h index fa3605bf..800d29e2 100644 --- a/console/nii_dicom.h +++ b/console/nii_dicom.h @@ -43,14 +43,14 @@ extern "C" { #define kCCsuf " CompilerNA" //unknown compiler! #endif #if defined(__arm__) || defined(__ARM_ARCH) - #define kCPUsuf " ARM" + #define kCPUsuf " ARM" #elif defined(__x86_64) #define kCPUsuf " x86-64" #else #define kCPUsuf " " //unknown CPU #endif -#define kDCMdate "v1.0.20210317" +#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 @@ -59,6 +59,7 @@ static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI dire #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 @@ -66,8 +67,9 @@ static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI dire #define kMANUFACTURER_TOSHIBA 4 #define kMANUFACTURER_UIH 5 #define kMANUFACTURER_BRUKER 6 -#define kMANUFACTURER_HITACHI 7 +#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 @@ -77,6 +79,26 @@ static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI dire #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 @@ -99,7 +121,26 @@ static const int kMaxDTI4D = kMaxSlice2D; //issue460: maximum number of DTI dire #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; @@ -135,6 +176,7 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; 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]; @@ -180,30 +222,37 @@ static const uint8_t MAX_NUMBER_OF_DIMENSIONS = 8; int xyzDim[5]; uint32_t coilCrc, seriesUidCrc, instanceUidCrc; int overlayStart[kMaxOverlay]; - int interp3D, 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 groupDelay, decayFactor, percentSampling,waterFatShift, numberOfAverages, imagingFrequency, patientWeight, zSpacing, zThick, pixelBandwidth, SAR, phaseFieldofView, accelFactPE, accelFactOOP, flipAngle, fieldStrength, TE, TI, TR, intenScale, intenIntercept, intenScalePhilips, gantryTilt, lastScanLoc, angulation[4]; + int 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 radiopharmaceutical[kDICOMStr], convolutionKernel[kDICOMStr], unitsPT[kDICOMStr], decayCorrection[kDICOMStr], attenuationCorrectionMethod[kDICOMStr],reconstructionMethod[kDICOMStr]; - char 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 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 readDICOM(char * fname); + 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); diff --git a/console/nii_dicom_batch.cpp b/console/nii_dicom_batch.cpp index 32233b78..0ec44bce 100644 --- a/console/nii_dicom_batch.cpp +++ b/console/nii_dicom_batch.cpp @@ -1,30 +1,30 @@ //#define myNoSave //do not save images to disk #ifdef _MSC_VER - #include - #define getcwd _getcwd - #define chdir _chrdir - #include "io.h" - //#include - #define MiniZ +#include +#define getcwd _getcwd +#define chdir _chrdir +#include "io.h" +//#include +#define MiniZ #else - #include - #ifdef myDisableMiniZ - #undef MiniZ - #else - #define MiniZ - #endif +#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 +#ifdef MiniZ +#include "miniz.c" //single file clone of libz +#else +#include +#endif #else - #undef MiniZ +#undef MiniZ #endif #include "tinydir.h" #include "print.h" @@ -37,6 +37,7 @@ #include "nii_foreign.h" #endif #include "nii_dicom.h" +#include "nii_ortho.h" #include //toupper #include #include @@ -47,21 +48,20 @@ #include #include #include -#include // clock_t, clock, CLOCKS_PER_SEC -#include "nii_ortho.h" +#include // clock_t, clock, CLOCKS_PER_SEC #if defined(_WIN64) || defined(_WIN32) - #include //write to registry +#include //write to registry #endif #ifndef M_PI - #define M_PI 3.14159265358979323846 +#define M_PI 3.14159265358979323846 #endif #if defined(_WIN64) || defined(_WIN32) - const char kPathSeparator ='\\'; - const char kFileSep[2] = "\\"; +const char kPathSeparator = '\\'; +const char kFileSep[2] = "\\"; #else - const char kPathSeparator ='/'; - const char kFileSep[2] = "/"; +const char kPathSeparator = '/'; +const char kFileSep[2] = "/"; #endif #ifdef USING_R @@ -69,144 +69,192 @@ #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]; + uint64_t indx, img; + uint32_t dimensionIndexValues[MAX_NUMBER_OF_DIMENSIONS]; }; - struct TSearchList { - unsigned long numItems, maxItems; - char **str; + unsigned long numItems, maxItems; + char **str; }; #ifndef PATH_MAX - #define PATH_MAX 4096 +#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 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'; + 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; +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) +#define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR) #endif #ifndef S_ISREG - #define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#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); +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) ); +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 - } +//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() + } +} // 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() +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 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"); +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; - } - 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 + } + 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 @@ -219,175 +267,183 @@ void geCorrectBvecs(struct TDICOMdata *d, int sliceDir, struct TDTI *vx, int isV 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]); + 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 mimimum 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 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[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; + } + //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 - } - 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]); - 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); + 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() +} // 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; + 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() + 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() -#ifndef USING_R #define myReadAsciiCsa -#endif #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 +// 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__)) +#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 @@ -407,7 +463,7 @@ void nii_saveText(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, * 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 + * 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) @@ -416,7 +472,7 @@ void nii_saveText(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, * 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) { +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; @@ -439,88 +495,96 @@ const void * memmem(const char *l, size_t l_len, const char *s, size_t s_len) { //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 +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; + 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' ) + 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 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; + if (!keyPos) + return ret; int i = (int)strlen(key); - while( ( i< remLength) && (keyPos[i] != 0x0A) ) { - if( keyPos[i] >= '0' && keyPos[i] <= '9' ) + 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 +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; + if (!keyPos) + return NAN; char str[kDICOMStr]; strcpy(str, ""); char tmpstr[2]; - tmpstr[1] = 0; + 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] == '-') ) { + while ((i < remLength) && (keyPos[i] != 0x0A)) { + if ((keyPos[i] >= '0' && keyPos[i] <= '9') || (keyPos[i] == '.') || (keyPos[i] == '-')) { tmpstr[0] = keyPos[i]; - strcat (str, tmpstr); + strcat(str, tmpstr); } i++; } - if (strlen(str) < 1) return NAN; + 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 +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; + if (!keyPos) + return 0.0; char str[kDICOMStr]; strcpy(str, ""); char tmpstr[2]; - tmpstr[1] = 0; + 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] == '-') ) { + while ((i < remLength) && (keyPos[i] != 0x0A)) { + if ((keyPos[i] >= '0' && keyPos[i] <= '9') || (keyPos[i] == '.') || (keyPos[i] == '-')) { tmpstr[0] = keyPos[i]; - strcat (str, tmpstr); + strcat(str, tmpstr); } i++; } - if (strlen(str) < 1) return 0.0; + 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' +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; + 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) ) { + 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 ++; + strcat(outStr, tmpstr); + outLen++; } if (keyPos[i] == '"') { - if (outLen > 0) break; + if (outLen > 0) + break; isQuote = true; } i++; @@ -528,110 +592,121 @@ void readKeyStr(const char * key, char * buffer, int remLength, char* outStr) { } //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; -} // phoeechoSpacingnixOffsetCSASeriesHeader() + //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; - float alFree[kMaxWipFree] ; - float adFree[kMaxWipFree]; - float alTI[kMaxWipFree]; - float dThickness, ulShape, sPositionDTra, sNormalDTra; + 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->refLinesPE = 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); +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; + 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 + 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 + //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 +#endif char keyStrLns[] = "sKSpace.lPhaseEncodingLines"; csaAscii->phaseEncodingLines = readKey(keyStrLns, keyPos, csaLengthTrim); char keyStrUcImg[] = "sSliceArray.ucImageNumb"; @@ -645,7 +720,7 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, char keyStrPF[] = "sKSpace.ucPhasePartialFourier"; csaAscii->partialFourier = readKey(keyStrPF, keyPos, csaLengthTrim); char keyStrES[] = "sFastImaging.lEchoSpacing"; - csaAscii->echoSpacing = readKey(keyStrES, keyPos, csaLengthTrim); + csaAscii->echoSpacing = readKey(keyStrES, keyPos, csaLengthTrim); char keyStrDS[] = "sDiffusion.dsScheme"; csaAscii->difBipolar = readKey(keyStrDS, keyPos, csaLengthTrim); if (csaAscii->difBipolar == 0) { @@ -657,24 +732,35 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, } 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); + readKeyStr(keyStrCoil, keyPos, csaLengthTrim, coilID); char keyStrCI[] = "sProtConsistencyInfo.tMeasuredBaselineString"; - readKeyStr(keyStrCI, keyPos, csaLengthTrim, consistencyInfo); + readKeyStr(keyStrCI, keyPos, csaLengthTrim, consistencyInfo); char keyStrCS[] = "sCoilSelectMeas.sCoilStringForConversion"; - readKeyStr(keyStrCS, keyPos, csaLengthTrim, coilElements); + readKeyStr(keyStrCS, keyPos, csaLengthTrim, coilElements); char keyStrSeq[] = "tSequenceFileName"; - readKeyStr(keyStrSeq, keyPos, csaLengthTrim, pulseSequenceDetails); + readKeyStr(keyStrSeq, keyPos, csaLengthTrim, pulseSequenceDetails); char keyStrWipMemBlock[] = "sWipMemBlock.tFree"; - readKeyStr(keyStrWipMemBlock, keyPos, csaLengthTrim, wipMemBlock); + readKeyStr(keyStrWipMemBlock, keyPos, csaLengthTrim, wipMemBlock); char keyStrPn[] = "tProtocolName"; - readKeyStr(keyStrPn, keyPos, csaLengthTrim, protocolName); + readKeyStr(keyStrPn, keyPos, csaLengthTrim, protocolName); char keyStrTE0[] = "alTE[0]"; csaAscii->TE0 = readKeyFloatNan(keyStrTE0, keyPos, csaLengthTrim); char keyStrTE1[] = "alTE[1]"; @@ -688,7 +774,7 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, if (keyPosTi) { for (int k = 0; k < kMaxWipFree; k++) { char txt[1024] = {""}; - sprintf(txt, "%s%d]", keyStrTiFree,k); + sprintf(txt, "%s%d]", keyStrTiFree, k); csaAscii->alTI[k] = readKeyFloatNan(txt, keyPos, csaLengthTrim); } } @@ -701,7 +787,7 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, if (keyPosFree) { for (int k = 0; k < kMaxWipFree; k++) { char txt[1024] = {""}; - sprintf(txt, "%s%d]", keyStrAlFree,k); + sprintf(txt, "%s%d]", keyStrAlFree, k); csaAscii->alFree[k] = readKeyFloat(txt, keyPos, csaLengthTrim); } } @@ -720,7 +806,7 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, if (keyPosFree) { for (int k = 0; k < kMaxWipFree; k++) { char txt[1024] = {""}; - sprintf(txt, "%s%d]", keyStrAdFree,k); + sprintf(txt, "%s%d]", keyStrAdFree, k); csaAscii->adFree[k] = readKeyFloatNan(txt, keyPos, csaLengthTrim); } } @@ -735,6 +821,9 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, 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); @@ -753,11 +842,14 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, 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); + 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); + 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); + 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); @@ -770,119 +862,127 @@ void siemensCsaAscii(const char * filename, TCsaAscii* csaAscii, int csaOffset, char keyStrSh7[] = "sGRADSPEC.alShimCurrent[4]"; shimSetting[7] = readKeyFloat(keyStrSh7, keyPos, csaLengthTrim); } - fclose (pFile); - free (buffer); + 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 +//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[]) { +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); + 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; + 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]; + 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 = hdrSz; hdrSz < cmpSz; hdrSz++) - if (pCmp[hdrSz] == 0) break; + 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 = hdrSz; hdrSz < cmpSz; hdrSz++) - if (pCmp[hdrSz] == 0) break; + 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); + 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 + 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 + 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 +#else ret = mz_inflate(&s, MZ_SYNC_FLUSH); if (ret != MZ_STREAM_END) { free(pUnCmp); return EXIT_FAILURE; } - #endif +#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] == '?')) + // + 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); + *sliceOrder = readKeyN1(keyStrSO, (char *)pUnCmp, unCmpSz); + char keyStrVO[] = "VIEWORDER"; + *viewOrder = readKey(keyStrVO, (char *)pUnCmp, unCmpSz); char keyStrMB[] = "MBACCEL"; - *mbAccel = readKey(keyStrMB, (char *) pUnCmp, unCmpSz); + *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 - } - } + *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); @@ -894,17 +994,19 @@ int geProtocolBlock(const char * filename, int geOffset, int geLength, int isV #endif //myReadGeProtocolBlock() void json_Str(FILE *fp, const char *sLabel, char *sVal) { // issue131,425 - if (strlen(sVal) < 1) return; + if (strlen(sVal) < 1) + return; unsigned char sValEsc[2048] = {""}; - unsigned char *iVal = (unsigned char *) sVal; + unsigned char *iVal = (unsigned char *)sVal; int o = 0; - for (int i = 0; i < strlen(sVal); i ++) { + 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++; + sValEsc[o] = '\\'; + o++; } - if ((sVal[i] >= 0x01) && (sVal[i] <= 0x07)) continue; //control characters like "bell" + 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 @@ -913,38 +1015,46 @@ void json_Str(FILE *fp, const char *sLabel, char *sVal) { // issue131,425 //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 ); + 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 ); + if (isnan(sVal)) + return; + fprintf(fp, sLabel, sVal); } //json_Float void print_FloatNotNan(const char *sLabel, int iVal, float sVal) { - if (isnan(sVal)) return; + if (isnan(sVal)) + return; printMessage(sLabel, iVal, sVal); } //json_Float @@ -954,58 +1064,73 @@ void json_Float(FILE *fp, const char *sLabel, float sVal) { printWarning(sLabel, sVal); return; } - if (sVal <= 0.0) return; - fprintf(fp, sLabel, sVal ); + if (sVal <= 0.0) + return; + fprintf(fp, sLabel, sVal); } //json_Float -void rescueProtocolName(struct TDICOMdata *d, const char * filename) { +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; + 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; + 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; +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"); + 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; + 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 + /*(0018,0024) SequenceName ep_b: dwi epfid2d: perf epfid2d: bold @@ -1030,13 +1155,13 @@ 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 + #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)) @@ -1048,47 +1173,58 @@ tse3d: T2*/ } }*/ //report vendor - if (d.fieldStrength > 0.0) fprintf(fp, "\t\"MagneticFieldStrength\": %g,\n", d.fieldStrength ); + 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_HITACHI: - fprintf(fp, "\t\"Manufacturer\": \"Hitachi\",\n" ); - break; - case kMANUFACTURER_UIH: - fprintf(fp, "\t\"Manufacturer\": \"UIH\",\n" ); - break; + 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"); + 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 ); + 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); @@ -1099,24 +1235,27 @@ tse3d: T2*/ 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; + 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); + 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.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\"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"); + 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); @@ -1129,42 +1268,43 @@ tse3d: T2*/ for (size_t i = 0; i < strlen(d.imageType); i++) { if (d.imageType[i] != '_') { if (isSep) - fprintf(fp, "\", \""); + 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.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); + 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){ + 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; + 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 + 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); + fprintf(fp, "\t\"AcquisitionTime\": \"%02d:%02d:%02.6f\",\n", ahour, amin, asec); //report date and time together if (!opts.isAnonymizeBIDS) { fprintf(fp, "\t\"AcquisitionDateTime\": "); @@ -1180,27 +1320,30 @@ tse3d: T2*/ 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 ); + 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 ); + 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\"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\"Unit\": \"%s\",\n", d.unitsPT); + 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); @@ -1210,8 +1353,9 @@ tse3d: T2*/ 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] ); + if (dti4D->decayFactor[i] < 0) + break; + fprintf(fp, "\t\t%g", dti4D->decayFactor[i]); } fprintf(fp, "\t],\n"); } @@ -1220,8 +1364,9 @@ tse3d: T2*/ 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] ); + if (dti4D->volumeOnsetTime[i] < 0) + break; + fprintf(fp, "\t\t%g", dti4D->volumeOnsetTime[i]); } fprintf(fp, "\t],\n"); } @@ -1230,42 +1375,66 @@ tse3d: T2*/ for (int i = 0; i < h->dim[4]; i++) { if (i != 0) fprintf(fp, ",\n"); - if (dti4D->frameDuration[i] < 0) break; - fprintf(fp, "\t\t%g", dti4D->frameDuration[i] / 1000.0 ); // from 0018,1242 ms -> sec + 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 - 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 ); + 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 ); - json_Float(fp, "\t\"RepetitionTime\": %g,\n", d.TR / 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_Float(fp, "\t\"InversionTime\": %g,\n", d.TI / 1000.0 ); - json_Float(fp, "\t\"FlipAngle\": %g,\n", d.flipAngle ); + 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" ); + 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; - #ifdef myReadAsciiCsa + 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]; @@ -1279,9 +1448,11 @@ tse3d: T2*/ 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 ); - } + 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) { @@ -1301,67 +1472,76 @@ tse3d: T2*/ 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"))) { + 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\"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 + 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")) { - 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 + 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")) { - json_FloatNotNan(fp, "\t\"InversionTime\": %g,\n", csaAscii.alTI[0] * (1.0/1000000.0)); //us -> sec - json_FloatNotNan(fp, "\t\"SaturationStopTime\": %g,\n", csaAscii.alTI[2] * (1.0/1000000.0)); //us -> sec + 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")) { - 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 + 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")) { - 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 + 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 + if (strstr(pulseSequenceDetails, "to_ep2d_VEPCASL")) { //Oxford 2D pCASL isOxfordASL = true; - 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 - //alTI[0] = 700000 - //alTI[2] = 1800000 - + 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 + 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 ++; + 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\t%g", csaAscii.alFree[i + 11] / 1000.0); //ms -> sec } fprintf(fp, "\t],\n"); } @@ -1380,29 +1560,31 @@ tse3d: T2*/ json_FloatNotNan(fp, newstr, csaAscii.adFree[k]); } } - if (strstr(pulseSequenceDetails,"jw_tgse_VEPCASL")) { //Oxford 3D pCASL + 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 - + 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 ++; + 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\t%g", csaAscii.alFree[i + 30] / 1000.0); //ms -> sec } fprintf(fp, "\t],\n"); } @@ -1422,13 +1604,21 @@ tse3d: T2*/ 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; + 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 @@ -1437,7 +1627,8 @@ tse3d: T2*/ } 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 (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++) { @@ -1448,112 +1639,181 @@ tse3d: T2*/ 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" ); + 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\"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) + 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 + 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.parallelReductionFactorInPlane > 0) {//AccelFactorPE -> phase encoding + 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) - //fprintf(fp, "\t\"ParallelReductionFactorInPlane\": %g,\n", d.accelFactPE); } - if (csaAscii.parallelReductionFactorInPlane != (int)(d.accelFactPE)) + 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) + 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); + //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 + //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 +#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 + 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.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 ); - + 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 + 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') + 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') + 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); //https://github.com/rordenlab/dcm2niix/issues/314 - if (d.accelFactOOP > 1.0) fprintf(fp, "\t\"ParallelReductionOutOfPlane\": %g,\n", d.accelFactOOP); + } + 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 + 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); + // effectiveEchoSpacing = d.CSA.sliceMeasurementDuration / (reconMatrixPE * 1000.0); if ((reconMatrixPE > 0) && (bandwidthPerPixelPhaseEncode > 0.0)) - effectiveEchoSpacing = 1.0 / (bandwidthPerPixelPhaseEncode * reconMatrixPE); + 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 + 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 - /* + // 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) @@ -1561,75 +1821,79 @@ tse3d: T2*/ 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 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; + 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); + effectiveEchoSpacing = totalReadoutTime / (reconMatrixPE - 1); } - json_Float(fp, "\t\"EffectiveEchoSpacing\": %g,\n", effectiveEchoSpacing); + 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; + double trueESfactor = 1.0; + if (d.accelFactPE > 1.0) + trueESfactor /= d.accelFactPE; if (phaseOversampling > 0.0) - trueESfactor *= (1.0 + phaseOversampling); + trueESfactor *= (1.0 + phaseOversampling); float derivedEchoSpacing = 0.0; derivedEchoSpacing = bandwidthPerPixelPhaseEncode * trueESfactor * reconMatrixPE; - if (derivedEchoSpacing != 0) derivedEchoSpacing = 1/derivedEchoSpacing; + if (derivedEchoSpacing != 0) + derivedEchoSpacing = 1 / derivedEchoSpacing; json_Float(fp, "\t\"DerivedVendorReportedEchoSpacing\": %g,\n", derivedEchoSpacing); - //TotalReadOutTime: Really should be called "EffectiveReadOutTime", by analogy with "EffectiveEchoSpacing". + //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 + // 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 ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0) && (d.manufacturer != kMANUFACTURER_UIH)) - fprintf(fp, "\t\"TotalReadoutTime\": %g,\n", effectiveEchoSpacing * (reconMatrixPE - 1.0)); - if (d.manufacturer == kMANUFACTURER_UIH) //https://github.com/rordenlab/dcm2niix/issues/225 + // 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); - json_Float(fp, "\t\"PixelBandwidth\": %g,\n", d.pixelBandwidth ); + 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)) { + 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"); + fprintf(fp, "\t\"PhaseEncodingAxis\": \"i\",\n"); } - if (((d.phaseEncodingRC == 'R') || (d.phaseEncodingRC == 'C')) && (!isSkipPhaseEncodingAxis) && (phPos >= 0)) { + 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"); + fprintf(fp, "\t\"PhaseEncodingDirection\": \"i"); else fprintf(fp, "\t\"PhaseEncodingDirection\": \"?"); //phaseEncodingDirectionPositive has one of three values: UNKNOWN (-1), NEGATIVE (0), POSITIVE (1) @@ -1649,53 +1913,88 @@ tse3d: T2*/ //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"); + 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\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 - 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"); - if (d.phaseEncodingRC == 'C') fprintf(fp, "\t\"InPlanePhaseEncodingDirectionDICOM\": \"COL\",\n" ); - if (d.phaseEncodingRC == 'R') fprintf(fp, "\t\"InPlanePhaseEncodingDirectionDICOM\": \"ROW\",\n" ); + 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", kDCMdate); //fprintf(fp, "\t\"ConversionSoftwareVersion\": \"%s\"\n", kDCMvers );kDCMdate fprintf(fp, "}\n"); - fclose(fp); -}// nii_SaveBIDSX() - -void nii_SaveBIDS(char pathoutname[], struct TDICOMdata d, struct TDCMopts opts, struct nifti_1_header *h, const char * filename) { -struct TDTI4D *dti4D; -dti4D->sliceOrder[0] = -1; -dti4D->volumeOnsetTime[0] = -1; -dti4D->decayFactor[0] = -1; -dti4D->intenScale[0] = 0.0; -dti4D->repetitionTimeExcitation = 0.0; -dti4D->repetitionTimeInversion = 0.0; -nii_SaveBIDSX(pathoutname, d, opts, h, filename, dti4D); -}// nii_SaveBIDSX() + fclose(fp); +} // nii_SaveBIDSX() -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)) ) ); +#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); } -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; +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 @@ -1704,45 +2003,47 @@ unsigned char * removeADC(struct nifti_1_header *hdr, unsigned char *inImg, int //#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) +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; + 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; + 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 +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)); + 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; + 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 + 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; + 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); @@ -1751,60 +2052,69 @@ unsigned char * reorderVolumes(struct nifti_1_header *hdr, unsigned char *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]; +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; + 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; +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; + 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) + //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); + images->addDeferredAttribute("bValues", std::vector(numVol, 0.0)); + images->addDeferredAttribute("bVectors", std::vector(numVol * 3, 0.0), numVol, 3); #else - char sep = '\t'; + char sep = '\t'; if (opts.isCreateBIDS) - sep = ' '; - //save bval - char txtname[2048] = {""}; - strcpy (txtname,pathoutname); - strcat (txtname,".bval"); + 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"); + strcpy(txtname, pathoutname); + strcat(txtname, ".bvec"); fp = fopen(txtname, "w"); for (int v = 0; v < (3); v++) { for (int i = 0; i < (numVol); i++) @@ -1813,93 +2123,102 @@ int * nii_saveDTI(char pathoutname[],int nConvert, struct TDCMsort dcmSort[],str } 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) { + } + 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; - } - 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]); + } + } + //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"); + 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)); + *numADC = 0; + bvals = (float *)malloc(numDti * sizeof(float)); int numGEwarn = 0; bool isGEADC = (dcmList[indx0].numberOfDiffusionDirectionGE == 0); for (int i = 0; i < numDti; i++) { @@ -1907,25 +2226,25 @@ int * nii_saveDTI(char pathoutname[],int nConvert, struct TDCMsort dcmSort[],str //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 - 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 (((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 + /*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); @@ -1933,8 +2252,8 @@ int * nii_saveDTI(char pathoutname[],int nConvert, struct TDCMsort dcmSort[],str return NULL; }*/ bool isIsotropic = false; - if ((*numADC == numDti) || (numGEwarn == numDti)) //issue 405: we now save bvals file for isotropic series - isIsotropic = true; + 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 @@ -1949,187 +2268,182 @@ int * nii_saveDTI(char pathoutname[],int nConvert, struct TDCMsort dcmSort[],str // 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)){ + 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) { + } 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"); + 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); + 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)); + int *volOrderIndex = (int *)malloc(numDti * sizeof(int)); for (int i = 0; i < numDti; i++) - volOrderIndex[i] = 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 + 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)); + TDTI *vxOrig = (TDTI *)malloc(numDti * sizeof(TDTI)); for (int i = 0; i < numDti; i++) - vxOrig[i] = vx[i]; - //remove ADC + 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 + 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]) + 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 + 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]); + 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); + 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.isSaveNRRD) { - 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); + 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() + free(vx); + return volOrderIndex; +} // nii_saveDTI() -float sqr(float v){ - return v*v; -}// sqr() +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 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 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]; + if (fabs(v.v[1]) > fabs(mx)) + mx = v.v[1]; + if (fabs(v.v[2]) > fabs(mx)) + mx = v.v[2]; return mx; } @@ -2137,83 +2451,95 @@ 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; + 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]); +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) +// 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; + 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); + 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; + 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; + 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); + 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)) + 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); + 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); + 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])); + //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 @@ -2223,118 +2549,211 @@ float intersliceDistance(struct TDICOMdata d1, struct TDICOMdata d2) { // 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. +// 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) { - //reports distance between two slices, signed as 2nd slice can be in front or behind 1st +// 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 + 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; + 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 value; - int index; +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).value > (*a2).value) return 1; - if((*a1).value < (*a2).value) 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[]) { - //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 isAscending1 = (dx > 0); - bool isConsistent = true; - for(int i=1; i < d3; i++) { - dx = intersliceDistanceSigned(dcmList[dcmSort[i-1].indx],dcmList[dcmSort[i].indx]); - bool isAscending = (dx > 0); - if (isAscending != isAscending1) isConsistent = false; //direction reverses - } - if (isConsistent) return true; - printWarning("Order specified by DICOM instance number is not spatial (reordering).\n"); - TFloatSort * floatSort = (TFloatSort *)malloc(d3 * sizeof(TFloatSort)); - for(int i=0; i < d3; i++) { - dx = intersliceDistanceSigned(dcmList[dcmSort[0].indx],dcmList[dcmSort[i].indx]); - floatSort[i].value = dx; - floatSort[i].index=i; - } - TDCMsort* dcmSortIn = (TDCMsort *)malloc(nConvert * sizeof(TDCMsort)); - for(int i=0; i < nConvert; i++) - dcmSortIn[i] = dcmSort[i]; - qsort(floatSort, d3, sizeof(struct TFloatSort), compareTFloatSort); //sort based on series and image numbers.... - for(int vol=0; vol < d4; vol++) { - int volInc = vol * d3; - for(int i=0; i < d3; i++) - dcmSort[volInc+i] = dcmSortIn[volInc+floatSort[i].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() -//#endif //myInstanceNumberOrderIsNotSpatial + 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++; - } + //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; +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 + // 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++) @@ -2357,42 +2776,46 @@ bool intensityScaleVaries(int nConvert, struct TDCMsort dcmSort[],struct TDICOMd 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 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) { +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"); + 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; +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 @@ -2401,449 +2824,477 @@ bool niiExists(const char*pathoutname) { 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) { + 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'; - } + 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 +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); +#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); + 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); +#else + printError("You do not have write permissions for the directory %s\n", opts.outdir); return EXIT_FAILURE; - #endif +#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" + } + } + 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 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) + 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 == '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); + strcat(outname, newstr); //isAcquisitionReported = true; } else { - #ifdef mySegmentByAcq sprintf(newstr, "%d", dcm.acquNum); - strcat (outname,newstr); - //isAcquisitionReported = true; - #else - printWarning("Ignoring '%%u' in output filename (recompile to segment by acquisition)\n"); - #endif - } + 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"); + strcat(outname, "Bruker"); else if (dcm.manufacturer == kMANUFACTURER_GE) - strcat (outname,"GE"); + strcat(outname, "GE"); else if (dcm.manufacturer == kMANUFACTURER_PHILIPS) - strcat (outname,"Philips"); + strcat(outname, "Philips"); else if (dcm.manufacturer == kMANUFACTURER_SIEMENS) - strcat (outname,"Siemens"); + strcat(outname, "Siemens"); else if (dcm.manufacturer == kMANUFACTURER_TOSHIBA) - strcat (outname,"Toshiba"); + strcat(outname, "Toshiba"); else if (dcm.manufacturer == kMANUFACTURER_CANON) - strcat (outname,"Canon"); + strcat(outname, "Canon"); else if (dcm.manufacturer == kMANUFACTURER_UIH) - strcat (outname,"UIH"); + strcat(outname, "UIH"); else - strcat (outname,"NA"); + strcat(outname, "NA"); } if (f == 'X') - strcat (outname,dcm.studyID); + 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= 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; - } + 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.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') || (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 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; - + 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); + 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, kForeignPathSeparator); + // 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) + 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 +#else mkdir(newdir, 0700); - #endif - } +#endif + } char ch[12] = {""}; - sprintf(ch,"%c",outname[pos]); - strcat (newdir,ch); - } - } + 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; + //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.isSaveNRRD) { +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'"); + strcat(niiFilename, ".nhdr'"); else - strcat(niiFilename,".nrrd'"); - } else { + strcat(niiFilename, ".nrrd'"); + } else { if (opts.isGz) - strcat(niiFilename,".nii.gz'"); + strcat(niiFilename, ".nii.gz'"); else - strcat(niiFilename,".nii'"); - } -}// nii_createDummyFilename() + strcat(niiFilename, ".nii'"); + } +} // nii_createDummyFilename() #ifndef myDisableZLib @@ -2853,275 +3304,286 @@ unsigned long mz_compressBound(unsigned long source_len) { } unsigned long mz_crc32(unsigned long crc, const unsigned char *ptr, size_t buf_len) { - return crc32(crc, ptr, (uInt) 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 +#define MZ_UBER_COMPRESSION 9 #endif #ifndef MZ_DEFAULT_LEVEL - #define MZ_DEFAULT_LEVEL 6 +#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 = (unsigned char *)malloc(hdrPadBytes); - unsigned char *pHdr; - if (!isSkipHeader) { +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)); + 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 + 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 + } + //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); + 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; +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_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; - } - if (strlen(data.manufacturersModelName) > 0) - images->addAttribute("scannerModelName", data.manufacturersModelName); - if (strlen(data.imageType) > 0) - images->addAttribute("imageType", data.imageType); - if (data.seriesNum > 0) - images->addAttribute("seriesNumber", int(data.seriesNum)); - if (strlen(data.seriesDescription) > 0) - images->addAttribute("seriesDescription", data.seriesDescription); - if (strlen(data.sequenceName) > 0) - images->addAttribute("sequenceName", data.sequenceName); - if (strlen(data.protocolName) > 0) - images->addAttribute("protocolName", data.protocolName); - if (strlen(data.studyDate) >= 8 && strcmp(data.studyDate,"00000000") != 0) - images->addDateAttribute("studyDate", data.studyDate); - if (strlen(data.studyTime) > 0 && strncmp(data.studyTime,"000000",6) != 0) - images->addAttribute("studyTime", data.studyTime); - if (data.fieldStrength > 0.0) - images->addAttribute("fieldStrength", data.fieldStrength); - if (data.flipAngle > 0.0) - images->addAttribute("flipAngle", data.flipAngle); - if (data.TE > 0.0) - images->addAttribute("echoTime", data.TE); - if (data.TR > 0.0) - images->addAttribute("repetitionTime", data.TR); - if (data.TI > 0.0) - images->addAttribute("inversionTime", data.TI); - if (!data.isXRay) { - if (data.zThick > 0.0) - images->addAttribute("sliceThickness", data.zThick); - if (data.zSpacing > 0.0) - 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) - effectiveEchoSpacing = data.effectiveEchoSpacingGE / 1000000.0; - - if (effectiveEchoSpacing > 0.0) - images->addAttribute("effectiveEchoSpacing", effectiveEchoSpacing); - if ((reconMatrixPE > 0) && (effectiveEchoSpacing > 0.0)) - images->addAttribute("effectiveReadoutTime", effectiveEchoSpacing * (reconMatrixPE - 1.0)); - if (data.pixelBandwidth > 0.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; iaddAttribute("sliceTiming", sliceTimes); - } - - if (strlen(data.patientID) > 0) - images->addAttribute("patientIdentifier", data.patientID); - if (strlen(data.patientName) > 0) - images->addAttribute("patientName", data.patientName); - if (strlen(data.patientBirthDate) >= 8 && strcmp(data.patientBirthDate,"00000000") != 0) - 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"); - if (data.patientWeight > 0.0) - images->addAttribute("patientWeight", data.patientWeight); - if (strlen(data.imageComments) > 0) - images->addAttribute("comments", data.imageComments); +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) { +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"); + if (imgsz > 1000000) + strcpy(blockSize, " -b 960"); char command[768]; - strcpy(command, "\"" ); - strcat(command, opts.pigzname ); - if ((opts.gzLevel > 0) && (opts.gzLevel < 12)) { + 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); @@ -3132,185 +3594,410 @@ int pigz_File(char * fname, struct TDCMopts opts, size_t imgsz) { } 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; +#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 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 + //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; + 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 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; + //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)) { + 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 + 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] = {""}; + 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"); + 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"); + 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 + 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"); + 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"); + 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]); + 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] ); + 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 ++; + while (n < nDim) { + fprintf(fp, " none"); + n++; } - fprintf(fp,"\n"); + fprintf(fp, "\n"); //centerings tag if (hdr.dim[0] < 4) //*check RGB, more dims - fprintf(fp,"centerings:%s cell cell cell\n", rgbNoneStr); + fprintf(fp, "centerings:%s cell cell cell\n", rgbNoneStr); else - fprintf(fp,"centerings:%s cell cell cell ???\n", rgbNoneStr); + fprintf(fp, "centerings:%s cell cell cell ???\n", rgbNoneStr); //kinds tag - fprintf(fp,"kinds:"); - if (hdr.datatype == DT_RGB24) fprintf(fp," RGB-color"); + 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) && (n < 3)) { + fprintf(fp, " space"); //dims 1..3 + n++; } - while (n < nDim ) { - fprintf(fp," list"); //dims 4..7 - n ++; + while (n < nDim) { + fprintf(fp, " list"); //dims 4..7 + n++; } - fprintf(fp,"\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); + 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); + 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); + 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 + // 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)) { @@ -3325,441 +4012,435 @@ int nii_saveNRRD(char * niiFilename, struct nifti_1_header hdr, unsigned char* i 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,"%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]); + 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]); + 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]); + 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"); + 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 + 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])) ) { + 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 + //(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); + fprintf(fp, "DWMRI_gradient_%04d:=isotropic\n", i); else - fprintf(fp,"DWMRI_gradient_%04d:=isotropic b=%g\n", i, dti4D->S[i].V[0]); + 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, "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); + 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 + 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); + 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() -#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 - -void removeSclSlopeInter(struct nifti_1_header* hdr, unsigned char* img) { +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.0),0.0)) || (!isSameFloat(fmod(hdr->scl_slope, 1.0),0.0))) return; + 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->dim[i] > 1) + nVox = nVox * hdr->dim[i]; if (hdr->datatype == DT_INT16) { - int16_t * img16 = (int16_t*) img; + int16_t *img16 = (int16_t *)img; int16_t mn, mx; mn = img16[0]; mx = mn; - for (int i=0; i < nVox; i++) { + 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; + 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++) + 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_slope = 1.0; hdr->scl_inter = 0.0; - return; + 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); + 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); } -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); -} +#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.isSaveNRRD) { - struct TDTI4D *dti4D = (struct TDTI4D *)malloc(sizeof(struct TDTI4D)); - int ret = nii_saveNRRD(niiFilename, hdr, im, opts, d, dti4D, 0); +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) + } + 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) ) { + 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"' + 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) + 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 + 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) { +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] = {""}; +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() - -/* -//this version can convert INT16->UINT16 -// some were concerned about this https://github.com/rordenlab/dcm2niix/issues/198 -void nii_scale16bitSigned(unsigned char *img, struct nifti_1_header *hdr){ - 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; - //clock_t start = clock(); - 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 - bool isConvertToUint16 = true; //if false output is always same as input: INT16, if true and no negative values output will be UINT16 - if ((isConvertToUint16) && (min16 >= 0)) - kMx = 64000; - int scale = kMx / (int)max16; - if (abs(min16) > max16) - scale = kMx / (int)abs(min16); - if (scale < 2) return; //already uses dynamic range - hdr->scl_slope = hdr->scl_slope/ scale; - if ((isConvertToUint16) && (min16 >= 0)) { //only positive values: save as UINT16 0..65535 - hdr->datatype = DT_UINT16; - uint16_t * uimg16 = (uint16_t*) img; - for (int i=0; i < nVox; i++) - uimg16[i] = (int)img16[i] * scale; - } else {//includes negative values: save as INT16 -32768..32768 - for (int i=0; i < nVox; i++) - img16[i] = img16[i] * scale; - } - printMessage("Maximizing 16-bit range: raw %d..%d\n", min16, max16); -}*/ + 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 + //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); + 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 + //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; + //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); + 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); + 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); +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); + 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"); - } +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; +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 @@ -3768,203 +4449,211 @@ void nii_check16bitUnsigned(unsigned char *img, struct nifti_1_header *hdr, int // 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); +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]; - } +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; +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); + 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; + // 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 { + } 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]; + 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 + 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 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 + 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; + 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]; + 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); + 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; + 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); + 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; + // 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 { + 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]; @@ -3977,532 +4666,418 @@ unsigned char * nii_saveNII3Dtilt(char * niiFilename, struct nifti_1_header * hd //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 + 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 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 + 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; + 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]; + 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); + 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"); + 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(" %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() - -/*int nii_saveNII3Deq(char * niiFilename, struct nifti_1_header hdr, unsigned char* im, struct TDCMopts opts, 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) ) 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 data with at least 3 slices."); - return EXIT_FAILURE; - } - //find lowest and highest slice - float lo = sliceMMarray[0]; - float hi = lo; - for (int i = 1; i < hdr.dim[3]; i++) { - if (sliceMMarray[i] < lo) - lo = sliceMMarray[i]; - if (sliceMMarray[i] > hi) - hi = sliceMMarray[i]; - } - if (isSameFloat(lo,hi)) return EXIT_SUCCESS; - - - float mn = fabs(sliceMMarray[1] - sliceMMarray[0]); - for (int i = 1; i < (hdr.dim[3]-1); i++) { - for (int j = i+1; j < hdr.dim[3]; j++) { - float dx = fabs(sliceMMarray[i] - sliceMMarray[j]); - if ((dx < mn) && (dx > 0.0)) - mn = dx; - } - } - if (mn <= 0.0f) { - printMessage("Unable to equalize slice distances: slice number not consistent with slice position.\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 - slices = (int)round((hi-lo+mn)/mn); //+mn: fence post - printMessage("lo=%g hi=%g mn=%g slices=%d\n", lo, hi, mn, slices); - 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); - 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); + 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 +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; + 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"); + 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 + 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 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; + 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 (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); + 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"); + 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! + 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); +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); + 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; +} // 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) + 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 + sliceSums[i] += imu16[j + sliceStart]; + else for (int j = 0; j < nVox2D; j++) - sliceSums[i] += im16[j+sliceStart]; + 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 + 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; + 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; + 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; + 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; + 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 + 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; + 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; + 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 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; + 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 +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); + 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 +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) - printWarning("Adjusting for negative MosaicRefAcqTimes (issue 271).\n"); + 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 (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 + 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]; + 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); + //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]; + 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]; + 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; @@ -4510,9 +5085,12 @@ void checkSliceTiming(struct TDICOMdata * d, struct TDICOMdata * d1, int verbose 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 (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++) @@ -4523,26 +5101,34 @@ void checkSliceTiming(struct TDICOMdata * d, struct TDICOMdata * d1, int verbose 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); + 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 (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 + 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; + minT1 -= minT1; } - if ((minT1 < 0.0) && (d->rtia_timerGE >= 0.0)) return; //use rtia timer + 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); @@ -4555,58 +5141,73 @@ void checkSliceTiming(struct TDICOMdata * d, struct TDICOMdata * d1, int verbose //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; + 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)) { +} //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)) { + } 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 (dcmList[indx0].CSA.sliceTiming[v] < mn) + mn = dcmList[indx0].CSA.sliceTiming[v]; } - if (mn < 0.0) mn = 0.0; + 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 (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]; + 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; + 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, bool is27r3, float groupDelaysec) { -//mb : multiband factor -//dim3 : number of slices in volume -//TRsec : repetition time in seconds -//isInterleaved : interleaved or sequential slice order -//is27r3 : software release 27.0 R03 or later +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) { @@ -4616,9 +5217,9 @@ void sliceTimeGE (struct TDICOMdata * d, int mb, int dim3, float TR, bool isInte 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; + sliceTiming[i] = (i / 2) * secPerSlice; else - sliceTiming[i] = (nOdd+((i+1)/2)) * secPerSlice; + 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]; @@ -4629,81 +5230,82 @@ void sliceTimeGE (struct TDICOMdata * d, int mb, int dim3, float TR, bool isInte } //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 +//#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 + 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])); - } + printMessage("%d %g %g\n", i, sliceTiming[i], d->CSA.sliceTiming[i]); + maxErr = max(maxErr, fabs(sliceTiming[i] - d->CSA.sliceTiming[i])); + } } - #endif +#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] =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); - } +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; +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; @@ -4712,29 +5314,53 @@ void sliceTimingGE_Testx0021x105E(struct TDICOMdata * d, struct TDCMopts opts, s 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]); + sliceTiming[v] = dcmList[dcmSort[v + j].indx].rtia_timerGE; + mn = min(mn, sliceTiming[v]); } - if (mn < 0.0) return; - float mxErr = 0.0; + 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, fabs(sliceTiming[v] - d->CSA.sliceTiming[v])); + 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]); + 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) { +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->is3DAcq) || (d->isLocalizer) || (hdr->dim[4] < 2)) return; //no need for slice times - if (hdr->dim[3] < 2) return; - if (d->manufacturer != kMANUFACTURER_GE) return; - //start version check: + 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] = " "; @@ -4745,16 +5371,17 @@ void sliceTimingGE(struct TDICOMdata * d, const char * filename, struct TDCMopts //used for oldSliceTimingGE if (!opts.isIgnorex0021x105E) { if ((geMajorVersionInt >= 28) && (d->CSA.sliceTiming[0] >= 0.0)) { - //if (opts.isVerbose > 1) + //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 + 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 @@ -4766,8 +5393,9 @@ void sliceTimingGE(struct TDICOMdata * d, const char * filename, struct TDCMopts 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, opts.isVerbose, &sliceOrderGE, &viewOrderGE, &mbAccel, &nSlices, &groupDelay, ioptGE); + 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; } @@ -4778,32 +5406,34 @@ void sliceTimingGE(struct TDICOMdata * d, const char * filename, struct TDCMopts bool isInterleaved = (sliceOrderGE != 0); groupDelay *= 1000.0; //sec -> ms // - // Estimate GE Slice Time only for EPI Multi-Phase (epi) or EPI fMRI/BrainWave (epiRT) + // Estimate GE Slice Time only for EPI Multi-Phase (epi) or EPI fMRI/BrainWave (epiRT) // - // BrainWave (epiRT) - if ((d->epiVersionGE == 1) || (strstr(ioptGE,"FMRI") != NULL)) { //-1 = not epi, 0 = epi, 1 = 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 + 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) { + if (groupDelay > 0) { d->TR += groupDelay; d->groupDelay = groupDelay; } // EPI Multi-Phase (epi) with Variable Delays (Unsupported) - if (groupDelay < -0.5) { - printWarning("SliceTiming Unspported: GE Multi-Phase EPI with Variable Delays\n"); + 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) ) { + 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; @@ -4818,17 +5448,22 @@ void sliceTimingGE(struct TDICOMdata * d, const char * filename, struct TDCMopts 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, is27r3, 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 +#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; +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; @@ -4836,63 +5471,77 @@ void reverseSliceTiming(struct TDICOMdata * d, int verbose, int nSL) { 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]; + 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) { +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].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; + 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; +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; + if ((ucMode < 1) || (ucMode == 3) || (ucMode > 4)) + return; float trSec = d->TR / 1000.0; - float delaySec = csaAscii.delayTimeInTR/ 1000000.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 + 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]; + 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] + 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]! + 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; @@ -4908,14 +5557,18 @@ void rescueSliceTimingSiemens(struct TDICOMdata * d, int verbose, int nSL, const #endif } -void sliceTimingUIH(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struct nifti_1_header * hdr, int verbose, const char * filename, int nConvert) { +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() + 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() } /* @@ -5004,7 +5657,6 @@ void oldSliceTimingGE(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struc 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 @@ -5012,23 +5664,25 @@ void oldSliceTimingGE(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struc } //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 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; + 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]; + 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]; + 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); + 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"); @@ -5037,240 +5691,221 @@ int sliceTimingCore(struct TDCMsort *dcmSort,struct TDICOMdata *dcmList, struct 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)) + 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; + 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 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]); -}*/ - -/*int issue377(struct TDICOMdata d, struct nifti_1_header *h) { - 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]; - } - printf("SeriesNumber((0020,0011))\tProtocolName(0018,1030)\tEchoTrainLength(0018,0091 or 2001,1013)\tWaterFatShift(2001,1022)\tImagingFrequency(0018,0084)\tReconMatrixPE\tPixelBandwidth(0018,0095)\tPhaseEncodingLines(0018,1310 or 0018,9231)\tPhaseEncodingSteps(0018,0089)\tPercentSampling(0018,0093)\tPercentPhaseFieldOfView(0018,0094)\n"); - printf("%ld\t%s\t%d\t%g\t%g\t%d\t%g\t%d\t%d\t%g\t%g\n", d.seriesNum, d.protocolName, d.echoTrainLength, d.waterFatShift, d.imagingFrequency, reconMatrixPE, d.pixelBandwidth, d.phaseEncodingLines, d.phaseEncodingSteps, d.percentSampling, d.phaseFieldofView ); - return 0; -}*/ - -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"); +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; - } + 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}; + 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 byt = (i >> 3); int bit = (i % 8); img[i] = ((bImg[byt] & mask[bit]) != 0); } - /* - if (isFlipY) { - unsigned char *tImg = (unsigned char *)malloc(nvox); - memcpy(&tImg[0], &img[0], nvox); - int i = 0; - for (int yi = y-1; yi >= 0; yi--) - for (int xi = 0; xi < x; xi++) { - img[(yi*x)+xi] = tImg[i]; - i++; - } - free(tImg); - }*/ - free(bImg); + 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); +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; + 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 +#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); + 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) +#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) //issue 384 - 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) ){ +#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++; - } - /*int nImg = 1+abs( dcmList[dcmSort[nConvert-1].indx].imageNum-dcmList[dcmSort[0].indx].imageNum); - if (((nConvert/nAcq) > 1) && ((nConvert%nAcq)==0) && (nImg == nConvert) && (dcmList[dcmSort[0].indx].locationsInAcquisition == 0) ) { - printMessage(" stacking %d acquisitions as a single volume\n", nAcq); - //some Siemens CT scans use multiple acquisitions for a single volume, perhaps also check that slice position does not repeat? - hdr0.dim[3] = nConvert; - } else*/ 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; + 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)))) { - //note: GE 0008,0032 unreliable, see mb=6 data from sw27.0 20201026 + //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; - } - + 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; @@ -5280,19 +5915,22 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc 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 (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) { @@ -5304,7 +5942,19 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc dcmList[indx0].TR = tr * 1000.0; //as msec } } - if ((maxtr - mintr) > toleranceSec) trVaries = true; + 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"); @@ -5313,91 +5963,95 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc //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; - //printf("%d %g\n", i, dcmList[dcmSort[i].indx].decayFactor); - nTR += 1; - if (nTR >= kMaxDTI4D) break; - } - if (dcmList[indx0].modality != kMODALITY_PT) + 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"); + // 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); - } + 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)) - 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) { + } //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 + } +#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++; + 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]); + float dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); if (dx < dxPrev) isInconsistenSliceDir = true; - if (isSameFloatGE(dxPrev, 0.0)) slicePositionRepeats++; + if (isSameFloatGE(dxPrev, 0.0)) + slicePositionRepeats++; dxPrev = dx; - } + } } - if ((isInconsistenSliceDir) && (slicePositionRepeats == 1)) { + 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); - dx = intersliceDistance(dcmList[dcmSort[0].indx],dcmList[dcmSort[1].indx]); + 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]); + 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]) ); @@ -5405,22 +6059,23 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc 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++; + 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]); + float dx = intersliceDistance(dcmList[dcmSort[0].indx], dcmList[dcmSort[i].indx]); if (dx < dxPrev) isInconsistenSliceDir = true; - if (isSameFloatGE(dxPrev, 0.0)) slicePositionRepeats++; + if (isSameFloatGE(dxPrev, 0.0)) + slicePositionRepeats++; dxPrev = dx; - } + } } if (!dxVaries) { printMessage("Slice re-ordering resolved inter-slice distance variability.\n"); free(sliceMMarray); - sliceMMarray = NULL; + sliceMMarray = NULL; } - } if (isInconsistenSliceDir) { printMessage("Unable to equalize slice distances: slice order not consistently ascending.\n"); @@ -5428,13 +6083,13 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc 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); +#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); + 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); + 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++) { @@ -5442,60 +6097,47 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc } printMessage("]\n"); } - } //imageNum not sequential + } //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; - } - /*if (nConvert > 1) { //next determine if TR is true time between volumes - double startTime = dcmList[indx0].acquisitionTime; - double endTime = startTime; - for (int i = 1; i < nConvert; i++) { - double sliceTime = dcmList[dcmSort[i].indx].acquisitionTime; - if (sliceTime < startTime) startTime = sliceTime; - if (sliceTime > endTime) endTime = sliceTime; - } - double seriesTime = (endTime - startTime); - if (endTime > 0) - printMessage("%g - %g = %g\n", endTime, startTime, seriesTime); - - }*/ - //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 + } //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; @@ -5503,209 +6145,229 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc // 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; + 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]); + 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); + 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]); - } + } //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; - } +#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; - } + 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); + 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; + 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! - } + 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.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"); + 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 + 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)) { - 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"); + 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]); + 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]); + 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; + 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.isSaveNRRD) - 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); + // 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 + 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"); + strcat(pathoutnameADC, pathoutname); + strcat(pathoutnameADC, "_ADC"); if (opts.isSave3D) nii_saveNII3D(pathoutnameADC, hdr0, imgM, opts, dcmList[dcmSort[0].indx]); else @@ -5713,20 +6375,25 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc } 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; + bool isOverlay = false; for (int i = 0; i < nConvert; i++) - if (dcmList[dcmSort[i].indx].overlayStart[j] > 0) isOverlay = true; - if (!isOverlay) continue; + if (dcmList[dcmSort[i].indx].overlayStart[j] > 0) + isOverlay = true; + if (!isOverlay) + continue; char pathoutnameROI[2048] = {""}; - strcat(pathoutnameROI,pathoutname); + strcat(pathoutnameROI, pathoutname); char append[128] = {""}; - sprintf(append,"_ROI%d",j+1); - strcat(pathoutnameROI,append); + 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; + 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; @@ -5748,64 +6415,65 @@ int saveDcm2NiiCore(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dc } //if overlay on slice } //for each volume } // - if (isFlipZ) - imgR = nii_flipZ(imgR, &hdrr); - if (isSetOrtho) - imgR = nii_setOrtho(imgR, &hdrr); + 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]); - } + 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.isSaveNRRD) - returnCode = nii_saveNRRD(pathoutname, hdr0, imgM, opts, dcmList[dcmSort[0].indx], dti4D, dcmList[indx0].CSA.numDti); + 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]); + 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) { - 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()! + 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 + // 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]); + 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); - return returnCode;//EXIT_SUCCESS; -}// saveDcm2NiiCore() + 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) { +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; @@ -5815,7 +6483,7 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis printError("Unexpected error for image with varying echo time or intensity scaling\n"); return EXIT_FAILURE; } - int ret = EXIT_SUCCESS; + 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]; @@ -5825,13 +6493,15 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis 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 (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; + if (echo > 1) + dcmList[indx].isMultiEcho = true; //check for repeated volumes int series = 1; for (int i = 0; i < dcmList[indx].xyzDim[4]; i++) @@ -5839,7 +6509,7 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis dti4D->gradDynVol[0] = 1; for (int i = 1; i < dcmList[indx].xyzDim[4]; i++) { for (int j = 0; j < i; j++) - if (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])) + 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++; @@ -5876,9 +6546,11 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis 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; + if (dti4D->gradDynVol[i] == s) { + //dti4D->gradDynVol[i] = s; //nVol ++; dcmList[indx].TE = dti4D->TE[i]; //dcmList[indx].intenScale = dti4D->intenScale[i]; @@ -5896,7 +6568,7 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis } } dcmList[indx].isScaleVariesEnh = false; - if (isScaleVariesEnh) { //check if intensity scale varies for this particular output image, this will force 32-bit output + 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) { @@ -5906,15 +6578,18 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis 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 + 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; + 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]; @@ -5922,37 +6597,40 @@ int saveDcm2Nii(int nConvert, struct TDCMsort dcmSort[],struct TDICOMdata dcmLis 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) + 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; + 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) { @@ -5960,78 +6638,40 @@ int compareTDCMsort(void const *item1, void const *item2) { 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 + 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); + //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 + // 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 + // 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 + // 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; + 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 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; - - int retval = 0; // tie - - if (dcm1->img < dcm2->img) - retval = -1; - else if (dcm1->img > dcm2->img) - retval = 1; - - if(retval == 0){ - // 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]){ - retval = -1; - break; - } - else if(dcm1->dimensionIndexValues[i] > dcm2->dimensionIndexValues[i]){ - retval = 1; - break; - } - } - } - return retval; -} //compareTDCMsort()*/ - -/*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); -}*/ - -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); + +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; + bool manufacturerVaries, modalityVaries, derivedVaries, acqNumVaries, dimensionVaries, dateTimeVaries, studyUidVaries, echoVaries, triggerVaries, phaseVaries, coilVaries, forceStackSeries, seriesUidVaries, nameVaries, nameEmpty, orientVaries; }; TWarnings setWarnings() { @@ -6055,42 +6695,47 @@ TWarnings setWarnings() { 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; +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; + 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; + 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; + 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; + 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")) ) { + 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; + printMessage("Diffusion images stacked despite varying series number (early Siemens DTI).\n"); + warnings->forceStackSeries = true; + isForceStackSeries = true; } } if (isForceStackSeries) @@ -6098,444 +6743,458 @@ bool isSameSet (struct TDICOMdata d1, struct TDICOMdata d2, struct TDCMopts* opt 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) + 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)) + } + 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]) ); + 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; + 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; + 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)) { //issue 384 - if (!warnings->triggerVaries) - printMessage("Slices not stacked: trigger time varies\n"); - warnings->triggerVaries = true; - return false; - } - if (d1.coilCrc != d2.coilCrc) { + 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; + 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() + 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); + 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; - 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] = 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); +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() + free(dcmList); + return ret; +} // singleDICOM() -size_t fileBytes(const char * fname) { - FILE *fp = fopen(fname, "rb"); - if (!fp) return 0; +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; + fclose(fp); + return fileLen; } //fileBytes() -void searchDirForDICOM(char *path, struct TSearchList *nameList, int maxDepth, int depth, struct TDCMopts* opts ) { - 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] != '.')) - searchDirForDICOM(filename, nameList, maxDepth, depth+1, opts); - 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); +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) - convert_foreign (filename, *opts); - #ifdef MY_DEBUG - printMessage("Not a dicom:\t%s\n", filename); - #endif + } 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 - } - tinydir_next(&dir); - } - tinydir_close(&dir); -}// 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); +#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 + 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 *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; + 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(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); +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)) ) { + 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) ) { + opts->ignoredPaths.push_back(sourcePath); + } else if ((opts->isIgnoreDerivedAnd2D && dcm.isDerived)) { printMessage("Ignoring derived %s\n", sourcePathPtr); - opts->ignoredPaths.push_back(sourcePath); + opts->ignoredPaths.push_back(sourcePath); } else { // Create an initial file name - char outname[PATH_MAX] = {""}; + char outname[PATH_MAX] = {""}; if (dcm.echoNum > 1) - dcm.isMultiEcho = true; + 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()); - } + // 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; + } + } + 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) { +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) { + return -1; + } + if (dir.n_files < 1) { if (opts->isVerbose > 0) printMessage("No files in %s\n", path); - return 0; - } + 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